React Hooks 文档翻译 - 7 - Hooks API Reference(Hooks API 参考)

目录

  1. 1. Basic Hooks
    1. 1.1. useState
      1. 1.1.1. Functional updates
      2. 1.1.2. Lazy initialization
    2. 1.2. useEffect
      1. 1.2.1. Cleaning up an effect
      2. 1.2.2. Timing of effects
      3. 1.2.3. Conditionally firing an effect
    3. 1.3. useContext
  2. 2. Additional Hooks
    1. 2.1. useReducer
      1. 2.1.1. Specifying the initial state
      2. 2.1.2. Lazy initialization
      3. 2.1.3. Bailing out of a dispatch
    2. 2.2. useCallback
    3. 2.3. useMemo
    4. 2.4. useRef
    5. 2.5. useImperativeHandle
    6. 2.6. useLayoutEffect
    7. 2.7. useDebugValue
      1. 2.7.1. Defer formatting debug values

翻译自:https://reactjs.org/docs/hooks-reference.html

Hooks 是 React 16.8 新增的功能。它允许你在不编写类的情况下使用状态和其他 React 特性。

If you’re new to Hooks, you might want to check out the overview first. You may also find useful information in the frequently asked questions section.

如果你是 Hooks 的新手,可能需要先查看概述。你还可以在常见问题部分找到有用的信息。

Basic Hooks

useState

const [state, setState] = useState(initialState);

返回一个状态值,以及一个更新它的函数。

在首次渲染时,返回的状态(state)与传递给它的第一个参数(initialState)的值相同。

setState 函数用于更新状态。它接受一个新的状态值,并将组件的重新渲染排入队列。

setState(newState);

在后续的重新渲染中,useState 返回的第一个值将始终是更新后的最新状态。

Note

React 保证 setState 函数是稳定的,并且重新渲染时不会改变。这就是为什么省略 useEffect 或useCallback 的依赖列表是安全地。

Functional updates

如果新状态需要使用先前的状态来计算,则可以传递函数给 setState 。该函数接收先前的状态值做为参数,并返回更新后的值。下面示例演示了使用两种形式的 setState 的计数器组件:

function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}

“+” 和 “-” 按钮使用函数形式,因为更新后的值基于先前的值。“重置”按钮则使用普通的形式,因为它总是将计数设置回 0。

Note

与类组件中的 setState 方法不同,useState 不会自动合并更新后的状态对象到之前的状态对象上。你可以通过将函数形式与对象扩展语法组合复制这种行为:

setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});

另一个选择是 useReducer,它更适合用来管理包含多个子值的状态对象。

Lazy initialization

initialState 参数是首次渲染时使用的状态。在随后的渲染中,它将被忽略。如果初始状态是一个复杂计算的结果,则可以改为提供函数参数,且该函数仅在首次渲染时执行:

const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});

useEffect

useEffect(didUpdate);

接受一个函数参数,包含命令式,有副作用的代码。

如突变,订阅,计时器,日志记录和其他副作用等都不允许在函数组件的主体内使用(也即不允许在 React 的渲染阶段运行)。这会导致 UI 中出现错误和不一致的混乱。

Instead, use useEffect. The function passed to useEffect will run after the render is committed to the screen. Think of effects as an escape hatch from React’s purely functional world into the imperative world.

相反,使用 useEffect。传递给 useEffect 的函数将在渲染更新到屏幕后运行。将 effects 视为从 React 的纯粹功能性世界进入命令式世界的“逃生舱”。

默认情况下,effects 在每次完成渲染后运行,但你可以选择仅在某些值发生更改时触发它。

Cleaning up an effect

Often, effects create resources that need to be cleaned up before the component leaves the screen, such as a subscription or timer ID. To do this, the function passed to useEffect may return a clean-up function. For example, to create a subscription:

通常, effect 创建的资源在组件从屏幕上消失前需要被清理,例如订阅数据源或定时器的 ID。为了清理,传递给 useEffect 的函数可以返回一个清理函数。例如,创建订阅的返回:

useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
subscription.unsubscribe();
};
});

清除函数在从 UI 中删除组件前运行,以防止内存泄漏。此外,如果组件渲染多次(通常都是这样),则在执行下一个 effect 前会清除先前的 effect 。在我们的示例中,意味着每次更新都会创建一个新订阅。要避免每次更新都触发 effect ,请参阅下一节。

Timing of effects

componentDidMountcomponentDidUpdate 不同,传递给 useEffect 的函数在布局和绘制到屏幕之后触发。这使得它适用于许多常见的副作用,例如设置订阅和事件处理,因为大多数此类型的工作不应该阻止浏览器更新屏幕的内容。

但是,并非所有 effect 都可以推迟。例如,对用户可见的 DOM 突变必须在下一次绘制之前同步触发,以便用户不会感觉到视觉上的不一致。(它们区别在概念上类似于被动事件侦听器和活动事件侦听器。)对于这些类型的 effects ,React 提供了一个名为 useLayoutEffect 的附加 Hook。它与 useEffect 具有相同的签名,仅仅在触发时机不同。

虽然 useEffect 延迟到浏览器绘制完成之后,但 React 保证它在任何新渲染发生前会被触发。在开始新的更新之前,React 将始终刷新先前渲染的 effect 。

Conditionally firing an effect

有条件地触发 effect

effect 的默认行为是在每次完成渲染后触发 effect 。这种情况下,如果任何一个传入的参数发生变化,则始终会重新创建 effect 。

但是,在某些情况下,可能是矫枉过正的,例如上一节中的订阅示例。我们无需在每次组件更新时创建新的订阅,除非 source 这个 props 发生了变化。

要实现这样的功能,请传递给 useEffect 第二个参数,它是 effect 所依赖的值数组。更改后的示例是这样的:

useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);

现在只有在 props.source 发生更改时才会重新创建订阅。

Note

如果使用此优化,请确保该数组使用组件作用域中的任意值(例如 propsstate),这些值随时间变化且被 effect 使用。否则,你的代码将引用之前渲染时的陈旧值。了解更多关于如何使用函数处理以及在数组值变化太频繁怎么解决的详细信息。

如果你想要运行 effects 一次并仅清理一次(在装载和卸载时),则可以将空数组([])作为第二个参数传递。这告诉 React 你的 effect 不依赖于来自 props 或 state 的任何值,所以它永远不需要重新运行。这不作为特殊情况处理 – 它遵循通常的依赖项数组的工作方式。

如果传递一个空数组([]),则 effect 内部的 props 和 state 将始终具有其初始值。虽然传递 [] 作为第二个参数更接近熟悉的 componentDidMountcomponentWillUnmount 心理模型,但通常有更好的解决方案来避免经常重新运行效果。此外,不要忘了 React 推迟 useEffect 到浏览器绘制完成后才执行,所以多做额外的工作也没什么问题。

我们建议使用 exhaustive-deps 规则作为 eslint-plugin-react-hooks 包的一部分。它会在错误地指定依赖项时发出警告并建议修复。

依赖的数组并不会作为参数传递给 effect 函数。但从概念上讲,它们就是这样表达的:effect 函数内引用的每个值应该出现在所依赖的数组中。将来,一个足够先进的编译器可以自动创建这个数组。

useContext

const context = useContext(Context);

接受 Context 对象(从 React.createContext 返回的值)并返回当前的 Context 值的函数,当前 Context 值由组件树上方最近的 <MyContext.Provider> 确定。

当组件最近的 <MyContext.Provider> 更新时,此 Hook 将触发重新渲染,并将最新的 Context 值传递给该MyContext provider。

不要忘记 useContext 的参数必须是 Context 对象本身:

  • 正确:useContext(MyContext)
  • 不正确:useContext(MyContext.Consumer)
  • 不正确:useContext(MyContext.Provider)

当 Context 值更改时,调用 useContext 的组件始终将重新渲染。如果重新渲染组件的代价很昂贵,可以使用 memoization 优化

Tip

如果你之前熟悉 Context API,则 useContext(MyContext) 等同于类中的 static contextType = MyContext,也等同于 <MyContext.Consumer>

