React 项目中使用 MobX 进行状态管理
说明
MobX 是一个简单的、可扩展的状态管理器,他通过透明的函数式编程使得状态管理变的简单和可扩展
React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而 MobX 提供机制来存储和更新应用状态供 React 使用。
参考 MobX 中文文档
概念
- State 状态
- Derivations 衍生,源自状态,并且会不再有进一步相互作用的东西
- Computed values 计算值,使用纯函数从当前可观察状态中衍生出的值
- Reactions 反应,当状态改变时,需要自动发生的副作用
- Actions 动作,任意一段可以改变状态的代码,尽量只在动作中修改状态
原则
- MobX 中支持单项数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图
- 状态改变时,所有衍生都会进行原子级的自动更新,因此不会观察到中间值
- 所有衍生默认都是同步更新,这意味着可以在改变状态之后更安全的检查计算值
- 计算值是延迟更新的,任何不在使用状态的计算值将不会更新
- 所有计算值都应该是纯净的,他们不应该用来改变状态
API
@observable
一个装饰器,用于将对象的某一部分变成可观察的@computed
一个装饰器,计算值,根据现有状态或其他计算值衍生出的值@computed.struct
用于比较结构,设置为 true 时,表达式的输出在结果上与先前的值进行比较,然后通知观察者相关的更改@computed.equals(comparer)
用于自定义比较结构,comparer 是一个自定义比较函数,mobx 提供三个内置比较器 同过import { comparer } from 'mobx'
得到comparer.default
等Autorun
当需要创建一个响应式函数,但是函数本省并不需要观察者时,可以使用此 api,与computed
相同点是都会响应依赖的变化,不同点是computed
会返回一个新的值,用作观察者观察,而autorun
没有观察者,他只是响应变化后执行一段代码@observer
由mobx-react
提供的装饰器,用来将 react 组件转换为响应式的组件,需要确保observer
是最深处(第一个应用)Provider
组件,由mobx-react
提供,它使用了 react 的上下文(context)机制,可以用来向下传递stores
@inject()
将组件链接到stores
,需要传递一个 stores 名称的列表给inject
使得 stores 可以作为组件的 props 使用componentWillReact
声明周期钩子,当组件因为它观察的数据发生变化,他会被安排重新渲染,这个时候componentWillReact
钩子会被触发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(); // 请求获取初始数据
}
}