乐趣区

react hooks 学习笔记

这篇为 react hooks 的学习笔记,仅对需要关注的重点做了记录。有关 react hooks 详细说明,参见官网 https://reactjs.org/docs/hook…
Rules of Hooks

不能将 hooks 放在循环、条件语句或者嵌套方法内。react 是根据 hooks 出现顺序来记录对应状态的
只在 function 组件和自定义 hooks 中使用 hooks。

命名规范

useState 返回数组的第二项以 set 开头(仅作为约定)
自定义 hooks 以 use 开头(可被 lint 校验)

API

useState useState 可传任意类型的变量或者返回任意类型变量的 function。useState 返回数组的第二个参数(setter),可传任意类型的变量,或者一个接收 state 旧值的 function,其返回值作为 state 新值
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
// Lazy initialization
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
return (
<>
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount – 1)}>-</button>
</>
);
}
set 方法不会像 setState 一样做 merge,所以建议如果数据结构简单,可以将变量根据数据结构需要放在不同的 useState 中,避免大量使用类似 {…state, value} 形势。如果数据结构复杂,建议使用 useReducer 管理组件的 state

useEffect
useEffect(effect, array);
effect 函数将在 componentDidAmount 时触发和 componentDidUpdate 时有条件触发。可以返回一个函数(returnFunction),returnFunction 将会在 componentWillUnmount 时触发和在 componentDidUpdate 时先于 effect 有条件触发。与 componentDidAmount 和 componentDidUpdate 不同之处是,effect 函数触发时间为在浏览器完成渲染之后。如果需要在渲染之前触发,需要使用 useLayoutEffect。第二个参数 array 作为有条件触发情况时的条件限制。

如果不传,则每次 componentDidUpdate 时都会先触发 returnFunction(如果存在),再触发 effect。
如果为空数组[],componentDidUpdate 时不会触发 returnFunction 和 effect。
如果只需要在指定变量变更时触发 returnFunction 和 effect,将该变量放入数组。

useContext 和 consumer 类似,仍然需要与 Provider 配合使用
const Context = React.createContext(‘light’);

// Provider
class Provider extends Component {
render() {
return (
<Context.Provider value={‘dark’}>
<DeepTree />
</Context.Provider>
)
}
}
// Consumer
function Consumer(props) {
const context = useContext(Context);
return (
<div>
{context} // dark
</div>
);
}

useReducer 用于管理复杂结构的状态对象,与 redux 的核心逻辑一致。
const [state, dispatch] = useReducer(reducer, initialState, {
type: ‘reset’,
payload: initialCount
});
// demo
const TodosDispatch = React.createContext(null);

function TodosApp() {
// Tip: `dispatch` won’t change between re-renders
const [todos, dispatch] = useReducer(todosReducer, initialState);

return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}

useCallback useCallback 和下面的 useMemo 是非常实用的提升性能的小工具。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b]
);
useCallback 返回一个 memoized 函数,在参数 a 和 b 都没有改变时,总是返回同一个函数。其具体实现逻辑基本如下:
let memoizedState = null;
function useCallback(callback, inputs) {
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [callback];
const prevState = memoizedState;
if (prevState !== null) {
const prevInputs = prevState[1];
if (areHookInputsEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
memoizedState = [callback, nextInputs];
return callback;
}
注:第二个参数目前只用于指定需要判断是否变化的参数,并不会作为形参传入回调函数。建议回调函数中使用到的变量都应该在数组中列出。以后的版本可能会将第二项数组参数移除,自动判断回调函数中使用到的变量是否变化来判断返回结果。

useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
与 useCallback 类似,返回一个 memoized 函数执行结果。useMemo(() => fn, inputs) 等价于 useCallback(fn, inputs) useMemo 可用于实现 PureComponent 子组件:
function Parent({a, b}) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}

useRef
const refContainer = useRef(initialValue);
类似 react.createRef()。在使用 hooks 的 function component 中,useRef 不仅可以用来做 DOM 的引用,还可以做来作为类似 class 的实例属性,因为相同位置的 useRef()每次返回的都是同一个对象。
function Timer() {
const intervalRef = useRef();

useEffect(() => {
const id = setInterval(() => {
// …
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});

// …
}
利用 useRef()的这种特性,还可以做很多其他有趣的事情,例如获取 previous props 或 previous state:
function Counter() {
const [count, setCount] = useState(0);

const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;

return <h1>Now: {count}, before: {prevCount}</h1>;
}

useImperativeMethods
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeMethods(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} … />;
}
FancyInput = React.forwardRef(FancyInput);
useImperativeMethods 提供了父组件直接调用子组件实例方法的能力。上例中,一个包含 <FancyInput ref={fancyInputRef} /> 的父组件,就可以调用 fancyInputRef.current.focus().

Tips

目前暂时没有与 getSnapshotBeforeUpdate 和 componentDidCatch 相匹配的 hooks。
使用 ESlint plugin(eslint-plugin-react-hooks)做代码校验。
Hooks 的调用只能放在 function component(首字母大写的 function)或者自定义 Hook(名为 useXxxx 的 function)中。
Hooks 在组件的每次 render 时都以完全相同的顺序执行。

用 React.memo 来实现 shouldComponentUpdate。
const Button = React.memo((props) => {
// your component
});

请将 refs 理解成 class 中的实例属性。

退出移动版