1. Why use Hooks
Side effects: behaviors such as data acquisition, subscription, scheduled tasks, and manual modification of ReactDOM can be called side effects; the emergence of Hooks can use useEffect to handle these side effects
Complex state management: In the past, third-party state managers such as redux and mobx were usually used to manage complex states, while Hooks can use useReducer and useContext to achieve complex state management;
Development efficiency and quality issues: Functional components are simpler, more efficient, and perform better than class components.
2. useState
const [state, setState] = useState(initState)
2.1 Each render is an independent closure
- Each render has its own props and state
- Each render has its own event handler
- The component function will be called every time it is rendered. The value of the number in each call is a constant, and it is assigned the state value in the current rendering.
- In a single render, props and state are always the same
function Counter1() {
let [info, setInfo] = useState({ number: 0 });
const alertNumber = () => {
setTimeout(() => {
alert(info.number);
}, 3000);
}
return (
<div>
<p>{info.number}</p>
<button onClick={() => setInfo({ number: info.number + 1 })}>+</button>
<button onClick={alertNumber}>alertNumber</button>
</div>
)
}
2.2 Functional update
If the new state needs to be calculated using the previous state, a function can be passed to setState, which will receive the previous state value and return an updated value
function Counter2() {
let [info, setInfo] = useState({ number: 0 });
function lazy() {
setTimeout(() => {
setInfo({ number: info.number + 1 })
}, 2000);
}
function lazyFunction() {
setTimeout(() => {
setInfo(info => ({ number: info.number + 1 }));
setInfo(info => ({ number: info.number + 1 }));
}, 3000);
}
return (
<div>
<p>{info.number}</p>
<button onClick={() => setInfo({ number: info.number + 1 })}>+</button>
<button onClick={lazy}>lazy</button>
<button onClick={lazyFunction}>lazyFunction</button>
</div>
)
}
2.3 Lazy initialization
- The initState parameter will only work in the initial rendering, subsequent renderings will be ignored
- If the initial state needs to be obtained through complex calculations, you can pass in a function, and calculate and return the initial state in the function. This function is only called during initial rendering
- Unlike the setState method of class, useState does not automatically merge and update objects. You can use functional setState combined with the spread operator to achieve the effect of merging objects.
function Counter3() {
let [info, setInfo] = useState(function () {
console.log('初始状态-----')
return { number: 0, name: "计数器" };
});
console.log("Counter5 render");
return (
<div>
<p>
{info.name}:{info.number}
</p>
<button onClick={() => setInfo({ ...info, number: info.number + 1 })}>
+
</button>
<button onClick={() => setInfo(info)}>+</button>
</div>
);
}
3. useEffect
useEffect(callback, array)
: a hook for handling side effects; it is also componentDidMount()、componentDidUpdate()、componentWillUnmount()、
the unification of these life cycle methods, one tops three!
- If the second parameter is not written, it will be executed as long as the state changes.
- When the second parameter is an empty array, no matter which state changes, it will not be executed, only executed once when the component is initialized.
- When there is a return value in the first callback function, it means that componentWIllUnmount is executed
4. Performance optimization
- Passing a callback function and an array of dependencies as arguments to useCallback will return a memoized version of the callback function that will only be updated when a dependency changes. (controls when components are updated)
- Passing a create function and an array of dependencies as arguments to useMemo will only recalculate the memoized value when a dependency changes. This optimization helps avoid expensive calculations on every render. (controls whether the component is updated)
let lastAddClick;
let lastChangeName;
function Counter4() {
let [number, setNumber] = useState(0);
let [name, setName] = useState('zhufeng');
//会在每次渲染的时候都 会生成一个新的函数
//只有在依赖的变量发生变化的时候才会重新生成
const addClick = useCallback(() => setNumber(number + 1), [number]);
console.log(lastAddClick === addClick);
lastAddClick = addClick;
const changeName = useCallback(() => setName(Date.now()), [name]);
console.log(lastChangeName === changeName);
lastChangeName = changeName;
return (
<div>
<p>{name}:{number}</p>
<button onClick={addClick}>addClick</button>
<button onClick={changeName}>changeName</button>
</div>
)
}
5. Hooks rules
Do custom hooks have to start with " **use**
"? It must be so. This convention is very important. Without it, React won't be able to automatically check if your Hook violates the rules of hooks, since there's no way to tell if a function contains a call to its internal Hook.
- Call hooks can only be used in react function components
- It needs to be called at the top level of the component, and cannot be called in the judgment or loop, because the hook is executed in sequence, and the addition is placed in the judgment. The first call is called, but the second is not called. bug.
Each Hook has two related functions: mountXxx()
and updateXxx()
, they are the Hook in the Mount stage (that is, the mounting of the component, or the initialization stage, or the When useXxx() is executed for the first time) and the logic of the Update phase (that is, the update of the component, or the component re-rendering phase). In order to facilitate management and invocation, react engineers store the logic of the Hook in the Mount phase into the ( HooksDispatcherOnMount
) object, and store the logic in the Update phase into the ( HooksDispatcherOnUpdate
) object
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useResponder: createDeprecatedResponderListener,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
};
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useResponder: createDeprecatedResponderListener,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
};
What does Hook do in the Mount phase?
Take useState as an example. The logic of useState in the Mount phase is written in the mountState()
method:
- Get the current Hook node and add the current Hook to the Hook linked list
- Initialize the state of the Hook, that is, read the initial state value
- Create a new linked list as an update queue to store update operations (setXxx())
- Create a dispatch method (that is, the second parameter of the array returned by useState: setXxx()), the purpose of this method is to modify the state, add this update operation to the update queue, and also combine the update with the current The fibers being rendered are bound
- A method that returns the current state and modifies the state (dispatch)
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 获取当前 Hook 节点,同时将当前 Hook 添加到 Hook 链表中
const hook = mountWorkInProgressHook();
// 初始化 Hook 的状态,即读取初始 state 值
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
// 创建一个新的链表作为更新队列,用来存放更新(setXxx())
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
// 创建一个 dispatch 方法(即 useState 返回的数组的第二个参数:setXxx()),
// 该方法的作用是用来修改 state,并将此更新添加到更新队列中,另外还会将改更新和当前正在渲染的 fiber 绑定起来
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
// 返回当前 state 和 修改 state 的方法
return [hook.memoizedState, dispatch];
}
Data structure for storing Hook - linked list
All Hooks in a function component are stored in the form of a linked list . Each node in the linked list is a Hook
export type Hook = {
memoizedState: any, // Hook 自身维护的状态
...
queue: UpdateQueue<any, any> | null, // Hook 自身维护的更新队列
next: Hook | null, // next 指向下一个 Hook
};
const [firstName, setFirstName] = useState('尼古拉斯');
const [lastName, setLastName] = useState('赵四');
useEffect(() => {})
How useState handles state updates
The update queue linked list queue
is used to store update operations. Each node in the linked list is an operation to update the state (that is, calling setXxx() once), so that the latest state can be obtained in the subsequent Update phase.
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
pending
: the latest pending update
dispatch
: method to update state (setXxx)
lastRenderedReducer
: The reducer used in the last rendering of the component (useState is actually a simplified version of useReducer, the reason why the user does not need to pass in the reducer when using useState is because useState uses react officially written by default reducer: basicStateReducer
)
lastRenderedState
: The state of the last rendering of the component
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。