阅读react-redux源码(七) - 实现一个react-redux

  • 阅读react-redux源码 - 零
  • 阅读react-redux源码 - 一
  • 阅读react-redux源码(二) - createConnect、match函数的实现
  • 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
  • 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates
  • 阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理
  • 阅读react-redux源码(六) - selectorFactory处理store更新
  • 阅读react-redux源码(七) - 实现一个react-redux

首先总结react-redux的实现原理,然后根据原理的指导来完成一个简易的react-redux。

  1. 之前说过可以引起React组件更新的元素有两种,一个是props的更新,一个是state的更新,但是props的跟新放眼整个react应用也是state的更新。所以React应用中组件重新渲染只能通过state的更新。
  2. 在React中需要跨层级传递消息可以使用Context。
  3. Redux可以通过subscribe来订阅store中state的更新。

react-redux是通过Context来向下跨组件传递store,然后后代组件通过某些方法来监听store中state的变化,后代组件监听到变化之后设置state来引起自身的更新。

在这其中需要注意的是后代组件监听的state之外的state变动需要避免,不要引起自身重新渲染,还有父组件重新render,子组件关注的props没有更新也需要避免重新render。

ReactRedux的使用方式

首先创建store:

import { createStore } from 'redux'const UPDATE_A = 'UPDATE_A'export function createUpdateA (payload) {return {type: UPDATE_A,payload}
}const initialState = {a: 1,b: 1
}function reducer (state = initialState, action) {const {type, payload} = actionswitch (type) {case UPDATE_A:return Object.assign({}, state, {a: payload})default:return state}}export default createStore(reducer)

将store提供给react-redux:

import ReactDOM from 'react-dom'
import React from 'react'
import Provider from './Provider'
import store from './store'
import ChildComponentA from './ChildComponentA'function App () {return (<Provider store={store}><CildComponentA /></Provider>)
}ReactDOM.render(<App />, document.querySelector('#root'))

将业务组件通过react-redux连接到store

import React from 'react'
import connect from './connect'
import { createUpdateA } from './store'function _ChildComponentA (props) {console.log('ChildComponentA执行了')return (<div><p>我是来自store的a:{props.a}</p><p onClick={() => props.updateA(props.a + 1)}>a+1</p></div>)
}function mapStateToProps (state) {return {a: state.a}
}function mapDispatchToProps (dispatch) {return {updateA(a) {dispatch(createUpdateA(a))}}
}export default connect(mapStateToProps, mapDispatchToProps)(_ChildComponentA)

上面呈现了react-redux的使用方式,主要使用了两个方法,一个是Provider,一个是connect。下面将这两个方法填起来。

react-redux的实现

import { createContext } from 'react'export const context = createContext(null)export default function Provider (props) {return (<context.Provider value={props.store}>{props.children}</context.Provider>)
}

这样Provider就完成了。

