看到 推上 有个老外讲述他如何用 MobX 来解耦业务逻辑和 UI 逻辑时,我被这个理念吸引住了,也决定引入 MobX 一试。业务逻辑被隔离出来后,代码逻辑变清晰了;MobX 也让我可以把 React 的 immutable 理念抛诸脑后,写代码起来也轻松了很多。
但我深度使用时,发现 MobX 时常有反直觉的不刷新问题。
比如有这么一个 CheckboxList
组件,它的功能是展示多个复选框,允许用户选中多个:
用这份代码实现:
import { Checkbox, Flex } from '@mantine/core';
import { observer } from 'mobx-react-lite';
import React from 'react';
type Props = {
data: string[];
selectedItems: string[];
setSelectedItems: (items: string[]) => void;
};
const CheckboxList = observer(({ data, selectedItems, setSelectedItems }: Props) => {
return (
<Flex gap="sm">
{data.map((item) => (
<Checkbox
key={item}
label={item}
checked={selectedItems.includes(item)}
onChange={(event) => {
let newSelectedItems: string[];
if (event.currentTarget.checked) {
newSelectedItems = [...selectedItems, item];
} else {
newSelectedItems = selectedItems.filter((selectedItem) => selectedItem !== item);
}
newSelectedItems.sort();
setSelectedItems(newSelectedItems);
}}
/>
))}
</Flex>
);
});
CheckboxList.displayName = 'CheckboxList';
export default CheckboxList;
它是一份完全合理的 React 代码。但是当你配合使用 MobX 的 observable 时,就会产生问题。下面的使用示例展示这个问题:
import { makeAutoObservable } from 'mobx';
import React from 'react';
import CheckboxList from '$src/component/general/CheckboxList';
class C {
selectedData: string[] = [];
constructor() {
makeAutoObservable(this);
}
setSelectedData(data: string[]) {
this.selectedData = data;
}
}
const data = ['a', 'b', 'c'];
export default function Test() {
const [c] = React.useState(new C());
return (
<CheckboxList
data={data}
selectedItems={c.selectedData}
setSelectedItems={c.setSelectedData.bind(c)}
/>
);
}
初看这份代码并没有什么问题。但事实上,无论你怎么点击那些复选框,页面都没有一丝变化。
原因在于:MobX 只能在你访问一个 observable(c
)的属性(selectedData
)时,才能 reactive 起来。而这份代码里面,我们将 c.selectedData
直接传给了 CheckboxList
组件,导致界面不 reactive。即是说,点击复选框时,c.setSelectedData
的确被调用了,c.selectedData
的确被更新了,但是 CheckboxList
感知不到这个更新,因此没有体现在 DOM 上。
这真是个很挫的限制。一个 workaround 是,把 CheckboxList
接受的 selectedData
的类型改了:
// 原来
selectedItems: string[];
// 改成
selectedItems: () => string[];
调用处也改成函数传入:
<CheckboxList
data={data}
selectedItems={() => c.selectedData}
setSelectedItems={c.setSelectedData.bind(c)}
/>
这样每次 CheckboxList
re-render 时,都会调用到 c.selectedData
,因此界面又 reactive 起来了。但这实在是很挫的做法。
而且在实际使用 MobX 过程中,发现网络上相关的内容还是挺少的。我选择 MobX 的原因在于:
- 网络上对 Redux 过于啰嗦的抱怨很多
- MobX 是发展了很多年的项目,应该相对成熟
但事实上 MobX 并不如我想象中省心。下回会直接用行业标准 RTK。