0%

react-epic 笔记

解决控制台 MobX 警告

MobX: Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed

MobX 在启用严格模式时,修改 observable state 的操作必须在 action 中进行。在异步动作的回调中执行的代码是不会被 action 包装的。在回调中修改 observable state 是无法通过 enforceActions 检查的。官方文档的解决办法是使用 Flow。但是因为需要用到 generator 和 yield,我选择使用了更简单的方法。

使用 runInAction 包裹在回调函数中执行的修改操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@action find() {
this.isLoading = true;
if (this.hasMore) {
Uploader.find(this.page, this.limit).then(
(newList: any) => {
this.append(newList);
runInAction(()=>{
this.page += 1;
if (newList.length < this.limit) {
this.hasMore = false;
}
})
}
).catch(() => {
message.error('加载失败').then();
})
}
}

MobX 6 使用装饰器踩坑

因为装饰器语法目前还不是 ES 的标准,所以 MobX 6 不再推荐使用,而更建议使用 makeObservable / makeAutoObservable 来代替。

如果想要继续使用原来的写法,则需要在构造函数中添加 makeObservable(this)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Todo {
id = Math.random()
@observable title = ""
@observable finished = false

constructor() {
// Mobx 6 版本需要加上这句话,才能实现响应式的状态更新
makeObservable(this)
}

@action
toggle() {
this.finished = !finished
}
}

React.lazy 和 suspense

React.lazy 可以实现组件的延迟加载,只有在组件渲染时再去加载包含引入路径的组件。

1
const Home = lazy(() => import('./pages/Home'))

而在延迟加载的过程中会有 loading 的过程,suspend 可以控制在 loading 过程中的显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<Suspense fallback={<div>Loading ...</div>}>
<Switch>
<Route path="/" exact>
<Home/>
</Route>
<Route path="/history">
<History/>
</Route>
<Route path="/about">
<About/>
</Route>
<Route path="/login">
<Login/>
</Route>
<Route path="/register">
<Register/>
</Route>
</Switch>
</Suspense>

使用 antd

在使用 mobx 搭配 antd 时发现删除 HistoryStore.list 中的数据并没有触发 UI 更新。在 StackOverflow 我发现了这个回答,解决了我的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const Table = observer(() => {
const {HistoryStore} = useStore();
const loadMore = () => {
HistoryStore.find();
};
useEffect(() => {
return () => {
HistoryStore.reset();
};
}, [HistoryStore]);
return (
<InfiniteScroll initialLoad={true} pageStart={1} loadMore={loadMore}
hasMore={!HistoryStore.isLoading && HistoryStore.hasMore} useWindow={true}>
<ListWrapper dataSource={toJS(HistoryStore.list)} renderItem={(item: any) => {
return (<List.Item key={item.id}>
<Img src={item.attributes.image.attributes.url} alt=""/>
<h5>{item.attributes.filename}</h5>
<Href href={item.attributes.image.attributes.url} target="_blank"
rel="noreferrer">{item.attributes.image.attributes.url}</Href>
<div >
<Button type="primary" onClick={() => HistoryStore.remove(item.id)}>删除图片</Button>
</div>
</List.Item>);
}}>
{HistoryStore.isLoading && HistoryStore.hasMore && (
<Loading>
<Spin tip="加载中"/>
</Loading>
)}
</ListWrapper>
</InfiniteScroll>
);
});

原因是 mobx 在 Table 组件中只会去监听 HistoryStore.list 指向的变化,并不会监听 HistoryStore.list 具体某一项属性的改变。而使用 toJS 创建克隆对象(读取HistoryStore 的每一项属性)后,mobx 可以在 Table 组件中监听到 list 长度的变化,从而触发 Table 组件重新渲染。事实上在这里改成下面的形式也可以触发重新渲染:

1
<ListWrapper dataSource={HistoryStore.list.length && History.list}/> //这里使用了 list 的 length 属性,使得 list 数组小于长度的索引指向的变化以及长度的变化都可以被 mobx 监听

这里有更明显的例子


总结