React常用Hook

237 阅读3分钟

React常用hook

1. useState

前端应用核心

只能写一些同步的计算逻辑

const [num, setNum] = useState()

2. useEffect

副作用

useEffect(() => {
	//操作
}, [...])
//[]为依赖, 当[]内的元素改变时, 就会执行useEffect, 若[] 为空, 则只在挂在与销毁时执行
//而且useEffect中可以执行一些异步任务,但需要使用async, await单独写一个函数

3. useLasyoutEffect

与useEffect类似

但区别在执行的时机

js的任务会在渲染的间隔中执行

异步执行useEffect中的副作用effect会在渲染结束后执行, 因此可能会出现页面的闪动, 因第一次渲染之后是旧值,effect更改了值,在渲染变为新值。

而useLayoutEffect中的effect的执行是同步的, 因此会在渲染前执行完成

但要注意effect可能会执行很久, 使用layout可能会阻塞渲染

4. useReducer

当使用useState之前想要执行一些固定的逻辑

interface Data {
    result: number;
}

interface Action {
    type: 'add' | 'minus',
    num: number
}
//要执行的逻辑
function reducer(state: Data, action: Action) {

    switch(action.type) {
        case 'add':
            return {
                result: state.result + action.num
            }
        case 'minus': 
            return {
                result: state.result - action.num
            }
    }
    return state;
}

function App() {
   //Reducer的参数<数据的类型, action的类型>
  const [res, dispatch] = useReducer<Reducer<Data, Action>>(reducer, { result: 0 });

  return (
    <div>
        <div onClick={() => dispatch({ type: 'add', num: 2 })}>加</div>
        <div onClick={() => dispatch({ type: 'minus', num: 1 })}>减</div>
        <div>{res.result}</div>
    </div>
  );
}

屏幕截图 2024-10-31 230212.png

屏幕截图 2024-10-31 230233.png

可以传入第二个参数

const [res, dispatch] = useReducer<Reducer<Data, Action>,  string >(reducer, 'zero', (param) => {
    return {
        result: param === 'zero' ? 0 : 1
    }
});

5. immer

操作对象结构比较复杂的时候使用

interface Data {
    a: {
        c: {
            e: number,
            f: number
        },
        d: number
    },
    b: number
}

此时我们想要修改某个值

function reducer(state: Data, action: Action) {

    switch(action.type) {
        case 'add':
            return {
                ...state,
                a: {
                    ...state.a,
                    c: {
                        ...state.a.c,
                        e: state.a.c.e + action.num,
                    },
                },
            }
    }
    return state;
}
//我们需要再把整个修改后的对象返回, 因为react的State不可变性

5.1 如何使用immer

//安装
npm install --save immer

//使用
return produce(state, (state) => {
    state.a.c.e += 100
})
//注意需要返回对象

6. useRef

保存 dom 引用

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

    useEffect(() => {
        inputRef.current?.focus();
    });

    return (
        <div>
            <input ref={inputRef}></input>
        </div>
    );
}

useRef 一般是用来存一些不是用于渲染的内容的。

7. forwardRef

把 ref 从子组件传递到父组件

const Guang: React.ForwardRefRenderFunction<HTMLInputElement> = (props, ref) => {
  return <div>
    <input ref={ref}></input>
  </div>
}

const WrapedGuang = React.forwardRef(Guang);

function App() {
  const ref = useRef<HTMLInputElement>(null);
 
  useEffect(()=> {
    console.log('ref', ref.current)
    ref.current?.focus()
  }, []);

  return (
    <div className="App">
      <WrapedGuang ref={ref}/>
    </div>
  );
}

export default App;

当想要暴露一些自定义内容时, 使用useImperativeHandle

const Guang: React.ForwardRefRenderFunction<RefProps> = (props, ref) => {
  const inputRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(ref, () => {
    return {
      aaa() {
        inputRef.current?.focus();
      }
    }
  }, [inputRef]);

  return <div>
    <input ref={inputRef}></input>
  </div>
}

const WrapedGuang = React.forwardRef(Guang);

function App() {
  const ref = useRef<RefProps>(null);
 
  useEffect(()=> {
    console.log('ref', ref.current)
    ref.current?.aaa();
  }, []);

  return (
    <div className="App">
      <WrapedGuang ref={ref}/>
    </div>
  );
}

8. useContext

跨任意层组件传递数据

const countContext = createContext(111);

function Aaa() {
  return <div>
      //这里使用provider
      <countContext.Provider value={222}>
        <Bbb></Bbb>
      </countContext.Provider>
  </div>
} 

function Bbb() {
  return <div><Ccc></Ccc></div>
}

function Ccc() {
    //这里获取
  const count = useContext(countContext);
  return <h2>context 的值为:{count}</h2>
}

用 createContext 创建 context 对象,用 Provider 修改其中的值, function 组件使用 useContext 的 hook 来取值,class 组件使用 Consumer 来取值。

9. memo

只有当props变化时才会重新渲染被包裹的组件

function Aaa() {
    
    const [count, setCount] = useState(2);

    useEffect(() => {
        setTimeout(()=> {
            setCount(Math.random());
        }, 2000)
    },[]);

    return <div>
        <MemoBbb count={2}></MemoBbb>
    </div>
} 

interface BbbProps {
    count: number;
}

function Bbb(props: BbbProps) {
    console.log('bbb render');

    return <h2>{props.count}</h2>
}

const MemoBbb = memo(Bbb);

9.1 useMemo与useCallback

防止props不必要的更新

const bbbCallback = useCallback(function () {
    // xxx
}, []);[]不变时,始终返回同一个function

useMemo保存值

const count2 = useMemo(() => {
    return count * 10;
}, [count]);

如果子组件用了 memo,那给它传递的对象、函数类的 props 就需要用 useMemo、useCallback 包裹,否则,每次 props 都会变,memo 就没用了。

反之,如果 props 使用 useMemo、useCallback,但是子组件没有被 memo 包裹,那也没意义,因为不管 props 变没变都会重新渲染,只是做了无用功。

也可以用 useMemo 来缓存计算量比较大的。