共计 5835 个字符,预计需要花费 15 分钟才能阅读完成。
MobX 用于状态治理,简略高效。本文将于 React 上介绍如何开始,包含了:
- 理解 MobX 概念
- 从零筹备 React 利用
- MobX React.FC 写法
- MobX React.Component 写法
能够在线体验:https://ikuokuo.github.io/sta…,代码见:https://github.com/ikuokuo/st…。
概念
首先,ui
是由 state
通过 fn
生成:
ui = fn(state)
在 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
筹备
React App
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
才行。
mobx-react-lite 与 React.FC
定义 Data Stores
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<HTMLDivElement> & {name?: string;} | |
const Pane: React.FC<PaneProps> = ({name, ...props}) => {const stores = useStores(); | |
return (<div {...props}> | |
{name && <h2>{name}</h2>} | |
<Row align="middle"> | |
<Col span="4">Count</Col> | |
<Col span="4">{stores.counter.count}</Col> | |
<Col> | |
<Button | |
type="text" | |
icon={<PlusOutlined />} | |
onClick={() => stores.counter.increase()} | |
/> | |
<Button | |
type="text" | |
icon={<MinusOutlined />} | |
onClick={() => stores.counter.decrease()} | |
/> | |
</Col> | |
</Row> | |
{/* ... */} | |
</div> | |
); | |
}; | |
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 = () => ( | |
<div> | |
<Provider {...stores}> | |
<h1>MobX with React.Component</h1> | |
<div style={{display: 'flex'}}> | |
<Pane name="Pane 1" style={{flex: 'auto'}} /> | |
<Pane name="Pane 2" style={{flex: 'auto'}} /> | |
</div> | |
</Provider> | |
</div> | |
); | |
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<HTMLDivElement> & {name?: string;}; | |
@inject('counter', 'themes') | |
@observer | |
class Pane extends React.Component<PaneProps, unknown> {get injected() {return this.props as (PaneProps & Stores); | |
} | |
render() {const { name, ...props} = this.props; | |
const {counter, themes} = this.injected; | |
return (<div {...props}> | |
{name && <h2>{name}</h2>} | |
<Row align="middle"> | |
<Col span="4">Count</Col> | |
<Col span="4">{counter.count}</Col> | |
<Col> | |
<Button | |
type="text" | |
icon={<PlusOutlined />} | |
onClick={() => counter.increase()} | |
/> | |
<Button | |
type="text" | |
icon={<MinusOutlined />} | |
onClick={() => counter.decrease()} | |
/> | |
</Col> | |
</Row> | |
<Row align="middle"> | |
<Col span="4">Theme</Col> | |
<Col span="4">{themes.currentTheme}</Col> | |
<Col> | |
<Select | |
style={{width: '60px'}} | |
value={themes.currentTheme} | |
showArrow={false} | |
onSelect={(v) => themes.setTheme(v)} | |
> | |
{themes.themes.map((t) => (<Select.Option key={t} value={t}> | |
{t} | |
</Select.Option> | |
))} | |
</Select> | |
</Col> | |
</Row> | |
</div> | |
); | |
} | |
} | |
export default Pane; |
最初
MobX
文档能够浏览一遍,理解有哪些内容。未波及的外围概念还有 Computeds, Reactions。
其中 MobX and React
一节,详解了于 React
中的用法及留神点,见:React 集成,React 优化。
GoCoding 集体实际的教训分享,可关注公众号!