You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
exportconstStoreContext=React.createContext();functionApp({ children }){const[state,setState]=useState({});return<StoreContext.Providervalue={{ state, setState }}>{children}</StoreContext.Provider>;}functionSon(){const{ state }=useContext(StoreContext);return<div>state是{state.xxx}</div>;}
importReact,{useContext}from'react';import{Store}from'redux';interfaceContextType{
store: Store;}exportconstContext=React.createContext<ContextType|null>(null);exportfunctionuseReduxContext(){constcontextValue=useContext(Context);if(!contextValue){thrownewError('could not find react-redux context value; please ensure the component is wrapped in a <Provider>',);}returncontextValue;}
实现Provider
importReact,{FC}from'react';import{Store}from'redux';import{Context}from'./Context';interfaceProviderProps{
store: Store;}exportconstProvider: FC<ProviderProps>=({ store, children })=>{return<Context.Providervalue={{ store }}>{children}</Context.Provider>;};
import{useReduxContext}from'./Context';import{Dispatch,Action}from'redux';exportfunctionuseDispatch<AextendsAction>(){const{ store }=useReduxContext();returnstore.dispatchasDispatch<A>;}
前言
各位使用react技术栈的小伙伴都不可避免的接触过
redux
+react-redux
的这套组合,众所周知redux是一个非常精简的库,它和react是没有做任何结合的,甚至可以在vue项目中使用。redux的核心状态管理实现其实就几行代码
它就是利用闭包管理了state等变量,然后在dispatch的时候通过用户定义reducer拿到新状态赋值给state,再把外部通过subscribe的订阅给触发一下。
那redux的实现简单了,react-redux的实现肯定就需要相对复杂,它需要考虑如何和react的渲染结合起来,如何优化性能。
目标
react-redux
v7中的hook用法部分Provider
,useSelector
,useDispatch
方法。(不实现connect
方法)预览
预览地址:https://sl1673495.github.io/tiny-react-redux
性能
说到性能这个点,自从React Hook推出以后,有了
useContext
和useReducer
这些方便的api,新的状态管理库如同雨后春笋版的冒了出来,其中的很多就是利用了Context
做状态的向下传递。举一个最简单的状态管理的例子
利用useState或者useContext,可以很轻松的在所有组件之间通过Context共享状态。
但是这种模式的缺点在于Context会带来一定的性能问题,下面是React官方文档中的描述:
想像这样一个场景,在刚刚所描述的Context状态管理模式下,我们的全局状态中有
count
和message
两个状态分别给通过StoreContext.Provider
向下传递Counter
计数器组件使用了count
Chatroom
聊天室组件使用了message
而在计数器组件通过Context中拿到的setState触发了
count
改变的时候,由于聊天室组件也利用
useContext
消费了用于状态管理的StoreContext,所以聊天室组件也会被强制重新渲染,这就造成了性能浪费。虽然这种情况可以用
useMemo
进行优化,但是手动优化和管理依赖必然会带来一定程度的心智负担,而在不手动优化的情况下,肯定无法达到上面动图中的重渲染优化。那么
react-redux
作为社区知名的状态管理库,肯定被很多大型项目所使用,大型项目里的状态可能分散在各个模块下,它是怎么解决上述的性能缺陷的呢?接着往下看吧。缺陷示例
在我之前写的类vuex语法的状态管理库react-vuex-hook中,就会有这样的问题。因为它就是用了
Context
+useReducer
的模式。你可以直接在 在线示例 这里,在左侧菜单栏选择
需要优化的场景
,即可看到上述性能问题的重现,优化方案也已经写在文档底部。这也是为什么我觉得
Context
+useReducer
的模式更适合在小型模块之间共享状态,而不是在全局。实现
介绍
本文的项目就上述性能场景提炼而成,由
聊天室
组件,用了store中的count
计数器
组件,用了store中的message
控制台
组件,用来监控组件的重新渲染。用最简短的方式实现代码,探究react-redux为什么能在
count
发生改变的时候不让使用了message
的组件重新渲染。redux的定义
redux的使用很传统,跟着官方文档对于TypeScript的指导走起来,并且把类型定义和store都export出去。
在项目中使用
可以看到,我们用
Provider
组件里包裹了Count
组件,并且把redux的store传递了下去在子组件里,通过
useDispatch
可以拿到redux的dispatch, 通过useSelector
可以访问到store,拿到其中任意的返回值。构建Context
利用官方api构建context,并且提供一个自定义hook:
useReduxContext
去访问这个context,对于忘了用Provider包裹的情况进行一些错误提示:对于不熟悉自定义hook的小伙伴,可以看我之前写的这篇文章:
使用React Hooks + 自定义Hook封装一步一步打造一个完善的小型应用。
实现Provider
实现useDispatch
这里就是简单的把dispatch返回出去,通过泛型传递让外部使用的时候可以获得类型提示。
泛型推导不熟悉的小伙伴可以看一下之前这篇:
进阶实现智能类型推导的简化版Vuex
实现useSelector
这里才是重点,这个api有两个参数。
selector
: 定义如何从state中取值,如state => state.count
equalityFn
: 定义如何判断渲染之间值是否有改变。在性能章节也提到过,大型应用中必须做到只有自己使用的状态改变了,才去重新渲染,那么
equalityFn
就是判断是否渲染的关键了。关键流程(初始化):
latestSelectedState
保存上一次selector返回的值。checkForceUpdate
方法用来控制当状态发生改变的时候,让当前组件的强制渲染。store.subscribe
订阅一次redux的store,下次redux的store发生变化执行checkForceUpdate
。关键流程(更新)
dispatch
触发了redux store的变动后,store会触发checkForceUpdate
方法。checkForceUpdate
中,从latestSelectedState
拿到上一次selector的返回值,再利用selector(store)拿到最新的值,两者利用equalityFn
进行比较。有了这个思路,就来实现代码吧:
总结
本文涉及到的源码地址:
https://github.com/sl1673495/tiny-react-redux
原版的react-redux的实现肯定比这里的简化版要复杂的多,它要考虑class组件的使用,以及更多的优化以及边界情况。
从简化版的实现入手,我们可以更清晰的得到整个流程脉络,如果你想进一步的学习源码,也可以考虑多花点时间去看官方源码并且单步调试。
The text was updated successfully, but these errors were encountered: