React 项目中使用 MobX 进行状态管理

React 项目中使用 MobX 进行状态管理

说明

MobX 是一个简单的、可扩展的状态管理器,他通过透明的函数式编程使得状态管理变的简单和可扩展

React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而 MobX 提供机制来存储和更新应用状态供 React 使用。

参考 MobX 中文文档

完整项目代码

概念

  1. State 状态
  2. Derivations 衍生,源自状态,并且会不再有进一步相互作用的东西
  3. Computed values 计算值,使用纯函数从当前可观察状态中衍生出的值
  4. Reactions 反应,当状态改变时,需要自动发生的副作用
  5. Actions 动作,任意一段可以改变状态的代码,尽量只在动作中修改状态

原则

  • MobX 中支持单项数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图
  • 状态改变时,所有衍生都会进行原子级的自动更新,因此不会观察到中间值
  • 所有衍生默认都是同步更新,这意味着可以在改变状态之后更安全的检查计算值
  • 计算值是延迟更新的,任何不在使用状态的计算值将不会更新
  • 所有计算值都应该是纯净的,他们不应该用来改变状态

API

  1. @observable 一个装饰器,用于将对象的某一部分变成可观察的
  2. @computed 一个装饰器,计算值,根据现有状态或其他计算值衍生出的值
  3. @computed.struct 用于比较结构,设置为 true 时,表达式的输出在结果上与先前的值进行比较,然后通知观察者相关的更改
  4. @computed.equals(comparer) 用于自定义比较结构,comparer 是一个自定义比较函数,mobx 提供三个内置比较器 同过 import { comparer } from 'mobx' 得到 comparer.default
  5. Autorun 当需要创建一个响应式函数,但是函数本省并不需要观察者时,可以使用此 api,与 computed 相同点是都会响应依赖的变化,不同点是 computed 会返回一个新的值,用作观察者观察,而 autorun 没有观察者,他只是响应变化后执行一段代码
  6. @observermobx-react 提供的装饰器,用来将 react 组件转换为响应式的组件,需要确保 observer 是最深处(第一个应用)
  7. Provider 组件,由 mobx-react 提供,它使用了 react 的上下文(context)机制,可以用来向下传递 stores
  8. @inject() 将组件链接到 stores,需要传递一个 stores 名称的列表给 inject 使得 stores 可以作为组件的 props 使用
  9. componentWillReact 声明周期钩子,当组件因为它观察的数据发生变化,他会被安排重新渲染,这个时候 componentWillReact 钩子会被触发
  10. action 动作,动作是用来修改状态的

完整案例代码

1. /index.js 入口文件

/*
   项目入口
*/
import './scss/index.scss';

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { useStrict } from 'mobx';
import { Provider } from 'mobx-react';
import Root from './router/routes';
import stores from './store/index';

const mountNode = document.getElementById('app');

useStrict(true);

const render = (Component, stores) => {
    // 卸载掉旧的组件
    ReactDOM.unmountComponentAtNode(mountNode);
    // 重新渲染新的组件
    ReactDOM.render(
        <Provider {...stores}>
            <BrowserRouter>
                <Component />
            </BrowserRouter>
        </Provider>,
        mountNode
    );
};

render(Root, stores);

/*
   这里的热更新只监视 store 部分代码变化,然后进行重新渲染组件,
   组件的热更新还是交给 react-hot-loader 组件处理(route文件中)
*/

if (module.hot) {
    module.hot.accept('./store/index.js', () => {
        console.log('mobx changed');
        render(
            require('./router/routes.js').default,
            require('./store/index.js').default
        );
    });
}

2. /store/index.js store 状态集合

/*
   合并后的 store
*/
import app from './modules/app';
import member from './modules/member';

const stores = {
    app,
    member
};

export default stores;

3. /store/modules/members.js 每一个页面的子 store

/*
   成员列表页
*/

import { observable, computed, action } from 'mobx';
// 将获取数据部分分离出去
import {
    obtainMemberList,
    postNewMember,
    deleteMember
} from '../../api/members';

class MemberStore {
    // 将需要观察的属性设置为可观察的
    @observable members;
    @observable filter;

    // 在这里给可观察的属性设置初始值
    constructor() {
        this.members = [];
        this.filter = '';
    }

    // 计算属性
    @computed
    get filtedMembers() {
        const members = [...this.members];
        if (this.filter === '') {
            return members;
        }

        const filterReg = new RegExp(this.filter, 'g');

        return members.filter(
            ({ name, tel }) => filterReg.test(name) || filterReg.test(tel)
        );
    }

    // 动作,代码专注于更新可观察属性,额外的操作分离出去
    @action
    changeMembers = members => {
        this.members = members;
    };
    @action
    changeFilter = newFilter => {
        this.filter = newFilter;
    };

    /*
      一些函数,包含更新可观察属性的部分已经被分离为 action
       在 action 中使用异步函数或者 promise 都比较麻烦,所以尽可能的分离,
       据文档指出,不但 异步函数需要被 @action
       await 后的代码如果更改了可观察属性,需要使用 runInAction 包裹
    */

    getMembers = async () => {
        const members = await obtainMemberList();

        this.changeMembers(members);
    };

    postMember = async newMember => {
        await postNewMember(newMember);
        await this.getMembers();
    };

    deleteMember = async memberId => {
        await deleteMember(memberId);
        await this.getMembers();
    };
}

// 返回一个实例
export default new MemberStore();

4. /container/Member.js 容器组件

/*
   成员列表
*/

import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
import MemberCom from '../components/member/index';

@inject('member') // 给组件注入其需要的 store,指定对应的子 store 名称
@observer // 将组件转化为响应式组件
export default class Member extends Component {
    constructor(props) {
        super(props);
    }
    render() {
        const { member } = this.props;

        return <MemberCom member={member} />;
    }
}

5. /components/member/index.js 展示组件

/*
   MemberCom
*/
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import Toolbar from './Toolbar';
import MemberList from './MemberList';

import './member.scss';

/*
   这里有个坑,只要子组件使用了 store 中的可观察属性,
   就需要通过 @observer 将其变成响应式组件
*/

@observer
export default class MemberCom extends Component {
    constructor(props) {
        super(props);
    }
    render() {
        const {
            filtedMembers,
            filter,
            postMember,
            deleteMember,
            changeFilter
        } = this.props.member;

        return (
            <div id="member-container">
                <Toolbar
                    filter={filter}
                    postMember={postMember}
                    changeFilter={changeFilter}
                />
                <MemberList
                    filtedMembers={filtedMembers}
                    deleteMember={deleteMember}
                />
            </div>
        );
    }
    componentWillMount() {
        const { getMembers } = this.props.member;
        getMembers(); // 请求获取初始数据
    }
}

猜你喜欢

转载自blog.csdn.net/mjzhang1993/article/details/79358899