React Hooks 完整学习笔记
Stella981
• 阅读
717
https://juejin.im/post/5e687c26e51d45272443f7d8
时长:预计 30 min
面向:React Hook 初学者
版本:React 16.13.0 + TypeScript 3.7.2
原文:获得更好阅读体验
即使文中代码片段均可直接运行,仍然建议将源代码仓库拉到本地跑起来之后边调试边阅读。
一:设计动机
组件之间复用状态逻辑很难
复杂组件变得难以理解
难以理解的 Class
详细参考这里
二:使用姿势
useState
import React from 'react';
const { useState } = React;
// https://zh-hans.reactjs.org/docs/hooks-state.html
// useState 是最简单的一个 hook
// 唯一需要注意的是不要尝试在循环或条件等不稳定的代码结构中编写
// 原因在这里 -> https://github.com/brickspert/blog/issues/26
export default function UseState() {
const [num1, setNum1] = useState(0);
const [num2, setNum2] = useState(0);
console.warn('render');
return (
useState Demo
num1:{num1}
num2:{num2}
<button onClick={() => setNum1(num1 + 1)}>add num1
<button onClick={() => setNum2(n => n + 1)}>add num2
);
}
复制代码
useEffect
import React from 'react';
const { useState, useEffect } = React;
// https://zh-hans.reactjs.org/docs/hooks-effect.html
// https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/
// 适用场景:
// 1. 模拟钩子函数可以进行清理操作
export default function UseEffect() {
const [num1, setNum1] = useState(0);
const [num2, setNum2] = useState(0);
// 相当于 componentDidMount + componentDidUpdate + componentWillUnmount
useEffect(() => {
console.log('useEffect1');
return () => {
console.log('_useEffect1');
};
});
// 相当于 componentDidMount + componentWillUnmount
// 注意 deps 参数为空数组,不同于不传!!
useEffect(() => {
console.log('useEffect2');
return () => {
console.log('_useEffect2');
}
}, []);
// 相当于 componentDidMount + componentDidUpdate + componentWillUnmount
// 相比于第一种情况更加精确
useEffect(() => {
console.log('useEffect3');
return () => {
console.log('_useEffect3');
}
}, [num1]);
console.warn('render');
return (
useEffect Demo
{num1}
<button onClick={() => setNum1(num1 + 1)}>更新 num1
{num2}
<button onClick={() => setNum2(num2 + 1)}>更新 num2
);
}
复制代码
useContext
import React, { useContext } from 'react';
const { useState, createContext } = React;
// https://zh-hans.reactjs.org/docs/hooks-reference.html#usecontext
// 适用场景:
// 1. 状态共享
// 唯一需要注意的是:
// 当组件上层最近的 <SizeContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 SizeContext provider 的 context value 值。
// 即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。
const SizeContext = createContext({
size: 0,
// 给子组件更改 context value 值暴露接口
setSize(size: number) { console.log(size) },
});
function ChildCom() {
const { size, setSize } = useContext(SizeContext);
function updatSize() {
setSize(size + 1);
}
console.warn('child srender');
return (
子组件 size:{size}
)
}
export default function UseContext() {
const [size, setSize] = useState(0);
console.warn('father render');
const value = {
size,
setSize
}
return (
<SizeContext.Provider value={value}>
根组件 size:{size}
</SizeContext.Provider>
);
}
复制代码
useMemo
import React from 'react';
const { useState, useMemo } = React;
// https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo
// 适用场景:
// 1. 性能优化:减少不必要的重复计算
function getGreetText(name: string) {
console.log('重新计算');
return 'hello' + name;
}
// function ChildNormal(props: { name1: string, name2: string }) {
// // 不管是 name1 还是 name2 变化都会导致重新计算
// const greet = getGreetText(props.name1);
// return (
// <>
// {greet}
// </>
// );
// }
function ChildUseMemo(props: { name1: string, name2: string }) {
// 只有在 name1 变化时候才会计算
const greet = useMemo(() => getGreetText(props.name1), [props.name1]);
console.log('child render');
return (
<>
name1: {props.name1}
name2: {props.name2}
greet: {greet}
</>
);
}
export default function UseMeno() {
const [name1, setName1] = useState('');
const [name2, setName2] = useState('');
return (
name1:<input type="text" onChange={(e) => setName1(e.target.value)} />
name2:<input type="text" onChange={(e) => setName2(e.target.value)} />
{/* greet normal: */}
greet useMemo:
);
}
复制代码
useCallback
import React, { useCallback } from 'react';
const { useState, memo } = React;
// https://zh-hans.reactjs.org/docs/hooks-reference.html#usecallback
// useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
// 适用场景:
// 1. 性能优化:将句柄传入做了 memo 的子组件
interface IChildProps {
count: number;
onAdd(): void;
}
const Child1 = memo(function (props: IChildProps) {
console.log('Child1 Render');
return (
<div style={{ border: '1px solid #000' }}>
Child1
count:{props.count}
);
});
const Child2 = memo(function (props: IChildProps) {
console.log('Child2 Render');
return (
<div style={{ border: '1px solid #000' }}>
Child2
count:{props.count}
);
});
export default function UseCallback() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const onAddCount1 = () => {
setCount1(count1 + 1);
}
// 标记是稳定的,count1 变化不会影响 Child2 的渲染
const onAddCount2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
console.log('Wrap Renders');
return (
);
}
复制代码
useLayoutEffect
import React from 'react';
const { useState, useLayoutEffect, useEffect } = React;
// https://zh-hans.reactjs.org/docs/hooks-reference.html#uselayouteffect
// 适用场景:
// 1. 解决闪烁问题
function Child1() {
const [value, setValue] = useState(0);
// 异步更新会出现闪烁
useEffect(() => {
if (value === 0) {
setValue(10 + Math.random() * 200);
}
}, [value]);
console.log('render', value);
return (
{value === 0 ?
xiixix
: value: {value}
}
<button onClick={() => setValue(0)}>click me
);
}
function Child2() {
const [value, setValue] = useState(0);
// 同步更新将不会出现闪烁
useLayoutEffect(() => {
if (value === 0) {
setValue(10 + Math.random() * 200);
}
}, [value]);
console.log('render', value);
return (
{value === 0 ?
xiixix
: value: {value}
}
<button onClick={() => setValue(0)}>click me
);
}
export default function UseLayoutEffect() {
return (
);
}
复制代码
useRef
import React, { useEffect, ChangeEvent, useState } from 'react';
const { useRef } = React;
// https://zh-hans.reactjs.org/docs/hooks-reference.html#useref
export default function UseRef() {
const [num, addNum] = useState(0);
// 1. 用于获取 DOM
const ref1 = useRef(null);
// 2. 用作实例属性的存储,useRef 在整个组件生命周期都会保持不变
const ref2 = useRef('0');
useEffect(() => {
console.log('num', ref2.current);
console.log('ref2.current', ref2.current);
});
const onClick = () => {
// current
指向已挂载到 DOM 上的文本输入元素
ref1.current?.focus();
};
const onChange = (e: ChangeEvent) => {
ref2.current = e.target.value
}
console.warn('render');
return (
useRef Demo
<button onClick={() => addNum(num + 1)}>addNum
);
}
复制代码
useImperativeHandle
import React from 'react';
const { useRef, useImperativeHandle, forwardRef } = React;
// https://zh-hans.reactjs.org/docs/hooks-reference.html#useimperativehandle
// 适用场景: ref 转发时候代理一层,做 API 的上层封装
interface IChildRef {
getHeight(): number;
}
const Child = forwardRef((props: {}, ref: React.Ref) => {
const divRef = useRef(null);
useImperativeHandle(ref, () => ({
getHeight: () => {
console.log('计算了高度');
return divRef.current?.clientHeight || 0;
}
}));
return (
<div ref={divRef} style={{ height: '100px', width: '100px', border: '1px solid #000' }}>
i am child
useReducer
import React from 'react';
const { useReducer } = React;
// https://zh-hans.reactjs.org/docs/hooks-reference.html#usereducer
// 适用场景;
// 1. 复杂的数据类型,需要差量更新
// 2. 可以获取到上一次的数据
// 3. 性能优化:稳定的 dispatch 句柄
function reducer(state: { count: number }, action: { type: 'increment' | 'decrement' }) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
export default function UseReducer() {
console.warn('render1');
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'decrement' })}>-
<button onClick={() => dispatch({ type: 'increment' })}>+
</>
);
}
复制代码
useContext + useReducer 实现全局共享状态
import React from 'react';
const { useReducer, createContext, useContext } = React;
type ISizeTypes = 'addWidth' | 'addHeight';
type ISize = { width: number, height: number }
const SizeContext = createContext([
{
width: 0,
height: 0
},
a => { }
]) as React.Context<[ISize, (a: { type: ISizeTypes }) => void]>;
const useSize = () => {
const [size, dispatch] = useContext(SizeContext);
function addWidth() {
dispatch({ type: 'addWidth' });
}
function addHeight() {
dispatch({ type: 'addHeight' });
}
return { size, addWidth, addHeight };
};
function reducer(state: ISize, action: { type: ISizeTypes }) {
switch (action.type) {
case 'addWidth':
return { ...state, width: state.width + 1 };
case 'addHeight':
return { ...state, height: state.height + 1 };
default:
throw new Error();
}
}
function Com1() {
const { size, addHeight, addWidth } = useSize();
return (
组件1
宽度: {size.width}
高度: {size.height}
);
}
function Com2() {
const { size, addHeight, addWidth } = useSize();
return (
组件2
宽度: {size.width}
高度: {size.height}
);
}
export default function UseReducer() {
console.warn('render1');
const sizeContext = useReducer(reducer, { width: 0, height: 0 });
return (
<>
<SizeContext.Provider value={sizeContext}>
</SizeContext.Provider>
</>
);
}
复制代码