import React, { useContext, useState, useEffect } from 'react'
import { context as defaultContext } from './provider'function mergeProps (stateProps, dispatchProps, ownProps) {return { ...ownProps, ...stateProps, ...dispatchProps }
}export default function connectHoc (mapStateToProps = () => ({}), mapDispatchToProps = () => ({})) {return function wrapWithConnect (wrappedComponent) {function connectFunction (props) {const [_, setState] = useState(0)const store = useContext(defaultContext)useEffect(() => {return store.subscribe(update)}, [])function update () {setState(times => ++times)}const stateProps = mapStateToProps(store.getState())const dispatchProps = mapDispatchToProps(store.dispatch)const allProps = mergeProps(stateProps, dispatchProps, props)return <wrappedComponent {...allProps} />}// 为了阻止父组件render带来的不必要更新return React.memo(ConnectFunction)}
}

到这就完成一半了,实现了子组件订阅store变化重新渲染的功能,并且可以避免因为父组件更新导致子组件重新渲染引起的性能问题。

还缺一半是store中不关心的state的更新也会引起子组件渲染,现在即使是更新了store中的bChildComponentA也会执行。

添加代码如下:

store.js:

const UPDATE_B = 'UPDATE_B'export function createUpdateB (payload) {return {type: UPDATE_B,payload}
}function reducer (state = initialState, action) {const {type, payload} = actionswitch (type) {...case UPDATE_B:return Object.assign({}, state, {b: payload})...}
}

添加组件文件childComponentB.js:

import React from 'react'
import connect from '../copyReactRedux2/connect'
import { createUpdateB } from './store.js'function _ChildComponentB (props) {console.log('connectFunctionB执行了')return (<div><p>我是来自store的b:{props.b}</p><p onClick={() => props.updateB(props.b + 1)}>b+1</p></div>)
}function mapStateToProps (state) {return {b: state.b}
}function mapDispatchToProps (dispatch) {return {updateB(b) {dispatch(createUpdateB(b))}}
}export default connect(mapStateToProps, mapDispatchToProps)(_ChildComponentB)

在index.js中添加代码:

function App () {return (...<CildComponentB />...)
}

准备就绪,点击b+1文字会发现控制台中不仅仅会打印 connectFunctionB执行了还会打印connectFunctionA执行了,这并不是我们希望的。

下面来修改connect的实现修复这个问题。

首先实现下mergeProps函数,让它具有对比记忆的特性,如果没有值改变则返回老的mergedProps。

mergeProps函数:

import { shallowEqual, strictEqual } from './equals'function mergeProps (stateProps, dispatchProps, ownProps) {return { ...ownProps, ...stateProps, ...dispatchProps }
}function mergedPropsFactory() {let hasOnceRun = falselet stateProps = nulllet dispatchProps = nulllet ownProps = nulllet mergedProps = nullreturn (newStateProps, newDispatchProps, newOwnProps) => {debuggerif (!hasOnceRun) {stateProps = newStatePropsdispatchProps = newDispatchPropsownProps = newOwnPropsmergedProps = mergeProps(stateProps, dispatchProps, ownProps)hasOnceRun = truereturn mergedProps}if (shallowEqual(stateProps, newStateProps) && shallowEqual(ownProps, newOwnProps)) {stateProps = newStatePropsdispatchProps = newDispatchPropsownProps = newOwnProps} else {stateProps = newStatePropsdispatchProps = newDispatchPropsownProps = newOwnPropsmergedProps = mergeProps(stateProps, dispatchProps, ownProps)}return mergedProps}
}

修改wrapWithConnect如下:

function wrapWithConnect (WrappedComponent) {function connectFunction (props) {const [_, setState] = useState(0)const store = useContext(defaultContext)useEffect(() => {return store.subscribe(update)}, [])function update () {if (cacheAllProps.current === mergeProps(mapStateToProps(store.getState()), cacheDispatchProps.current, cacheOwnProps.current)) returnsetState(times => ++times)}const mergeProps = useMemo(() => (mergedPropsFactory()), [])const stateProps = mapStateToProps(store.getState())const dispatchProps = mapDispatchToProps(store.dispatch)const allProps = mergeProps(stateProps, dispatchProps, props)const cacheAllProps = useRef(null)const cacheOwnProps = useRef(null)const cacheStatePros = useRef(null)const cacheDispatchProps = useRef(null)useEffect(() => {cacheAllProps.current = allPropscacheStatePros.current = statePropscacheDispatchProps.current = dispatchPropscacheOwnProps.current = props}, [allProps])return <WrappedComponent {...allProps} />}// 为了阻止父组件render带来的不必要更新return React.memo(connectFunction)}
function is(x, y) {if (x === y) {return x !== 0 || y !== 0 || 1 / x === 1 / y} else {return x !== x && y !== y}
}export function shallowEqual(objA, objB) {if (is(objA, objB)) return trueif (typeof objA !== 'object' ||objA === null ||typeof objB !== 'object' ||objB === null) {return false}const keysA = Object.keys(objA)const keysB = Object.keys(objB)if (keysA.length !== keysB.length) return falsefor (let i = 0; i < keysA.length; i++) {if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||!is(objA[keysA[i]], objB[keysA[i]])) {return false}}return true
}export function strictEqual (a, b) {return a === b
}

到这里整个都完整了,上面的代码实现了将React组建连接到redux,响应store中state的变动,并且还能做到规避不必要的更新。

  • 阅读react-redux源码 - 零
  • 阅读react-redux源码 - 一
  • 阅读react-redux源码(二) - createConnect、match函数的实现
  • 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
  • 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates
  • 阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理
  • 阅读react-redux源码(六) - selectorFactory处理store更新
    • 阅读react-redux源码(七) - 实现一个react-redux

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/361983.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[读书笔记]TCP/IP详解V1读书笔记-4 5

IP地址与以太网地址之间的关系 R P发送一份称作A R P请求的以太网数据帧给以太网上的每个主机。这个过程称作广播&#xff0c;在32 bit的I P地址和采用不同网络技术的硬件地址之间提供动态映射 ----------------------------------------- arp以太网帧的类型字段为x 0 8 0 6&am…

未来是Apache Karaf上的微服务架构

这是Jamie Goodyear的客座博客文章&#xff08; 博客 &#xff0c; icbts &#xff09;。 他是Savoir Technologies的开源倡导者&#xff0c;Apache开发人员和计算机系统分析师&#xff1b; 他为全球大型组织设计&#xff0c;批判和支持了体系结构。 他拥有纽芬兰纪念大学的计…

springcloud微服务多节点高性能、高可用、高并发部署

1. 共有三个服务 discovery服务&#xff0c;domain服务&#xff0c;gateway服务。 discovery服务是用来注册其他服务的&#xff0c;作为服务治理用。 domain服务是主业务服务。 gateway服务是所有服务的一个入口&#xff0c;用来做一些服务的判断和过滤用。 2. 有三台机器分别为…

只能是数字、字母、-和_

在文本框的keypress事件调用下面函数。 如 <input disabled"disabled" type"text" iduserNameToEdit οnkeypress"TextValidate()" /> 如果在文本框中按下特殊字符键&#xff0c;则显示警告信息&#xff0c;或者输入框不接受非法输入。 …

代码风格之Prettier简介

多人协作中统一的代码风格有利于项目的发展这是共识&#xff0c;但是采用什么标准来统一代码这选择就相对纷杂。项目刚开始使用了ESLint来规范代码&#xff0c;但是ESLint默认是支持JavaScript&#xff0c;加上配置可以支持TypeScript&#xff0c;而样式的支持则需要再配置Styl…

带有Swagger的Spring Rest API –集成和配置

如今&#xff0c;公开的API终于获得了应有的关注&#xff0c;公司也开始意识到其战略价值。 但是&#xff0c;使用第三方API确实是一项繁琐的工作&#xff0c;尤其是当这些API维护不当&#xff0c;设计不当或缺少任何文档时。 这就是为什么我决定四处寻找可以为集成编程人员和其…

A customized combobox with JQuery

要求实现一个轻量级的在客户端筛选的combobox&#xff0c;支持大数据量&#xff08;超过1000个items&#xff09;&#xff0c;能快速检索内容&#xff0c;并支持数据的设置和活动等基本操作。在这之前尝试过使用Jquery UI的Autocomplete&#xff0c;但是当数据量太大时客户端检…

使用内存回流的方法来实现将image的内容转换为 byte[]

在今天的开发中老大不知道怎么突发奇想&#xff0c;要使用Image的Byte数据。当时使用老几种方式效果均不理想&#xff0c;最后发现其实可以使用内存回流的方式来实现。多的不说老&#xff0c;马上贴上代码&#xff1a;/**//// <summary> /// 将byte[]转换为Image…

TypeScript中的class声明了什么

在初看TypeScript的时候在这里卡住的时间难以估计&#xff0c;并不能很好的理解”换个角度说&#xff0c;我们可以认为类具有 实例部分与 静态部分这两个部分。“这句话。今天再回头看这部分文档&#xff0c;在同事的帮助下突然有了比较通透的理解。 class Greeter {static st…

CentOS 6下搭建Apache+MySQL+PHP+SSL

网上的一些文章都已经比较老了&#xff0c;现在版本高了之后&#xff0c;其实配置是很省力的&#xff08;不考虑什么负载的话&#xff09; 分享全过程&#xff0c;出了文中提到的安装epel rpmfushion 源指令不同外&#xff0c;其他的过程也适用与Centos 5 1.安装CentOS 6 ,可以…

通过设计国际象棋游戏来了解策略模式

今天&#xff0c;我们将借助一个示例来尝试了解策略模式。 我们将考虑的示例是国际象棋游戏。 这里的目的是解释策略模式&#xff0c;而不是构建全面的国际象棋游戏解决方案。 策略模式&#xff1a;策略模式被称为行为模式-用于管理对象之间的算法&#xff0c;关系和职责。 策…

vs2010 问题 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏

vs2010 问题 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏 在安装 VS2010 后&#xff0c;再安装 VS2012 VS2015 等&#xff0c;原来的 .NET 4.0 会被替换为 .NET 4.5。不会恢复 .NET 4.0 。这时&#xff0c;VS2010的 cvtres.exe 就无法使用了。如果 PATH…

Nginx 使用try_files遇到的问题

背景&#xff1a; root /some/path; location / {try_files $uri $uri/ /dist/index.html; }使用React之类的的库来开发前端页面的时候&#xff0c;因为是单页应用所以需要上面的Nginx配置&#xff0c;用来在找不到html文件的时候内部重定向到/dist/index.html文件。 服务器上…

群发邮件

最近&#xff0c;通过两周的学习&#xff0c;对.net 的基础知识有了进一步的了解。觉得自己可以写个小程序了。于是花了两天时间写了一个 群发邮件的一个WinForm小程序。自己在这里小秀一下&#xff0c;表扬及鼓励一下自己。哈哈&#xff01; 此小程序在发送邮件的基础上还添加…

深入研究ES6 Generators

ES6 Generators系列&#xff1a; ES6 Generators基本概念深入研究ES6 GeneratorsES6 Generators的异步应用ES6 Generators并发 如果你还不知道什么是ES6 generators&#xff0c;请看我的前一篇文章“ES6 Generators基本概念” 。如果你已经对它有所了解&#xff0c;本文将带你…

在JavaEE中使用CDI的简单面向方面的编程(AOP)

我们编写满足特定业务逻辑的服务API。 涵盖所有服务API&#xff08;如安全性&#xff0c;日志记录&#xff0c;审核&#xff0c;度量延迟等&#xff09;的跨领域问题很少。 这是一个重复的非业务代码&#xff0c;可以在其他方法之间重用。 重用的一种方法是将这些重复的代码移入…

sessionStorage什么时候失效

最近在调试程序的时候无意间看到 cookie 的过期时间是 session&#xff0c;这个 session 表示的是什么时候过期&#xff1f;牵扯出来另一个存储方案 sessionStorage 存储的数据又是什么时候过期呢&#xff1f; 在查找相关资料的时候总会看到会话结束的时候 cookie 会被清除&am…

ES6 解构赋值详解

解构赋值是对赋值运算符的扩展&#xff0c;可以将属性/值从对象/数组中取出&#xff0c;赋值给其他变量。 一、数组的解构赋值 1、基本用法 只要等号两边的模式相同&#xff0c;左边的变量就会被赋予对应的值。 let [a, [[b], c]] [1, [[2], 3]]; a // 1 b // 2 c // 3 let [a…

软件著作权申请流程

一、填写计算机软件著作权登记申请表&#xff08;表格1份&#xff09;包括软件全称、简称、版本号、开发完成日期、软件开发情况&#xff08;独立开发、合作开发、委托开发、下达任务开发&#xff09;、原始取得权利情况、继受取得权利情况、权利范围、软件用途和技术特点&…

Npm install failed with “cannot run in wd”

Linux环境下&#xff0c;root账户&#xff0c;安装某些npm包的时候报下面的错误&#xff0c;例如安装grunt-contrib-imagemin时&#xff1a; Error: EACCES, mkdir /usr/local/lib/node_modules/coffee-scriptnpm ERR! { [Error: EACCES, mkdir /usr/local/lib/node_modules/c…