本文梳理React状态管理的一些方案,包括以下内容
Context
用法redux
使用和简易实现mobx
介绍使用React开发一个页面,我们通常将一个页面划分若干个区域(这些区域存在嵌套关系,一个父区域可能包含多个子区域)。每个区域对应一个组件,每个组件可能有一些状态。
- 只属于该组件的状态用
state
表示- 兄弟组件之间的状态需要提取到父组件中,用
state
表示,使用props
向下传递- 不同层级组件之间的状态,如果提取到祖先组件的
state
中,props
往下传递会非常冗肿,React
官方提供了Context
方案,解决跨组件通信问题。
Context
提供了一个无需为每层组件手动添加 props
,就能在组件树间进行数据传递的方法。
何时使用Context
Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
使用Context
之前的考虑
Context
主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
官方给了一个theme
案例,非常清晰,这里为了讲下面的内容还是把代码贴出来
jsximport React from "react";
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
export default class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “yellow” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="yellow">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “yellow”。
static contextType = ThemeContext; // 固定写法,感觉像hack
render() {
return <button style={{backgroundColor: this.context}}>ThemedButton</button>;
}
}
// 使用context步骤
// 1. 创建 createContext
// 2. Provider接收value,以保证有传下去的数据
// 3. 接收 Consumer或者class.contextType
上述案例有个梗,
static contextType = ThemeContext;
注入 this.context
获取,这是react的固定写法
该方式并不适用Function组件。
修改ThemedButton
为function
jsxfunction ThemedButton() {
let bgColor = React.useContext(ThemeContext);
return <button style={{backgroundColor: bgColor}}>
ThemedButton
</button>;
}
用户信息和主题色是项目中常用的两个全局状态
jsxconst ThemeContext = React.createContext('light');
const PersonContext = React.createContext('XXX');
function Ancestor() {
const [color, setColor] = React.useState('yellow');
const [person, setPerson] = React.useState({name: 'zs', age: 12})
return <ThemeContext.Provider value={color}>
<button onClick={() => setColor(color === 'yellow' ? 'blue' : 'yellow')}>修改颜色</button>
<button onClick={() => setPerson(Object.assign({}, person, {
name: person.name === 'zs' ? 'ls' : 'zs'
}))}>修改Person</button>
<PersonContext.Provider value={person}>
<Parent></Parent>
</PersonContext.Provider>
</ThemeContext.Provider>
}
function Parent() {
return <Child/>
}
class Child extends React.Component {
render() {
return (
<div>
<h3>MultiContextUse</h3>
<ThemeContext.Consumer>
{theme => (
<PersonContext.Consumer>
{user => <div style={{backgroundColor: theme}}>{user.name}</div>}
</PersonContext.Consumer>
)}
</ThemeContext.Consumer>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Ancestor/>);
多个context
使用 通过Context.Consumer
标签来接收内容,符合JSX
语法。
注意事项
- 要把value 状态提升到父节点的 state里面 原因
在React
的官方文档中, Context
被归类为高级部分,属于React的⾼级API,但官⽅并不建议在稳定版的App中使用Context。
不过,这并非意味着我们不需要关注 Context 。
事实上,很多优秀的React组件都通过Context来完成⾃己的功能,
react-redux
的<Provider />
,就是通过Context
提供一个全局态的store
,react-router
通过 Context
管理路由状态等。在React组件开发中,如果用好 Context ,可以让你的组件变得强⼤,而且灵活。
函数组件中可以通过useContext引⼊上下文。
redux是JavaScript应⽤的状态容器。它保证程序⾏为⼀致性 且易于测试。
你可能不需要redux Redux 是负责组织 state 的工具,但你也要考虑它是否适合你的情况。不要因为有⼈告诉你要用 Redux就去用,花点时间好好想想使⽤了 Redux 会带来的好处或坏处。
在下⾯的场景中,引入 Redux 是⽐较明智的:
- 你有着相当大量的、随时间变化的数据;
- 你的 state 需要有一个单一可靠数据来源;
- 你觉得把所有 state 放在最顶层组件中已经无法满足需要了。
- 某个组件的状态需要共享。
以上内容摘抄自官网
redux
是 JavaScript
应⽤的状态容器,提供可预测化的状态管理。它保证程序行为一致性且易于测试。
这里写了一下redux, react-redux的简易实现
jsx// redux 完整使用
import React, {Component} from "react";
import {bindActionCreators} from 'redux'
import {connect} from 'react-redux'
// connect帮组⼦组件与store链接,其实就是⾼阶组件,这⾥返回的是⼀个新的组件
export default connect(
// mapStateToProps Function (state, [ownProps])
// state => ({count: state}),
// !谨慎使⽤ownProps,如果它发⽣变化,mapStateToProps就会执⾏,⾥⾯的state会被重新计算,容易影响性能
(state, ownProps) => {
// console.log('mapStateToProps ownProps ', ownProps);
return {
count: state
}
},
// mapDispatchToProps Object/Function 如果不定义 默认把dispatch注⼊组件
// 如果是对象的话,原版的dispatch就没有被注⼊了
// {
// add: () => ({type: 'ADD'})
// },
// Function (dispatch, [ownProps])
// !谨慎使⽤ownProps,如果它发⽣变化,mapDispatchToProps就会执⾏,容易影响性能
(dispatch) => {
let res = {
add: () => ({type: "ADD"}),
minus: () => ({type: "MINUS"})
};
res = bindActionCreators(res, dispatch);
return {
dispatch,
...res
};
},
// mergeProps Function
// 如果指定了这个参数,`mapStateToProps()` 与 `mapDispatchToProps()` 的执⾏结果和组件⾃身的 `props` 将传⼊到这个回调函数中。
// (stateProps, dispatchProps, ownProps) => {
// console.log("mergeProps", stateProps, dispatchProps); //sy-log
// return {omg: "omg", ...stateProps, ...dispatchProps};
// }
)(class ReactReduxPage extends Component{
render() {
// console.log(this.props);
const {count, dispatch, add, minus} = this.props;
return <div>
<h3>ReactReduxPage</h3>
<p>{count}</p>
<button onClick={() => dispatch({type: "ADD"})}>
add use dispatch
</button>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
}
});
jsximport React, {Component} from 'react';
const ValueContext = React.createContext();
export const connect = (
mapStateToProps = state => state,
mapDispatchToProps,
) => WrapperComponent => {
return class extends Component {
static contextType = ValueContext;
constructor(props) {
super(props);
this.state = {
props: {}
};
}
componentDidMount() {
this.update();
const {subscribe} = this.context;
subscribe(() => {
this.update();
})
}
update () {
const {getState, dispatch} = this.context;
const stateProps =
mapStateToProps(getState());
let dispatchProps;
if(typeof mapDispatchToProps === 'function') {
dispatchProps = mapDispatchToProps(dispatch, this.props)
} else if (typeof mapDispatchToProps === 'object') {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch)
} else {
dispatchProps = {
dispatch,
}
}
this.setState({
props: {
...stateProps,
...dispatchProps,
}
})
}
render () {
return <WrapperComponent {...this.props} {...this.state.props}/>;
}
}
}
export class Provider extends Component {
render () {
return <ValueContext.Provider value={this.props.store}>
{this.props.children}
</ValueContext.Provider>;
}
}
function bindActionCreators(creators, dispatch) {
const obj = {}
for(let prop in creators) {
obj[prop] = bindActionCreator(creators[obj], dispatch)
}
return obj;
}
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(args))
}
先来看一下官方定义
简单可扩展的状态管理
总结: 对于一般应用而言使用redux即可,对于复杂或性能要求较高的业务场景则使用mobx。
参考资料:我为什么从redux迁移到了mobx
npx create-react-app XXX
cd XXX && npm i && npm eject
npm i mobx mobx-react
package.json
babel增加pluginsjson[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
App.js
jsximport React from 'react';
import { makeObservable, observable, action } from 'mobx';
import { Observer } from 'mobx-react';
class App extends React.Component {
constructor(props) {
super(props);
makeObservable(this);
}
@observable
count = 0;
@action
add = () => {
++this.count;
}
render() {
return <Observer>
{
() => <div>
<p>{this.count}</p>
<button onClick={this.add}>ADD</button>
</div>
}
</Observer>
}
}
export default App;
完整案例, 点击这里
函数式的优势
面向对象的优势
Redux | Mobx |
---|---|
数据流程很自然,需要依据对象引用是否变化来控制更新粒度。 | 数据流流动不自然,只有用到的数据才会引发绑定,局部精确更新,但免去了粒度控制烦恼。 |
如果充分利用时间回溯的特征,可以增强业务的可预测性与错误定位能力 | 时间回溯能力较为复杂 |
时间回溯代价很高,因为每次都要更新引用,除非增加代码复杂度,或使用 immutable。 | 自始至终一份引用,不需要 immutable,也没有复制对象的额外开销。 |
时间回溯的另一个代价是 action 与 reducer 完全脱节,数据流过程需要自行脑补。原因是可回溯必然不能保证引用关系。 | 数据流动由函数调用一气呵成,便于调试。 |
引入中间件,其实主要为了解决异步带来的副作用,业务逻辑或多或少参杂着 magic。 | 业务开发不是脑力活,而是体力活,少一些 magic,多一些效率。 |
但是灵活利用中间件,可以通过约定完成许多复杂的工作。 | 由于没有 magic,所以没有中间件机制,没法通过 magic 加快工作效率(这里 magic 是指 action 分发到 reducer 的过程)。 |
对 typescript 支持困难。 | 完美支持 typescript。 |
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!