MobX 用于状态管理,简单高效。本文将于 React 上介绍如何开始,包括了:
- 了解 MobX 概念
- 从零准备 React 应用
- MobX React.FC 写法
- MobX React.Component 写法
概念 首先,
ui
是由 state
通过 fn
生成:ui = fn(state)
【React|React MobX 开始】在 React 里,
fn
即组件,依照自己的 state
渲染。如果
state
是共享的,一处状态更新,多处组件响应呢?这时就可以用 MobX
了。MobX
数据流向如下:ui
↙↖
action → state
ui
触发 action
,更新 state
,重绘 ui
。注意是单向的。了解更多,请阅读 MobX 主旨 。这里讲下实现时的主要步骤:
- 定义数据存储类
Data Store
- 成员属性为
state
,成员函数为action
- 用
mobx
标记为observable
- 成员属性为
- 定义
Stores Provider
- 方式一
React.Context
:createContext
包装Store
实例,ui
useContext
使用 - 方式二
mobx-react.Provider
:直接包装Store
实例,提供给Provider
,ui
inject
使用
- 方式一
- 实现
ui
组件
- 用
mobx
标记为observer
- 获取
stores
,直接引用state
- 若要更新
state
,间接调用action
- 用
stores
目录,定义各类 store
的 state
action
,异步操作也很简单。了解更多,请阅读:- 定义数据存储
- 异步 actions
yarn create react-app start-react --template typescript
cd start-react
React Router
路由库,以便导航样例。
yarn add react-router-dom
Antd
组件库,以便布局 UI。
yarn add antd @ant-design/icons
高级配置,
yarn add @craco/craco -D
yarn add craco-less
craco.config.js
配置了深色主题:const path = require('path');
const CracoLessPlugin = require('craco-less');
const { getThemeVariables } = require('antd/dist/theme');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: getThemeVariables({
dark: true,
// compact: true,
}),
javascriptEnabled: true,
},
},
},
},
],
webpack: {
alias: { '@': path.resolve(__dirname, './src') },
},
};
ESLint
VSCode 安装 ESLint Prettier 扩展。初始化
eslint
:$ npx eslint --init
? How would you like to use ESLint? · style
? What type of modules does your project use? · esm
? Which framework does your project use? · react
? Does your project use TypeScript? · No / Yes
? Where does your code run? · browser
? How would you like to define a style for your project? · guide
? Which style guide do you want to follow? · airbnb
? What format do you want your config file to be in? · JavaScript
配置
.eslintrc.js
.eslintignore
.vscode/settings.json
,详见代码。并于 package.json
添加:"scripts": {
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern node_modules/"
},
执行
yarn lint
通过, yarn start
运行。到此, React Antd 应用就准备好了。初始模板如下,可见首个提交:
文章图片
MobX
yarn add mobx mobx-react
mobx-react
包含了 mobx-react-lite
,所以不必安装了。- 如果只用 React.FC (HOOK) 时,用
mobx-react-lite
即可。 - 如果要用 React.Component (Class) 时,用
mobx-react
才行。
makeAutoObservable 定义数据存储模型后,于构造函数里调用
makeAutoObservable(this)
即可。stores/Counter.ts
:import { makeAutoObservable } from 'mobx';
class Counter {
count = 0;
constructor() {
makeAutoObservable(this);
}increase() {
this.count += 1;
}decrease() {
this.count -= 1;
}
}export default Counter;
React.Context Stores
React.Context
可以很简单的传递 Stores
。stores/index.ts
:import React from 'react';
import Counter from './Counter';
import Themes from './Themes';
const stores = React.createContext({
counter: new Counter(),
themes: new Themes(),
});
export default stores;
创建一个
useStores
的 Hook
,简化调用。hooks/useStores.ts
:import React from 'react';
import stores from '../stores';
const useStores = () => React.useContext(stores);
export default useStores;
Pane 组件,使用 Stores
组件用
observer
包装,useStores
引用 stores
。Pane.tsx
:import React from 'react';
import { Row, Col, Button, Select } from 'antd';
import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
import { observer } from 'mobx-react-lite';
import useStores from './hooks/useStores';
type PaneProps = React.HTMLProps & {
name?: string;
}const Pane: React.FC = ({ name, ...props }) => {
const stores = useStores();
return ({name && {name}}
Count
{stores.counter.count}
{/* ... */});
};
Pane.defaultProps = { name: undefined };
export default observer(Pane);
mobx-react 与 React.Component 定义 Data Stores
makeObservable + decorators 装饰器在
MobX 6
中放弃了,但还可使用。首先,启用装饰器语法。
TypeScript
于 tsconfig.json
里启用:"experimentalDecorators": true,
"useDefineForClassFields": true,
定义数据存储模型后,于构造函数里调用
makeObservable(this)
。在 MobX 6
前不需要,但现在为了装饰器的兼容性必须调用。stores/Counter.ts
:import { makeObservable, observable, action } from 'mobx';
class Counter {
@observable count = 0;
constructor() {
makeObservable(this);
}@action
increase() {
this.count += 1;
}@action
decrease() {
this.count -= 1;
}
}export default Counter;
Root Stores 组合多个
Stores
。stores/index.ts
:import Counter from './Counter';
import Themes from './Themes';
export interface Stores {
counter: Counter;
themes: Themes;
}const stores : Stores = {
counter: new Counter(),
themes: new Themes(),
};
export default stores;
父组件,提供 Stores
父组件添加
mobx-react.Provider
,并且属性扩展 stores
。index.tsx
:import React from 'react';
import { Provider } from 'mobx-react';
import stores from './stores';
import Pane from './Pane';
const MobXCLS: React.FC = () => (MobX with React.Component);
export default MobXCLS;
Pane 组件,注入 Stores
组件用
observer
装饰,同时 inject
注入 stores
。Pane.tsx
:import React from 'react';
import { Row, Col, Button, Select } from 'antd';
import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
import { observer, inject } from 'mobx-react';
import { Stores } from './stores';
type PaneProps = React.HTMLProps & {
name?: string;
};
@inject('counter', 'themes')
@observer
class Pane extends React.Component {
get injected() {
return this.props as (PaneProps & Stores);
}render() {
const { name, ...props } = this.props;
const { counter, themes } = this.injected;
return ({name && {name}}
Count
{counter.count}
counter.increase()}
/>
}
onClick={() => counter.decrease()}
/>
Theme
{themes.currentTheme}
themes.setTheme(v)}
>
{themes.themes.map((t) => ({t}))}
);
}
}export default Pane;
最后
MobX
文档可以浏览一遍,了解有哪些内容。未涉及的核心概念还有 Computeds, Reactions。其中
MobX and React
一节,详解了于 React
中的用法及注意点,见:React 集成,React 优化。GoCoding 个人实践的经验分享,可关注公众号!