useContext(MyContext) 只允许你读取 Context 并订阅其更改。你仍然需要组件树中的 <MyContext.Provider>提供 Context 的值。

Additional Hooks

以下 Hooks 可能是上一节中基本的 Hooks 的变体,也可能仅用于特定的边缘情况。现在不用急着学习它们。

useReducer

const[state, dispatch]=useReducer(reducer, initialArg, init);

useState 的替代方案。接受类型为 (state, action) => newState 的 reducer,并返回与当前状态配对的 dispatch 方法。(如果你熟悉 Redux,你肯定了解它是如何工作的。)

当你有多个涉及子值的复杂状态逻辑或下一个状态取决于前一个状态时,useReducer 通常优于 useStateuseReducer 还允许你优化触发深度更新的组件的性能,因为你可以传递 dispatch 而不是使用回调

这是 useState 一节的计数器示例,使用 reducer 重写为:

const initialState = {count: 0};

function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}

function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}

Note

React 确保 dispatch 函数是稳定的,并且在重新渲染时不会改变。这就是为什么可以安全地省略 useEffectuseCallback 的依赖项列表。

Specifying the initial state

指定初始状态

有两种不同的方法初始化 useReducer 状态。你可以根据用例选择其中一个。最简单方法是将初始状态作为第二个参数传递给useReducer

const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);

Note

React 不使用 Redux 推广的 state = initialState 参数惯例。初始值有时需要依赖于 props,因此采用在调用 Hooks 时指定。如果你强烈希望这么做,可以调用 useReducer(reducer, undefined, reducer) 来模拟Redux 的行为,但我们并不鼓励。

Lazy initialization

懒初始化

你还可以推迟创建初始状态。为此,可以将 init 函数作为第三个参数传递。初始状态将设置为 init(initialArg)

它允许你提取计算初始状态的逻辑到 reducer 的外部。这对于稍后重置状态以响应操作也很方便:

function init(initialCount) {
return {count: initialCount};
}

function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}

function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>

Reset
</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}

Bailing out of a dispatch

如果从 Reducer Hook 返回与当前状态相同的值,则 React 将不渲染子组件且不触发 effects。(React 使用Object.is 比较算法 。)

注意,React 可能仍需要在停止渲染子组件前再次渲染该组件。这不应该不是问题,因为 React 不会不必要地“深入”到子组件树中。如果你在渲染时进行了昂贵的计算,则可以使用 useMemo 对其进行优化。

useCallback

const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);

返回一个 memoized 的回调。

传递内联回调函数和数组依赖。useCallback 将返回一个回调函数的 memoized 版本,它只有在其中一个依赖项发生更改时才会更改。这非常有用,通过将回调传递给依赖引用相等性判断优化过的子组件,可以防止不必要的渲染(例如,shouldComponentUpdate)。

useCallback(fn, deps) 等同于 useMemo(() => fn, deps).

Note

依赖的数组并不会作为参数传递给回调函数。虽然概念上,代表了这样的含义:回调函数中引用的每个值也应该出现在依赖项数组中。将来,一个足够先进的编译器可以自动创建这个数组。

我们建议使用 exhaustive-deps 规则作为 eslint-plugin-react-hooks 包的一部分。它会在错误地指定依赖项时发出警告并提供修复建议。

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

返回一个 memoized 的值。

传递一个执行“创建”的函数和依赖的变量的数组。useMemo 只会在其中一个依赖的变量发生更改时重新计算 memoized的值。此优化有助于避免在每个渲染时进行昂贵的计算。

请记住,传递给 useMemo 的函数在渲染中运行。不要做那些在渲染时通常不会做的事情。例如,副作用通常属于useEffect,而不是useMemo

如果未提供数组,则将在每次渲染是都会重新计算值。

你可以依赖 useMemo 作为性能优化,而不是语义保证。将来,React 可能会选择“忘记”一些以前记忆的值,并在下一次渲染时重新计算它们,例如为屏幕外组件释放内存。写代码时,确保在没有 useMemo 的情况下仍可正常工作 – 然后再考虑添加它优化性能。

