React Hooks

作者:renzp94
时间:2021-07-29 10:18:19

HooksReact16.8新增的特性,可以在不编写class的情况下使用state以及其他特性,Hooks的出现主要是解决组件之间的复用状态逻辑困难,复杂组件难理解等问题

Hook 规则

  • 只在最顶层使用Hook,即:不要在循环,条件判断或嵌套函数中调用Hook
  • 只在React函数中调用Hook

State Hook:useState

使用useState创建一个响应式的状态,接收一个初始值(可以是函数且函数只在初始渲染时调用),返回一个数组,数组第一个元素是state,第二个元素是更新state的函数。如果需要依据当前的stateprops更新state,则更新state的函数可以接收一个函数,函数的参数是当前的state,返回一个需要更新的值。需要多个状态时则使用useState创建多个state即可

import React, { useState } from 'react';

const App = () => {
    const [count, setCount] = useState(0);
    const onDec = () => setCount((count) => count - 1);
    const onAdd = () => setCount((count) => count + 1);
    const onReset = () => setCount(0);

    return (
        <>
            <button onClick={onDec}>-</button>
            <span>{count}</span>
            <button onClick={onAdd}>+</button>
            <button onClick={onReset}>reset</button>
        </>
    );
};

export default App;

Effect Hook:useEffect

useEffect代替了生命周期componentDidMountcomponentDidUpdatecomponentWillUnmount。其接收两个参数:第一个参数是一个函数,此函数就相当于componentDidMountcomponentDidUpdate,此函数可返回一个函数,返回的函数相当于componentWillUnmount。第二个参数是一个数组(非必传),是Effect Hook的依赖项,如果不传,则组件首次挂载后以及state发生变化后会触发,如果传空数组,则只在组件首次挂载后触发,如果数组中传入依赖项,则组件首次挂载后和依赖项发生变化时会触发。可使用多个useEffect,执行顺序是声明的顺序

import React, { useEffect, useState } from 'react';

const App = () => {
    const [count, setCount] = useState(0);
    const onDec = () => setCount((count) => count - 1);
    const onAdd = () => setCount((count) => count + 1);
    const onReset = () => setCount(0);

    const [message, setMessage] = useState('React');
    const onChangeMessage = () => setMessage('Vue');

    useEffect(() => console.log('只会在组件挂载后触发'), []);
    useEffect(() => console.log('组件挂载后及状态发生变化后触发'));
    useEffect(() => console.log('组件挂载后及count变化后触发'), [count]);
    useEffect(() => {
        const timer = setInterval(() => {
            console.log('定时器运行中...');
        }, 1000);

        return () => {
            console.log('组件卸载前触发');
            clearInterval(timer);
        };
    });

    return (
        <>
            <button onClick={onDec}>-</button>
            <span>{count}</span>
            <button onClick={onAdd}>+</button>
            <button onClick={onReset}>reset</button>
            <div onClick={onChangeMessage}>{message}</div>
        </>
    );
};

export default App;

useContext

接收一个Context对象(React.createContext的返回值)并返回当前context的值,当前的context的值取决于上层组件中距离最近的Context.Providervalue属性决定

import React, { createContext, useContext, useState } from 'react';

interface IProvider {
    count: number;
    onDec: () => void;
    onAdd: () => void;
}
const context = createContext<IProvider | null>(null);

const { Provider } = context;

const Counter = () => {
    const { count } = useContext(context) as IProvider;
    return <span>{count}</span>;
};

const CountButton = (props: any) => {
    const { onDec, onAdd } = useContext(context) as IProvider;

    return <button onClick={props.type === 'dec' ? onDec : onAdd}>{props.children}</button>;
};

function App() {
    const [count, setCount] = useState(0);

    const provider = {
        count,
        onDec: () => setCount((count) => count - 1),
        onAdd: () => setCount((count) => count + 1)
    };

    return (
        <Provider value={provider}>
            <CountButton type="dec">-</CountButton>
            <Counter />
            <CountButton type="add">+</CountButton>
        </Provider>
    );
}

export default App;

useReducer

useState的替代方案,第一个参数接收像(state,action) => newStatereducer,第二个参数接收一个初始值,返回当前的statedispatch方法

import React, { useReducer } from 'react';

const reducer = (state: any, action: { type: string }) => {
    switch (action.type) {
        case 'dec':
            return { count: state.count - 1 };
        case 'add':
            return { count: state.count + 1 };
    }
};

const App = () => {
    const [state, dispatch] = useReducer(reducer, { count: 0 });
    return (
        <>
            <button onClick={() => dispatch({ type: 'dec' })}>-</button>
            <span>{state?.count}</span>
            <button onClick={() => dispatch({ type: 'add' })}>+</button>
        </>
    );
};

export default App;

useCallback

把内联回调函数及依赖项数组作为参数传入useCallback,返回该回调函数的memoized版本的回调函数。用于性能优化(可缓存函数),一般是在父组件中通过useCallback缓存函数,当父组件内的数据发生变化重新渲染时,只要不是useCallback的依赖项发生变化,子组件就不会重新渲染,子组件需要通过React.memo处理一下。

import React, { memo, useCallback, useEffect, useState } from 'react';

const Message = memo((props: { msg: string; onChange: () => void }) => {
    console.log('Message渲染');
    return <span onClick={props.onChange}>Message组件:{props.msg}</span>;
});

const App = () => {
    const [count, setCount] = useState(0);
    const onDec = () => setCount((count) => count - 1);
    const onAdd = () => setCount((count) => count + 1);

    const [msg, setMsg] = useState('hello');
    // const onChangeMsg = () =>  setMsg('world')
    const onChangeMsg = useCallback(() => setMsg('world'), [msg]);

    return (
        <>
            <button onClick={onDec}>-</button>
            <span>{count}</span>
            <button onClick={onAdd}>+</button>
            <Message msg={msg} onChange={onChangeMsg} />
        </>
    );
};

export default App;

如果不使用useCallback,则当改变count是也会触发Message组件的渲染,当使用useCallback之后,只有当msg发生变化时,Message组件才会重新渲染

useMemo

把函数和依赖项数组作为参数传入useMemo,返回该回调函数的memoized版本的回调函数,它仅在某个依赖项改变时才会重新计算memoized值。用于性能优化(可缓存数据),如果一个组件内存在多个可变数据,当一个数据发生变化则所有数据的处理函数都会被调用,使用useMemo可将某些操作绑定到指定数据,即:指定依赖,当依赖发生变化时才会重新执行操作。

import React, { useMemo, useState } from 'react';

const App = () => {
    const [count, setCount] = useState(0);
    const onDec = () => setCount((count) => count - 1);
    const onAdd = () => setCount((count) => count + 1);

    const [msg, setMsg] = useState('Hello');
    const onChangeMsg = () => setMsg('Hi');
    // const getFullMsg = () => {
    //   console.log('getFullMsg调用')
    //   return `${msg} World`
    // }

    const getFullMsg = useMemo(() => {
        console.log('getFullMsg调用');
        return `${msg} World`;
    }, [msg]);

    return (
        <>
            <div>
                <button onClick={onDec}>-</button>
                <span>{count}</span>
                <button onClick={onAdd}>+</button>
            </div>
            <div>
                <div>{msg}</div>
                <button onClick={onChangeMsg}>改变msg:{getFullMsg}</button>
            </div>
        </>
    );
};

export default App;

上述例子如果不使用useMemo,则更新count也会触发getFullMsg函数

特别需要注意的是:useMemo返回的是一个值,不是一个函数

useRef

返回一个可变的ref对象,其.current属性被初始化为传入的数据.一般用于获取DOM元素

import React, { useEffect, useRef } from 'react';

const App = () => {
    const inputRef = useRef<HTMLInputElement>(null);

    useEffect(() => {
        inputRef.current!.focus();
    });
    return <input type="text" ref={inputRef} />;
};

export default App;

useImperativeHandle

可以在使用ref时自定义暴露给父组件的实例值,需要和forwardRef配合使用.在子组件暴露指定的内容,父组件通过ref.current获取当前暴露的内容

import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';

const Input = forwardRef((_, ref) => {
    const inputRef = useRef<HTMLInputElement>(null);

    useImperativeHandle(ref, () => ({
        autoFocus: () => inputRef.current?.focus()
    }));

    return <input type="text" ref={inputRef} />;
});

const App = () => {
    const inputRef = useRef<any>(null);

    useEffect(() => inputRef.current?.autoFocus());
    return <Input ref={inputRef} />;
};

export default App;

useLayoutEffect

useEffect一样,不过useLayoutEffect是等所有的DOM更新之后同步调用effect.

useDebugValue

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