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

目录

Context
Basic Usage
How does the function component to use Context?
Multiple context uses cases
Summarize
redux
Introduction
handwritten Redux
react-redux
Usage
简易实现
MobX
Why do we need MobX when we already have Redux?
Use case
聊一聊数据流管理方案
函数式 vs 面向对象
Redux vs Mobx

This article outlines several solutions for react state management, and including the following.

  1. Context Usage
  2. the redux usage and simple implementation
  3. mobx introduce

When develop a webpage in using React, we typically divide the page into several regions(these regions are nested, and a parent region may contain multiple child regions). each region corresponds to a component, and each component will have same state.

  • State belonging only to this component is represented by state
  • State between sibling components need to be extracted to the parent component, represented by state, and passed down through props.
  • State between components at different levels: if extracted to the ancestor component's state , passing down props would be very cumbersome, React office provides the Context solution to solve cross component communication problems.

Context

官方context使用 redux中文文档

Context provides a way to pass data between component trees without having manually add props to each component.

When to use Context
The purpose of Context is to share data that is global to component trees, such as currently authenticated user, theme, or prefered language.

Considerations before using Context
The primary use case for Context is when the components at different levels need to access the same data. Using it with caution, as it can reduce component reusability.

If you just want to avoid passing some props layer by layer, component composition(component composition) is sometimes a better solution than context.

Basic Usage

The offical documentation provides a very clear example of theme, but I'll paste the code here to explain the following content.

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

How does the function component to use Context?

Modify ThemedButton to function

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

Multiple context uses cases

User information and theme color are two commonly used global states in a project.

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/>);

multiple context usage through Context.Consumer tag to receive context, comforms to JSX syntax。

Precautions

  • The value state needs to be promoted to parent node's data reason

Summarize

In the offical React documentation, Context is categoried as advanced section, belonging to React's high-level API, but the offical remmendation is not to use Context in stable versions of apps. However, this doesn't mean we don't need to pay attention to Context. In fact many excellent React components use Context to accomplish their own functionality.

  • For example, <Provider /> in react-redux, provides a global store through Context
  • The routing component react-router manages routing state and other aspects through Context.

In react component development, using context efficiently can make your components powerful and flexible.

Functional component can import a context by using useContext.

redux

redux is a state container in JavaScript applications. It ensures consistent application behavior and easy to test.

image.png

Introduction

You may not need redux
Redux is tool responsibilty for organizing state, but you also need to consider whether it suits you situation. Don't just use redux because someone tells you to. Take some time to think carefully about the benefits and drawbacks of using Redux.

In the following scenario, introducing Redux is a wiser choice:

  • You have a considerable amount of data that changes over time;
  • You state needs a single, reliable source of data;
  • You fell that putting all the state in the top-level component is no longer sufficient to meet you needs.
  • The state of a component needs to be shared.

Redux is a state container for JavaScript applications, providing predictable state management. It ensures consistent program behavior and is easy to test.

image.png

handwritten Redux

Here, I've written a simple implementation of Redux and React-redux.

源码点这里

react-redux

Usage

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

Chinese document

Office definition

  • redux Predicate state management container for JavaScript application
  • mobx Simple and scalable state management

Why do we need MobX when we already have Redux?

  • In the react ecosystem, Redux is undoubtedly the dominant state management tool, while MobX is regional power.
  • Redux is relatively easy to learn, while mobx has more APIs , and is more relatively more complex.
  • Redux emphasizes data immutability, make data trackability convenient and easy enabling time travel functionality; in mobx data is mutable, and time travel is very costly.
  • Redux doesn't know state changes and updates all subscribes during dispatch. You need to optimize this by spliting the store or using the shouldComponentUpdate lifecycle.
  • Data in MobX is relative and performs dependency collection(similar to Vue). It knows which content needs to be updated, so for complex applications, Mobx can achieve good performance without much processing.

Sumarize: Both Redux and Mobx have their advantages and disadvantages. For general applications, Redux is sufficient, while MobX is used for complex and high-performance business scenarios.

Refference:Why I migrated from Redux to Mobx

Use case

  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;

Complete case, click here

聊一聊数据流管理方案

函数式 vs 面向对象

函数式的优势

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

面向对象的优势

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

Redux vs Mobx

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

本文作者:郭敬文

本文链接:

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