• 手写Redux(二):实现React-redux


    在React中,组件和组件之间通过props传递数据的规范,极大地增强了组件之间的耦合性,而context类似全局变量一样,里面的数据能被随意接触就能被随意修改,每个组件都能够改context里面的内容会导致程序的运行不可预料。

    Redux是一个独立专门用于做状态管理的JS库,帮助开发出行为稳定可预测的、易于测试的应用程序,通过react-redux,可集中式管理React应用中多个组件共享的状态。

    1 前置知识,context和高阶组件

    在实现自己的React-redux之前,需要先了解下以下2个东西:React的context和高阶组件。

    1.1 React中的Context

    在开发react项目时,我们会写很多的组件,同时组件之间嵌套组成一棵组件树,组件之间需要共享数据,如果通过props的方式传递数据的话,就需要将共享数据保存在这些组件的公共父节点组件的state,再通过props往下传递;如果还需要修改这些共享数据的话,需层层传递修改数据的回调函数,这简直就是地狱,完全无法维护。 为此,React的Context提供了一种在组件之间共享数据的方式,而不必显式地通过组件树的逐层传递props,其设计目的是为了共享那些对于一个组件树而言是“全局”的数据。 我们看一个具体示例,比如我们有这样一棵组件树,看如何通过Context实现组件树的数据共享:

    import React,{Component} from 'react';
    import ReactDOM from 'react-dom/client';
    import PropTypes from 'prop-types';
    
    class Index extends Component {// 如果你要给组件设置 context,那么 childContextTypes 是必写的// 它的作用其实 propsType 验证组件 props 参数的作用类似。不过它是验证 getChildContext 返回的对象。static childContextTypes = {themeColor: PropTypes.string}constructor () {super()this.state = { themeColor: 'red' }}// 设置 context 的过程,它返回的对象就是 contextgetChildContext () {return { themeColor: this.state.themeColor }}render () {return (
    )} } class Header extends Component {render () {return (

    Header

    </div>)} } class Content extends Component {render () {return (<div><h2>Content</h2></div>)} } class Title extends Component {// static contextTypes = {themeColor: PropTypes.string}render () {return (// 获取context中的数据<h1 style={<!-- -->{ color: this.context.themeColor }}>Title</h1>)} } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<Index /> ); <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{"spm":"1001.2101.3001.7365"}"><img class="look-more-preCode contentImg-no-view" src="https://1000bd.com/contentImg/2022/06/27/191644837.png" alt="" title=""></span></div><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li></ul></pre> <p><code>Index</code>把<code>state.themeColor</code>放到某个地方,这个地方是每个<code>Index</code>的子组件都可以访问到的。当某个子组件需要的时候就直接去那个地方拿就好了,而不需要一层层地通过<code>props</code>来获取。不管组件树的层次有多深,任何一个组件都可以直接到这个公共的地方提取<code>themeColor</code>状态。 <code>Context</code>就是这么一个东西,某个组件只要往自己的<code>Context</code>里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的<code>Context</code>只有 <strong>它的子组件</strong> 能够访问,它的父组件是不能访问到的。</p> <h4><a name="t2"></a><a id="12__38"></a>1.2 高阶组件</h4> <p>高阶组件在概念上很简单,但却非常常用、实用的东西,被大量 React.js 相关的第三方库频繁地使用,如果你能够灵活地使用高阶组件,可以让你代码更加优雅,复用性、灵活性更强。</p> <p><strong>高阶组件就是一个函数,传给它一个组件,它返回一个新的组件,这个新的组件会使用你传给它的组件作为子组件。</strong></p> <p>一个简单的高阶组件:</p> <pre data-index="1" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' export default (WrappedComponent) => {class NewComponent extends Component {// 可以做很多自定义逻辑render () {return <WrappedComponent />}}return NewComponent } <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li></ul></pre> <p>它就是简单的构建了一个新的组件类<code>NewComponent</code>,然后把传进入去的<code>WrappedComponent</code>渲染出来,这个例子看着好像没啥用,但是我们可以在<code>render()</code>之前做很多自定义逻辑,比如从远程获取数据,通过props传给子组件。</p> <p>下面再给出个实用点的例子:</p> <p>src/wrapWithRemoteData.js</p> <pre data-index="2" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' // 替代ajax const getRemoteDate = key => {if (key==='user') return '张三';else if (key==='content') return 'Redux 是 JavaScript 状态容器,提供可预测化的状态管理。'; } export default (WrappedComponent, key) => {class NewComponent extends Component {constructor () {super()this.state = { data: null }}componentWillMount () {let data = getRemoteDate(key)this.setState({ data })}render () {return <WrappedComponent data={this.state.data} />}}return NewComponent } <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li></ul></pre> <p>src/InputWithUser.js</p> <pre data-index="3" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React,{Component} from "react"; import wrapWithRemoteData from './wrapWithRemoteData' class InputWithUserName extends Component {render () {return <>user: <input value={this.props.data} /></>} } export default wrapWithRemoteData(InputWithUserName, 'user') <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li></ul></pre> <p>src/textareaWithContent.js</p> <pre data-index="4" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React,{Component} from "react"; import wrapWithRemoteData from './wrapWithRemoteData' class TextareaWithContent extends Component {render () {return <textarea value={this.props.data} />} } export default wrapWithRemoteData(TextareaWithContent, 'content') <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li></ul></pre> <p>可以看到,<code>wrapWithRemoteData</code>作为一个高阶组件,接收2个参数:</p> <p>1.<strong>WrappedComponent:</strong> 被包装的子组件;<br> 2.<strong>key:</strong> 数据key,根据该值获取远程数据,并通过<code>props</code>传给<code>WrappedComponent</code>;</p> <img src="https://1000bd.com/contentImg/2024/04/16/8e48a4ad609aaa70.png"> <p><code>InputWithUser</code>生成一个新的输入框,其内容在高级组件通过<code>key='user'</code>远程数据访问获取; <code>textareaWithContent</code>生成一个新的文本域,其内容在高级组件通过<code>key='content'</code>远程数据访问获取。</p> <p>到这里,高阶组件的作用其实不言而喻,其实就是为了组件之间的代码复用。组件可能有着某些相同的逻辑,把这些逻辑抽离出来,放到高阶组件中进行复用。高阶组件内部的包装组件和被包装组件之间通过 props 传递数据。</p> <h3><a name="t3"></a><a id="2_createStore_100"></a>2 使用createStore管理共享数据</h3> <p>了解完<code>Context</code>和高阶组件的概念后,我们使用上一篇文章【xxx】中的<code>createStore</code>结合React组件来实现自己的<code>react-redux</code>。 设计这样一棵组件树:</p> <img src="https://1000bd.com/contentImg/2024/04/16/4c5ea8404218d5b5.png"> <p>其中,<code>Index</code>组件中使用<code>Context</code>维护全局组件颜色的状态变量<code>themeColor</code>,<code>ThemeSwitch</code>中两个按钮控制所有组件的颜色。</p> <p>使用<code>create-react-app</code>脚手架创建一个新的react项目<code>my-redux</code>,在src/下添加以下文件:</p> <p>src/index.js</p> <pre data-index="5" class="set-code-hide prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import ReactDOM from 'react-dom/client'; import Header from './Header' import Content from './Content' import PropTypes from 'prop-types' import './index.css' function createStore(reducer) {let state = nullconst listeners = []const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = reducer(state, action)listeners.forEach((listener) => listener())}dispatch({}) // 初始化 statereturn { getState, dispatch, subscribe } } // 负责主题颜色的 reducer const themeReducer = (state, action) => {if (!state) return {themeColor: 'red' // 状态名themeColor,初始值red}switch (action.type) {case 'CHANGE_COLOR': // 只允许一种操作:修改themeColorreturn { ...state, themeColor: action.themeColor }default:return state} } // 创建store const store = createStore(themeReducer) class Index extends Component {// 给顶层父组件组件设置 context,childContextTypes 是必写的static childContextTypes = {store: PropTypes.object}// 把 store 设置到 context 中的过程,它返回的对象就是 contextgetChildContext () {return { store }}render () {return (<div><Header /><Content /></div>)} } const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Index /> ); <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{"spm":"1001.2101.3001.7365"}"><img class="look-more-preCode contentImg-no-view" src="https://1000bd.com/contentImg/2022/06/27/191644837.png" alt="" title=""></span></div><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li><li style="color: rgb(153, 153, 153);">20</li><li style="color: rgb(153, 153, 153);">21</li><li style="color: rgb(153, 153, 153);">22</li><li style="color: rgb(153, 153, 153);">23</li><li style="color: rgb(153, 153, 153);">24</li></ul></pre> <p>src/header.js</p> <pre data-index="6" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import PropTypes from 'prop-types' class Header extends Component {static contextTypes = {store: PropTypes.object}constructor () {super()this.state = { themeColor: '' }}// 首次渲染 & 注册渲染回调函数componentWillMount () {const { store } = this.contextthis._updateThemeColor()store.subscribe(() => this._updateThemeColor())}_updateThemeColor () {// 获取context中的storeconst { store } = this.contextconst state = store.getState()this.setState({ themeColor: state.themeColor })}render () {return (<h1 style={<!-- -->{ color: this.state.themeColor }}>Header</h1>)} } export default Header <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li></ul></pre> <p>src/content.js</p> <pre data-index="7" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import PropTypes from 'prop-types' import ThemeSwitch from './ThemeSwitch' class Content extends Component {static contextTypes = {store: PropTypes.object}constructor () {super()this.state = { themeColor: '' }}// 首次渲染 & 注册渲染回调函数componentWillMount () {const { store } = this.contextthis._updateThemeColor()store.subscribe(() => this._updateThemeColor())}_updateThemeColor () {// 获取context中的storeconst { store } = this.contextconst state = store.getState()this.setState({ themeColor: state.themeColor })}render () {return (<div><p style={<!-- -->{ color: this.state.themeColor }}>Content</p><ThemeSwitch /></div>)} } export default Content <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li></ul></pre> <p>src/themeSwitch.js</p> <pre data-index="8" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import PropTypes from 'prop-types' class ThemeSwitch extends Component {static contextTypes = {store: PropTypes.object}constructor () {super()this.state = { themeColor: '' }}// 首次渲染 & 注册渲染回调函数componentWillMount () {const { store } = this.contextthis._updateThemeColor()store.subscribe(() => this._updateThemeColor())}_updateThemeColor () {// 获取context中的storeconst { store } = this.contextconst state = store.getState()this.setState({ themeColor: state.themeColor })}// dispatch action 去改变颜色handleSwitchColor (color) {const { store } = this.contextstore.dispatch({type: 'CHANGE_COLOR',themeColor: color})}// button组件绑定点击回调方法render () {return (<div><buttonstyle={<!-- -->{ color: this.state.themeColor }}onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button><buttonstyle={<!-- -->{ color: this.state.themeColor }}onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button></div>)} } export default ThemeSwitch <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li></ul></pre> <p>启动服务后,点击两种按钮就可以触发<code>dispatch</code>回调,修改<code>store</code>中的<code>state</code>,更改整体颜色: <img src="https://1000bd.com/contentImg/2024/04/16/735747fd8804be48.png"></p> <p>1.在顶层父组件<code>Index</code>中,创建了一个<code>store</code>用来存取全局共享数据;<br> 2.在顶层父组件<code>Index</code>中,创建了一个<code>themeReducer</code>用来初始化全局颜色状态数据,并定义只允许<code>CHANGE_COLOR</code>这个<code>action</code>来操作数据;<br> 3.通过<code>Context</code>将该状态共享给<code>Index</code>所有的子组件;<br> 4.子组件中在<code>componentWillMount</code>生命周期里获取状态数据,渲染组件颜色,并通过<code>subscribe</code>注册状态变化时重新渲染的回调方法。</p> <h3><a name="t4"></a><a id="3__178"></a>3 使用高阶组件封装重复代码</h3> <h4><a name="t5"></a><a id="31__181"></a>3.1 代码复用性的问题</h4> <p>上面一节,我们将上篇文章中实现的<code>createStore</code>版本的<code>Redux</code>应用到了实际的组件树中,但是还存在两个问题:</p> <p>1.有大量重复的逻辑:它们基本的逻辑都是,取出<code>Context</code>,取出里面的<code>store</code>,然后用里面的状态设置自己的状态,这些代码逻辑其实都是相同的。<br> 2.对<code>Context</code>依赖性过强:这些组件都要依赖<code>Context</code>来取数据,使得这个组件复用性基本为零。想一下,如果别人需要用到里面的<code>ThemeSwitch</code>组件,但是他们的组件树并没有<code>Context</code>也没有<code>store</code>,他们没法用这个组件了。</p> <p>对于第一个问题,根据上面高阶组件章节,可以把一些可复用的逻辑放在高阶组件当中,高阶组件包装的新组件和原来组件之间通过<code>props</code>传递信息,减少代码的重复程度。 第二个问题,可以通过纯函数的思想解决。到底什么样的组件才叫复用性强的组件?如果一个组件对外界的依赖过于强,那么这个组件的移植性会很差,就像这些严重依赖<code>Context</code>的组件一样。一个组件的渲染只依赖于外界传进去的<code>props</code>和自己的<code>state</code>,而并不依赖于其他的外界的任何数据,也就是说像纯函数一样,给它什么,它就吐出(渲染)什么出来,这种组件的复用性是最强的。</p> <p>看下如何通过高阶组件、纯函数的思想解决这两个问题:</p> <pre data-index="9" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import PropTypes from 'prop-types' export connect = (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}// TODO: 如何从 store 取数据?render () {return <WrappedComponent />}}return Connect } <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li></ul></pre> <p><code>connect</code>函数接受一个组件<code>WrappedComponent</code>作为参数,把这个组件包含在一个新的组件<code>Connect</code>里面,<code>Connect</code>会去<code>context</code>里面取出<code>store</code>。现在要把<code>store</code>里面的数据取出来通过<code>props</code>传给<code>WrappedComponent</code>,这样做就解决前面提出的两个问题:</p> <p>1.<code>Context</code>中的<code>store</code>注入到<code>WrappedComponent</code>中的逻辑由<code>connect</code>高阶组件统一完成;<br> 2.<code>WrappedComponent</code>作为纯函数本身不依赖于其他的外界的任何数据,该组件的渲染只依赖于外界传进去的<code>props</code>和自己的<code>state</code>。</p> <h4><a name="t6"></a><a id="32_mapStateToPropsmapDispatchToProps_204"></a>3.2 mapStateToProps和mapDispatchToProps</h4> <p>解决了代码复用性的问题后,又出现了两个新的问题:</p> <p>1.每个<code>WrappedComponent</code>需要的<code>state</code>是不同,不应该整个<code>state</code>传给<code>WrappedComponent</code>,而是需要做个转换,按需分配;<br> 2.同理,不应该把<code>dispatch</code>的完整功能传给<code>WrappedComponent</code>,而是传递与这个<code>WrappedComponent</code>相关的操作;</p> <p>用户可以自定义两个转换函数,对<code>state</code>和<code>dispatch</code>做转换,如:</p> <pre data-index="10" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">// 只传递 state.themeColor, state.themeName const mapStateToProps = (state) => {return {themeColor: state.themeColor,themeName: state.themeName} } <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li></ul></pre> <pre data-index="11" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">// 只允许 CHANGE_COLOR 这个action const mapDispatchToProps = (dispatch) => {return {onSwitchColor: (color) => {dispatch({ type: 'CHANGE_COLOR', themeColor: color })}} } <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li></ul></pre> <h4><a name="t7"></a><a id="33_connect_224"></a>3.3 connect</h4> <p>综合3.1和3.2提出的问题,这一节提出我们的解决方案,实现这样一个方法<code>connect</code>:</p> <p><strong>入参:</strong> 接收 <code>mapStateToProps</code>,<code>mapDispatchToProps</code></p> <p><strong>出参:</strong> 生成一个高阶组件,这个高阶组件内部获取Context的store,将<code>store</code>中的<code>state</code>、<code>dispatch</code>分别应用入参的<code>mapStateToProps</code>,<code>mapDispatchToProps</code>转换函数转换后,作为<code>props</code>传递给<code>WrappedComponent</code></p> <p>具体用法如下:</p> <pre data-index="12" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">const higherOrderComponent = connect(mapStateToProps, mapDispatchToProps); // 返回一个高阶组件 const NewComponent = higherOrderComponent(WrappedComponent); // 高阶组件包裹子组件 <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li></ul></pre> <p>将上面两条语句合并成一条</p> <pre data-index="13" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">const NewComponent = connect(mapStateToProps, mapDispatchToProps)(WrappedComponent) <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li></ul></pre> <p>connect、WrappedComponent的关系如下图所示:</p> <img src="https://1000bd.com/contentImg/2024/04/16/81de4d03a7e24cd8.png"> <p>下面给出具体实现:</p> <p>src/react-redux.js</p> <pre data-index="14" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import PropTypes from 'prop-types' export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {class Connect extends Component {// 子组件获取context中数据时用于校验类型static contextTypes = {store: PropTypes.object}constructor () {super()this.state = {// 存放转换后的state、转换后的dispatch、组件本身接收的propsallProps: {}}}componentWillMount () {// 获取context中的storeconst { store } = this.contextthis._updateProps()// 注册状态数据变化时的回调store.subscribe(() => this._updateProps())}_updateProps () {const { store } = this.context// 转换statelet stateProps = mapStateToProps? mapStateToProps(store.getState(), this.props): {} // 防止 mapStateToProps 没有传入// 转换dispatchlet dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props): {} // 防止 mapDispatchToProps 没有传入// 存放转换后的state、转换后的dispatch、组件本身接收的propsthis.setState({allProps: {...stateProps,...dispatchProps,...this.props}})}render () {// 将转换后的state、转换后的dispatch、组件本身接收的props 作为props传给子组件return <WrappedComponent {...this.state.allProps} />}}return Connect } <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li></ul></pre> <p>以ThemeSwitch为例,看下如何使用connect进行改造:</p> <p>src/ThemeSwitch.js</p> <pre data-index="15" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from './react-redux' class ThemeSwitch extends Component {// 类型检查static propTypes = {themeColor: PropTypes.string,onSwitchColor: PropTypes.func}// 按钮点击操作回调handleSwitchColor (color) {if (this.props.onSwitchColor) {// 调用转换后的 onSwitchColorthis.props.onSwitchColor(color)}}render () {return (<div><buttonstyle={<!-- -->{ color: this.props.themeColor }}onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button><buttonstyle={<!-- -->{ color: this.props.themeColor }}onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button></div>)} } // 将state转换后通过props注入被包裹的组件 const mapStateToProps = (state) => {return {themeColor: state.themeColor} } // 将props转换后通过props注入被包裹的组件 const mapDispatchToProps = (dispatch) => {return {onSwitchColor: (color) => {dispatch({ type: 'CHANGE_COLOR', themeColor: color })}} } ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch) export default ThemeSwitch <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li></ul></pre> <p>src/Header.js</p> <pre data-index="16" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from './react-redux' class Header extends Component {static propTypes = {themeColor: PropTypes.string}render () {return (<h1 style={<!-- -->{ color: this.props.themeColor }}>Header</h1>)} } const mapStateToProps = (state) => {return {themeColor: state.themeColor} } Header = connect(mapStateToProps)(Header) export default Header <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li></ul></pre> <p>src/content.js</p> <pre data-index="17" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import PropTypes from 'prop-types' import ThemeSwitch from './ThemeSwitch' import { connect } from './react-redux' class Content extends Component {static propTypes = {themeColor: PropTypes.string}render () {return (<div><p style={<!-- -->{ color: this.props.themeColor }}>Content</p><ThemeSwitch /></div>)} } const mapStateToProps = (state) => {return {themeColor: state.themeColor} } Content = connect(mapStateToProps)(Content) export default Content <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li></ul></pre> <p>src/index.js</p> <pre data-index="18" class="set-code-hide prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">import React, { Component } from 'react' import ReactDOM from 'react-dom/client'; import Header from './Header' import Content from './Content' import PropTypes from 'prop-types' import './index.css' function createStore (reducer) {let state = nullconst listeners = []const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = reducer(state, action)listeners.forEach((listener) => listener())}dispatch({}) // 初始化 statereturn { getState, dispatch, subscribe } } const themeReducer = (state, action) => {if (!state) return {themeColor: 'red'}switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state} } const store = createStore(themeReducer) class Index extends Component {static childContextTypes = {store: PropTypes.object}getChildContext () {return { store }}render () {return (<div><Header /><Content /></div>)} } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<Index /> ); <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><div class="hide-preCode-box"><span class="hide-preCode-bt" data-report-view="{"spm":"1001.2101.3001.7365"}"><img class="look-more-preCode contentImg-no-view" src="https://1000bd.com/contentImg/2022/06/27/191644837.png" alt="" title=""></span></div><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li></ul></pre> <p>通过这一节的改造,与第2节中的基础实现相比,<code>ThemeSwitch</code>、<code>Header</code>、<code>Content</code>中的</p> <p>1.获取<code>context</code>中的<code>store</code>;<br> 2.通过<code>subscribe</code>注册共享状态数据变化时回调;<br> 3.直接调用<code>dispatch</code>修改共享状态数据;</p> <p>这3类操作都被优化掉了,我们的组件变得更加干净,取而代之的是,通过<code>props</code>将这些状态、操作回调方法传给对应组件,这3类操作如下所示:</p> <pre data-index="19" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">... // 首次渲染 & 注册渲染回调函数 componentWillMount () {const { store } = this.contextthis._updateThemeColor()store.subscribe(() => this._updateThemeColor()) } _updateThemeColor () {// 获取context中的storeconst { store } = this.contextconst state = store.getState()this.setState({ themeColor: state.themeColor }) } ... <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li></ul></pre> <pre data-index="20" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">... // dispatch action 去改变颜色 handleSwitchColor (color) {const { store } = this.contextstore.dispatch({type: 'CHANGE_COLOR',themeColor: color}) } ... <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li></ul></pre> <h3><a name="t8"></a><a id="4_Provider_364"></a>4 Provider</h3> <p>通过上一节改造,<code>ThemeSwitch</code>、<code>Header</code>、<code>Content</code>已经变得非常干净了,还剩<code>Index</code>公共父组件中依然包含<code>Contex</code>、<code>createStore</code>的代码逻辑,这一节讲解通过<code>Provider</code>优化<code>Index</code>父组件实现。</p> <p>由于<code>Index</code>组件是所有组件的公共父组件,所以将<code>Contex</code>设置在<code>Index</code>中,这样他的所有子组件才能共享<code>Context</code>中的数据。我们可以额外构建一个组件来做这种脏活,然后让这个组件成为组件树的根节点,那么它的子组件都可以获取到<code>Context</code>了。</p> <p>src/react-redux.js中新增如下代码</p> <pre data-index="21" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">export class Provider extends Component {static propTypes = {store: PropTypes.object,children: PropTypes.any}static childContextTypes = {store: PropTypes.object}getChildContext () {return {store: this.props.store}}render () {return (<div>{this.props.children}</div>)} } <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li></ul></pre> <p><code>Provider</code>做的事情也很简单,它就是一个容器组件,会把嵌套的内容原封不动作为自己的子组件渲染出来。它还会把外界传给它的<code>props.store</code>放到<code>Context</code>,这样子组件<code>connect</code>的时候都可以获取到。</p> <p>可以用它来重构我们的 src/index.js:</p> <pre data-index="22" class="set-code-show prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)" style="position: unset;">class Index extends Component {// 删除 Index 里面所有关于 context 的代码// static childContextTypes = {// store: PropTypes.object// }// getChildContext () {// return { store }// }render () {return (<div><Header /><Content /></div>)} } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<Provider store={store}><Index /></Provider> ); <div class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"></div></code><ul class="pre-numbering" style="opacity: 0.870126;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li></ul></pre> <p>这样我们就把所有关于<code>Context</code>的代码从Index组件里面删除了。</p> <img src="https://1000bd.com/contentImg/2024/04/16/43ffb3939ed7399f.png"> <h3><a name="t9"></a><a id="5__395"></a>5 总结</h3> <p>这几节的成果就是 <code>react-redux</code> 这个文件里面的两个内容:<code>connect</code> 函数和 <code>Provider</code> 容器组件。理解了</p> <p>1.为什么要 connect,<br> 2.为什么要 mapStateToProps 和 mapDispatchToProps,<br> 3.什么是 Provider,</p> <p>这就是 react-redux 的基本内容,当然它是一个简易 react-redux,很多地方需要完善。</p> <h3><a name="t10"></a><a id="_406"></a>最后</h3> <p>最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。<br> <img src="https://1000bd.com/contentImg/2023/11/06/065924445.jpeg" alt=""><br> <img src="https://1000bd.com/contentImg/2023/11/06/065924133.png" alt=""><br> <img src="https://1000bd.com/contentImg/2023/11/06/065924500.png" alt=""><br> <img src="https://1000bd.com/contentImg/2023/11/06/065924425.png" alt=""></p> <blockquote> <p><strong>有需要的小伙伴,可以点击下方卡片领取,无偿分享</strong></p> </blockquote> </div> </div> </li> <li class="list-group-item ul-li"> <b>相关阅读:</b><br> <nobr> <a href="/Article/Index/1266555">【EKF】EKF原理</a> <br /> <a href="/Article/Index/786472">如何将wav转为mp3格式,wav中间mp3步骤</a> <br /> <a href="/Article/Index/1074836">json入门教程+在java中的一些便捷操作</a> <br /> <a href="/Article/Index/1291491">《C和指针》笔记27:递归</a> <br /> <a href="/Article/Index/1540567">java Web 疫苗预约管理系统用eclipse定制开发mysql数据库BS模式java编程jdbc</a> <br /> <a href="/Article/Index/1375974">KubeVela交付</a> <br /> <a href="/Article/Index/1135030">【OpenCV-Python】教程:3-7 Canny边缘检测</a> <br /> <a href="/Article/Index/749097">从购买服务器到网站搭建成功保姆级教程~超详细</a> <br /> <a href="/Article/Index/916789">java计算机毕业设计基于安卓Android微信的团务智慧管理小程序uniAPP</a> <br /> <a href="/Article/Index/1486915">从零开始封装 vue 组件</a> <br /> </nobr> </li> <li class="list-group-item from-a mb-2"> 原文地址:https://blog.csdn.net/web220507/article/details/128135981 </li> </ul> </div> <div class="col-lg-4 col-sm-12"> <ul class="list-group" style="word-break:break-all;"> <li class="list-group-item ul-li-bg" aria-current="true"> 最新文章 </li> <li class="list-group-item ul-li"> <nobr> <a href="/Article/Index/1484446">攻防演习之三天拿下官网站群</a> <br /> <a href="/Article/Index/1515268">数据安全治理学习——前期安全规划和安全管理体系建设</a> <br /> <a href="/Article/Index/1759065">企业安全 | 企业内一次钓鱼演练准备过程</a> <br /> <a href="/Article/Index/1485036">内网渗透测试 | Kerberos协议及其部分攻击手法</a> <br /> <a href="/Article/Index/1877332">0day的产生 | 不懂代码的"代码审计"</a> <br /> <a href="/Article/Index/1887576">安装scrcpy-client模块av模块异常,环境问题解决方案</a> <br /> <a href="/Article/Index/1887578">leetcode hot100【LeetCode 279. 完全平方数】java实现</a> <br /> <a href="/Article/Index/1887512">OpenWrt下安装Mosquitto</a> <br /> <a href="/Article/Index/1887520">AnatoMask论文汇总</a> <br /> <a href="/Article/Index/1887496">【AI日记】24.11.01 LangChain、openai api和github copilot</a> <br /> </nobr> </li> </ul> <ul class="list-group pt-2" style="word-break:break-all;"> <li class="list-group-item ul-li-bg" aria-current="true"> 热门文章 </li> <li class="list-group-item ul-li"> <nobr> <a href="/Article/Index/888177">十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!</a> <br /> <a href="/Article/Index/797680">奉劝各位学弟学妹们,该打造你的技术影响力了!</a> <br /> <a href="/Article/Index/888183">五年了,我在 CSDN 的两个一百万。</a> <br /> <a href="/Article/Index/888179">Java俄罗斯方块,老程序员花了一个周末,连接中学年代!</a> <br /> <a href="/Article/Index/797730">面试官都震惊,你这网络基础可以啊!</a> <br /> <a href="/Article/Index/797725">你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法</a> <br /> <a href="/Article/Index/797702">心情不好的时候,用 Python 画棵樱花树送给自己吧</a> <br /> <a href="/Article/Index/797709">通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!</a> <br /> <a href="/Article/Index/797716">13 万字 C 语言从入门到精通保姆级教程2021 年版</a> <br /> <a href="/Article/Index/888192">10行代码集2000张美女图,Python爬虫120例,再上征途</a> <br /> </nobr> </li> </ul> </div> </div> </div> <!-- 主体 --> <!--body结束--> <!--这里是footer模板--> <!--footer--> <nav class="navbar navbar-inverse navbar-fixed-bottom"> <div class="container"> <div class="row"> <div class="col-md-12"> <div class="text-muted center foot-height"> Copyright © 2022 侵权请联系<a href="mailto:2656653265@qq.com">2656653265@qq.com</a>    <a href="https://beian.miit.gov.cn/" target="_blank">京ICP备2022015340号-1</a> </div> <div style="width:300px;margin:0 auto; padding:0px 5px;"> <a href="/regex.html">正则表达式工具</a> <a href="/cron.html">cron表达式工具</a> <a href="/pwdcreator.html">密码生成工具</a> </div> <div style="width:300px;margin:0 auto; padding:5px 0;"> <a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010502049817" style="display:inline-block;text-decoration:none;height:20px;line-height:20px;"> <img src="" style="float:left;" /><p style="float:left;height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">京公网安备 11010502049817号</p></a> </div> </div> </div> </div> </nav> <!--footer--> <!--footer模板结束--> <script src="/js/plugins/jquery/jquery.js"></script> <script src="/js/bootstrap.min.js"></script> <!--这里是scripts模板--> <!--scripts模板结束--> </body> </html>