一、前言
知己知彼,能力屡战屡败,所以咱们想学好学深一个货色,肯定要去理解他的由来,写过react的人大部分都应该理解redux,所以咱们明天就来聊聊react hooks;请收下我这个包袱,学习是干燥的,所以时不时的给本人找点乐子。为什么我要忽然加这么一个梗也是有起因的,我记得redux是一位叫Dan的人写的,忘了记没记错,咱们就先叫他Dan(诞)总吧,随着redux位置的晋升,Dan总也顺其自然的进了facebook,负责react-hooks的开发,你看我这个梗是不是连上了;
二、为什么说redux和react-hooks有些渊源?
其实redux很好了解,基于公布订阅模式,在外部实现一个状态(鸡),而后加一些约定和限度,内部不能够间接扭转这个状态,只能通过redux提供的一个代理办法去触发这个状态的扭转,使用者能够通过redux提供的订阅办法订阅事件,当状态扭转的时候,redux帮忙咱们公布这个事件,使得各个订阅者取得最新状态,是不是很简略,大道至简~
上面提供了个简版的redux代码,有趣味的能够看一下
/** * * @param {*} reducer 解决状态办法 * @param {*} preloadState 初始状态 */function createStore(reducer, preloadState) { /** 存储状态 */ let state = preloadState; /** 存储事件 */ const listeners = []; /** 获取状态办法,返回state */ const getState = () => { return state; }; /** * 订阅事件,将事件存储到listeners中 * @param {*} listener 事件 */ const subscribe = (listener) => { listeners.push(listener); }; /** * 派发action,公布事件 * @param {*} action 动作 */ const dispatch = (action) => { state = reducer(state, action); listeners.forEach((listener) => listener()); }; /** 状态机 */ const store = { getState, subscribe, dispatch, }; return store;}export default createStore;
为什么说react-hooks跟redux有渊源,因为Dan总把redux的这种思维带到了react-hooks中,通过创立一个公共hookStates来存储所有的hooks,通过增加一个索引hookIndex来程序执行每一个hook,所以react-hooks规定hooks不能应用在if语句和for语句中,来放弃hooks按程序执行,在react-hooks中有一个叫useReducer的hooks,正是按redux的这个思维实现的,而咱们罕用的useState只是useReducer的一个语法糖,有人可能会说这个hookStates会不会很大,所有的hooks都存在里边,毕竟一个我的项目中有那么多组件,我说的只是简略的实现原理,咱们晓得react在16的几个版本之后就退出了fiber的概念,在react外部实现中,会把每个组件的hookStates寄存到fiber节点上,来保护各个节点的状态,react每次render之后都会把hookIndex置为0,来保障下次hooks执行是从第一个开始执行,每个hooks执行完也会把hookIndex++,上面是useReducer的简略实现:
/** @param hookStates 存储所有hooks */const hookStates = [];/** @param hookIndex 索引 */let hookIndex = 0;/** * * useReducer * @param {*} reducer 状态处理函数 * @param {*} initialState 初始状态,能够传入函数实现懒加载 * @returns */function useReducer(reducer, initialState) { hookStates[hookIndex] = hookStates[hookIndex] || typeof initialState === "function" ? initialState() : initialState; /** * * dispatch * @param {*} action 动作 * @returns */ const dispatch = (action) => { hookStates[hookIndex] = reducer ? reducer(hookStates[hookIndex], action) : action; /** React外部办法实现从新render */ scheduleUpdate(); }; return [hookStates[hookIndex++], dispatch];}
前文说过useState是useReducer的语法糖,所以上面是useState的实现
/** * * useState 间接将reducer传为null,调用useReducer并返回 * @param {*} initialState 初始化状态 * @returns */function useState(initialState) { return useReducer(null, initialState);}
二、如何实现useState扭转值之后立即获取最新的状态?
之前铺垫了一堆,终于到正题了,本想直奔主题,一不小心变成了介绍react-hooks的文章,不过多理解一点究竟不会亏什么,react-hooks的思维其实是同步逻辑,然而在react的合成事件中状态更新是异步的,看一下上面这个场景,应用useState申明了一个状态state,又别离申明了两个函数setT和func,在setT中,调用setstate更新状态,而后调用func函数,在func中间接打印以后状态state
function App() { const [state, setstate] = useState(0); const setT = () => { setstate(2); func(); }; const func = () => { console.log(state); }; return ( <div className="App"> <header className="App-header"> <button onClick={setT}>set 2</button> </header> </div> );}
当咱们点击set 2 按钮时,控制台打印的还是上一次的值0,而不是最新的2
因为在react合成事件中扭转状态是异步的,出于缩小render次数,react会收集所有状态变更,而后比对优化,最初做一次变更,在代码中能够看出,func的调用和setstate在同一个宏工作中,这是react还没有render,所以间接应用state获取的必定是上一次闭包里的值0
有的人可能会说,间接将最新的值当作参数传递给func不就行了吗,对,这也是一种解决办法,然而有时不只是一个状态,可能要传递的参数很多,再有也是出于对react-hooks的深入研究,所以我抉择通过自定义hooks实现在useState扭转值之后立即获取到最新的值,咱们先看下实现的成果,代码变更如下:
function App() { const [state, setstate] = useState(0); const setT = () => { setstate(2); func(); }; /** 将func的办法传递给useSyncCallback而后返回一个新的函数 */ const func = useSyncCallback(() => { console.log(state); }); return ( <div className="App"> <header className="App-header"> <button onClick={setT}>set 2</button> </header> </div> );}export default App;
成果如下:
能够看出当咱们点击set 2按钮之后,控制台输入的间接是2,所以达到了在setstate之后立即获取了最新状态
三、如何实现useSyncCallback?
先把代码放到这里,大家能够看一下
/* * @lastTime: 2021-03-05 15:29:11 * @Description: 同步hooks */import { useEffect, useState, useCallback } from 'react'const useSyncCallback = callback => { const [proxyState, setProxyState] = useState({ current: false }) const Func = useCallback(() => { setProxyState({ current: true }) }, [proxyState]) useEffect(() => { if (proxyState.current === true) setProxyState({ current: false }) }, [proxyState]) useEffect(() => { proxyState.current && callback() }) return Func}export default useSyncCallback/* * @lastTime: 2021-02-26 15:29:11 * @param: callback为回调函数 * @Description: 用法 const newFunc = useSyncCallback(yourCallback) */
在这里还须要介绍一个知识点useEffect,useEffect会在每次函数式组件render之后执行,能够通过传递第二个参数,配置依赖项,当依赖项变更useEffect才会更新,如果第二个参数传递一个空数组[],那么useEffect就会只执行一次,react-hooks正是应用useEffect来模仿componentDidMount和componentDidUpdate两个生命周期,也能够通过在useEffect中return一个函数模仿componentWillUnmount,那么useEffect是如何实现在render之后调用的呢,原理就是在useEffect hooks中会封装一个宏工作,而后把传进来的回调函数放到宏工作中去执行,上面实现了一个简版的useEffect:
/** @param hookStates 存储所有hooks */const hookStates = [];/** @param hookIndex 索引 */let hookIndex = 0;/** * * useReducer * @param {*} callback 副作用函数 * @param {*} dependencies 依赖项 * @returns */function useEffect(callback, dependencies) { /** 判断以后索引下hookStates是否存在值 */ if (hookStates[hookIndex]) { /** 如果存在就取出,并构造出destroyFunc销毁函数,和上一次依赖项 */ const [destroyFunc, lastDep] = hookStates[hookIndex]; /** 判断以后依赖项中的值和上次依赖项中的值有没有变动 */ const isSame = dependencies && dependencies.every((dep, index) => dep === lastDep[index]); if (isSame) { /** 如果没有变动就把索引加一,hooks向后遍历 */ hookIndex++; } else { /** 如果有变动,并且存在销毁函数,就先调用销毁函数 */ destroyFunc && destroyFunc(); /** 创立一个新的宏工作,调用callback获取最新的销毁函数,并将销毁函数和依赖项存入hookStates中 */ setTimeout(() => { const destroyFunction = callback(); hookStates[hookIndex++] = [destroyFunction, dependencies]; }); } } else { /** 如果hookStates中不存在,证实是首次执行,间接创立一个宏工作,调用callback获取最新的销毁函数,并将销毁函数和依赖项存入hookStates中 */ setTimeout(() => { const destroyFunction = callback(); hookStates[hookIndex++] = [destroyFunction, dependencies]; }); }}
我曾经将useEffect的实现步骤写到了正文里,有趣味的能够看一下,接下来咱们就来应用useEffect实现咱们想要的后果,因为useEffect是在react组件render之后才会执行,所以在useEffect获取的状态肯定是最新的,所以利用这一点,把咱们写的函数放到useEffect执行,函数里获取的状态就肯定是最新的
useEffect(() => { proxyState.current && callback() })
首先,在useSyncCallback中创立一个标示proxyState,初始的时候会把proxyState的current值赋成false,在callback执行之前会先判断current是否为true,如果为true就容许callback执行,若果为false,就跳过不执行,因为useEffect在组件render之后,只有依赖项有变动就会执行,所以咱们无奈掌控咱们的函数执行,在useSyncCallback中创立一个新的函数Func,并返回,通过这个Func来模仿函数调用,
const [proxyState, setProxyState] = useState({ current: false })
在这个函数中咱们要做的就是变更prxoyState的current值为true,来使得让callback被调用的条件成立,同时触发react组件render这样外部的useEffect就会执行,随后调用callback实现咱们想要的成果。
四、小结
因为在工作中遇到了这个场景,所以写了此次分享,通过实现一个自定义hooks来达到给组内人复用,解决相似的问题,如果机智的你发现了我的小谬误,还请斧正,最初来句鸡汤 “春风得意时布好局,腹背受敌时有进路”,继续学习~