Note

依赖的数组并不会作为参数传递给回调函数。虽然概念上,代表了这样的含义:回调函数中引用的每个值也应该出现在依赖项数组中。将来,一个足够先进的编译器可以自动创建这个数组。

我们建议使用 exhaustive-deps 规则作为 eslint-plugin-react-hooks 包的一部分。它会在错误地指定依赖项时发出警告并提供修复建议。

useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。

一个常见的用例是强行访问子组件对象:

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

本质上,useRef 就像一个“盒子”,可以在其 .current 属性中保存一个可变值。

你可能了解 refs 主要是作为 访问 DOM 的一种方式。如果使用 <div ref={myRef} /> 将 ref 对象传递给 React,只要该节点发生更改,React 就会将其 .current 属性设置为相应的DOM 节点。

但是, useRef() 比 ref 属性更有用。使用它随手保存任何可变值 与你在类中使用实例字段的方式类似,这很方便。

这样可以工作,因为 useRef() 创建了一个普通的 JavaScript 对象。 useRef() 与自己创建 {current: ...} 对象之间的唯一区别是 useRef 会在每次渲染时为你提供同一个 ref 对象。

请记住,useRef 在内容更改时不会通知你。.current 属性的改变不会导致重新渲染。如果要在 React 将引用附加到 DOM 节点或从 DOM 节点分离时运行某些代码,可使用callback ref替代。

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 自定义使用 ref 时公开给父组件的实例值。与以前一样,在大多数情况下应避免使用 refs 的命令式代码。useImperativeHandle 应与 forwardRef 一起使用:

function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

在这个例子里,渲染了 <FancyInput ref={fancyInputRef} /> 的父组件能够调用 fancyInputRef.current.focus()

useLayoutEffect

签名与 useEffect 相同,但在所有 DOM 突变后会同步触发。使用它从 DOM 读取布局并同步重新渲染,在 useLayoutEffect 内部计划的更新将在浏览器有机会重绘前同步刷新到屏幕。

如果可能的话,首选标准的 useEffect 以避免阻塞视觉更新。

Tip

如果要从类组件迁移代码,请注意 useLayoutEffect 在与 componentDidMountcomponentDidUpdate 相同的阶段触发。但是,我们建议优先使用 useEffect ,只在 useEffect 导致问题时,尝试使用 useLayoutEffect

如果使用服务端渲染,记住,在下载完 JavaScript 前,useLayoutEffectuseEffect 都不能运行。这就是 React 在服务器渲染的组件包含 useLayoutEffect 时会发出警告。要解决这个问题,要么将该逻辑移动到useEffect(如果是首次渲染则不需要),要么延迟显示该组件直到客户端渲染后(如果 useLayoutEffect 运行时, HTML 看起来不正常)。

要从服务器呈现的 HTML 中排除需要布局效果的组件,请使用 showChild && <Child /> 来条件地渲染它,并使用 useEffect(() => { setShowChild(true); }, []) 延迟显示。这样,UI 不会出现展示错误。

useDebugValue

useDebugValue(value)

useDebugValue 可用于在 React DevTools 中显示自定义挂钩的标签。

例如,在“构建自己的Hooks”中描述的 useFriendStatus 的自定义 Hook:

function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);

// ...

// Show a label in DevTools next to this Hook
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');

return isOnline;
}

Tip

我们不建议将调试值添加到每个自定义 Hook。它对于作为共享库中的一部分的自定义 Hook 最有价值。

Defer formatting debug values

推迟格式化调试值

在某些情况下,格式化显示值可能是昂贵的操作。除非检查 Hook,否则没有必要。

因此,useDebugValue 接受格式化函数作为可选的第二个参数。只有在检查 Hooks 时才会调用此功能。它接收调试值作为参数,并应返回格式化后的显示值。

例如,返回 Date 值的自定义 Hook 可以通过传入以下格式化程序来避免不必要地调用 toDateString 函数:

useDebugValue(date, date => date.toDateString());
知识共享许可协议 知识共享许可协议 知识共享许可协议 本网站原创内容(非转载文章)采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。