React + MobX 快速上手

1 篇文章 0 订阅

一、Mobx机制介绍:

Mobx 是简单、可扩展的状态管理,当应用状态更新时,任何源自应用状态的东西都将自动地获得。React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而 MobX 提供机制来存储和更新应用状态供 React 使用。

Mobx 中文文档

Mobx 英文文档

 Mobx 的运行机制如下图:

首先从左往右看,事件触发了 Actions,Actions 作为唯一修改 State 的方式,修改了 State,State 的修改更新了计算值 Computed,计算值的改变引起了 Reactions 的改变,导致了 UI 的改变,Reactions 可以经过事件调用 Actions。

二、开发环境


安装 mobx 和 React 绑定库 mobx-react:

npm install mobx mobx-react --save

要启用 ESNext 的装饰器 (可选), 参见下面:

安装修饰符修饰器插件

// 修饰符的插件
npm install --save-dev babel-plugin-transform-decorators-legacy

// 装饰器的一个插件
npm install @babel/plugin-proposal-decorators

配置babelrc文件,在根目录创建一个.babelrc文件(存在.babelrc文件的直接进行配置), 输入以下内容:

{
    "plugins":[
        [
          "@babel/plugin-proposal-decorators",
          {
              "legacy":true
          }
        ],
        [
          "@babel/plugin-proposal-class-properties",
          {
              "loose":true
          }
          ]
          ],
    "presets":[
        "react-app"
        ]

}

三、常用API介绍及用法

1、@observable (定义变量状态)observable 用于定义可观察状态,观测的数据可以是数字、字符串、数组、对象等。

使用@observable定义的变量是可以被修改的,如果是常量或者不可再被修改的变量就可以不用@observable修饰

例:从mobx中引入observable,  import {observable} from 'mobx'; 使用如下:

@observable count: number = 0;
@observable tool: string = "";
sex: string = 'nan';
@observable classCurrentId: string = '';
@observable classDataList: any = [];

注意:被 observable 观测数据的修改是同步的,不像 setState 那样是异步。
 

