Skip to content

引入Mobx #28

@sunyongjian

Description

@sunyongjian

跟 Redux 相比

  • 函数式 VS 面向对象
  • redux 需要 connect,也需要 Immutable Data,reducer,action,文件、代码量较多,概念也多。 mobx 直接引用对象组织,修改数据。
  • redux 数据流动很自然,任何 dispatch 都会导致广播,需要依据对象引用是否变化来控制更新粒度。mobx 数据流流动不自然,只有用到的数据才会引发绑定,局部精确更新,但免去了粒度控制烦恼。
  • redux 有时间回溯,每个 action 都被记录下来,可预测性,定位错误的优势。mobx 只有一份数据引用,不会有历史记录。
  • redux 引入中间件去解决异步操作,以及很多复杂的工作。mobx 没有中间件,数据改了就是改了,没有让你增加中间件的入口。

为什么用 mobx

  • 简单,概念,代码少
  • class 去定义、组织 store,数据、computed、action 定义到一块,结构更清晰,面向对象的思维更适合快速的业务开发
  • 某个 store 的引用不一定非在组件中才能取到,因为是对象,可以直接引用。比如在 constant.js 文件中可以定义一些来自 store 的变量。
  • 据说效率更高。mobx 会建立虚拟推导图 (virtual derivation graph),保证最少的推导依赖

mobx 概念

flow
上图是 mobx 结合 react 使用的数据流图。

  • Observable state

    给数据对象添加可观测的功能,支持任何数据结构。

    class State {
      @observable price = 10;
    }
  • Computed values

    某个 state 发生变化时,需要自动计算的值。比如说单价变化,总价的计算

    class State {
      @observable price = 10;
      @observable count = 0;
      @computed get total() {
        return price * count;
      }
    }
  • Reactions

    Reactions 和 Computed 类似,都是 state 变化后触发。但它不是去计算值,而是会产生副作用,比如 console、网络请求、react dom 更新等。mobx 提供了三个函数用于自定义 reactions。

    const state = new State;
    autorun(() => {
      console.log("Current Price : " + state.price);
    })

    每当 state 中的单价 price 发生变化,控制台都会打印。注意这里 state.count 变化是不会执行的。

    react 组件 reactions。利用 mobx-react 中的 observer 对 react 组件进行包装。

    import React, {Component} from 'react';
    import {observer} from "mobx-react";
    
    const View = (price, count) => <div>
      {price * count}
    </div>
    
    @observer
    class Container extends Component {
      render() {
        return (
          <div>
            <Input />
            <View 
              price={state.price} 
              count={state.count}
            />
          </div>
        )
      }
    }
  • Actions

    个人觉得 mobx 中的 action 不像 redux 中是必需的,算是我们把一些修改 state 的操作都规范的用 action 标注,并且可以描述这个 action。随意的 state.price = 5 更改 state 都是可以起到作用的,只不过这样的话会很乱,你完全不知道哪里的操作引起了 state 的变化,所以 mobx 是建议你对 state 的副作用操作,都用 @aciton 去装饰。

    class State {
      @observable price = 10;
      @observable count = 0;
      @computed get total() {
        return price * count;
      }
      @action priceChange(val) {
        this.price = val;
      }
      @action countChange(val) {
        this.count = val;
      }
    }
    const state = new State;
    
    class Input extends Component {
      handleChange = type => e => {
        const value = e.target.value;
        this.props[`${type}Change`](value);
      }
      render() {
        return (
          <div>
            价格:<input type='number' onChange={this.handleChange('price')} />
            数量:<input type='number' onChange={this.handleChange('count')} />
          </div>
        )
      }
    }
    const View = (total) => <div>
      {total}
    </div>
    
    @observer
    class Container extends Component {
      render() {
        const { total, priceChange, countChange } = state;
        return (
          <div>
            <Input 
              priceChange={priceChange}
              countChange={countChange}
            />
            <View 
              total={total}
            />
          </div>
        )
      }
    }

    ps: 以上代码我都没跑过- -,有任何问题概不负责

异步action

只需要把异步操作、请求也放到 @action 里就好了。
假设我们已经封装好了一个 fetch 的方法,并返回一个 promise,现在去使用一个异步 action 请求 list 数据

  @observable list = [
    loading: false,
    dataSource: [],
  ];
  @action getList = async () => {
    this.list.loading = true;
    const data = await fetch();
    this.list.dataSource = data;
  }

常见问题

  • observable 之后的数组并不是普通数组的形式,所以有时候在组件内部做判断的时候可能会有问题,通常用 toJS() 转化一下。

  • observer 不要放到顶层 Page,因为当随便一个 state 的属性都改变,整个 Page 都会 render,即使其他 children 组件的 dom 结构没变,但还是会有一些性能开销的。所以 observer 尽量去包装小组件。

  • 与自定义的 hoc 连用的时候,observer要 放到最里面。因为要包装组件的 render 函数,还要收集 state 的依赖。形如

    @inject('store')
    @myHOC
    @observer
    class A extends Component {
        
    }

    如果 observer 在外层,state 改变组件是不会做相应的。

  • 不必像 redux 那样,把所有的 store 都注入 Provider, 虽然 mobx 支持这么做。一些公用的 store,比如用户信息啦,可以注入 Provider,然后子组件通过 inject 注入。但是大部分不能公用的列表数据,可以直接 import

    import listStore from 'stores/list';
    
    @observer class List extends Component {
      render() {
        return (
          <ul>
            {listStore.list.map(item => <li>{item.name}</li>)}
          </ul>
        )
      }
    }
    
  • mobx 和 react 的 state

    使用了 mobx,你会发现 setState 那种写法以及不能立刻生效是很不习惯的。大部分情况下,你都可以不去写 state,而是通过 observable 去定义变量,observer 包装组件,这些变量也可以定义到组件内部(可观察的局部组件状态)。例如:

    import {observer} from "mobx-react"
    import {observable} from "mobx"
    
    @observer class Count extends React.Component {
      @observable num = 0
    
      componentDidMount() {
        setInterval(() => {
          this.num++
        }, 1000)
      }
    
      render() {
        return <span>Count: { this.num } </span>
      }
    })
  • 其他,参照官方文档中文链接

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions