• Mobx采用单向数据流: 动作 改变 状态 ,从而改变 衍生 (视图)
  • 状态 改变时所有 衍生 都会进行原子级的自动更新,所以永远不可能观察到中间值
  • 计算值 是延迟更新的,任何不在使用状态的计算值将不会更新,直到需要它进行副作用(I / O)操作时。 如果视图不再使用,那么它会自动被垃圾回收
  • 所有的 计算值 都应该是 纯净 的,它们不应该用来改变 状态 (使用纯函数生成计算值)
  • import { observable, autorun } from 'mobx';
    var todoStore = observable({
        /* 一些观察的状态 */
        todos: [],
        /* 推导值 */
        get completedCount() {
            return this.todos.filter(todo => todo.completed).length;
    /* 观察状态改变的函数 */
    autorun(function() {
        console.log("Completed %d of %d items",
            todoStore.completedCount,
            todoStore.todos.length
    /* ..以及一些改变状态的动作 */
    todoStore.todos[0] = {
        title: "Take a walk",
        completed: false
    // -> 同步打印 'Completed 0 of 1 items'
    todoStore.todos[0].completed = true;
    // -> 同步打印 'Completed 1 of 1 items'
    

    Action

    @action.bound

    对类方法添加改装饰器的效果是,将其中的this绑定为当前实例,也因此对于箭头函数形式的类方法无需使用该装饰器(它根据作用域链自动绑定了当前实例)

    runInAction

    这是个简单的工具函数,它接收代码块并在(异步的)动作中执行。这对于即时创建和执行动作非常有用,例如在异步过程中。runInAction(f)action(f)() 的语法糖。

    异步action

    关于action非常重要的一点是:当它运行时,只会在触发当前运行环境内(或者说当前执行栈)的observable状态的更新(一种特殊情况是async/await函数,虽然看上去代码块是同一个执行栈内,但实际上每一个await都会触发一个新的异步函数的执行栈,所以只有第一个await之前的代码会触发状态更新)。这意味着当配置了只允许action修改状态时,以下情况下状态更新会出问题

  • 在action中调用其它非action函数
  • 在action中执行setTimeout、promise.then、async/await语句等异步代码,如果这些代码在改变状态时没有使用action
  • 异步action的可选方法包括

  • 将异步回调函数封装成action
  • 在异步回调函数中修改状态的部分,使用runInAction执行
  • 异步代码写在generator函数中,并使用mobx.flow封装(function*代替async、yield代替await)
  • mobx.configure({ enforceActions: true })
    class Store {
        @observable githubProjects = []
        @observable state = "pending"
        fetchProjects = flow(function * () { // <- 注意*号,这是生成器函数!
            this.githubProjects = []
            this.state = "pending"
            try {
                const projects = yield fetchGithubProjectsSomehow() // 用 yield 代替 await
                const filteredProjects = somePreprocessing(projects)
                // 异步代码块会被自动包装成动作并修改状态
                this.state = "done"
                this.githubProjects = filteredProjects
            } catch (error) {
                this.state = "error"
    
  • 不能将js基础类型数据变为observable,这是做不到的,mobx只能监听引用类型数据
  • 在react组件中应用@observer,实质是用autorun封装了其render方法,使其对observable状态的变动做出反应及时更新
  • 在 ES5 中没有继承数组的可靠方法,因此 observable 数组继承自对象。 这意味着一般的库没有办法识别出 observable 数组就是普通数组(比如Array.isArray),使用 isObservableArray(observable) 来检查是否是 observable 数组。
  • 计算值默认在未被观察时会暂停计算,除非设置为强制保持活动
  • reactions只有当其观察的所有状态都被垃圾回收了才会被垃圾回收,所以当不再需要使用它们的时候,推荐使用清理函数(这些方法返回的函数)来停止它们继续运行
  • 在React项目中的实战方式

  • 首先需要有若干个Mobx Store用于:存储应用状态数据(即state,及其衍生状态computed values)、提供更新状态的能力(action)
  • 在某个父组件通过Provider将Store下发,使得子组件能够获取到
  • 通过inject和observer方法封装子组件,将组件变为Mobx视图并将指定的Store注入进其props中
  • 被注入的组件从props中拿到store后获取其state进行业务渲染,获取其action用于更新state,同时触发组件重渲染
  • // count.ts
    class Store {
        @observable count: number = 0;
        @action.bound
        setCount = (newCount: number): void => {
            this.count = newCount;
    export default Store;
    // parent.tsx
    import { Provider, observer } from 'mobx-react';
    import CountStore from '@store/count';
    const countStore = new CountStore();
    const Parent: React.FC = () => {
        const stores = { countStore };
        return (
            <Provider {...stores}>
              <Child />
            </Provider>
    export default Parent;
    // child.tsx
    import { inject, observer } from 'mobx-react';
    const Child: React.FC = ({ countStore }) => { // 这里会有ts提示:props不存在countStore的定义
        const { count, setCount } = countStore;
        const handleClick = () => setCount(count + 1);
        return (
            <div onClick={handleClick}>{count}</div>
    export default inject('countStore')(observer(Child));
    

    使用React Hooks

    与上述方式不同之处在于,不是通过inject注入+props获取的方式,而是直接在子组件中使用hooks来获取Store,这样的好处在于将Store从组件的props中剥离出来,ts类型定义更方便(如果采用上面的代码,会发现Child组件的props中未定义countStore属性而导致ts类型错误)

    // useMobxStores.ts
    mport * as React from 'react';
    import { MobXProviderContext } from 'mobx-react';
    const useMobxStores = <T extends Record<string, any>>(): T => {
      return React.useContext<T>(MobXProviderContext as React.Context<T>);
    export default useMobxStores;
    
    // child.tsx
    import { inject, observer } from 'mobx-react';
    import CountStore from '@store/count';
    interface Stores {
        countStore: CountStore;
    const Child: React.FC = () => {
        const { countStore } = useMobxStores<Stores>() // 通过泛型指定了countStore的类型
        const { count, setCount } = countStore;
        const handleClick = () => setCount(count + 1);
        return (
            <div onClick={handleClick}>{count}</div>
    export default observer(Child);
    复制代码
    分类:
    前端
    标签: