这篇为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中的实例属性。