React 的状态管理工具有 Redux 和 mobx,听说他们各有千秋。作为一个俩都没用过的菜鸟,经过一系列不是很深层次的了解之后后,发现 Redux 学习成本稍高,样板代码稍多,短暂的思考了一下,决定使用 mobx 作为下个项目的状态管理工具。
但是。。。由于了解的没有那么深刻,在搭建这个项目的时候,果不其然又踩了很多的坑,下面,我将记录下我的学习(踩坑)历程,方便后来者及时规避,谨慎敲码。
第一步:mobx 基础知识学习
@observable 装饰器
mobx 使用类定义语法,将需要管理的状态放在类中,使用@observable 装饰器将对象转化为可观察的对象。 例如:
class store {
@observable title = ''
}
@observable 可修饰的数据类型有:JS基本数据类型、引用类型、普通对象、类实例、数组和映射。
需要注意的是,使用 @observable 修饰过后的数组,对象,map等复杂的数据结构,类型就是(observable)对象,比如:
class store {
@observable todos = [1,2,3]
console.log(Array.isArray(this.todos))
}
得到的结果将是 false,虽然它的类型不再是数组,但是它可以使用所有数组的方法,也可以正常的访问所有数组的元素。如果你想将 observable 对象转换为原始类型,可以使用 toJS
函数。
toJS
是 mobx 提供的一个工具函数。他可以递归地将一个(observable)对象转换为 javascript 结构,使用方法如下:
import { toJS } from 'mobx'
class store {
@observable todos = [1,2,3]
console.log(Array.isArray(toJS(this.todos)))
}
这样得到的结果就将是 true 了
@computed
@computed 计算属性,即根据当前现有的值或者状态得到其他衍生的值,例如:
class store {
@observable firstName = ''
@observable lastName = ''
@computed get fullName () {
return this.firstName + '·' + this.lastName
}
}
autorun
当你想创建一个响应式函数,而该函数本身永远不会有观察者时,可以使用 mobx.autorun。当可观察数据发生变化时,autorun 会自动去执行依赖可观察数据的行为。 例如:
class Store {
@observable firstName = ''
@observable lastName = ''
}
const store = new Store()
autorun(()=>{
console.log(store.firstName + '·' + store.lastName)
})
store.firstName = 'lily'
store.lastName = 'martin'
执行后会发现 autorun 会执行 3 次,每次更改 firstName 或 lastName 它都会执行
autorun 与 computed: 如果你想响应式的产生一个可以被其它 observer 使用的值,请使用 @computed,如果你不想产生一个新值,而想要达到一个效果,请使用 autorun
@action
@action 是任何用来修改状态的东西,@action.bound 则还可以用来自动地将动作绑定到目标对象。 例如:
class Ticker {
@observable tick = 0
@action.bound
increment() {
this.tick++ //this 永远是正确的
}
}
const ticker = new Ticker()
setInterval(ticker.increment, 1000)
mobx 注入
学习完基本的 mobx 知识点后, 我创建了两个 store 文件,分别用来存储 user 相关的数据和 订单相关的数据,以下是示例:
// store/user.js
class userStoreClass {
@observable userId = ''
@observable phone = ''
...
}
const userStore = new userStoreClass()
export default userStore
// store/order.js
class orderStoreClass {
@observable orderId = ''
@obervable orderTime = ''
...
}
const orderStore = new orderStoreClass()
export defalut orderStore
然后将两个 store 注入到 index 中
// index.js
import { Provider } from 'mobx-react';
import orderStore from './stores/order'
import userStore from './stores/user'
const stores = {
orderStore, userStore
};
ReactDOM.render(
<Provider {...stores}>
<App />
</Provider>, document.getElementById('root'))
mobx 使用
首先引入
import { observer, inject } from 'mobx-react'
@inject('userStore')
@observer
class ViewBox extends React.Component {
}
@observer: observer 函数/装饰器可以用来将 React 组件转变成响应式组件。 它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。 observer 是由单独的 mobx-react 包提供的。
@inject: 从mobx-react4 开始,要连接到这些 stores,必须要传递一个 stores 名称的列表给 inject,这使得 stores 可以作为组件的 props 使用。
引用完成后,我们可以直接在 prop 中使用 store 中的数据和方法。 例如:
import { observer, inject } from 'mobx-react'
@inject('userStore')
@observer
class ViewBox extends React.Component {
render() {
return <div>{this.props.userStore.phone}</div>;
}
}
阶段总结:
这样一系列流程下来, mobx 算是可以搭好了,可以愉快的在上面码代码了,但是码着码着,突然想到试试React hooks。
众所周知 hooks 是 react 近年来比较重大的一次更新,看完文档之后也是心痒痒。但是 hooks 必须要在函数组件中使用,但是函数组件要怎么注入 mobx 呢?试了一下直接跟 class 组件相同的引用方式,发现果然 想的美。。。怎么办呢?翻看 mobx 的中文文档,没有找到与 hooks 相关的一点点蛛丝马迹(怀念Vue...)只能忍着脑壳疼去翻官方文档。。。
mobx + hooks 探索
一顿搜索猛如虎,终于我找到了~~
首先上链接:mobx-react.js.org/libraries#c…
首先,官方给出了 hooks 相关的几个版本推荐:
- 如果你还没有使用 hooks,安心使用mobx-react @ 5 吧~
- 如果你想将项目迁移到 hooks,你的项目中既有函数式组件又有class组件,你可以使用包含
mobx-react-lite
的mobx-react @ 6
,并将其自动用于函数式组件。 - 如果你的项目完全使用函数式组件,直接使用
mobx-react-lite
,它支持hooks并且更快更小!
那么我们要怎么结合hooks使用mobx呢?上链接:mobx-react.js.org/recipes-con…
由于我们的项目基本上都会涉及到多个store,我就直接看 Multiple global stores
这一节啦
结合我们上面的例子,流程与官方例子一致。
先创建两个仓库
// store/user.js
export default class userStoreClass {
@observable userId = ''
@observable phone = ''
...
}
// store/order.js
export default class orderStoreClass {
@observable orderId = ''
@obervable orderTime = ''
...
}
注意:与上面的例子不同的是,我这里导出的是 class 而不是 class 的实例,因为官方文档中指出:
全局保存 class 实例被认为是一种不好的做法,因为在一些应用程序的管理单元和集成测试时,会引起问题。
既然没有导出实例,那我们在index.js 引用的时候也要注意了,不能直接将 class 注入到项目中,必须先实例化
// index.js
import { Provider } from 'mobx-react';
import orderStoreClass from './stores/order'
import userStoreClass from './stores/user'
const stores = {
orderStore:new orderStoreClass()
userStore:new userStoreClass()
};
ReactDOM.render(
<Provider {...stores}>
<App />
</Provider>, document.getElementById('root'))
这样我们才能继续在 class 组件中使用 mobx
继续 hooks 的兼容,接下来我们要创建一个 storesContext,来管理我们的store:
// src/contexts/index.js
import React from 'react'
import orderStoreClass from './stores/order'
import userStoreClass from './stores/user'
export const storesContext = React.createContext({
orderStore:new orderStoreClass()
userStore:new userStoreClass()
})
最后,让我们使用自定义useStores hook 来访问导出的storeContext值。
// src/hooks/use-stores.js
import React from 'react'
import { storesContext } from '../contexts'
export const useStores = () => React.useContext(storesContext)
现在我们就可以在函数式组件中使用 store 啦
import React from 'react'
import { observer } from 'mobx-react'
import { useStores } from '../hooks/use-stores'
// src/components/Counter.js
export const Counter = observer(() => {
const { userStoreClass } = useStores()
return (
<>
<div>{userStoreClass.userId}</div>
<button onClick={() => userStoreClass.add()}>++</button>
<button onClick={() => userStoreClass.toggle()}>--</button>
</>
)
})
以上