本页面介绍了React Hooks 的所有API 。
如果是新了解Hooks,可以先看之前的页面。您还可以在FAQ部分找到有用的信息。
- 基础 API
- useState
- useEffect
- useContext
- 额外的 API
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
基础 API
useState
const [state, setState] = useState(initialState);
返回一个有状态的值和一个用以更新该值的函数。
在初次render
期间,返回的状态值state
与传递的第一个参数的值相同(initialState
)。
setState
函数用来更新该值。它接受一个新的状态值并将组件重新渲染。
setState(newState);
在后续重新render
期间,useState
返回的第一个值将始终是应用更新后的最新状态。
更新函数
如果新的state需要使用当前的state来计算,你可以给setState
传递一个函数。这个函数将会接收当前的状态值,并返回更新的值。这是一个使用两种形式的setState
的计数器组件的示例:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
"+"和"-"
按钮使用的是函数形式,因为它更新状态是基于当前的状态值。而重置按钮使用的是正常形式,因为他总是让计数器回归0
注意
和类组件中的setState
不同,useState
不会自动合并更新对象。您可以通传递函数的方式并用扩展语法来将新旧对象合并:setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });
另一个方法是
useReducer
,它更适合管理包含多个子值的状态对象。懒初始值
initialState
参数是初始渲染期间使用的状态。在随后的渲染中,它被会忽略。如果初始状态是昂贵计算的结果,则可以改为传递函数,该函数仅在初始渲染时执行:const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; });
阻止状态更新
如果你在更新hooks状态时,传递了相同的值,React不会重新渲染子节点或者触发
effect
。(React使用Object.is
来比较)useEffect
useEffect(didUpdate);
接收一个函数,用以在更新后立刻执行副作用。
函数组件的主体内部不允许使用变异,订阅,计时器,日志记录和其他副作用(称为React的渲染阶段)。这样做会导致UI中的错误和不一致性混乱。
你可以使用useEffect
。传递给它的函数将会在每次渲染到屏幕上之后执行。将Effect想象成将从React的纯粹功能性世界进入命令式世界的大门。
默认情况下,effect在每次完成渲染后运行,但您可以选择仅在某些值发生更改时触发它。
需要清理的副作用
通常,effect会创建在组件离开屏幕之前需要清理的资源,例如订阅或计时器ID。为此,给useEffect
传递的函数可以返回一个清理函数。例如,要创建订阅:
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清理订阅
subscription.unsubscribe();
};
});
清除功能会在从UI中删除组件之前运行,以防止内存泄漏。此外,如果组件渲染多次(通常如此),则在执行下一个effect之前会清除先前的effect。 在我们的示例中,这意味着每次更新都会创建一个新订阅。要避免在每次更新时触发效果,请参阅下一节。
effect 的触发节点
与componentDidMount
和componentDidUpdate
不同,在延迟事件期间,传递给useEffect
的函数会在布局和绘制后触发。这使得它适用于许多常见的副作用,例如设置订阅和事件处理程序,因为大多数类型的工作不应阻止浏览器更新屏幕。
不是所有的副作用都是这样。例如,用户可见的DOM突变必须在下一次绘制之前同步触发,这样用户就不会感觉到视觉不一致。对于这些类型的效果,React提供了一个名为useLayoutEffect
的额外Hook。它与useEffect
具有相同的签名,仅在触发时有所不同。
虽然useEffect
延迟到浏览器绘制之后,但它一定会在任何新渲染之前触发。在开始新的更新之前,React将始终刷新先前渲染的效果。
条件触发 effect
effect 的默认行为是在每次完成渲染后触发。这样,如果其中一个输入发生变化,则始终会重新创建effect。
但是,在某些情况下,这可能有点不合实际,例如上一节中的订阅示例。只有当props数据更改时,我们才会再次创建新订阅。
要实现此功能,可以传递第二个参数给useEffect,它是effect所依赖的值数组。我们更新的示例现在看起来像这样:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
现在,订阅只会在props.source
改变时才会重新创建。
传递空数组[]
意味着告诉React你的effect不依赖任何值的更新,所有只会在组件挂载时运行,并在卸载时清理,更新期间不会再执行。
注意:
数组参数不会传递给effect函数。但从概念上讲,这就是它们所代表的内容:effect函数中引用的每个状态值也应出现在输入数组中。以后,先进的编译器可以自动创建这个数组。
useContext
const context = useContext(Context);
接受一个context对象(是React.createContext
的返回值)并返回当前的context值,该值由最近的发布者提供。
发布者更新时,这个Hook会触发一个具有最新context值的rerender。
额外的Hooks
以下Hook可以是上一节中基本Hook的变体,也可以仅用于特定边缘情况。不必预先学习它们。
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState 的替代方案。接收一个类型为(state, action) => newState
的 reducer ,并返回当前的值和改变该值的dispatch
方法。
当你有复杂的状态逻辑涉及多个子值或下一个状态依赖于前一个时,你就可以用useReducer
替代useState
。useReducer还允许您优化触发深度更新的组件的性能,因为你可以传递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>
</>
);
}
指定初始状态
这里有两种不同的方法来初始化useReducer状态。您可以根据用例选择其中一个。将初始状态作为第二个参数传递的最简单方法:
const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);
注意:
React不使用Redux推荐的state = initialState
参数约定。初始值有时需要依赖于props,因此应在Hook调用中指定。如果您对此感到不满,可以调用useReducer(reducer,undefined,reducer)
来模拟Redux行为,但不鼓励它。延迟初始化
您还可以延迟创建初始状态。为此,您可以将
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>
</>
);
}
更新机制
如果从Reducer Hook返回与当前状态相同的值,则React将退出而不渲染子节点或触发effect。 (React使用Object.is比较算法。)
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
返回一个记忆化(缓存返回值的技术)回调函数。
传递一个回调函数和控制数组。useCallback
将返回一个记忆化版本,该版本仅在其中一个输入发生更改时才会更改。当使用回调函数优化组件渲染时这非常有用,返回结果一致将不会触发重绘。(例如:shouldComponentUpdate
)。
useCallback(fn, inputs)
等价于 useMemo(() => fn, inputs)
。
注意:
输入数组不作为参数传递给回调函数。但是,这就是它们所代表的内容:回调中引用的每个值也应该出现在输入数组中。以后,先进的编译器可以自动创建这个数组。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个记忆化的值。
需要传递一个创建该值的函数和控制数组。useMemo
只会在其中一个输入发生变化时重新计算记忆化值。此优化有助于避免在每个渲染上进行昂贵的计算。
请记住,传递给useMemo的函数在渲染期间运行。不要做那些在渲染时通常不会做的事情。例如:副作用。
如果未提供控制数组,则只要将新函数实例作为第一个参数传递,就会计算新值。 (在每次渲染时调用内联函数)。
您可以依赖useMemo
进行性能优化,而不是语义保证。在将来,React可能会选择可以“忘记”一些以前记忆的值,并在下一次渲染时重新计算它们,例如为屏幕外组件释放内存。编写代码时要保证在没有useMemo的情况下仍可正常工作 – 然后添加它以优化性能。
注意:
输入数组不作为参数传递给回调函数。但是,这就是它们所代表的内容:回调中引用的每个值也应该出现在输入数组中。以后,先进的编译器可以自动创建这个数组。
useRef
const refContainer = useRef(initialValue);
useRef
返回一个可变的ref对象,其.current
属性被初始化为传递的参数(initialValue
)。返回的对象将持续整个组件的生命周期。
常见的用例是强制访问一个子组件:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向了已挂载的input元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
注意,useRef()
比ref
属性更有用。保存任何可变值的方法类似于在类中使用实例字段。
useImperativeHandle
useImperativeHandle(ref, createHandle, [inputs])}
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
以避免视窗停顿。
注意:
如果您正在从类组件迁移代码,则useLayoutEffect
会在与componentDidMount
和componentDidUpdate
相同的阶段触发,因此如果您不确定使用哪个effect hook,则可能风险最小。
useDebugValue
useDebugValue(value)
useDebugValue
可用于在React DevTools
中显示自定义挂钩的标签。
例如,参考“构建自己的hooks”中描述的useFriendStatus
自定义Hook:
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在 DevTools 中,该 hooks 会增加一个显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
注意:
我们不建议将调试值添加到每个自定义Hook。对于共享库一部分的自定义Hook,它相当有用。
推迟格式化调试值
在某些情况下,格式化显示值可能是一项昂贵的操作。除非实际检查Hook,否则也没有必要。
因此,useDebugValue接受格式化函数作为可选的第二个参数。只有在检查hooks时才会调用此功能。它接收调试值作为参数,并应返回格式化的显示值。
例如,自定义Hook返回Date值,可以通过传递以下格式化程序来避免不必要地调用toDateString函数:
useDebugValue(date, date => date.toDateString());