2023-09-04
ReactJS
00
请注意,本文编写于 444 天前,最后修改于 443 天前,其中某些信息可能已经过时。

目录

Context
基本使用
Function组件如何使用?
多个context使用案例
总结
redux
介绍
手写redux
react-redux
使用
简易实现
mobx
已有了redux为什么还有mobx?
使用案例
聊一聊数据流管理方案
函数式 vs 面向对象
Redux vs Mobx

本文梳理React状态管理的一些方案,包括以下内容

  1. Context用法
  2. redux使用和简易实现
  3. mobx介绍

使用React开发一个页面,我们通常将一个页面划分若干个区域(这些区域存在嵌套关系,一个父区域可能包含多个子区域)。每个区域对应一个组件,每个组件可能有一些状态。

  • 只属于该组件的状态用state表示
  • 兄弟组件之间的状态需要提取到父组件中,用state表示,使用props向下传递
  • 不同层级组件之间的状态,如果提取到祖先组件的state中,props往下传递会非常冗肿,React官方提供了Context方案,解决跨组件通信问题。

Context

官方context使用 redux中文文档

Context提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

何时使用Context
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

使用Context之前的考虑
Context主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。

基本使用

官方给了一个theme案例,非常清晰,这里为了讲下面的内容还是把代码贴出来

jsx
import 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组件。

Function组件如何使用?

修改ThemedButtonfunction

jsx
function ThemedButton() { let bgColor = React.useContext(ThemeContext); return <button style={{backgroundColor: bgColor}}> ThemedButton </button>; }

多个context使用案例

用户信息和主题色是项目中常用的两个全局状态

jsx
const 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

redux是JavaScript应⽤的状态容器。它保证程序⾏为⼀致性 且易于测试。

image.png

介绍

你可能不需要redux Redux 是负责组织 state 的工具,但你也要考虑它是否适合你的情况。不要因为有⼈告诉你要用 Redux就去用,花点时间好好想想使⽤了 Redux 会带来的好处或坏处。

在下⾯的场景中,引入 Redux 是⽐较明智的:

  • 你有着相当大量的、随时间变化的数据;
  • 你的 state 需要有一个单一可靠数据来源;
  • 你觉得把所有 state 放在最顶层组件中已经无法满足需要了。
  • 某个组件的状态需要共享。
    以上内容摘抄自官网

reduxJavaScript应⽤的状态容器,提供可预测化的状态管理。它保证程序行为一致性且易于测试。

image.png

手写redux

这里写了一下redux, react-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> } });

简易实现

jsx
import 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)) }

mobx

官方文档

先来看一下官方定义

  • redux JavaScripty应用可预测的状态管理容器
  • mobx 简单可扩展的状态管理

已有了redux为什么还有mobx?

  • 在react生态中,redux可谓是状态管理中的霸主了,mobx则是一方诸侯。
  • redux上手相对简单,而mobx,API较多,相对复杂一些。
  • redux强调数据不可变,方便数据追溯,很容易实现时间旅行的功能;mobx中数据是可变的,然而时间回溯代价就很高。
  • redux并知道state是否发生变化,dispatch的时候会更新所有的订阅者,你需要通过拆分store或使用shouldComponentUpdate周期进行优化
  • mobx中的数据是响应式的,会进行依赖收集(与vue思想类似),知道要更新哪些内容,所以对于复杂应用,mobx不需要做过多处理就能达到很好的性能。

总结: 对于一般应用而言使用redux即可,对于复杂或性能要求较高的业务场景则使用mobx。

参考资料:我为什么从redux迁移到了mobx

使用案例

  1. 接入准备
  • npx create-react-app XXX
  • cd XXX && npm i && npm eject
  • npm i mobx mobx-react
  • 修改package.json babel增加plugins
json
[ "@babel/plugin-proposal-decorators", { "legacy": true } ]
  1. 代码示例
  • App.js
jsx
import 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;

完整案例, 点击这里

聊一聊数据流管理方案

函数式 vs 面向对象

函数式的优势

  • 纯函数无副作用,可时间回溯,适合并发
  • 数据流变化处理很拿手,比如rxjs
  • 对于复杂数据逻辑、科学计算的开发和维护效率更高

面向对象的优势

  • javascript的鸭子类型,标明它基于对象,不适合完全函数式的表达
  • 数学思维和数据处理适合函数式,技术是为业务服务的,而业务模型适合用面向对象。
  • 业务开发和做研究不同,逻辑严谨的函数式相当完美,但别指望每个程序员都愿意消耗大量脑细胞解决日常业务问题。

Redux vs Mobx

ReduxMobx
数据流程很自然,需要依据对象引用是否变化来控制更新粒度。数据流流动不自然,只有用到的数据才会引发绑定,局部精确更新,但免去了粒度控制烦恼。
如果充分利用时间回溯的特征,可以增强业务的可预测性与错误定位能力时间回溯能力较为复杂
时间回溯代价很高,因为每次都要更新引用,除非增加代码复杂度,或使用 immutable。自始至终一份引用,不需要 immutable,也没有复制对象的额外开销。
时间回溯的另一个代价是 action 与 reducer 完全脱节,数据流过程需要自行脑补。原因是可回溯必然不能保证引用关系。数据流动由函数调用一气呵成,便于调试。
引入中间件,其实主要为了解决异步带来的副作用,业务逻辑或多或少参杂着 magic。业务开发不是脑力活,而是体力活,少一些 magic,多一些效率。
但是灵活利用中间件,可以通过约定完成许多复杂的工作。由于没有 magic,所以没有中间件机制,没法通过 magic 加快工作效率(这里 magic 是指 action 分发到 reducer 的过程)。
对 typescript 支持困难。完美支持 typescript。

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!