只用两个自定义 Hooks 就能替代 React-Redux ?

bb6c73626541e53d2cae41396e5ef55f.gif

作者 | 👽

来源 | 前端Sharing

前言

之前有朋友问我,React Hooks 能否解决 React 项目状态管理的问题。这个问题让我思索了很久,最后得出的结论是:能,不过需要两个自定义 hooks 去实现。那么具体如何实现的呢?那就是今天要讲的内容了。

通过本文,你能够学习以下内容:

  • useContext ,useRef ,useMemo,useEffect 的基本用法。

  • 如何将不同组件的自定义 hooks 建立通信,共享状态。

  • 合理编写自定义 hooks , 分析 hooks 之间的依赖关系。

  • 自定义 hooks 编写过程中一些细节问题。

带着如上的知识点,开启阅读之旅吧~

e7f9f5fd57715a4c08eae4efca044d59.png

设计思路

首先,看一下要实现的两个自定义 hooks 具体功能。

  • useCreateStore 用于产生一个状态 Store ,通过 context 上下文传递 ,为了让每一个自定义 hooks useConnect 都能获取 context 里面的状态属性。

  • useConnect 使用这个自定义 hooks 的组件,可以获取改变状态的 dispatch 方法,还可以订阅 state ,被订阅的 state 发生变化,组件更新。

如何让不同组件的自定义 hooks 共享状态并实现通信呢?

首先不同组件的自定义 hooks ,可以通过 useContext 获得共有状态,而且还需要实现状态管理和组件通信,那么就需要一个状态调度中心来统一做这些事,可以称之为 ReduxHooksStore ,它具体做的事情如下:

  • 全局管理 state, state 变化,通知对应组件更新。

  • 收集使用 useConnect 组件的信息。组件销毁还要清除这些信息。

  • 维护并传递负责更新的 dispatch 方法。

  • 一些重要 api 要暴露给 context 上下文,传递给每一个 useConnect

1、useCreateStore 设计

首先 useCreateStore 是在靠近根部组件的位置的, 而且全局只需要一个,目的就是创建一个 Store ,并通过 Provider 传递下去。

使用:

const store = useCreateStore( reducer , initState )

参数:

  • reducer :全局 reducer,纯函数,传入 state 和 action ,返回新的 state 。

  • initState :初始化 state 。

返回值:为 store 暴露的主要功能函数。

2、Store设计

Store 为上述所说的调度中心,接收全局 reducer ,内部维护状态 state ,负责通知更新 ,收集用 useConnect 的组件。

const Store = new ReduxHooksStore(reducer,initState).exportStore()

参数:接收两个参数,透传 useCreateStore 的参数。

3、useConnect设计

使用 useConnect 的组件,将获得 dispatch 函数,用于更新 state ,还可以通过第一个参数订阅 state ,被订阅的 state 改变 ,会让组件更新。

// 订阅 state 中的 number 
const mapStoreToState = (state)=>({ number: state.number  })
const [ state , dispatch ] = useConnect(mapStoreToState)

参数:

  • mapStoreToState:将 Store 中 state ,映射到组件的 state 中,可以做视图渲染使用。

  • 如果没有第一个参数,那么只提供 dispatch 函数,不会订阅 state 变化带来的更新。

返回值:返回值是一个数组。

  • 数组第一项:为映射的 state 的值。

  • 数组第二项:为改变 state 的 dispatch 函数。

4、原理图

a6d0dade729244b23872c6aeec1e5d49.png

7.jpg

a036d6e42e95e200d798e9a4e5ad0e54.png

useCreateStore

export const ReduxContext = React.createContext(null)
/* 用于产生 reduxHooks 的 store */
export function useCreateStore(reducer,initState){const store = React.useRef(null)/* 如果存在——不需要重新实例化 Store */if(!store.current){store.current  = new ReduxHooksStore(reducer,initState).exportStore()}return store.current
}

useCreateStore 主要做的是:

  • 接收 reducerinitState ,通过 ReduxHooksStore 产生一个 store ,不期望把 store 全部暴露给使用者,只需要暴露核心的方法,所以调用实例下的 exportStore抽离出核心方法。

  • 使用一个 useRef 保存核心方法,传递给 Provider

0afe3787b6d0547ccda8ca4f735f2cb4.png

状态管理者 —— ReduxHooksStore

接下来看一下核心状态 ReduxHooksStore 。

