Mobx如此简单

4,695 阅读6分钟

本文说明:mobx v5.0.0^、mobx一些基本概念、mobx与react 一起使用的技巧、mobx在使用时注意点

  • mobx 是什么呢?为什么要开发出这个库呢?
  • mobx 里的一些关键概念联系:actions、state、computed values、reaction
  • mobx 常用的一些 API:@observable、@autorun、@when、@computed、@action
  • mobx 中调试追踪的工具
  • mobx 中哪些地方值得注意

Mobx是什么?

它就是简单的、可扩展的状态管理。 因为通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。

主要概念有哪些?

  • actions:一些改变状态值(state)的动作。
  • state:可观察的状态值
  • computed value:根据state,用pure function计算出来的值
  • reactions:因state或computed value变化而引起的反应,主要指视图UI重新渲染

常用装饰器

  • @observable 将一个变量变得可观察
  • @observer 常用于React组件,可监视其render函数里使用的可观察变量,从而作出相应reactions
  • @autorun 常用于组件类或store类的constructor里,用来创建实例时,可监视其函数参数里使用的可观察变量,从而作出相应reactions,一般是将函数再执行一遍。
  • @when 有条件的@autorun
  • @computed 通过可观察变量经过纯函数计算得来的值,使用时才会计算,没有使用时不会去计算
  • @action 能改变可观察变量值的操作(一般是函数方法)

如果不习惯用装饰器,用一些常用的函数方法也可以,本文只是写个人觉得的最佳实践,但后面项目实现时会实践。

TodoList 项目实现对比:github.com/peidongGuo/…

第一组:有没有使用Mobx

// 无 mobx
class TodoList extends Component {
 state = { //组件数据只能在state里,更改只能用setState()
    inputItem: "",
    listFlag: "all",
    allList: []
  };

  handleInputItem = e => {
    this.setState({
      inputItem: e.target.value
    });
  };

  handleItemStatus = (e, index) => {
    e.persist();
    let items = this.state.allList;
    console.log(items, index);
    items[index].isCompleted = e.target.checked;
    this.setState({
      allList: items
    });
  };
  ...
  render(){
    ...
    <input
                className="new-todo"
                placeholder="What needs to be done?"
                value={this.state.inputItem}
                onChange={this.handleInputItem}
                onKeyDown={this.handleAddItem}
                autoFocus={true}
              />
    ...
     <li
                        className={item.isCompleted ? "completed" : ""}
                        className={item.isEditing ? "editing" : ""}
                        key={index}
                        onDoubleClick={() => {
                          this.handleItemEdit(index, true);
                        }}
                      >
                        <div className="view">
                          <input
                            className="toggle"
                            type="checkbox"
                            checked={item.isCompleted}
                            onChange={e => {
                              this.handleItemStatus(e, index);
                            }}
                          />
                          <label>{item.title}</label>
                          <button
                            className="destroy"
                            onClick={() => {
                              this.handleItemDelete(index);
                            }}
                          />
                        </div>
                        <input
                          className="edit"
                          value={item.title}
                          onChange={e => {
                            this.handleItemTitle(e, index);
                          }}
                          onBlur={e => {
                            this.handleItemEdit(index, false);
                          }}
                        />
                      </li>
    ...
  }
  ...
  }
// 有 mobx 而且使用装饰器语法
@observer  // 将组件变得可观察
class TodoListMobx extends Component {
  @observable inputItem = "";  // 将属性变成可观察属性
  @observable listFlag = "all";
  @observable allList = [];

  @action // 此操作会引起可观察属性的变化
  handleInputItem = e => {
    console.log(e.target);
    this.inputItem = e.target.value; // 直接修改可观察属性
    console.log(this.inputItem);
  };

  @action
  handleItemToggle = (e, index) => {
    e.persist();
    this.allList[index].isCompleted = e.target.checked;
  };
  ...
  render(){  // 可观察的组件当其包含的可观察属性变化后,render会再次执行。
      ...
      <li
                        className={LiClass}
                        key={index}
                        onDoubleClick={() => {
                          item.isEdit = true;
                        }}
                      >
                        <div className="view">
                          <input
                            className="toggle"
                            type="checkbox"
                            checked={item.isCompleted}
                            onChange={e => {
                              item.isCompleted = !item.isCompleted;
                            }}
                          />
                          <label>{item.title}</label>
                          <button
                            className="destroy"
                            onClick={e => {
                              this.handleItemDelete(index);
                            }}
                          />
                        </div>
                        <input
                          className="edit"
                          value={item.title}
                          onChange={e => {
                            item.title = e.target.value;
                          }}
                          onBlur={e => {
                            item.isEdit = false;
                          }}
                        />
                      </li>
    ...
  }
  ...
 }

以上两组代码对比可以看到:mobx的加入将react组件里的数据变化操作简单化,机动化渲染UI。而且,整体代码书写上简单不少,而且理解上更容易。

第二组:有没有使用装饰器

使用装饰器的代码还是参考第一组的第二块代码

// 有 mobx 而且 不使用装饰器语法

