用mobx代替redux

时间: 11/09/2017作者: ll浏览量: 3757

1. 原因

为什么不用redux和this.setState,因为redux写起来繁琐,this.setState对于新手来说不是很友好,即使是有经验的React程序员在使用setState时,也很容易出bug,Bug产生的原因是React的state是异步的;

来看看官方怎么总结使用setState可能会出现的的问题的:React的 官方文档

  • 永远不要直接修改this.state,需要通过调用this.setState方法来替换你修改后的值。把this.state当做不可变数据来处理。
  • setState()不会马上去改变this.state,而是会排队等待处理,所以当你调用setState()后访问this.state,有可能会返回旧的state。
  • 当你调用setState()时,无法保证是同步执行的,因为为了保证性能可能会被批处理。
  • setState()总是会触发render()进行重新渲染,除非在shouldComponentUpdata()控制了渲染逻辑。如果用了可变数据结构以及在shouldComponentUpdata()中并没有控制渲染逻辑,调用setState()将不会触发重新渲染。

总的来说,使用setState会带来三个问题:

1.1 setState是异步的

许多开发人员起初并没有意识到这一点,当你设置了新的state,却发现没有变化,这是setState最诡异的地方,因为setState调用的时候看起来并不是异步。比如下面的代码:

class Select extends React.Component {

  constructor(props, context) {

    super(props, context)

    this.state = {

      selection: props.values[0]

    };

  }

  render() {

    return (

      <ul onKeyDown={this.onKeyDown} tabIndex={0}>

        {this.props.values.map(value =>

          <li

            className={value === this.state.selection ? 'selected' : ''}

            key={value}

            onClick={() => this.onSelect(value)}

          >

            {value}

          </li> 

        )}  

      </ul>

    )

  }

  onSelect(value) {

    this.setState({

      selection: value

    })

    this.fireOnSelect()

  }

  onKeyDown = (e) => {

    const {values} = this.props

    const idx = values.indexOf(this.state.selection)

    if (e.keyCode === 38 && idx > 0) { /* up */

      this.setState({

        selection: values[idx - 1]

      })

    } else if (e.keyCode === 40 && idx < values.length -1) { /* down */

      this.setState({

        selection: values[idx + 1]

      })  

    }

    this.fireOnSelect()

  }

  fireOnSelect() {

    if (typeof this.props.onSelect === "function")

      this.props.onSelect(this.state.selection) /* not what you expected..*/

  }

}

ReactDOM.render(

  <Select 

    values={["State.", "Should.", "Be.", "Synchronous."]} 

    onSelect={value => console.log(value)}

  />,

  document.getElementById("app")

)

乍一看没什么问题,但是这个select组件有一个bug,上面的git图已经很好的证明了。onSelect()方法触发时总是得到前一个state.selection的值,因为setState还没有完成,fireOnSelect就被调用了。我认为应该把setState重新命名为scheduleState或者要求传入回调。

1.2 setState引起没有必要的渲染

setState的第二个问题在于它总是会触发重新渲染,很多时候这种渲染是没有必要的。你可以通过printWasted>printWasted(React提供的性能工具)来检测它会在什么时候重新渲染。从三个方面来粗略的讲为什么重新渲染有时候是没有必要的:

  • 当新的state和旧的state是一样的。可以通过shouldComponentUpdate来解决,也可以用纯渲染库来解决。

  • 只有某些时候state的改变才和渲染有关系。

  • 第三,某些时候state和视图层一点关系都没有,比如用来管理事件的监听器,定时器等相关的state

1.3 setState不可能管理所有组件的状态

接着上面最后那点说,不是所有的组件状态都需要通过setState来储存和更新。大多数复杂的组件通常需要管理定时器循环,接口请求,事件等等。如果用setState来管理,不仅仅会引起没有必要渲染,而且会造成死循环。

2. 使用mobx

这里主要介绍怎么结合react使用 首先需要安装mobx相关依赖库

npm install mobx mobx-react --save

在React中,我们一般会把和页面相关的数据放到state中,在需要改变这些数据的时候,我们会去用setState这个方法来进行改变。 先设想一个最简单的场景,页面上有个数字0和一个按钮。点击按钮我要让这个数字增加1,就让我们要用Mobx来处理这个试试。

import React from 'react';
import { observable, useStrict, action } from 'mobx';
import { observer } from 'mobx-react';
useStrict(true);

class MyState {
  @observable num = 0;
  @action addNum = () => {
    this.num++;
  };
}

const newState = new MyState();

@observer
export default class App extends React.Component {

  render() {
    return (
      <div>
        <p>{newState.num}</p>
        <button onClick={newState.addNum}>+1</button>
      </div>
    )
  }
}

上例中我们使用了一个MyState类,在这个类中定义了一个被观测的num变量和一个action函数addNum来改变这个num值。 之后我们实例化一个对象,叫做newState,之后在我的React组件中,我只需要用@observer修饰一下组件类,便可以愉悦地使用这个newState对象中的值和函数了。

跨组件交互

在不使用其它框架、类库的情况下,React要实现跨组件交互这一功能相对有些繁琐。通常我们需要在父组件上定义一个state和一个修改该state的函数。然后把state和这个函数分别传到两个子组件里,在逻辑简单,且子组件很少的时候可能还好,但当业务复杂起来后,这么写就非常繁琐,且难以维护。而用Mobx就可以很好地解决这个问题。来看看以下的例子:

class MyState {
  @observable num1 = 0;
  @observable num2 = 100;

  @action addNum1 = () => {
    this.num1 ++;
  };
  @action addNum2 = () => {
    this.num2 ++;
  };
  @computed get total() {
    return this.num1 + this.num2;
  }
}

const newState = new MyState();

const AllNum = observer((props) => <div>num1 + num2 = {props.store.total}</div>);

const Main = observer((props) => (
  <div>
    <p>num1 = {props.store.num1}</p>
    <p>num2 = {props.store.num2}</p>
    <div>
      <button onClick={props.store.addNum1}>num1 + 1</button>
      <button onClick={props.store.addNum2}>num2 + 1</button>
    </div>
  </div>
));

@observer
export default class App extends React.Component {

  render() {
    return (
      <div>
        <Main store={newState} />
        <AllNum store={newState} />
      </div>
    );
  }
}

有两个子组件,Main和AllNum (均采用无状态函数的方式声明的组件) 在MyState中存放了这些组件要用到的所有状态和函数。 之后只要在父组件需要的地方实例化一个MyState对象,需要用到数据的子组件,只需要将这个实例化的对象通过props传下去就好了。

那如果组件树比较深怎么办呢? 我们可以借助React15版本的新特性context来完成。它可以将父组件中的值传递到任意层级深度的子组件中。 详情可以查看React的官方文档 React context

3. 参考文章

  1. Mobx使用详解
  2. mobx.js 官网
关注下面的标签,发现更多相似的文章

ll

前端工程师
微信公众号:极客教程(geekjc),极客教程网: https://www.geekjc.com 专注web前后端架构,带你走进全栈的世界。
极客教程网
——一个你值得来的网站
编程笔记:学习分享,与君共勉!
小书本:系统完整的学习!
在线工具:极客工具,在线工具,在线运行
前端导航:前端导航,前端资源聚合平台
零花钱:简单点点手机,做做任务赚点零花钱
微信公众号
极客教程(geekjc)
QQ群:
495489065

Copyright © 2019 - ~ All Rights Reserved. Made By ll 备案号:粤ICP备15001588号