React Hooks

121 阅读4分钟

1. useSate 是同步还是异步

2. useEffect 和 useLayoutEffect 区别

3. useRef 和 React.createRef 的区别

4. useMemo

语法:let xxx = useMemo(cb, [deps]);

第一次组件渲染的时候,cb执行;

后期只有依赖项deps发生改变,cb才会再执行;

每一次会把cb执行的返回值赋给xxx;

useMemo 具备”计算缓存“,若依赖的state没有改变,cb没有触发执行的时候,xxx获取的是上一次计算的缓存值。

总结:useMemo 就是一个优化的Hook函数。

如果函数组件中,有消耗性能/时间的计算操作,尽可能使用useMemo缓存起来,设置对应的state依赖项;

这样可以保证,当非依赖项state发生改变,不会去处理一些没必要的操作,提高组件的更新速度。

实例:

import { useMemo, useState } from 'react';

const Demo = () => {
  const [supportNum, setSupportNum] = useState(10);
  const [oppositionNum, setOppositionNum] = useState(5);
  const [x, setX] = useState(0);
  // 缓存 ratio
  const ratio = useMemo(() => {
    const total = supportNum + oppositionNum;
    let ratio = '-';
    if (total > 0) {
      ratio = ((supportNum / total) * 100).toFixed(2) + '%';
    }
    return ratio;
  }, [supportNum, oppositionNum]);
  return (
    <div>
      <div>支持:{supportNum}</div>
      <div>反对:{oppositionNum}</div>
      <div>支持率:{ratio}</div>
      <div>其他:{x}</div>
      <button
        onClick={() => {
          setSupportNum(supportNum + 1);
        }}
      >
        Support
      </button>
      <button
        onClick={() => {
          setOppositionNum(oppositionNum + 1);
        }}
      >
        Oppose
      </button>
      <button
        onClick={() => {
          setX(x + 1);
        }}
      >
        do other things
      </button>
    </div>
  );
};
export default Demo;

效果:

截屏2023-03-17 22.22.09.png

5.useCallback

语法:const xxx = useCallback(cb, [deps]);

组件第一次渲染,useCallback执行,创建一个函数cb,赋值给xxx;

组件后续每一次更新,xxx 是否会改变依赖于deps是否改变:如果改变,新创建cb赋值给xxx,如果不变,xxx使用缓存;

如果deps为空数组,则xxx是组件第一次渲染的cb,不用每次函数更新都重新开辟函数堆内存,将地址赋给xxx。

import { useCallback, useState } from 'react';

const Demo = () => {
  const [x, setX] = useState(0);
  /**
    const handle = ()=>{
       // 第一次:0x001 第二次:0x002
      // 直接写,Demo组件每次更新,handle地址都会变
    }
 */
  const handle = useCallback(() => {
    // 第一次:0x001 第二次:0x001
    // 使用useCallback,依赖项为空数组,
    // 这个小函数在Demo组件第一次渲染的时候创建并缓存,
    // 后续每次组件更新,函数地址都没变
  }, []);
  return (
    <div>
      <div>{x}</div>
      <button onClick={handle}>do other things</button>
    </div>
  );
};
export default Demo;

useCallback 用来缓存函数,但不需要每个函数都用它包起来,因为其内部计算缓存也会消耗性能。

useCallback 场景使用:父组件给子组件传递的 props 中有函数,用 useCallback 包起来。

import React, { useState } from 'react';

/*
const Child = () => {
// 如果不用memo包起来,无论子组件的props有没有变,每次父组件更新,Child都会执行
  console.log('==child');
  return <div>children</div>;
};
*/
const Child = React.memo((props: { handle: () => void }) => {
    // 如果子组件的props都没有变化,不会执行,
    // 相当于 class 组件继承了 React.PureComponent
  console.log('==child');
  return <div onClick={props.handle}>children</div>;
});
const Parent = () => {
  console.log('==parent');
  const [num, setNum] = useState(0);
  /*
  const handle = () => {
      // handle 如果不用useCallback包起来,则会触发Child更新
    console.log('===handle');
  }; 
  */
  const handle = useCallback(() => {
    console.log('===handle');
  }, []);
  return (
    <div>
      <div>{num}</div>
      <button
        onClick={() => {
          setNum(num + 1);
        }}
      >
        add
      </button>
      <Child handle={handle}/>
    </div>
  );
};
export default Parent;

useMemo 和 useCallback 对比

(1)useMemo 是缓存变量,useCallback 是缓存函数

