美文网首页
React Hook

React Hook

作者: forever_提拉米苏 | 来源:发表于2021-05-21 14:24 被阅读0次

    react 16.8 以后加上了 react hook,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。16.8之前,react组件可以分为类组件和函数组件。

    • 函数组件一定是无状态组件,展示型组件(渲染组件)一般是无状态组件;
    • 类组件既可以是有状态组件,又可以是无状态组件。类组件也可以叫容器组件,一般有交互逻辑和业务逻辑,而容器型组件一般是有状态组件。

    我们为什么要拥抱react hook?由于类组件存在以下几点问题:

    • 组件变得复杂和难以维护,业务变得复杂之后,组件之间共享状态变得频繁,此时组件将变得非常难以理解和维护,复用状态逻辑更是难上加难。
    • 满天class导致的热重载和性能问题,class自生具有的复杂度和组件嵌套过深props层级传递。
    • 函数式组件没有状态

    下面逐一介绍官方提供的hook API。
    1.useState()
    作用:返回一个状态以及能修改这个状态的setter,在其他语言称为元组(tuple),一旦mount之后只能通过这个setter修改这个状态。


    useState函数申明

    2.useEffect(callback, arr)
    作用:处理函数组件中的副作用,如异步操作、延迟操作等。useEffect有两个参数,callback和数组依赖项,无arr时相当于componentDidMount生命周期,有arr时相当componentDidMount和componentDidUpdata生命周期。如果callback中有return,则相当于componentWillUnmount。

    3.useContext
    作用:跨组件共享数据钩子,使用可分为三步:

    • 首先使用React.createContext API创建Context,由于支持在组件外部调用,因此可以实现状态共享
    export const MyContext = React.createContext(null);
    
    • 使用Context.Provider API在上层组件挂载状态
    <MyContext.Provider value={value}>
      <childComponent />
    </MyContext.Provider>
    
    • 获取上层组件中距离当前组件最近的<MyContext.Provider> 的 value
    const value = useContext(MyContext)   // MyContext 为 context 对象(React.createContext 的返回值) 
    

    useContext和传统的props传参分别适用于那些场景
    useContext的应用场景:
    1.全局状态的定义,即可以被不同层级的组件所需要。
    2.多个组件之间传参(他们之间可能是跨多层级即祖孙关系传参)时。
    传统props的应用场景:
    如果普通的父子组件之间传参,即父子组只有单纯的一层时,用props传参更省事

    4.useReducer

    语法:const [state, dispatch] = useReducer(reducer, initialArg, init);

    作用:用于管理复杂的数据结构(useState一般用于管理扁平结构的状态),基本实现了redux的核心功能。useState的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

    const initialState = {count: 0};
    const reducer = (state, action)=> {
      switch (action.type) {
        case 'increment':
          return {count: state.count + 1};
        case 'decrement':
          return {count: state.count - 1};
        default:
          return state;
      }
    }
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({type: 'decrement'})}>-</button>
          <button onClick={() => dispatch({type: 'increment'})}>+</button>
        </>
      );
    

    5.useMemo、useCallback
    这俩个Api与性能优化有关。react中,性能的优化点在于:

    • 调用setState,就会触发组件的重新渲染,无论前后的state是否不同
    • 父组件更新,子组件也会自动的更新

    基于上面的两点,我们通常的解决方案是:使用immutable进行比较,在不相等的时候调用setState;在shouldComponentUpdate中判断前后的props和state,如果没有变化,则返回false来阻止更新。
    在hooks出来之后,我们能够使用function的形式来创建包含内部state的组件。但是,使用function的形式,失去了上面的shouldComponentUpdate,我们无法通过判断前后状态来决定是否更新。而且,在函数组件中,react不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。

    useCallback和useMemo的参数跟useEffect一致。useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

    语法:
    useMemo:const A = useCallback(fnB, [a]) 调用fnB函数并返回其结果
    useCallback:const fnA = useCallback(fnB, [a]) 返回fnB函数

    通过一个例子来看useMemo的作用:

    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
    const expensive = () => {
            console.log('compute');
            let sum = 0;
            for (let i = 0; i < count * 100; i++) {
                sum += i;
            }
            return sum;
    }
    return <>
            <h4>{count}-{val}-{expensive()}</h4>
            <div>
                <button onClick={() => setCount(count + 1)}>+c1</button>
                <input value={val} onChange={event => setValue(event.target.value)}/>
            </div>
    </>;
    

    不使用useMemo,无论count还是val改变都会执行expensive()

    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
    const expensive = useMemo(() => {
            console.log('compute');
            let sum = 0;
            for (let i = 0; i < count * 100; i++) {
                sum += i;
            }
            return sum;
    }, [count])
    return <>
            <h4>{count}-{val}-{expensive()}</h4>
            <div>
                <button onClick={() => setCount(count + 1)}>+c1</button>
                <input value={val} onChange={event => setValue(event.target.value)}/>
            </div>
    </>;
    

    使用useMemo,只有在count发生改变使才会会执行expensive()

    useCallback跟useMemo比较类似,但是使用场景不同:比如有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
    
    const callback = useCallback(() => {
        console.log('callback执行');
        return count;
    }, [count]);
    
    return <>
        <h4>{count}</h4>
        <Child callback={callback} />
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)} />
        </div>
    </>
    
    const Child = (props: any) => {
        const [count, setCount] = useState(() => props.callback());
    
        useEffect(() => {
            setCount(props.callback());
        }, [props.callback]);
    
        return <div>{count}</div>;
    };
    

    6.useRef

    语法:const refContainer = useRef(initialValue);

    useRef是一个方法,返回一个可变的 ref 对象;其 .current 属性被初始化为传入的参数(initialValue);可以保存任何类型的值:dom、对象等任何可变值;返回的 ref 对象在组件的整个生命周期内保持不变;修改 ref 的值是不会引发组件的重新 render 。

    useRef非常常用的一个操作,访问DOM节点,对DOM进行操作,监听事件等等,如下:

    const inputEl = useRef(null);
    const onButtonClick = () => {
        // `current` 指向已挂载到 DOM 上的文本输入元素
        inputEl.current.focus();
    };
    return (
        <>
          <input ref={inputEl} type="text" />
          <button onClick={onButtonClick}>Focus the input</button>
        </>
    );
    

    除了传统的用法之外,它还可以“跨渲染周期”保存数据。

    const likeRef = useRef(1);
    const [like, setLike] = useState(1);
    const onButtonClick = () => {
        setTimeout(() => {
            console.log(likeRef.current); //11
            console.log(like); // 1
        }, 2000);
    };
    return (
      <>
        <button
            onClick={() => {
               likeRef.current++;
               setLike(like + 1);
        }}>
        {like}
        </button>
        <button onClick={onButtonClick}>打印like</button>
      </>
    )
    

    在上面的例子中,点击了打印like按钮后,连续点击数字按钮,会发现2s后likeRef.current打印出11,而like打印出1。

    因为,在任意一次渲染中,props和 state 是始终保持不变的,如果props和state在任意不同渲染中是相互独立的话,那么使用到他们的任何值也是独立的。所以onButtonClick时拿到的时未点击数字按钮时的like值。
    而ref 在所有 render 都保持着唯一的引用,因此所有的对 ref 的赋值或者取值拿到的都是一个最终的状态,而不会存在隔离。

    7.useImperativeHandle

    语法:useImperativeHandle(ref, createHandle, [deps])

    当userRef用于一个组件时,useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值;如果不使用,父组件的ref(chidlRef)访问不到任何值(childRef.current==null);且useImperativeHandle 应当与 forwardRef 一起使用。

    const RefDemo= ()=>{
        const childRef = useRef<any>(null);
        const onButtonClick = () => {
            childRef.current?.say();
        };
        return (
        <div>
            <Child ref={childRef} />
            <button onClick={onButtonClick}>say123</button>
        </div>
        )
    };
    export default RefDemo;
    const Child = React.forwardRef((props, ref) => {
        useImperativeHandle(ref, () => ({
            say: () => {
                console.log('123');
            },
        }));
        return (
            <>
                <h4>子组件</h4>
            </>
        );
    });
    
    • React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。
    • React.forwardRef接受渲染函数作为参数,React将使用prop和ref作为参数来调用此函数。

    8.useLayoutEffect
    用法与useEffect 相同,但它会在所有的 DOM 变更之后同步调用(立即执行)。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。由于会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制。
    使用场景:当useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现闪屏问题。

    9.useDebugValue
    useDebugValue 用于在 React 开发者工具中显示 自定义 Hook 的标签。
    useDebugValue 接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。

    相关文章

      网友评论

          本文标题:React Hook

          本文链接:https://www.haomeiwen.com/subject/djeejltx.html