解决控制台 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() { 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}/>
|
这里有更明显的例子。
总结