import { unstable_batchedUpdates } from 'react-dom'
class ReduxHooksStore {constructor(reducer,initState){this.name = '__ReduxHooksStore__'this.id = 0this.reducer = reducerthis.state = initStatethis.mapConnects = {}}/* 需要对外传递的接口 */exportStore=()=>{return {dispatch:this.dispatch.bind(this),subscribe:this.subscribe.bind(this),unSubscribe:this.unSubscribe.bind(this),getInitState:this.getInitState.bind(this)}}/* 获取初始化 state */getInitState=(mapStoreToState)=>{return mapStoreToState(this.state)}/* 更新需要更新的组件 */publicRender=()=>{unstable_batchedUpdates(()=>{ /* 批量更新 */Object.keys(this.mapConnects).forEach(name=>{const { update } = this.mapConnects[name]update(this.state)})})}/* 更新 state  */dispatch=(action)=>{this.state = this.reducer(this.state,action)// 批量更新this.publicRender()}/* 注册每个 connect  */subscribe=(connectCurrent)=>{const connectName = this.name + (++this.id)this.mapConnects[connectName] =  connectCurrentreturn connectName}/* 解除绑定 */unSubscribe=(connectName)=>{delete this.mapConnects[connectName]}
}

状态

  • reducer:这个 reducer 为全局的 reducer ,由 useCreateStore 传入。

  • state:全局保存的状态 state ,每次执行 reducer 会得到新的 state 。

  • mapConnects:里面保存每一个 useConnect 组件的更新函数。用于派发 state 改变带来的更新。

方法

负责初始化:

  • getInitState:这个方法给自定义 hooks 的 useConnect 使用,用于获取初始化的 state 。

  • exportStore:这个方法用于把 ReduxHooksStore 提供的核心方法传递给每一个 useConnect 。

负责绑定|解绑:

  • subscribe:绑定每一个自定义 hooks useConnect 。

  • unSubscribe:解除绑定每一个 hooks 。

负责更新:

  • dispatch:这个方法提供给业务组件层,每一个使用 useConnect 的组件可以通过 dispatch 方法改变 state ,内部原理是通过调用 reducer 产生一个新的 state 。

  • publicRender:当 state 改变需要通知每一个使用 useConnect 的组件,这个方法就是通知更新,至于组件需不需要更新,那是 useConnect  内部需要处理的事情,这里还有一个细节,就是考虑到 dispatch 的触发场景可以是异步状态下,所以用 React-DOM 中 unstable_batchedUpdates 开启批量更新原则。

175904a6b1944300857ef5274a7a071d.png

useConnect

useConnect 是整个功能的核心部分,它要做的事情是获取最新的 state ,然后通过订阅函数 mapStoreToState 得到订阅的 state ,判断订阅的 state 是否发生变化。如果发生变化渲染最新的 state 。

export function useConnect(mapStoreToState=()=>{}){/* 获取 Store 内部的重要函数 */const contextValue = React.useContext(ReduxContext)const { getInitState , subscribe ,unSubscribe , dispatch } = contextValue/* 用于传递给业务组件的 state  */const stateValue = React.useRef(getInitState(mapStoreToState))/* 渲染函数 */const [ , forceUpdate ] = React.useState()/* 产生 */const connectValue = React.useMemo(()=>{const state =  {/* 用于比较一次 dispatch 中,新的 state 和 之前的state 是否发生变化  */cacheState: stateValue.current,/* 更新函数 */update:function (newState) {/* 获取订阅的 state */const selectState = mapStoreToState(newState)/* 浅比较 state 是否发生变化,如果发生变化, */const isEqual = shallowEqual(state.cacheState,selectState)state.cacheState = selectStatestateValue.current  = selectStateif(!isEqual){/* 更新 */forceUpdate({})}}}return state},[ contextValue ]) // 将 contextValue 作为依赖项。React.useEffect(()=>{/* 组件挂载——注册 connect */const name =  subscribe(connectValue)return function (){/* 组件卸载 —— 解绑 connect */unSubscribe(name)}},[ connectValue ]) /* 将 connectValue 作为 useEffect 的依赖项 */return [ stateValue.current , dispatch ]
}

初始化

  • 用 useContext 获取上下文中, ReduxHooksStore 提供的核心函数。

  • 用 useRef 来保存得到的最新的 state 。

  • 用 useState 产生一个更新函数 forceUpdate ,这个函数只是更新组件。

注册|解绑流程

  • 注册:通过 useEffect 来向 ReduxHooksStore 中注册当前 useConnect 产生的 connectValue ,connectValue 是什么马上会讲到。subscribe 用于注册,会返回当前 connectValue 的唯一标识 name 。

  • 解绑:在 useEffect 的销毁函数中,可以用调用 unSubscribe 传入 name 来解绑当前的 connectValue

connectValue是否更新组件

