变更一个嵌套对象,因为对象是可变的,所以在 js 中成为了一个问题
const a = {b:{c:1}}
const d = a
a.b.c = 2
console.log(Object.is(a,d))
a 属性的变化,反应不到 a 上
话说这不是废话么?都用的 const 声明的
但是这个常识,在代码中并不显而易见
在 React/Vue 中,因为是数据驱动的(Angular zone.js 为事件驱动,下面单独讨论),所以,如果数据的变化没有被检测到,那么视图必定不会更新
const [state, setState] = useState({b:{c:1}})
useEffect(()=>{
setState(res => {
res.b.c = 2
return res
})
},[]}
扑街!
因为 setState 的返回值与 state 并没有变化,变更无效
那怎么办?
一位奇怪的同学突然站起来:好办!
const [state, setState] = useState({b:{c:1}})
useEffect(()=>{
setState(res => {
res.b.c = 2
return lodash.cloneDeep(res)
})
},[])
……
注意,任何时候,除非是跨平台(跨平台优先序列化,比如JSON),否这 ——
深复制应该被明令禁止!
你的逻辑只需要通知视图变更,并不是要一个复杂度非常高的多实例
单实例和多实例,是两个完全不同的逻辑,如果对象特别大,涉及图像,视频的话?你敢这么操作么?
那么修改为,只需要视图知道我变化了,即,只需要返回一个新 state
export default function App() {
const [state, setState] = useState({ b: { c: 1 } });
useEffect(() => {
setState((res) => {
res.b.c = 2;
return { ...res };
});
}, []);
return <h2>{state.b.c}</h2>;
}
没问题,实现了!
等等,真的实现了么?
return {…res} 的操作,实际上是个浅拷贝,只要是拷贝,在数据量大的情况下,都很危险!
换句话说,深拷贝有问题,难道浅拷贝就是对的?
不不不,深拷贝浅拷贝都是错误答案!那什么才是正确答案呢?
只通知框架进行变更,才是正确答案
即,只需要返回一个新值(新的引用),不要产生其它逻辑:
export default function App() {
const [state, setState] = useState({value: { a: { b: { c: 1 } } }});
useEffect(() => {
setState((res) => {
res.value.a.b.c = 2
return {value: res.value}
});
}, []);
return <h2>{state.value.a.b.c}</h2>;
}
这样就可以了
等等,有没有发现什么不得了的东西?
// vue
const data = ref({a:{b:{c:1}}})
data.value.a.b.c = 2
是的,开心的事情发生了,ref 的 value 好像有什么神奇的功效
不过,因为本身 data 就是被代理的,所以 ref 和 reactive 在这里没有本质区别
react 还可以利用函数,来进行变更(返回新的函数一样可以)
只是注意,useState 传函数的话,默认是惰性初始化,所以,需要再来一下箭头函数
这样,就避免了任何复制
等等,这就完了么?
还没完呢,着啥急!
如果每次访问和变更数据,都需要 state().a.b.c.d.e...
那岂不是每个组件都要写一大长串的代码?
如何将依赖进行聚焦,让共享状态的子组件,只考虑自己需要的数据呢?
对象代理
// react
const {data,setData} = useContext(Parent)
const setPartial = useCallback((res)=>{
setData(res=>{
const value = res()
value.a.b.c = res
return ()=>value
})
},[])
// 此时运行函数,仅仅是获取引用的计算,复杂度1
const currentData = data()
const partial = useMemo(()=>{
currentData.a.b.c
},[currentData])
// vue
const {data} = inject('parent')
const partial = toRef(toRef(toRef(data,'a'),'b'),'c')
此时,React 的 partial,setPartial 和 Vue 的 partial,形成了跨组件的响应性!并做好了关注度分离
这个代理过程,我们叫做:
数据上的 Aggregation (分形聚合)
这里出现了数据上的 Aggregation,而上一篇文章出现了事件的 Aggregation
两种 Aggregation 又会碰处怎样的火花呢?请大家拭目以待~
Angular?
对不起,Angular 这里可以聊的很少,为什么?
在上一篇文章中,大家知道,配合 Rxjs 使用的 Angular,终极目标之一就是,搞掉所有的中间状态
因此,set 是不需要进行代理的 ……
而 get 方法,对于 Rxjs 来说,是个流操作 pipe(pluck('a','b','c'))
并且,事件发生,变更检测发生,因此,你没有通知框架数据变更的需求……
好像 Angular + Rx 确实很厉害,当然,只是头发掉的多,哈哈哈
不要使用全局状态管理库!
是的,你想各方面做到极致的话,不要使用全局状态管理库!
原因很简单,看到前面对象代理的问题没?请问关注度如何分离?
即便可以 mapGetters, 异步,惰性加载什么,代理之后还可能附加更多逻辑,难道还有弄个 mapSetter,mapSetterAysnc,mapSetterAftterEffect 不成?
废代码太多,会干扰你的开发的~
okay,数据流,事件流分形已过,现在又加上了数据结构分形
很快啊!
心潮澎湃的超级功能,就要来了
敬请期待 DDD/微服务 (DDD 的本质是一种软件设计方法,而微服务架构是具体的实现方式)
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 杜帅在掘金 原文链接:https://juejin.im/post/6944382681400475678