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 的时间
与 componentDidMount
和 componentDidUpdate
不同,传递给 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
会在与componentDidMount
和componentDidUpdate
相同的阶段触发,因此如果你不确定要使用哪种 effect Hook ,它可能是风险最小的。