class TodoListMobxNoDeco extends Component {
  inputItem = "";  
  listFlag = "all";
  allList = [];

 
  handleInputItem = e => {
    console.log(e.target);
    this.inputItem = e.target.value; 
    console.log(this.inputItem);
  };

  
  handleItemToggle = (e, index) => {
    e.persist();
    this.allList[index].isCompleted = e.target.checked;
  };
  ...
  render(){  // 可观察的组件当其包含的可观察属性变化后,render会再次执行。
      ...
      <li
                        className={LiClass}
                        key={index}
                        onDoubleClick={() => {
                          item.isEdit = true;
                        }}
                      >
                        <div className="view">
                          <input
                            className="toggle"
                            type="checkbox"
                            checked={item.isCompleted}
                            onChange={e => {
                              item.isCompleted = !item.isCompleted;
                            }}
                          />
                          <label>{item.title}</label>
                          <button
                            className="destroy"
                            onClick={e => {
                              this.handleItemDelete(index);
                            }}
                          />
                        </div>
                        <input
                          className="edit"
                          value={item.title}
                          onChange={e => {
                            item.title = e.target.value;
                          }}
                          onBlur={e => {
                            item.isEdit = false;
                          }}
                        />
                      </li>
    ...
  }
  ...
 }
 decorate(TodoListMobxNoDeco, {
  inputItem: observable,
  allList: observable,
  listFlag: observable,
  handleAddItem: action,
  handleClearCompleted: action,
  handleInputItem: action,
  handleItemDelete: action,
  handleItemToggle: action,
  handleListChg: action
});

TodoListMobxNoDeco = observer(TodoListMobxNoDeco);

export default TodoListMobxNoDeco;

能过这一组例子对比会发现,装饰器只是一个语法糖,可以完成不使用,但使用后,可让代码整洁不少。

一些常用调试追踪工具函数,会在下面的实践中使用

  1. trace
  2. mobx-react-devtools
  3. spy

记住以下几点

  • mobx 只对observables 属性的引用监视,不会对其值进行监视
import DevTools from "mobx-react-devtools";
...
render(){return (
    <div>
        <DevTools />
    </div>
)}
...

//Counter组件,简单展示
@observer
class Counter extends Component {
  @observable count = 0; // 将count变成可观察属性

  countCopy = this.count;  // 被赋值给一个本地变量,不会随着this.count的变化而变化

  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <span>这是 @observable 变量 count:{this.count}</span>
        <br />
        <span>这是 @observable 变量 count的本地拷贝:{this.countCopy}</span>
        <br />
        <span onClick={this.handleAdd}>Count加一</span>
        <br />
        <span onClick={this.handleDec}>Count减一</span>
      </div>
    );
  }

  @action
  handleAdd = () => {
    this.count++;
  };

  @action
  handleDec = () => {
    this.count--;
  };
}

可以看到拷贝出来的变量不会再变化

  • 尽量晚些引用observables属性的子属性
// 这个例子中,父组件在使用子组件时直接赋值了整个observable,导到如果此Observable地址不变,子组件极有可能不会重新渲染

class SubCounter extends Component {
  render() {
   const count=this.props.count;
    return (
      <div>
        <span>
          这是子组件显示父组件 @observable 变量 countObj.count1:
          {count.count1}
        </span>
        <br />
        <span>
          这是子组件显示父组件 @observable 变量 countObj.count1:
          {count.count2}
        </span>
      </div>
    );
  }
}

@observer
class Counter extends Component {
  @observable count = 0;

  countCopy = this.count;

  @observable countObj = {
    count1: 1,
    count2: 2
  };

  constructor(props) {
    super(props);
  }
  render() {
    const countCopy2 = this.count;
    return (
      <div>
       // 这里直接引用了整个observable,导到如果此Observable地址不变,子组件极有可能不会重新渲染
        <SubCounter count={this.countObj} /> 
        <span>
          这是 @observable 变量 countObj.count2:{this.countObj.count2}
        </span>
        <br />
        <span>这是 @observable 变量 count的本地拷贝:{this.countCopy}</span>
        <br />
        <span onClick={this.handleAdd}>CountObj.count1加一</span>
        <br />
        <span onClick={this.handleDec}>Count减一</span>
      </div>
    );
  }

  @action
  handleAdd = () => {
    this.countObj.count1++;
  };

  @action
  handleDec = () => {
    this.count--;
  };
}
export default Counter;
  • 嵌套组件最好每层都是observer
@observer //如果注掉,那么不会监视传过来的count, 自然不会重新渲染
class SubCounter extends Component {
  render() {
    return (
      <div>
        <span>
          这是子组件显示父组件 @observable 变量 countObj.count1:
          {this.props.count.count1}
        </span>
        <br />
        <span>
          这是子组件显示父组件 @observable 变量 countObj.count1:
          {this.props.count.count2}
        </span>
      </div>
    );
  }
}

@observer
class Counter extends Component {
  @observable count = 0;

  countCopy = this.count;

  @observable countObj = {
    count1: 1,
    count2: 2
  };

  constructor(props) {
    super(props);
  }
  render() {
    const countCopy2 = this.count;
    return (
      <div>
        <SubCounter count={this.countObj} />
        <span>
          这是 @observable 变量 countObj.count2:{this.countObj.count2}
        </span>
        <br />
        <span>这是 @observable 变量 count的本地拷贝:{this.countCopy}</span>
        <br />
        <span onClick={this.handleAdd}>CountObj.count1加一</span>
        <br />
        <span onClick={this.handleDec}>Count减一</span>
      </div>
    );
  }

  @action
  handleAdd = () => {
    this.countObj.count1++;
  };

  @action
  handleDec = () => {
    this.count--;
  };
}
export default Counter;