以前有朋友问我,React Hooks 可否解决 React 项目状态管理的问题。这个问题让我思索了好久,最后得出的结论是:能,不过须要两个自定义 hooks 去实现。那么具体如何实现的呢? 那就是今天要讲的内容了。前端
经过本文,你可以学习如下内容:react
带着如上的知识点,开启阅读之旅吧~(创做不易,但愿屏幕前的你能给笔者赏个赞,以此鼓励我继续创做前端硬文。)面试
首先,看一下要实现的两个自定义 hooks 具体功能。redux
useCreateStore
用于产生一个状态 Store ,经过 context 上下文传递 ,为了让每个自定义 hooks useConnect
都能获取 context 里面的状态属性。useConnect
使用这个自定义 hooks 的组件,能够获取改变状态的 dispatch 方法,还能够订阅 state ,被订阅的 state 发生变化,组件更新。如何让不一样组件的自定义 hooks 共享状态并实现通讯呢?api
首先不一样组件的自定义 hooks ,能够经过 useContext
得到共有状态,并且还须要实现状态管理和组件通讯,那么就须要一个状态调度中心来统一作这些事,能够称之为 ReduxHooksStore
,它具体作的事情以下:数组
useConnect
组件的信息。组件销毁还要清除这些信息。dispatch
方法。useConnect
。首先 useCreateStore
是在靠近根部组件的位置的, 并且全局只须要一个,目的就是建立一个 Store
,并经过 Provider
传递下去。缓存
使用:markdown
const store = useCreateStore( reducer , initState )
复制代码
参数:dom
reducer
:全局 reducer,纯函数,传入 state 和 action ,返回新的 state 。initState
: 初始化 state 。返回值:为 store 暴露的主要功能函数。异步
Store 为上述所说的调度中心,接收全局 reducer ,内部维护状态 state ,负责通知更新 ,收集用 useConnect 的组件。
const Store = new ReduxHooksStore(reducer,initState).exportStore()
复制代码
参数:接收两个参数,透传 useCreateStore 的参数。
使用 useConnect 的组件,将得到 dispatch 函数,用于更新 state ,还能够经过第一个参数订阅 state ,被订阅的 state 改变 ,会让组件更新。
// 订阅 state 中的 number
const mapStoreToState = (state)=>({ number: state.number })
const [ state , dispatch ] = useConnect(mapStoreToState)
复制代码
参数:
mapStoreToState
:将 Store 中 state ,映射到组件的 state 中,能够作视图渲染使用。dispatch
函数,不会订阅 state 变化带来的更新。返回值:返回值是一个数组。
dispatch
函数。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
主要作的是:
接收 reducer
和 initState
,经过 ReduxHooksStore 产生一个 store ,不指望把 store 所有暴露给使用者,只须要暴露核心的方法,因此调用实例下的 exportStore
抽离出核心方法。
使用一个 useRef
保存核心方法,传递给 Provider
。
接下来看一下核心状态 ReduxHooksStore 。
import { unstable_batchedUpdates } from 'react-dom'
class ReduxHooksStore {
constructor(reducer,initState){
this.name = '__ReduxHooksStore__'
this.id = 0
this.reducer = reducer
this.state = initState
this.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] = connectCurrent
return 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 开启批量更新原则。
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 = selectState
stateValue.current = selectState
if(!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 ]
}
复制代码
初始化
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 有一些局限性,好比:
接下来就是验证效果环节,我模拟了组件通讯的场景。
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 } =action
if(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根组件
{ mesA:'111',mesB:'111' }
Index组件
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>
}
复制代码
mes1 ,mes2
属性上。本文经过两个自定义 hooks 实现了 React-Redux 的基本功能,这个模式在真实项目中可使用吗? 我以为若是是小型项目,是彻底可使用的,对于大型项目仍是用 React Redux 或者其余成熟的状态管理工具。
今天给你们推荐一本掘金小册 《React进阶实践指南》,本文中的自定义 hooks 也是小册自定义 hooks 章节中的一个案例。小册还有不少自定义 hooks 设计案例,并且自定义 hooks 设计和实践章节都在持续更新维护中,汇聚了笔者多年的心血,感兴趣的同窗能够了解如下,下面是小册的介绍。
本小册从基础进阶篇,优化进阶篇,原理进阶篇,生态进阶篇,实践进阶篇,五个方向详细探讨 React 使用指南 和 原理介绍。
至于小册为何叫进阶实践指南,由于在讲解进阶玩法的同时,也包含了不少实践的小demo。还有一些面试中的问答环节,让读者从面试上脱颖而出。
目前小册已经上线,这里有 2 个 7 折的优惠码奉上,先到先得。