const [x, setX] = useState(0);
let num = useMemo(()=>{
        return x/2;
    },[x]);
const handle = useCallback(()=>{
    // 这个函数的引用会被赋值handle
},[]);

(2)useMemo 其实也可以缓存函数,但是代码看起来很冗余

let handle = useMemo(()=>{
    // 返回一个函数
    return ()=>{
        // 这个函数的引用会被赋值handle
       }
    },[]);

6.useContext

# React 中的数据流

7.useReducer

useReducer 是对 useState 的升级处理,其原理和redux差不多,基于 reducer 和 dispatch 更新状态和视图,但是 useReducer 是管理单个组件的状态,如果在父组件显示 state 的值,子组件调用 dispatch 方法修改 state,那么组件将不会更新。

普通处理的时候,基本都用 useState

但是如果一个组件很复杂,需要用到大量的状态/大量修改状态逻辑,可以使用 useReducer 来统一管理状态

实例:

// reducer.ts
import { VoteStateType } from '../../store/type';

export const initialState = {
  supportNum: 10,
  oppositeNum: 5,
};
const reducer = (state: VoteStateType, action: { type: string }) => {
  state = { ...state };
  switch (action.type) {
    case 'support':
      state.supportNum++;
      break;
    case 'oppose':
      state.oppositeNum++;
      break;
  }
  return state;
};
export default reducer;
// vote 组件
import * as React from 'react';
import './index.less';
import VoteMain from './VoteMain';
import VoteFooter from './VoteFooter';
import reducer, { initialState } from './reducer';
import { Button } from 'antd';

const Vote = () => {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  return (
    <div className='vote-box'>
      <div className='header'>
        <h2 className='title'>学习Redux真好玩</h2>
        <span className='num'>{state.supportNum + state.oppositeNum}</span>
      </div>
      <div className='main'>
        <p>支持人数:{state.supportNum}人</p>
        <p>反对人数:{state.oppositeNum}人</p>
      </div>
      <div className='footer'>
        <Button
          type='primary'
          onClick={() => {
            dispatch({ type: 'support' });
          }}
        >
          支持
        </Button>
        <Button
          type='primary'
          danger
          onClick={() => {
            dispatch({ type: 'oppose' });
          }}
        >
          反对
        </Button>
      </div>
    </div>
  );
};

export default Vote;

自定义 Hook

作用:提取封装一些公共的处理逻辑。

1. useWindowSize: 监听window窗口变化,返回长宽

import { useLayoutEffect, useState } from 'react';

const useWindowSize = () => {
  const [size, setSize] = useState<number[]>([0, 0]);
  const updateSize = () => {
    setSize([window.innerWidth, window.innerHeight]);
  };
  useLayoutEffect(() => {
    window.addEventListener('resize', updateSize);
    updateSize();
    return () => {
      window.removeEventListener('resize', updateSize);
    };
  }, []);
  return size;
};
export default useWindowSize;

2. usePartialState:(相当于setState)

const usePartialState = (initialState: object) => {
  // 支持部分状态修改
  const [data, setData] = useState(initialState);
  const setPartial = (partialState: object) => {
    setData({ ...data, ...partialState });
  };
  return [data, setPartial];
};

3. useDidMount:(相当于componentDidMount生命周期函数)

const useDidMount = (title: string) => {
  // 第一次渲染完,修改标题
  useEffect(() => {
    document.title = title;
  }, []);
};

4. useEvent:

const useEvent = (fn) => {
  const fnRef = useRef(null);
  useLayoutEffect(() => {
    fnRef.current = fn;
  });
  return useCallback((...args) => {
    const fn = fnRef.current;
    return fn(...args);
  }, []);
};

5. useThrottle:节流

const useThrottle = (fn, wait, deps = []) => {
  const fnRef = useRef(fn);
  const timerRef = useRef(null);
  useEffect(() => {
    fnRef.current = fn;
  }, [fn]);
  return useCallback((...args) => {
    if (!timerRef.current) {
      timerRef.current = setTimeout(() => {
        delete timerRef.current;
        fnRef.current.apply(this, ...args);
      }, wait);
    }
  }, deps);
};

6. useDebounce:防抖

const useDebounce = (fn, wait, deps = []) => {
  const fnRef = useRef(fn);
  const timerRef = useRef(null);
  useEffect(() => {
    fnRef.current = fn;
  }, [fn]);
  return useCallback((...args) => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }
    timerRef.current = setTimeout(() => {
      fnRef.current.apply(this, ...args);
    }, wait);
  }, deps);
};