一、前言
知己知彼,能力屡战屡败,所以咱们想学好学深一个货色,肯定要去理解他的由来,写过 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 来达到给组内人复用,解决相似的问题,如果机智的你发现了我的小谬误,还请斧正,最初来句鸡汤“春风得意时布好局,腹背受敌时有进路”,继续学习~