沐光

记录在前端之路的点点滴滴

React Hook 初学记录

前言

此篇为我从 Vue 转 React,初学 React Hook 的一些记录,记录一些写法和注意项。

useState

此 hook 相当于 Vue 中 Date 函数返回的对象的值,返回一个数组:

1
2
3
4
// state: 对应的值
// setState: 更新改值的函数
// initialState: 初始化值
const [state, setState] = useState(initialState);

useState 的两种写法:

1
2
3
4
5
6
7
8
// 对于简单值可直接设置
useState(initVal);
// 对于复杂的操作,可使用函数处理
useState(() => {
// initVal 仅在初始化渲染时起作用
const initVal = complexFunc(props)
return initVal;
})

setState 有两种使用方式:

1
2
3
4
5
6
7
// 直接设置新值
setState(newVal);
// 基于旧值操作获得新值
setState((preVal) => {
// ...
return newVal;
});

useEffect

此 hook 相当于 Vue 的 updated 钩子,其会在组件渲染到屏幕之后延迟执行(会保证在任何新的渲染前执行),也可以配置让其仅在某些值改变的时候执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// return 方法用于清楚一些副作用函数(如:removeEventListener)
// 注:不能传入异步函数
useEffect(() => {
someFunc();

return () => {
someClearFunc();
}
})

// 在某些值改变的情况下执行
// 注意:没有 someVal 时改 hook 仅会在初始化时执行
useEffect(() => {
someFunc();

return () => {
someClearFunc();
}
}, [someVal])

如果需要在所有 DOM 变动之后同步调用 effect,则需要使用到 useLayoutEffect,但推荐一开始时优先使用 useEffect 方法。

若要从服务端渲染的 HTML 中排除依赖布局 effect 的组件,可以通过使用 showChild && <Child /> 进行条件渲染,并使用 useEffect(() => { setShowChild(true); }, []) 延迟展示组件。这样,在客户端渲染完成之前,UI 就不会像之前那样显示错乱了。(如弹框组件初始化问题)

useContext

可以理解为 Vue 的 Provide/Inject 钩子(不是 ProvideReactive/InjectReactive):

1
2
3
4
5
6
7
8
9
10
11
const MyContext = React.createContext(/* default value */);

function App() {
return (
<MyContext.Provider value={themes.dark}>
// Components
</MyContext.Provider>
);
}

useContext(MyContext)

Provider 内如果绑定了 value(即使是 undefined),default value 也不会生效

useReducer

简化版的 Redux,与 useState 的区别是需要配置 reducer 函数,处理不同 type 的操作。

项目开发可能使用的不是很多,不做过多记录

useCallback

可以理解成 Vue 的 computed 钩子,不过其记录的是函数,使其仅在依赖项变更时才更新函数。当依赖参数不经常变时,可以优化子组件的渲染(返回的是子组件)。

1
2
3
4
5
// 依赖项数组不会作为参数传给回调函数
const memoizedCallback = useCallback(
() => { doSomething(a, b); },
[a, b],
);

此 hook useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
注:滥用的话不仅不会提升性能,反而会降低性能。

useMemo

相当于 Vue 的 computed 钩子了,有助于避免在每次渲染时都进行高开销的计算。

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

传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo
大致流程:Dom 变更 -> useMemo -> 所有 Dom 渲染完成 -> useEffect

useRef

可以理解成 Vue 的 Ref 钩子,但是其不仅仅局限于保存 DOM 节点,其 .current 属性中可以保存一个可变值。

1
const refContainer = useRef(initialValue);

useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
注: 当 ref 对象内容发生变化时,useRef不会通知你。变更 .current 属性不会引发组件重新渲染。

useImperativeHandle

增强版 useRef,可以自定义设置在使用 ref 时需要暴露给父组件的实例值,且useImperativeHandle 应当与 forwardRef 一起使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 基本方法
useImperativeHandle(ref, createHandle, [deps]);

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

在大多数情况下,应当避免使用 ref 这样的命令式代码,可考虑使用 useImperativeHandle 做一层属性上的封装。

useLayoutEffect

它会在所有的 DOM 变更之后同步调用 effect,在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。
useLayoutEffectcomponentDidMountcomponentDidUpdate 的调用阶段是一样的。

useDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

1
2
3
4
5
6
7
8
9
10
11
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);

// ...

// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');

return isOnline;
}

不推荐你向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值。

参考文档