写在前面
Hook 是 React 16.8 新增的特性
它的出现是为了解决以下问题:在组件间复用状态逻辑很难;复杂组件变得难以理解;难以理解的class
简言之,Hook可以在非class的情况下使用更多的React特性
Hook规则
- 只在最顶层使用Hook,不要在循环、条件或嵌套函数中调用Hook,因为这样做可能会出现bug
- 只在React函数中调用Hook
Hook API
1.useState
基本用法
const [ state, setState ] = useState(initialState);
返回一个state以及更新它的函数
与class组件中的setState方法不同,useState不会自动合并更新对象,而是会将其替换,如需实现,可以用下面的方法:
setState(prevState => {
return {...prevState, ...updatedValues};
});
2.useEffect
基本用法
useEffect(didUpdate);
该Hook接收一个包含命令式、且可能有副作用代码的函数
可以返回一个清除函数以在组件卸载时清除effect创建的诸如订阅或定时器ID等资源:
useEffect(() => {
const subscription = props.source.subcribe();
return () => {
// 清除订阅
subcription.unsubcribe();
}
});
如果组件多次渲染,则在执行下一个effect之前,清除上一个effect
执行时机
与 componentDidMount
、componentDidUpdate
不同的是,在浏览器完成布局与绘制之后,传给 useEffect
的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。
然而,并非所有 effect 都可以被延迟执行。例如,在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React 为此提供了一个额外的 useLayoutEffect
Hook 来处理这类 effect。它和 useEffect
的结构相同,区别只是调用时机不同。
虽然 useEffect
会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的 effect。
条件执行
可以给useEffect传递第二个参数,它是一个数组,存放effect更新所依赖的值,当数组内的值发生变化,才会调用;(若传一个空数组,则表示只运行一次)
useEffect(
() => {
const subcription = props.source.subcribe();
return () => {
subcription.unsubcribe();
};
},
[props.source],
);
3.useContext
基本用法
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
4.useReducer
基本用法
const [state, dispatch] = useReducer(reducer, initialArg, init);
useReducer是useState的一个替代方案,适用于复杂逻辑或是有多个子集的state
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() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
5.useCallback
基本用法
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
6.useMemo
基本用法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized
值。
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入 useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect
的适用范畴,而不是 useMemo
。
7.useRef
基本用法
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
一个常见的用例便是命令式地访问子组件:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
8.useImperativeHandle
基本用法
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
可以让你在使用 ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与 forwardRef
一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
9.useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
尽可能使用标准的 useEffect 以避免阻塞视觉更新。
10.useDebugValue
基本用法
useDebugValue(value)
useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}