-
Notifications
You must be signed in to change notification settings - Fork 1
Description
通过 observer 包裹的组件只可以订阅到在他们自己渲染的期间的可观察对象
如果要将可观察对象 objects / arrays / maps 传递到子组件中, 他们必须被 observer 包裹。 通过 callback 回调的组件也是一样。
如果你非要传递可观察对象到未被observer包裹的组件中, 要么是因为它是第三方组件,要么你需要组件对Mobx 无感知,那你必须在传递前 转换可观察对象为显式 。
比如浅拷贝一遍:
const plainObject = { ...observableObject };
const plainArray = observableArray.slice();
const plainMap = new Map(observableMap);如果修改的属性比较深,则需要将修改属性整条链路上的对象都进行浅拷贝,嫌麻烦可以使用 toJS 工具函数。
将 observer 组件内使用到的数据属性 observable 化,传入非组件的数据不要 observable 化。比如使用 Table 组件,只需要将 data 使用 observable.ref 或 observable.shallow 做浅层次的转化为可观察。
使用 observable.ref 需要将传入的 data 整体替换才能触发更新,使用 observable.shallow 可以针对 data 做 push、pop 等浅层的数据修改,但只能触发当前组件的render,无法触发 Table 的更新,因为传入Table组件的 data 地址没有改变。
因为数组传入了一个非 observer 组件,React 中需要避免更改正用于 props 或 state 的引用数据类型的属性,一般在 shouldComponentUpdate 中只会做浅比较,修改引用类型数据的属性无法触发组件更新。
如果需要对传入 data 进行 push、pop 等操作, 可以使用结构重新赋值,比如
push→data = [...data, newItem]pop→data = [...data.slice(0, -1)]splice→data = [ ...data.slice(0, i), ...data.slice(i + 1)]
如果是需要修改比较深层的数据,比如修改某一行的某个属性,比较麻烦:
data = Object.assign([...data], { [i]: { ...data[i], [key]: value }})
不考虑性能可以直接 toJS / deepClone ,但是这会导致所有的子组件都更新。
当处理深层嵌套对象时,以 immutable 的方式更新它们令人费解。如遇到此类问题,请参阅 Immer 或 immutability-helper。这些库会帮助你编写高可读性的代码,且不会失去 immutability 带来的好处。
之前有个误解,observable 数据发生变更,observer 组件及其子组件使用到相关数据的地方都会自动更新。但这都是之前 YY 的,以为用的 mobx 后,react 就能完全像 vue 一样使用了。
而事实是没有被 observer 处理的组件就是普通的 react 组件,使用的还是 react 的那套更新机制。
observer 组件的更新只有当组件 render 中的依赖的数据发生变更才会 update 。像上述的 table data 内部值发生变更不会触发 update ,仅仅 data 的数量、或者值的引用发生变更,才会触发 update 。子元素的 update 就归子元素自己的更新机制管理了。
分析一下 observer 组件的更新过程:
依赖的库:mobx-react / mobx-react-lite
后者仅支持函数式组件,前者在后者基础上增加对 class 组件的支持。
对于 class 组件,内部使用 Component.prototype.forceUpdate.call(this) 来触发更新,在使用 makeClassComponentObserver 处理组件的过程中,使用 createReactiveRender 重写了 render:
target.render = function () {
if (!isUsingStaticRendering()) {
this.render = createReactiveRender.call(this, originalRender);
}
return this.render();
};createReactiveRender 方法内部通过调用 reactiveRender → createReaction 收集依赖触发更新:
const createReaction = () => {
const reaction = new Reaction(`${initialName}.render()`, () => {
if (!isRenderingPending) {
isRenderingPending = true;
if (this[mobxIsUnmounted] !== true) {
let hasError = true;
try {
setHiddenProp(this, isForcingUpdateKey, true);
if (!this[skipRenderKey]) {
Component.prototype.forceUpdate.call(this);
}
hasError = false;
} finally {
setHiddenProp(this, isForcingUpdateKey, false);
if (hasError) {
reaction.dispose();
// Forces reaction to be re-created on next render
this.render[mobxAdminProperty] = null;
}
}
}
}
});
reaction['reactComponent'] = this;
return reaction;
};函数式组件则是通过 useObserver 来进行转换,内部通过自定义的 forceUpdate 方法触发更新,类似 ahooks 中的 useUpdate 实现:
const [, setState] = React.useState();
const forceUpdate = () => setState([] as any);内部同样时通过创建一个 Reaction 来收集依赖触发更新:
const newReaction = new Reaction(
observerComponentNameFor(baseComponentName),
() => {
if (trackingData.mounted) {
// We have reached useEffect(), so we're mounted, and can trigger an update
forceUpdate();
} else {
trackingData.changedBeforeMount = true;
}
},
);