  • connectValue :真正地向 ReduxHooksStore 注册的状态,首先用 useMemo 来对 connectValue 做缓存,connectValue 为一个对象,里面的 cacheState 保留了上一次的 mapStoreToState 产生的 state ,还有一个负责更新的 update 函数。

  • 更新流程 :当触发 dispatch 在 ReduxHooksStore 中,会让每一个 connectValue 的 update 都执行, update 会触发映射函数 mapStoreToState 来得到当前组件想要的 state 内容。然后通过 shallowEqual 浅比较新老 state 是否发生变化,如果发生变化,那么更新组件。完成整个流程。

  • shallowEqual :这个浅比较就是 React 里面的浅比较,在第 11 章已经讲了其流程,这里就不讲了。

分清依赖关系

  • 首先自定义 hooks useConnect 的依赖关系是上下文 contextValue 改变,那么说明 store 发生变化,所以重新通过 useMemo 产生新的 connectValue 。所以 useMemo 依赖 contextValue。

  • connectValue 改变,那么需要解除原来的绑定关系,重新绑定。useEffect 依赖 connectValue。

局限性

整个 useConnect 有一些局限性,比如:

  • 没有考虑 mapStoreToState 可变性,无法动态传入 mapStoreToState 。

  • 浅比较,不能深层次比较引用数据类型。

f9dcc0e84f58cf73087ff227bf80d8f2.png

使用与验证效果

接下来就是验证效果环节,我模拟了组件通信的场景。

根部组件注入 Store

import { ReduxContext , useConnect , useCreateStore } from './hooks/useRedux'
function  Index(){const [ isShow , setShow ] =  React.useState(true)console.log('index 渲染')return <div><CompA /><CompB /><CompC />{isShow &&  <CompD />}<button onClick={() => setShow(!isShow)} >点击</button></div>
}function Root(){const store = useCreateStore(function(state,action){const { type , payload } =actionif(type === 'setA' ){return {...state,mesA:payload}}else if(type === 'setB'){return {...state,mesB:payload}}else if(type === 'clear'){ //清空return  { mesA:'',mesB:'' }}else{return state}},{ mesA:'111',mesB:'111' })return <div><ReduxContext.Provider value={store} ><Index/></ReduxContext.Provider></div>
}

Root根组件

  • 通过 useCreateStore 创建一个 store ,传入 reducer 和 初始化的值 { mesA:'111',mesB:'111' }

  • 用 Provider 传递 store。

Index组件

  • 有四个子组件 CompA , CompB ,CompC ,CompD 。其中 CompD 是 动态挂载的。

业务组件使用

function CompA(){const [ value ,setValue ] = useState('')const [state ,dispatch ] = useConnect((state)=> ({ mesB : state.mesB }) )return <div className="component_box" ><p> 组件A</p><p>组件B对我说 : {state.mesB} </p><input onChange={(e)=>setValue(e.target.value)}placeholder="对B组件说"/><button onClick={()=> dispatch({ type:'setA' ,payload:value })} >确定</button></div>
}function CompB(){const [ value ,setValue ] = useState('')const [state ,dispatch ] = useConnect((state)=> ({ mesA : state.mesA }) )return <div className="component_box" ><p> 组件B</p><p>组件A对我说 : {state.mesA} </p><input onChange={(e)=>setValue(e.target.value)}placeholder="对A组件说"/><button onClick={()=> dispatch({ type:'setB' ,payload:value })} >确定</button></div>
}function CompC(){const [state  ] = useConnect((state)=> ({ mes1 : state.mesA,mes2 : state.mesB }) )return <div className="component_box" ><p>组件A : {state.mes1} </p><p>组件B : {state.mes2} </p></div>
}function CompD(){const [ ,dispatch  ] = useConnect( )console.log('D 组件更新')return <div className="component_box" ><button onClick={()=> dispatch({ type:'clear' })} > 清空 </button></div>
}
  • CompA 和 CompB 模拟组件双向通信。

  • CompC 组件接收 CompA 和 CompB 通信内容,并映射到 mes1 ,mes2 属性上。

  • CompD 没有 mapStoreToState ,没有订阅 state ,state 变化组件不会更新,只是用 dispatch 清空状态。

效果

930e7954311896ffc0174b26e8addf8e.gif

总结

本文通过两个自定义 hooks 实现了 React-Redux 的基本功能,这个模式在真实项目中可以使用吗?我觉得如果是小型项目,是完全可以使用的,对于大型项目还是用 React Redux 或者其他成熟的状态管理工具。

2f998b0fe0e014f42b8ab69c03bde2b7.gif

1d7f2f9d0fb92fca0fb2eb04f392e979.png

往期推荐

如何跨 Namespace 同步 Secret 和 ConfigMap?

掘地三尺搞定 Redis 与 MySQL 数据一致性问题

Redis 内存满了怎么办?这样置才正确!

云原生的本手、妙手和俗手

01846192e10b52c1e09ee060d40a28a0.gif

点分享

ebb23e0d5be8d7be4ff2c6b46d4cdb60.gif

点收藏

0d221440c6fadcaa07a3436a45dfe7df.gif

点点赞

e73fb672c9c4158b2ed287cbd91639db.gif

点在看

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

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

相关文章

java queue源码_Java高并发系列之ArrayBlockingQueue源码解析

JUC包下定义了一个接口&#xff1a;BlockingQueue。其实现类有ArrayBlockingQueue等。本文先来介绍一下ArrayBlockingQueue。从字面可以看出&#xff0c;ArrayBlockingQueue是一种基于数组的阻塞队列&#xff0c;阻塞队列在线程池中会经常使用到。首先来看看ArrayBlockingQueue…

圆桌对话:云时代下,企业运维面临的挑战与机遇

简介&#xff1a;四位企业运维大咖展开对话&#xff0c;讨论“云时代下&#xff0c;企业运维面临的挑战与机遇”。 编者按&#xff1a;上云&#xff0c;已经成为了企业势不可挡的选择。云计算所拥有的“软件定义一切”的特性&#xff0c;推动了敏捷弹性、DevOps、智能运维和基…

揭晓阿里云神龙团队拿下TPCx-BB排名第一的背后技术

简介&#xff1a;近日&#xff0c;TPC Benchmark Express-BigBench(简称TPCx-BB)公布了最新的世界排名&#xff0c;阿里云自主研发的神龙大数据加速引擎获得了TPCx-BB SF3000排名第一的成绩。TPCx-BB测试分为性能与性价比两个维度。其中&#xff0c;在性能维度&#xff0c;在本…

聊聊分布式一致性算法协议 Paxos

作者 | 码哥字节来源 | 码哥字节Google的粗粒度锁服务Chubby的设计开发者Burrows曾经说过&#xff1a;所有一致性协议本质上要么是Paxos要么是其变体。网上有很多讲解Paxos算法的文章&#xff0c;但是质量层次不齐。今天笔者带大家深入聊一下PaxosPaxos是什么&#xff1f;Paxos…

java jdk myeclipse_java初体验(JDK+myeclipse)

前一段时间突击了C语言&#xff0c;主要是针对文件的操作&#xff0c;学习C的目的就是利用C处理oracle数据文件&#xff0c;在脱离oracle软件的情况下&#xff0c;提取出特定表的数据。行链接和行迁移再加上cluster表搞的头大&#xff0c;暂且一放&#xff0c;学习下java,了解下…

专访阿里云王伟民:一站式全链路,阿里云向云原生数据库2.0跃迁

简介&#xff1a;阿里云连续第二年进入Gartner《全球云数据库魔力象限》领导者象限&#xff0c;意味着国产数据库正在迅速崛起。 数据库与操作系统、中间件并称为基础软件&#xff0c;“核高基”中的“基”指的就是这三类基础软件产品&#xff0c;它们在软件产业中有举足轻重的…

媒体声音 | 云数据库,谁才是领导者?

简介&#xff1a;你们从2021年Gartner云数据库管理系统魔力象限中看到了什么…… 2021年新冠疫情进入第二年&#xff0c;对全球的社会、经济而言是不平凡之年&#xff0c;这句话也可用于概括云数据库的发展。随着中国厂商逐步进入全球云数据库市场重要舞台&#xff0c;我们也看…

再聊数据中心网络

作者 | 鲜枣课堂来源 | 小枣君本着“将通信科普到底”的原则&#xff0c;今天&#xff0c;我再继续聊一下这个话题。故事还是要从头开始说起。1973年夏天&#xff0c;两名年轻的科学家&#xff08;温顿瑟夫和罗伯特卡恩&#xff09;开始致⼒于在新⽣的计算机⽹络中&#xff0c;…

面向中后台复杂场景的低代码实践思路

简介&#xff1a;现实中&#xff0c;业务场景多&#xff0c;迭代频繁&#xff0c;变化快到跟不上&#xff0c;规则可能由多人掌握&#xff0c;无法通过一个人了解全貌&#xff1b; 还有业务所在行业固有的复杂度和历史包袱&#xff0c;这些问题都会让我们感到痛苦。 除了逻辑问…

阿里云发布云数据中心专用处理器CIPU, 构建新一代云计算架构体系

6月13日&#xff0c;阿里云智能总裁张建锋在峰会上正式发布CIPU&#xff08;Cloud infrastructure Processing Units&#xff09;&#xff0c;这是为新型云数据中心设计的专用处理器&#xff0c;未来将替代CPU成为云计算的管控和加速中心。 在这个全新体系架构下&#xff0c;C…

Java依赖冲突高效解决之道

简介&#xff1a;由于阿里妈妈联盟团队负责业务的特殊性&#xff0c;系统有庞大的对外依赖&#xff0c;依赖集团六七十个团队服务及N多工具组件&#xff0c;通过此文和大家分享一下我们积累的一些复杂依赖有效治理的经验&#xff0c;除了简单技术技巧的总结外&#xff0c;也会探…

多分支集成发布各种坑怎么填?

简介&#xff1a;一文为你详细介绍云效分支模式的原理及实践&#xff0c;云效 Flow 这套灵活高效的分支模式可以让用户只关心集成和发布哪些特性分支&#xff0c;而对发布分支创建和管理、分支间合并等一系列工作&#xff0c;托付给云效完成。 小明的研发团队要发布一个版本&a…

Gartner:中国企业构建边缘计算解决方案最佳实践

作者 | Gartner研究总监 李晶 供稿 | Gartner 随着中国企业数字化成熟度和渗透度的不断提升&#xff0c;基础设施和运营 (I&O) 团队和领导者所需要提供的数字基础设施的位置也在逐渐增加&#xff0c;从云端、数据中⼼&#xff0c;延伸到了⽹络边缘&#xff0c;并且每个位置…

php生成pdf文档,PHP生成PDF文件类库大全[开源]

虽然 PHP 有附 PDFlib &#xff0c;不过使用起来实在有点复杂。(PHP 说明文件中的范例)FPDF虽然现在已经停止更新了&#xff0c;但 FPDF 可谓是元老级的 PDF 链接库&#xff0c;短短的几行程序就可以产生出 PDF 档案。最可怕的是现今的PHP PDF 链接库大多是由 FPDF 衍生出来的。…

从阿里核心场景看实时数仓的发展趋势

简介&#xff1a;随着2021年双11的完美落幕&#xff0c;实时数仓技术在阿里双11场景也经历了多年的实践和发展。从早期的基于不同作业的烟囱式开发&#xff0c;到基于领域分层建模的数仓引入&#xff0c;再到分析服务一体化的新型融合式一站式架构&#xff0c;开发效率逐步提升…

QUIC技术创新 让视频和图片分发再提速

简介&#xff1a;在1月12日的「阿里云CDN产品发布会-新一代传输协议QUIC让CDN更快一步」之上&#xff0c;阿里云技术专家淮叶分享了QUIC技术及其应用落地实践&#xff0c;内容包含&#xff1a;QUIC协议介绍、相比TCP有哪些优势、应用场景以及技术落地实践中的协议库选择&#x…

Spring Boot Serverless 实战 | Serverless 应用的监控与调试

简介&#xff1a;Spring Boot 是基于 Java Spring 框架的套件&#xff0c;它预装了 Spring 的一系列组件&#xff0c;让开发者只需要很少的配置就可以创建独立运行的应用程序。在云原生的环境中&#xff0c;有大量的平台可以运行 Spring Boot 应用&#xff0c;例如虚拟机、容器…

一文读懂 Serverless 的起源、发展和落地实践

简介&#xff1a;Serverless 适合哪些业务场景&#xff1f;它可以对业务产生何种价值呢&#xff1f; 讲师 | 洛浩&#xff08;阿里云云原生高级架构师&#xff09; Serverless 的发展轨迹 2012 年&#xff0c;Serverless 这个单词第一次出现&#xff0c;由 Iron 公司提出&…

Mendix:数字化转型下一个目标,提供准时制信息

作者 | Mendix公司首席低代码解决方案官Jethro Borsje 供稿 | Mendix 从孤立系统到支持决策的信息体系 二十世纪下半叶&#xff0c;丰田开发的“Toyota Production System”&#xff08;TPS&#xff09;曾帮助公司提高了效率并能快速生产出高质量的汽车&#xff0c;TPS的价值得…

实战经验 | 怎样才能提升代码质量?

简介&#xff1a;提升代码质量的三个有效方法&#xff1a;领域建模、设计原则、设计模式。 影响代码差的根因 差代码的体现 我们可以列举出非常多质量差的代码的表现现象&#xff0c;如名字不知所意、超大类、超大方法、重复代码、代码难懂、代码修改困难……其中最为影响代码…