mobx介绍
- mobx是一个功能强大,上手容易的状态管理工具。
- MobX背后的哲学很简单:任何源自应用状态的东西都应该自动地获得。
- 利用getter和setter来收集组件的数据依赖关系,从而在数据发生变化的时候精确知道哪些组件需要重绘。
mobx和redux的区别
- mobx更趋向于面向对象编程(OOP)
- 对一份数据直接进行修改操作,不需要始终返回一个新的数据
- 并非单一store,可以多store
- redux默认以javascript原生对象形式存储数据,而mobx使用的是可观察对象
优缺点
优点
- 学习成本小
- 面向对象编程,对TS友好
缺点
- mobx过于自由,提供的约束和模板代码很少,代码编写自由,如果不做一些约定,比较容易导致团队代码风格不同一
- 相关的中间件很少,逻辑层业务整合是问题
mobx原则
MobX 支持单向数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图。
当状态改变时,所有衍生都会进行原子级的自动更新。因此永远不可能观察到中间值。
所有衍生默认都是同步更新。这意味着例如动作可以在改变状态之后直接可以安全地检查计算值。
mobx安装
yarn add -D mobx mobx-react
mobx的简单使用
import {observable,autorun} from 'mobx'
observable负责将数据转换为可观察的对象或者数组,
每次改数据时,就会在autorun的回调函数中触发,autorun相当于监听者。
对于普通数据的监听
// 对于普通数据的监听
let observableGender = observable.box('男');
// 监听变化(第一次必须执行,之后每次改变也会执行)
autorun(() => {console.log('observableNumber: ', observableGender.get());
});
// 设置
setTimeout(() => {observableGender.set('女');
}, 1000);
组件第一次渲染时,autorun会自动执行一次,如上例,以后每次相关可观察值改变,都会打印1次。
注意,在vconsole中可以看到mobx警告。
[MobX] Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed
mobx由于启用了严格模式,因此不允许在不使用action的情况下直接更改(观察到)值。异步的函数不能直接更改数据。
要更改可观察值,需要通过action触发。
对于对象的监听
// 对于对象的监听-------
let observableObj = observable.map({name: 'wuqing',age: 30,
});
autorun(() => {console.log('observableObj对象属性name:', observableObj.get('name'));
});
setTimeout(() => {// 只会打印1次,autorun只会监听与其相关的属性// observableObj.set('age', 100);observableObj.set('name', 'wuqing1');
}, 3000);
autorun只会监听与其相关的属性
对象监听另外一种写法:
let observableObj = observable({name: 'wuqing',age: 30,
});
autorun(() => {console.log('observableObj对象属性name:', observableObj.name);
});
setTimeout(() => {observableObj.name="wuqing1";
}, 3000);
为了代码风格统一,可以开启严格模式。
配置enforceActions: ‘always’,等于开启严格模式,必须用action触发数据更新。
若配置never,则不会启用。
import { observable, configure } from 'mobx';// 严格模式
configure({enforceActions: 'always',
});const store = observable({showMessage: false,name: '张伟',
});export default store;
配置严格模式后,若不通过action直接修改store数据,会报错 Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed.
严格模式默认应该是开启的。
可观察值只允许通过action方法改,不能直接改
import { observable, configure, action } from 'mobx';
// 严格模式
configure({enforceActions: 'always',
});
const store = observable({showMessage: false,name: '张伟',changeNameAction() {// 可观察值name的方法this.name = '刘德华';},},{changeNameAction: action, // 标记action是用于专门修改可观察值name的方法}
);
export default store;
上面换一种写法,用ES7注解器的写法是:
@observable和@action都是装饰器,通俗来说装饰器是将传入的值或者函数进行处理从而返回更加强大的值或者函数。
class Store {@observable name = '张伟';@observable showMessage = false;@action changeNameAction() {// 可观察值name的方法this.name = '刘德华' + Math.random();console.log('aaaa-->', this.name);}
}// 单例模式,无论谁导入,都是同一个对象
const store = new Store();export default store;
到这里,会发现react如果没有经过配置,直接使用decorators装饰器语法会报错,
Support for the experimental syntax ‘decorators’ isn’t currently enabled
因为react默认是不支持装饰器语法,需要做一些配置来启用装饰器语法。
step1:
在 tsconfig.json 中启用编译器选项 “experimentalDecorators”: true
vscode点击设置,输入搜索experimentalDecorators
step2:
安装支持修饰器所需依赖。
yarn add -D @babel/core @babel/plugin-proposal-decorators @babel/preset-env
创建.babelrc文件,配置
{"presets": ["@babel/preset-env"],"plugins": [["@babel/plugin-proposal-decorators",{"legacy": true}]]
}
step3:
安装依赖
yarn add -D customize-cra react-app-rewired
在项目根目录下创建 config-overrides.js 并写入以下内容,覆盖默认配置。
const path = require('path')
const { override, addDecoratorsLegacy } = require('customize-cra')function resolve(dir) {return path.join(__dirname, dir)
}const customize = () => (config, env) => {config.resolve.alias['@'] = resolve('src')if (env === 'production') {config.externals = {'react': 'React','react-dom': 'ReactDOM'}}return config
};
module.exports = override(addDecoratorsLegacy(), customize())
step4:
修改package.json文件中 scripts 脚本。
"scripts": {"start": "react-app-rewired start","build": "react-app-rewired build","test": "react-app-rewired test","eject": "react-scripts eject"}
上面4个步骤配置完成后,如果mobx修饰器还是不起作用,就可能是mobx版本有问题,执行step5。
step5:
执行下面命令
yarn add -D mobx@5 mobx-react@5
执行到step5,就能成功使用mobx修饰器了。
注意,如果报错
Parsing error: Cannot use the decorators and decorators-legacy plugin together
可以创建.eslintrc.js文件,配置即可解决eslint报错问题
parserOptions: {parser: 'babel-eslint',ecmaFeatures: {// 支持装饰器legacyDecorators: true,},},
mobx核心API介绍
observable: observable是将类属性等进行标记,实现对其的观察。
actions:动作,通过action改变state。action函数是对传入的function进行一次包装,使得function中的observable对象的变化能够被观察到,从而触发相应的衍生。
autoRun:当你想创建一个响应式函数,但是该函数永远没有观察者,此时使用autorun。这通常是当你需要从反应式代码桥接到命令式代码的情况,例如打印日志、持久化或者更新UI的代码。
当使用 autorun 时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发。
相比之下,computed(function) 创建的函数只有当它有自己的观察者时才会重新计算,否则它的值会被认为是不相关的。 经验法则:如果你有一个函数应该自动运行,但不会产生一个新的值,请使用autorun。 其余情况都应该使用 computed。
装饰器
runInAction:工具函数,是创建异步action的一种方式。
action只会对当前包装/装饰的函数做出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应!这意味着如果 action 中存在 setTimeout、promise 的 then 或 async 语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中。
@observable
定义 state
@computed
可以定义在相关数据发生变化时自动更新的值,像这样的计算可以类似于 MS Excel 这样电子表格程序中的公式。每当只有在需要它们的时候,它们才会自动更新。
@action
定义操作 state 的方法。
@observer
用来将 React 组件转变成响应式组件。observer 是由单独的 mobx-react 包提供的。
@inject
将组件连接到提供的 stores。
mobx-react 包还提供了 Provider 组件,它使用了 React 的上下文(context)机制,可以用来向下传递 stores。 要连接到这些 stores,需要传递一个 stores 名称的列表给 inject,这使得 stores 可以作为组件的 props 使用。
使用
新建store.js,封装Store Class专门用于状态管理,再export Store的实例对象store。
import { action, configure, observable, runInAction } from 'mobx';
// 严格模式
configure({enforceActions: 'always',
});
class Store {@observable name = '张伟';@observable showMessage = false;@observable messageList = [];@action changeNameAction() {// 可观察值name的方法this.name = '刘德华' + Math.random();}@action async getMessageList() {const datas = await getMessageListApi();// 异步runInAction(()=>{this.messageList=datas})}
}
// 单例模式,无论谁导入,都是同一个对象
const store = new Store();export default store;function getMessageListApi() {return new Promise((resolve) => {return setTimeout(() => {const list = [];for (let i = 0; i < 5; i++) {list.push(`MSG-${Math.random()}`);}resolve(list);}, 1500);});
}
从mobx-react中引入Provider,全局注册并注入store实例,内部组件就能使用到store中的state
import { Provider } from 'mobx-react';
import store from './mobx-demo/Store/store-1';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(// store属性名自命名,为了区别,写了一个demo名<Provider demoStore={store}><App /></Provider>
);
内部组件需要使用state时,先要用@inject装饰器注入store,再用@observer将组件标记为 observer,当组件中使用到的store state发生变化时,组件会自动响应更新。
@inject("demoStore") // 通过inject修饰器注入store
@observer
export default class App extends Component {componentDidMount() {console.log('name-->', this.props.demoStore.name);}render() {return <RouterProvider router={routes}></RouterProvider>;}
}