Hooks API 参考

Hooks 是一项新功能提案,可让你在不编写类的情况下使用 state(状态) 和其他 React 功能。它们目前处于 React v16.7.0-alpha 中,并在 一个开放RFC 中进行讨论。

此页面描述了 React 中内置的 Hook API。

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

基本的 Hooks

useState

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

返回 stateful(有状态) 值,以及更新这个状态值的函数。

在初始渲染的时候,返回的状态( state )与作为第一个参数( initialState )传递的值相同。

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

setState(newState);

在后续重新渲染期间,useState 返回的第一个值将始终是应用更新后的最新 state(状态) 。

函数式更新

如果使用先前 state(状态) 计算新 state(状态) ,则可以将函数传递给 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>
    </>
  );
}

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

注意

与类组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以通过将函数更新器表单与对象扩展语法结合来复制此行为:

setState(prevState => {
  // Object.assign 也是可行的
  return {...prevState, ...updatedValues};
});

另一个选项是 useReducer ,它更适合管理包含多个子值的 state(状态) 对象。

延迟初始化

initialState 参数是初始渲染期间使用的状态。 在随后的渲染中,它会被忽略了。 如果初始状态是高开销的计算结果,则可以改为提供函数,该函数仅在初始渲染时执行:

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

useEffect

useEffect(didUpdate);

接受一个函数,该函数包含强制性,可能有 effectful(副作用) 代码的函数。

函数式组件的主体内部不允许发生改变,订阅,计时器,日志记录和其他 side effects (称为React的 渲染阶段 )。这样做会导致UI中的错误和不一致性的混乱。

相反,使用 useEffect 。传递给 useEffect 的函数将在渲染结束后运行。可以将 effects 视为是从 React 的纯函数世界到命令式的一个逃生出口。

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

清理 effect

通常,effects 会创建一些资源,在组件卸载时需要清理,例如 subscription(订阅) 或 计时器 ID。为此,传递给 useEffect 的函数可能会返回一个清理函数。例如,要创建订阅:

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

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

effect 的时间

componentDidMountcomponentDidUpdate 不同,传递给 useEffect 的函数在延迟事件期间在 layout(布局) 和 paint(绘制) 后触发。 这使得它适用于许多常见的 side effects ,例如设置订阅和事件处理程序,因为大多数类型的工作不应阻止浏览器更新屏幕。

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

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

条件控制的 effect

effects 的默认行为是在每次完成渲染后触发。 这样,如果其中一个输入发生变化时,则始终会重新创建 effects 。

但是,在某些情况下,这可能是不需要的,例如上一节中的订阅示例。 仅当 source 属性已更改时,我们无需在每次更新时创建新订阅。

要实现此功能,请将第二个参数传递给 useEffect ,它是 effect 所依赖的值数组。 我们更新的示例现在看起来像这样:

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

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

传入一个空数组 [] 输入告诉 React 你的 effect 不依赖于组件中的任何值,因此该 effect 仅在 mount 时运行,并且在 unmount 时执行清理,从不在更新时运行。

注意

输入数组不作为参数传递给效果函数。 但从概念上讲,这就是它们所代表的内容:效果函数中引用的每个值也应出现在输入数组中。 将来,一个足够先进的编译器可以自动创建这个数组。

useContext

const context = useContext(Context);

接受一个 context(上下文)对象(从 React.createContext 返回的值)并返回当前 context 值,由最近 context 提供程序给 context 。

当提供程序更新时,此 Hook 将使用最新的 context 值触发重新渲染。

额为的 Hooks

下面的 Hooks 要么是上一节中基本 Hooks 的变体,要么只用于特定的边缘情况。所以学习式无需感动有压力。

useReducer

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

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

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

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return initialState;
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      // A reducer must always return a valid state.
      // Alternatively you can throw an error if an invalid action is dispatched.
      return state;
  }
}

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

延迟初始化

useReducer 接受可选的第三个参数 initialAction 。如果提供,则在初始渲染期间应用初始操作。这对于计算包含通过 props(属性) 传递的值的初始 state(状态) 非常有用:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return {count: action.payload};
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      // A reducer must always return a valid state.
      // Alternatively you can throw an error if an invalid action is dispatched.
      return state;
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(
    reducer,
    initialState,
    {type: 'reset', payload: initialCount},
  );

  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>
    </>
  );
}

当你涉及多个子值的复杂 state(状态) 逻辑时,useReducer 通常优于 useState 。它还允许你优化触发深度更新的组件的性能,因为 你可以传递调度而不是回调

useCallback

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

返回一个 memoized 回调。

传递内联回调和一组输入。 useCallback 将返回一个 callback(回调) 的 memoized 版本,该版本仅在其中一个输入发生更改时才会更改。 当将回调传递给依赖于引用相等性的优化子组件,以防止不必要的渲染(例如,shouldComponentUpdate)时,这非常有用。

useCallback(fn, inputs) 等价于 useMemo(() => fn, inputs)

注意

输入数组不作为参数传递给效果函数。 但从概念上讲,这就是它们所代表的内容:效果函数中引用的每个值也应出现在输入数组中。 将来,一个足够先进的编译器可以自动创建这个数组。

useMemo

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

返回一个 memoized 值。

传递 “create” 函数和输入数组。 useMemo 只会在其中一个输入发生更改时重新计算 memoized 值。 此优化有助于避免在每个渲染上进行高开销的计算。

如果未提供数组,则只要将新函数实例作为第一个参数传递,就会计算新值。 (在每个渲染上使用内联函数。)

注意

输入数组不作为参数传递给效果函数。 但从概念上讲,这就是它们所代表的内容:效果函数中引用的每个值也应出现在输入数组中。 将来,一个足够先进的编译器可以自动创建这个数组。

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()ref 属性更有用。与在类中使用 instance(实例) 字段的方式类似,它可以 方便地保留任何可变值

useImperativeMethods

useImperativeMethods(ref, createInstance, [inputs])

useImperativeMethods 自定义使用 ref 时公开给父组件的实例值。与往常一样,在大多数情况下应避免使用refs的强制代码。 useImperativeMethods 应与 forwardRef 一起使用:

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

在此示例中,渲染 <FancyInput ref={fancyInputRef} /> 的父组件将能够调用 fancyInputRef.current.focus()

useLayoutEffect

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

在尽可能的情况下首选标准的 useEffect ,以避免阻止视觉更新。

提示

如果你正在从类组件迁移代码,则 useLayoutEffect 会在与 componentDidMountcomponentDidUpdate 相同的阶段触发,因此如果你不确定要使用哪种 effect Hook ,它可能是风险最小的。