2、@observer (观察者被observer修饰的组件,将会根据组件内使用到的被observable修饰的state的变化而自动重新渲染

一般用在修饰类class前面,内部有可观察的变量的话(通过@observable定义的),添加@observer,即可实时观察数据变化,重新render

例:从mobx-react中引入observer,import {observer} from "mobx-react";

如下: 只要store中的count变化了,下面这个Timer组件就会重新render,重新更新,这就是@observer的用途

import store from './mobx/store';//引入store

@observer 
class Timer extends React.Component {
    render() {
        return (
            <div>{store.count}</div>
        )
    }
};

3、reaction (监听):组件中通过reaction监听store数据变化,变化之后在组件内部作出相关响应

可以监听一个store变量,也可以监听多个store变量,如下所示

监听一个的例子:

// 监听classCurrentId的变化,newClassId为最新的数据
reaction(() => store.mobTest.classCurrentId, (newClassId) => {
    //dosomething
})


// 监听classDataListLength变化, newClassDataList为最新的数据
reaction(() => store.mobTest.classDataListLength, (newClassDataList) => {
    //dosomething
});

销毁:

let disposeOrgChange = reaction(
      () => this.global.currentOrgInfo.id,
      (id) => {
          if (id) {
             //doSomething
          }
       }
);

在组建销毁的时候执行
this.disposeOrgChange && this.disposeOrgChange();

监听多个变量例子:

/**
   监听screenMode和ClassType.type的变化,下面newData为最新数据,可以通过
   newData.mode, newData.type获取最细的数据
/*

reaction(() => ({ mode: store.home.screenMode, type: store.ClassType.type }), newData => {
      //doSomething
})

4、toJS():将观察者对象转换成正真定义的格式,如将观察者数组转换成数组,将观察者对象转换成对象

如:在store中定义了一个可修改的变量为数组格式:@observable answerList: any[] = []; 我们在获取最新answerList数据的时候,拿到的不是真正意义上的数组,是被观察着数组,这时候我们必须通过toJS转换一下就可以了

例子:

// answerList为store中定义的被观察的数组,使用reaction监听的时候,通过toJS转换成真正的数组
reaction(() => toJS(store.answer.answerList), newValue => {
    //doSomething
})

5、@inject (注入):注入store中的方法和变量,然后组件里就可以直接通过this.props.xxx的方式使用其他store中的变量或方法

使用inject注入要注意,需要在跟组件将App组件使用Provider包裹,如下

import { Provider } from 'mobx-react'

//store为定义的mobx引入的store文件
class App extends Component{
    render(){
        return(
            <Provider store={store}>
                <ToDoApp/>
            </Provider>
        )
    }
}

组件中使用案例:

import store from '../mobx/store'; //直接引入store

interface IProps {
  store?: any; //通过App传入的store
}

interface IState {
}

@inject('store');//注入所有store
class PokeButton extends React.Component<IProps, IState> {
    //doSomething 组件里面就可以通过 this.props.store.xxx获取任意store数据
}

6、@computed (计算属性):使用@computed修饰的方法,内部只要有一个变量变化,就会重新计算,会在依赖的状态发生变化时会重新运行

注意点:

computed 值会被缓存:每当读取 computed 值时,如果其依赖的状态或其他 computed 值未发生变化,则使用上次的缓存结果,以减少计算开销。

computed 值会惰性计算:只有 computed 值被使用时才重新计算值。反之,即使 computed 值依赖的状态发生了变化,但是它暂时没有被使用,那么它不会重新计算。

例子:

//store中定义  
@computed get classDataListLength() {
    return this.classDataList.length;
}

使用:classDataListLength变化时执行

// 监听mobx 班级选中列表元素变化
reaction(() => store.mobTest.classDataListLength, (newClassDataList) => {
    //doSomething
});

7、autorun(自定义反应):用于定义响应函数,并在定义时立即执行一次。 以后,每当依赖状态发生变化时,autorun 自动重新运行。

当你想创建一个响应式函数,而该函数本身永远不会有观察者时,可以使用 mobx.autorun。 这通常是当你需要从反应式代码桥接到命令式代码的情况,例如打印日志、持久化或者更新UI的代码。 当使用 autorun 时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发。 相比之下,computed(function) 创建的函数只有当它有自己的观察者时才会重新计算,否则它的值会被认为是不相关的。 经验法则:如果你有一个函数应该自动运行,但不会产生一个新的值,请使用autorun。 其余情况都应该使用 computed。 Autoruns 是关于 启动效果 (initiating effects) 的 ,而不是产生新的值。 如果字符串作为第一个参数传递给 autorun ,它将被用作调试名。

传递给 autorun 的函数在调用后将接收一个参数,即当前 reaction(autorun),可用于在执行期间清理 autorun。

就像@observer 装饰器/函数,autorun 只会观察在执行提供的函数时所使用的数据。

var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));

var disposer = autorun(() => console.log(sum.get()));
// 输出 '6'
numbers.push(4);
// 输出 '10'

disposer();
numbers.push(5);
// 不会再输出任何值。`sum` 不会再重新计算。

8、@action(动作):用于定义状态修改操作

常用案例:只有通过@action才可以修改store数据

import {observable, action} from 'mobx';

class AnswerData {
  @observable currentState: string = '';
  @observable micList: any[] = [];
  @observable teaStatus: string = 'set';
  @observable timer: number = 0;
  timerId: any = null;

  startTimer = () => {
    this.timerId = setInterval(() => {
      this.timer += 1
    }, 1000);
  }

  @action changeState (state: string) {
    this.currentState = state;
  }

  @action setList (l: any[]) {
    this.micList = l;
    this.startTimer();
  }

  @action teaQuizEnd = () => {
    this.teaStatus = 'reStart';
  }
}

export const answer = new AnswerData();

异步操作处理:

@action 只会对当前运行的函数做出反应,对于一些不在当前函数中调用的回调是无效的,例如 setTimeout 回调、Promisethenasync 语句。这些回调如果修改了状态,也应该用 action 包裹起来。以下方法可处理该问题:

修改状态的语句用 runInAction 包裹起来,runInAction包裹执行完成之后再修改

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    async fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = await fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            // await 之后,再次修改状态需要动作:
            runInAction(() => {
                this.state = "done"
                this.githubProjects = filteredProjects
            })
        } catch (error) {
            runInAction(() => {
                this.state = "error"
            })
        }
    }
}

用法:

action(fn)

action(name, fn)

@action classMethod() {}

@action(name) classMethod () {}

@action boundClassMethod = (args) => { body }

@action(name) boundClassMethod = (args) => { body }

@action.bound classMethod() {}

@action.bound(function() {})

注意:action只能影响正在运行的函数,而无法影响当前函数调用的异步操作

  @action createRandomContact () {
    superagent.get('https://randomuser.me/api/').set('Accept', 'application/json')
      .end(action("createRandomContact-callback", (error: any, results: any) => {
        if (error) {
          console.error(error);
        } else {
          console.log(results)
        }
    }));
  }

在 end 中触发的回调函数,被 action 给包裹了,这就很好验证了上面的那句话,action 无法影响当前函数调用的异步操作,而这个回调毫无疑问是一个异步操作,所以必须再用一个 action 来包裹住它,这样程序才不会报错。

如果你使用 async function 来处理业务,那么我们可以使用 runInAction 这个 API 来解决问题。

import {observable, action, runInAction} from 'mobx';

class Store {
  @observable name = '';
  @action load = async () => {
    const data = await getData();
    runInAction(() => {
      this.name = data.name;
    });
  }
}

你可以把 runInAction 有点类似 action(fn)() 的语法糖,调用后,这个 action 方法会立刻执行。

交流


1、QQ群:可添加qq群共同进阶学习: 进军全栈工程师疑难解  群号:   856402057

2、公众号:公众号「进军全栈攻城狮」 ,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值