MobX
MobX 是一个状态管理库,它会自动收集并追踪依赖,开发人员不需要手动订阅状态,当状态变化之后 MobX 能够精准更新受影响的内容,另外它不要求 state 是可 JSON 序列化的,也不要求state 是 immutable,MobX 推荐的数据流如下图所示:
本文先以一个 demo 单独介绍 Mobx 的用法,再介绍如何将 Mobx 与 React 结合实现 React 应用程序的状态管理。
从一个 demo 开始
这部分用 MobX + TypeScript 实现一个 TODO List 的 demo,MobX 的版本为 6.5.0,TypeScript 的版本为 4.5.4,将 TypeScript 编译器配置项 useDefineForClassFields 设置为 true。
创建类并将其转化成可观察对象
创建 ToDoItem 类和 ToDoList 类,ToDoItem 类的代码如下:
import { makeObservable, observable, action } from 'mobx'
class ToDoItem {
id: number
name: string
status: 0 | 1
changeStatus(status: Status) {
this.status = status
}
constructor(name: string) {
this.id = Uid ++
this.name = name
this.status = 0
// 注意这里
makeObservable(this, {
status: observable,
changeStatus: action
})
}
}
用 makeObservable 将 ToDoItem 实例变成可观察的,用 observable 标记 status 字段,让 MobX 跟踪它的变化,changeStatus 方法用于修改 status 的值,所以用action标记它。
ToDoList 类比 ToDoItem 类复杂一些,它收集 Todo-List Demo 需要的全部数据,代码如下:
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
class ToDoList {
searchStatus?: 0 | 1
list: ToDoItem[] = []
get displayList() {
if (!this.searchStatus) {
return this.list
} else {
return this.list.filter(item => item.status === this.searchStatus)
}
}
changeStatus(searchStatus: Status | undefined) {
this.searchStatus = searchStatus
}
addItem(name: string) {
this.list.push(new ToDoItem(name))
}
async fetchInitData() {
await waitTime()
// 注意这里
runInAction(() => {
this.list = [new ToDoItem('one'), new ToDoItem('two')]
})
}
constructor() {
makeObservable(this, {
searchStatus: observable,
list: observable,
displayList: computed,
changeStatus: action,
addItem: action
})
}
}
与 ToDoItem 相比,ToDoList 多使用了 computed 标记,这是因为 displayList 的值由 searchStatus 和 list 通过一个纯函数计算而来,所以它被标记为 computed。fetchInitData 是一个异步方法,在其中用 runInAction 创建一个立即执行的 action 去修改 list 的值,从 fetchInitData 的实现可以看出,异步修改 state 和同步修改 state 没有差别,只要保证 state 是在 action 中修改的即可。
使用可观察对象
在上一步的 ToDoList 和 ToDoItem 的构造函数中,我们调用了 makeObservable 方法,并用合适的注解去标记实例字段,接下来用一段代码验证 MobX 是否按照要求跟踪state的变化。代码如下:
import { autorun} from 'mobx'
autorun(() => { console.log(toDoList.list.length) }) // line A
autorun(() => { console.log(toDoList.list) }) // line B
autorun 接收一个函数,该函数同步执行过程中访问的 state 或计算值发生变化时,它会自动运行,另外,调用 autorun 时,该函数也会运行一次。使用 toDoList.addItem 方法往 list 数组中 push 一个事项,你会发现上述 line A 的函数会运行,但是 line B 的函数不会运行;使用 toDoList.fetchInitData 方法给 list 数组赋值,line A 和 line B 的函数都会运行,出现这种差异是因为 autorun 使用全等(===)运算符确定两个值是否相等,但它认为 NaN 等于 NaN 。
用如下一段代码验证 MobX 是否按照要求跟踪 ToDoItem 实例的 state 的变化:
import { autorun} from 'mobx'
autorun(() => {
if (toDoList.list.length) {
console.log(toDoList.list[0]?.status)
}
})
reaction(() => toDoList.list.length, () => {
toDoList.list[0].changeStatus(1)// 修改status的值
})
当 reaction 的第一个参数返回 true 时,它的第二个参数会自动执行,上述代码在 reaction 中修改 toDoItem 的 status 字段,修改之后 autorun 能成功运行一次。
MobX 与 React 集成
现在将上一步的 TODO List 与 React 结合,为此需要安装 mobx-react-lite 或 mobx-react,mobx-react 比 mobx-react-lite 的功能更多,同时它的体积也更大,如果你的项目只使用函数组件,那么推荐安装 mobx-react-lite 而非 mobx-react,为了演示更多的用法本小节安装 mobx-react。另外,本小节会用到装饰器语法,所以要将 TypeScript 编译器配置项 experimentalDecorators 设置为true。下面是一个 MobX + React 的简单示例:
import { observer } from 'mobx-react'
import toDoList, { Status } from '../../mobx/todo'
const ToDoListDemoGlobalInstance= observer(
class extends React.Component<{}, {}> {
componentDidMount() {
// 3s之后修改 searchStatus 的值
setTimeout(() => {
toDoList.changeStatus(Status.finished)
}, 3000);
}
render() {
return (
<div>searchStatus: {toDoList.searchStatus}</div>
)
}
}
)
observer 是一个高阶组件,它会订阅组件在渲染期间访问的可观察对象,可观察对象指的是用 makeAutoObservable 、makeObservable 或 observable 转换之后的对象,当组件渲染期间访问的 state 和计算值发生变化时,组件会重新渲染。上述代码,组件被装载 3s 后将修改 searchStatus 的值,由于 render 方法访问了 searchStatus 的值,所以组件会重新渲染。observer 除了以高阶组件的形式使用之外,还能以装饰器的形式使用。
在组件中使用可观察对象
下面介绍 6 种在组件中使用 MobX 可观察对象的写法。
1. 访问全局的类实例
上一个示例代码便是在组件中直接访问全局的类实例,在这里不再举更多的示例代码。
2. 通过 props
这种方式是指将可观察对象通过 props 的形式传递到组件中,代码如下:
import { observer } from 'mobx-react'
import toDoList, { Status } from '../../mobx/todo'
@observer
class ToDoListDemoByProps extends React.Component<{toDoList: ToDoList}, {}> {
componentDidMount() {
setTimeout(() => {
toDoList.changeStatus(Status.finished)
}, 3000);
}
render() {
// 读取props中的可观察对象
return (
<div>ToDoListDemoByProps - searchStatus: {this.props.toDoList.searchStatus}</div>
)
}
}
//使用ToDoListDemoByProps
<ToDoListDemoByProps toDoList={toDoList}/>
3. 通过 React Context
这种方式是指通过 React Context 让可观察对象在整个被 Context.Provider 包裹的组件树中共享,代码如下:
import { observer } from 'mobx-react'
import toDoList, { Status, ToDoList } from '../../mobx/todo'
// 创建一个用observer包裹的函数组件
const ToDoListDemoByContext = observer(() => {
// 在函数组件中使用Context
const context = useContext(todoContext);
useEffect(() => {
setTimeout(() => {
context.changeStatus(Status.finished)
}, 3000);
})
return (
<div>ToDoListDemoByContext - searchStatus: {context.searchStatus}</div>
)
})
// 往Context传值
<todoContext.Provider value={toDoList}>
<ToDoListDemoByContext/>
</todoContext.Provider>
4. 在组件中实例化 observable class 并存储它的实例
这种方式指的是在组件作用域中实例化类,并且将结果保存到组件的某个字段中,如果在函数组件中使用这种方式,那么还需要用到 useState。代码如下:
const ToDoListFuncDemoLocalInstance= observer(() => {
// 实例化类
const [ todoList ] = useState(() => new ToDoList())
useEffect(() => {
setTimeout(() => {
// 使用实例方法更新状态
todoList.changeStatus(Status.finished)
}, 3000);
})
return (
<div>ToDoListDemoLocalInstance - searchStatus: {todoList.searchStatus}</div>
)
})
对于类组件而言,只需要将 new ToDoList() 的结果保存在它的实例属性上,之后在组件中访问该实例属性,代码如下:
@observer
class ToDoListClassDemoLocalInstance extends React.Component<{}, {}> {
todoList = new ToDoList()
// other
}
5. 在组件中调用 observable 方法创建可观察对象
这种方式不使用类去创建可观察对象,而是使用 observable 方法创建可观察对象,与第 4 种方式一样,如果在函数组件中还要用到 useState,代码如下
import { observable } from 'mobx'
const LocalObservableDemo = observer(() => {
// 调用observable
const [counter] = useState(() => observable({
count: 0,
addCount() {
this.count ++
}
}))
return <>
<div>{counter.count}</div>
<button onClick={() => counter.addCount()}>add</button>
</>
})
上述代码使用 mobx 导出的 observable 方法创建一个可观察对象,并在函数组件使用该对象,当它的 count 属性值发生变化时,组件将重新渲染。对于类组件而言只需要将 observable 函数的结果保存到实例属性上即可。
6. 在函数组件中使用 useLocalObservable
useLocalObservable 是 useState + observable 简写版本,只能在函数组件中使用,代码如下:
import { observer, useLocalObservable } from 'mobx-react'
const UseLocalObservableDemo = observer(() => {
const counter = useLocalObservable(() => ({
count: 0,
addCount() {
this.count ++
}
}))
return <>
<div>{counter.count}</div>
<button onClick={() => counter.addCount()}>add</button>
</>
})
对于函数组件而言,useLocalObservable 只是一个自定义Hook,它返回一个可观察对象。
有多种方式让组件在渲染阶段使用可观察对象,不管是哪种方式,组件都必须具备观察能力,否则,当渲染期间访问的 state 和计算值发生变化时,组件不会重新渲染。笔者在使用 MobX 做状态管理时,最常用的方式是第 1 和第 2 种。第 4、5、6 种方式必要性不大。
让组件具备观察能力
observer 是让组件具备观察能力最常见的方式,在这里介绍另一种让组件具备观察能力的方式,即:Observer组件。用法如下:
import { Observer } from 'mobx-react'
class ObservableDemo extends React.Component<{},{}> {
render() {
return (
<>
<div>{toDoList.searchStatus || '-'}</div> {}
<Observer>
{() => <div>{toDoList.searchStatus || '-'}</div>} {}
</Observer>
</>
)
}
}
Observer 组件会创建一个匿名的观察区域,在上述代码中,如果 toDoList.searchStatus 的值发生变化,那么 lineB 会重新渲染,但是 lineA 不会重新渲染。
总结
将 MobX 与 React 结合在一起的关键在于用 observer 包裹组件以及在组件中读取可观察对象,observer 不关心可观察对象从哪里来,也不关心如何读取可观察对象,只关心在组件中可观察对象是否可读。对于习惯面向对象编程的工程师而言,用 MobX 做状态管理会比用 Redux 做状态管理更得心应手。
以上就是Mobx 实现 React 应用的状态管理的详细内容,更多关于Mobx React 应用状态管理的资料请关注编程网其它相关文章!