• 阅读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

阅读react-redux源码(七) - 实现一个react-redux相关推荐

  1. redux源码分析之一:createStore.js

    欢迎关注redux源码分析系列文章: redux源码分析之一:createStore.js redux源码分析之二:combineReducers.js redux源码分析之三:bindActionC ...

  2. redux源码分析之二:combineReducers.js

    欢迎关注redux源码分析系列文章: redux源码分析之一:createStore.js redux源码分析之二:combineReducers.js redux源码分析之三:bindActionC ...

  3. 逐行阅读redux源码(二)combineReducers

    前情提要 逐行阅读redux源码(一)createStore 认识reducers 在我们开始学习源码之前,我们不妨先来看看何谓reducers: 如图所见,我们可以明白, reducer 是用来对初 ...

  4. [react] 你阅读了几遍React的源码?都有哪些收获?你是怎么阅读的?

    [react] 你阅读了几遍React的源码?都有哪些收获?你是怎么阅读的? 0遍 +1 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎大家一起讨论 主目录 与歌 ...

  5. 学习 redux 源码整体架构,深入理解 redux 及其中间件原理

    如果觉得内容不错,可以设为星标置顶我的公众号 1. 前言 你好,我是若川.这是学习源码整体架构系列第八篇.整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是 ...

  6. Redux 源码解读 —— 从源码开始学 Redux

    已经快一年没有碰过 React 全家桶了,最近换了个项目组要用到 React 技术栈,所以最近又复习了一下:捡起旧知识的同时又有了一些新的收获,在这里作文以记之. 在阅读文章之前,最好已经知道如何使用 ...

  7. 深入理解redux之从redux源码到react-redux的原理

    在使用react的过程中,用redux来管理应用中的状态,使应用流更清晰的同时也会有小小的疑惑,比如reducer在redux中时怎么发挥作用的,为什么只要写好reducer,和dispatch特定a ...

  8. Android ---- Ijkplayer阅读native层源码之IjkMediaPlayer_prepareAsync(五)

    整章目录:Android------- IjkPlayer 源码学习目录 本篇会有很多源代码,请注意阅读每行代码上面的注释. 本篇介绍的主要内容为上图红框圈起部分: IjkMediaPlayer_pr ...

  9. 前端战五渣学React——JSX React.createElement() React.ReactElement()源码

    最近<一拳超人>动画更新第二季了,感觉打斗场面没有第一季那么烧钱了,但是剧情还挺好看的,就找了漫画来看.琦玉老师真的厉害!!!打谁都一拳,就喜欢看老师一拳把那些上来就吹牛逼的反派打的稀烂, ...

最新文章

  1. 如何充分利用JavaScript(ES6)中的解构功能
  2. 600页!分享珍藏很久的《推荐系统学习手册》(附链接)
  3. JavaScript基础——Date对象
  4. java获取content-disposition_java – Content-Disposition附件不起作用 – 将...
  5. 二级c语言上机编程技巧,二级C语言上机编程题技巧总结
  6. 什么时候出python4_Python4要来了?快来看看Python之父怎么说
  7. 天翼云从业认证(1.3)了解数据库的概念、SQL、关系型数据库、大数据和 NoSQL 数据库
  8. mysql8.0.22 win7_ArcGIS10.8地理信息软件中英文版安装教程
  9. sql数据库简单增删改查
  10. 算法4------字符串的字典序最长子序列
  11. java singleton inner class_Java面向对象设计模式-单例模式
  12. 存储过程出错会回滚吗_一个人做梦过程中不小心挂了,梦境会继续吗?
  13. 解决Cannot dlopen some GPU libraries.问题
  14. 二次无约束二值优化模型(The Quadratic Unconstrained Binary Optimization(QUBO) model)
  15. hg8546m虚拟服务器,华为HG8546路由及WIFI配置说明
  16. 华为手机什么时候更新鸿蒙系统_华为鸿蒙2.0系统什么时候可以用 华为鸿蒙2.0系统升级方法介绍[多图]...
  17. 【性能优化】记录一次YounGC峰值优化
  18. Picture 线段树扫描线求轮廓线
  19. 计算机常用快捷键(世上最全)
  20. Asp.net Core使用Microsoft.Office.Interop.Word转换文档

热门文章

  1. network 拦截不到东西是怎么做到的?_都说读中职院校学不到东西,中职学生到底是怎么学习的?...
  2. 模板库 | 销售管理类报表,邀您提反馈
  3. sqoop导数据出现问题
  4. laravel API开发,使用dingo/api
  5. C#控件之Repeater控件使用
  6. CentOS7桌面版系统使用的一些小技巧
  7. 来呀,快活呀。iOS 超级码农群:538549344
  8. Mac OS X下安装nvm的方法
  9. Discuz UCenter 修改手记 - 2014.12.19
  10. FastReport.Net 使用字符串