React Hooks 简介:zh-hans.reactjs.org/docs/hooks-…
一句话简介
React Hooks 添加于 React 16.8,用于 React 中函数组件的一类特殊函数,它们允许你在不编写类组件而是使用简单的函数组件(Function Component,以下称FC) 的情况下依然享受到状态、上下文、副作用等 React 特性。
Why Hooks
没有 Hooks 的函数组件太简陋,难堪大任
在没有 Hooks 的时候,FC 只是一个函数,它没有实例、没有生命周期,执行完即销毁,无法保存state,也无法使用 ref 对 FC 进行指向。这些局限使得当时的 FC 只能用于写一些静态的纯组件。 有了Hooks之后,FC也可以使用许多 React 特性,极大程度消除了以上局限。
类组件自身也存在问题
1.在复杂组件中,耦合的逻辑代码很难分离
React 组件的基本思想是分离 UI 和逻辑,但是这样的设计很难分离业务代码。相反的,在各个生命周期钩子中,很多没有关联的业务代码也要被迫放在一起——因为他们都需要在某个阶段被执行。而 Hooks 将组件中相互关联的部分拆分成更小的函数,在不破坏其功能的情况下让我们更好地维护每个逻辑。 2. 对资源的管理难以做到统一 同样由于生命周期的问题,对一个资源或事件需要在Mount钩子中完成申请或监听,而在Unmount钩子中销毁。生成和销毁的逻辑不在一起,难以管理,也容易忘记同步修改。有了 Hooks,只需要在useEffect的函数中写生成逻辑,在返回的闭包中写销毁逻辑即可。 3. 组件间逻辑复用困难 在 React 中,给组件添加一些各个组件都可以复用的逻辑比较困难。尽管 React 提供了 render props、高阶组件(HOC)等方案,但是这类方案需要重新组织组件结构,既麻烦又降低了代码的可阅读性。为了避免使用HOC带来的一些问题,React 需要为多组件共享逻辑提供更好的原生途径。Hooks 提供了自定义 Hook ,来撰写需要复用的逻辑。 4. 学习成本高昂 与 Vue 的易于上手不同,由于类组件使用 JavaScript 中的 class 实现,开发 React 的类组件需要比较扎实的 JavaScript 基础,尤其是关于 this 的指向、闭包、bind等相关概念的理解。增加了上手门槛。 Hooks 的出现,使得上述问题得到了不同程度的解决。
State Hooks
useState
useState函数接收一个参数,可以是类型任意的一个值或返回一个任意类型值的函数。其执行结果为一个长度为2的数组,[0]是状态值,[1]是修改状态值的 setter
函数。
// 创建state的两种方式
// 直接给定初始值,在渲染过程中 state 会一直保持该值
const [stateA, setStateA] = useState<Type>(initialState);
// 使用函数计算初始值,该函数只会在第一次渲染时执行一次,一般用于初始状态较为复杂的情况
const [stateB, setStateB] = useState<Type>(()=>{const initialState = expensiveCompute();return initialState;
});
//更新state的两种方式
// 直接给定新值,组件使用新值重渲染
setStateA(newStateA);
// 若新state需要依赖旧state的值,则传入一个参数为旧state的函数,返回值为新state值
setStateB((prevStateB)=>{const newStateB = doSth(prevStateB);return newStateB;
});
// 需要注意,与class的setState函数不同,useState并不会在set的时候merge所有改动。
下面来一个例子:
import React, { useState } from 'react';
const Example = () => {const [count, setCount] = useState(0);return (<div><p>You clicked {count} times</p><button onClick={() => setCount(prevcount=>(prevCount + 1))}>Click me</button></div>);
}
整个 Hooks 运作过程:
1.函数组件 Example
第一次执行函数时 useState
进行初始化,其传入的参数 0
就是 count
的初始值;
2.返回的 VDOM 中使用到了 count
属性,其值为 0
;
3.通过点击按钮,触发 setCount
函数,传入修改 count
的值,然后重新执行函数(就像类组件中重新执行 render 函数一样);
4.第二次及以后执行函数时,依旧通过 useState
来获取 count
及修改 count
的方法 setCount
,只不过不会执行 count
的初始化,而是使用其上一次 setCount
传入的值。
通过多次调用useState()
,一个函数组件可以拥有多个状态。
function MyComponent() {const [state1, setState1] = useState(initial1);const [state2, setState2] = useState(initial2);const [state3, setState3] = useState(initial3);// ...
}
复制代码
需要注意的,要确保对useState()
和其他 Hooks 中每种 Hook 的多次调用在渲染之间始终保持相同的顺序,且要放置在函数组件顶层,不能放置在循环语句、条件语句和嵌套中(原因后面会讲)。 为什么使用一个useState
函数我们就可以在一个函数组件中存储状态,每次调用同一函数的时候可以获取上次的状态?接下来我们来看看 React 的源码。
// react-reconciler/src/ReactFiberHooks.js
export function renderWithHooks<Props, SecondArg>(current: Fiber | null,workInProgress: Fiber,Component: (p: Props, arg: SecondArg) => any,props: Props,secondArg: SecondArg,nextRenderLanes: Lanes
): any {renderLanes = nextRenderLanes;currentlyRenderingFiber = workInProgress;workInProgress.memoizedState = null;workInProgress.updateQueue = null;workInProgress.lanes = NoLanes;//注意这里,当函数初次初始化时会调用onMount,如果非初次渲染会调用onUpdateReactCurrentDispatcher.current =current === null || current.memoizedState === null? HooksDispatcherOnMount: HooksDispatcherOnUpdate;let children = Component(props, secondArg);// Check if there was a render phase updateif (didScheduleRenderPhaseUpdateDuringThisPass) {// Keep rendering in a loop for as long as render phase updates continue to// be scheduled. Use a counter to prevent infinite loops.let numberOfReRenders: number = 0;do {didScheduleRenderPhaseUpdateDuringThisPass = false;invariant(numberOfReRenders < RE_RENDER_LIMIT,"Too many re-renders. React limits the number of renders to prevent " +"an infinite loop.");numberOfReRenders += 1;// Start over from the beginning of the listcurrentHook = null;workInProgressHook = null;workInProgress.updateQueue = null;ReactCurrentDispatcher.current = HooksDispatcherOnRerender;children = Component(props, secondArg);} while (didScheduleRenderPhaseUpdateDuringThisPass);}ReactCurrentDispatcher.current = ContextOnlyDispatcher;const didRenderTooFewHooks =currentHook !== null && currentHook.next !== null;renderLanes = NoLanes;currentlyRenderingFiber = (null: any);currentHook = null;workInProgressHook = null;didScheduleRenderPhaseUpdate = false;invariant(!didRenderTooFewHooks,"Rendered fewer hooks than expected. This may be caused by an accidental " +"early return statement.");if