前言
从React Hooks发布以来,整个社区都以积极的态度去拥抱它、学习它。期间也涌现了很多关于React Hooks 源码解析的文章。本文就以笔者自己的角度来写一篇属于自己的文章吧。希望可以深入浅出、图文并茂的帮助大家对React Hooks的实现原理进行学习与理解。本文将以文字、代码、图画的形式来呈现内容。主要对常用Hooks中的 useState、useReducer、useEffect 进行学习,尽可能的揭开Hooks的面纱。
使用Hooks时的疑惑
Hooks的面世让我们的Function Component逐步拥有了对标Class Component的特性,比如私有状态,生命周期函数等。useState与useReducer这两个Hooks让我们可以在 Function Component里使用到私有状态。而useState其实就是阉割版的useReducer,这也是我那它们两个放在一起讲的原因。应用一下官方的例子:
function PersionInfo ({
initialAge,initialName}) {
const [age, setAge] = useState(initialAge);
const [name, setName] = useState(initialName);
return (
<>
Age: {
age}, Name: {
name}
<button onClick={
() => setAge(age + 1)}>Growing up</button>
</>
);
}
通过 useState 我们可以初始化一个私有状态,它会返回这个状态的最新值和一个用来更新状态的方法。而useReducer则是针对更复杂的状态管理场景:
const initialState = {
age: 0, name: 'Dan'};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {
...state, age: state.age + action.age};
case 'decrement':
return {
...state, age: state.age - action.age};
default:
throw new Error();
}
}
function PersionInfo() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Age: {
state.age}, Name: {
state.name} <button onClick={
() => dispatch({
type: 'decrement', age: 1})}>-</button>
<button onClick={
() => dispatch({
type: 'increment', age: 1})}>+</button>
</>
);
}
同样也是返回当前最新的状态,并返回一个用来更新数据的方法。在使用这两个方法的时候也许我们会想过这样的问题:
const [age, setAge] = useState(initialAge);
const [name, setName] = useState(initialName);
React内部是怎么区分这两个状态的呢? Function Component 不像 Class Component那样可以将私有状态挂载到类实例中并通过对应的key来指向对应的状态,而且每次的页面的刷新或者说组件的重新渲染都会使得 Function 重新执行一遍。所以React中必定有一种机制来区分这些Hooks。
const [age, setAge] = useState(initialAge);
// 或
const [state, dispatch] = useReducer(reducer, initialState);
另一个问题就是React是如何在每次重新渲染之后都能返回最新的状态? Class Component因为自身的特点可以将私有状态持久化的挂载到类实例上,每时每刻保存的都是最新的值。而 Function Component 由于本质就是一个函数,并且每次渲染都会重新执行。所以React必定拥有某种机制去记住每一次的更新操作,并最终得出最新的值返回。当然我们还会有其他的一些问题,比如这些状态究竟存放在哪?为什么只能在函数顶层使用Hooks而不能在条件语句等里面使用Hooks?
答案尽在源码之中
我们先来了解useState以及useReducer的源码实现,并从中解答我们在使用Hooks时的种种疑惑。首先我们从源头开始:
import React, {
useState } from 'react';
在项目中我们通常会以这种方式来引入useState方法,被我们引入的这个useState方法是什么样子的呢?其实这个方法就在源码 packages/react/src/ReactHook.js 中。
// packages/react/src/ReactHook.js
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
// ...
return dispatcher;
}
// 我们代码中引入的useState方法
export function useState(initialState) {
const dispatcher = resolveDispatcher()