React 笔记— Hooks
本文基于官方 v18.1.0 文档。
Introducing Hooks
They let you use state and other React features without writing a class.
It’s hard to reuse stateful logic between components
现在可以通过自定义 Hook 提取封装带状态的逻辑。
Complex components become hard to understand
useEffect
可以把复杂逻辑分拆到多个小的函数中。
Classes confuse both people and machines
Hooks at a Glance
解构赋值语法
React assumes that if you call useState
many times, you do it in the same order during every render.
It serves the same purpose as componentDidMount
, componentDidUpdate
, and componentWillUnmount
in React classes, but unified into a single API.
- Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
- Only call Hooks from React function components. Don’t call Hooks from regular JavaScript functions.
The state of each component is completely independent. Hooks are a way to reuse stateful logic, not state itself. In fact, each call to a Hook has a completely isolated state — so you can even use the same custom Hook twice in one component.
Custom Hooks are more of a convention than a feature. If a function’s name starts with ”use
” and it calls other Hooks, we say it is a custom Hook.
Using the State Hook
Using the Effect Hook
you can think of useEffect Hook as componentDidMount
, componentDidUpdate
, and componentWillUnmount
combined.
Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
Experienced JavaScript developers might notice that the function passed to useEffect
is going to be different on every render. This is intentional….Every time we re-render, we schedule a different effect, replacing the previous one.
Unlike componentDidMount
or componentDidUpdate
, effects scheduled with useEffect
don’t block the browser from updating the screen.
React performs the cleanup when the component unmounts….React also cleans up effects from the previous render before running the effects next time.
Tip: Optimizing Performance by Skipping Effects
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect.
Rules of Hooks
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions.
Only Call Hooks from React Functions
Don’t call Hooks from regular JavaScript functions.
So how does React know which state corresponds to which useState call? The answer is that React relies on the order in which Hooks are called.
Building Your Own Hooks
A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
Hooks API Reference
Lazy initial state
If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render.
Batching of state updates
Before React 18, only updates inside React event handlers were batched. Starting with React 18, batching is enabled for all updates by default.
In the rare case that you need to force the DOM update to be applied synchronously, you may wrap it in flushSync
. However, this can hurt performance so do this only where needed.
1 | // Force this state update to be synchronous. |
…the previous effect is cleaned up before executing the next effect.
Timing of effects
…the function passed to useEffect fires after layout and paint, during a deferred event.
就是前边说的,effects scheduled with
useEffect
don’t block the browser from updating the screen.useEffect
不阻塞屏幕刷新。
However, not all effects can be deferred. For example, a DOM mutation that is visible to the user must fire synchronously before the next paint so that the user does not perceive a visual inconsistency.
之前的解决方案就是
useLayoutEffect
,18+ 又多了上边提到的flushSync
。
Conditionally firing an effect
…make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect.
1 | const value = useContext(MyContext); |
Accepts a context object (the value returned from React.createContext
) and returns the current context value for that context. The current context value is determined by the value
prop of the nearest <MyContext.Provider>
above the calling component in the tree.
useContext(MyContext)
only lets you read the context and subscribe to its changes. You still need a <MyContext.Provider>
above in the tree to provide the value for this context.
1 | const [state, dispatch] = useReducer(reducer, initialArg, init); |
An alternative to useState
. Accepts a reducer of type (state, action) => newState
, and returns the current state paired with a dispatch
method.
1 | const initialState = {count: 0}; |
Lazy initialization
…you can pass an init
function as the third argument. The initial state will be set to init(initialArg)
.
1 | const memoizedCallback = useCallback( |
useCallback
will return a memoized version of the callback that only changes if one of the dependencies has changed.
useCallback(fn, deps)
is equivalent to useMemo(() => fn, deps)
.
1 | const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); |
useMemo
will only recompute the memoized value when one of the dependencies has changed.
If no array is provided, a new value will be computed on every render.
useRef()
is useful for more than the ref
attribute.
useRef
实际就是返回了一个 js object,只不过生命周期是持续整个组件生命周期的,所以除了用于ref
,任何其它需要的场景都可以用。
1 | useImperativeHandle(ref, createHandle, [deps]) |
自定义
ref.current
的值。createHandle
的结果对象作为ref.current
的新值。
useImperativeHandle
should be used with forwardRef
.
同
useEffect
作用,但是触发时机、对 DOM 的更新都是同步的、阻塞的。
useDebugValue
can be used to display a label for custom hooks in React DevTools.
1 | const deferredValue = useDeferredValue(value); |
将
value
记忆化。碰到 urgent 更新时,会用旧值渲染;非 urgent 时,以新值渲染。跟防抖不同,useDeferredValue
保证最后一定会以新值渲染一次。所以useDeferredValue
有可能会触发两次渲染。
1 | const [isPending, startTransition] = useTransition(); |
Returns a stateful value for the pending state of the transition, and a function to start it.
Updates in a transition yield to more urgent updates such as clicks.
将一个函数调用变为转场。回调函数会至少延迟一个渲染周期后被调用。React 把更新分成两类:Urgent 和 Transition。转场是可以 pending 的,
startTransition
被调用到回调函数被调用前,isPending
为true
。
生成一个唯一字符串。
Hooks FAQ
元素大小不变的情况下不需要
ResizeObserver
,用 callback ref。妙啊!
1 | function MeasureExample() { |
How do I implement shouldComponentUpdate?
You can wrap a function component with React.memo
to shallowly compare its props:
1
2
3 const Button = React.memo((props) => {
// your component
});
The useMemo
Hook lets you cache calculations between multiple renders by “remembering” the previous computation:
1 | const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); |
Conveniently, useMemo
also lets you skip an expensive re-render of a child:
1 | function Parent({ a, b }) { |
How to create expensive objects lazily?
1 | function Table(props) { |
To avoid re-creating the ignored initial state, we can pass a function to useState
:
1 | function Table(props) { |