React - 内置常用hooks

154 阅读4分钟

1. useState

useState 函数接收一个参数作为状态的默认值,函数返回结果为一个数组,数组第一项为状态值,第二项是一个可以变更状态值的函数。

import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const onClickButton = () => {
    // 第一种方式
    setCount(count + 1);
    console.log(count); // 值得注意的是,set更新函数是异步的,打印的count仍然更新前的值
    // 第二种方式
    setCount((prevState) => prevState + 1);
  };
  return (
    <>
      <div>{count}</div>
      <button onClick={onClickButton}>按钮</button>
    </>
  );
}

2. useRef

使用 .current 获取DOM节点。需要注意的是,自定义组件要使用 forwardRef 包装,才能使用 useRef

import { useRef } from "react"

export default function App() {
  const inputRef = useRef<HTMLInputElement>(null);
  return (
    <>
      <input ref={inputRef}></input>
      <button onClick={() => {
        // 获取input输入框并聚焦
        inputRef.current?.focus()
      }}>按钮</button>
    </>
  )
}

3. useImperativeHandle

useImperativeHandle 搭配 forwardRef 在自定义组件时,暴露组件的方法供外部调用。

import { useRef, useImperativeHandle, forwardRef } from "react";

const Child = forwardRef((props, ref) => {
  const inputRef = useRef<any>(null);
  // 暴露方法
  useImperativeHandle(ref, () => {
    return {
      // 使输入框聚焦
      focusInput: () => inputRef.current?.focus(),
      // 返回值
      getValue: () => inputRef.current?.value,
    };
  });
  return <input ref={inputRef} />;
});

export default function App() {
  const childRef = useRef<any>(null);
  return (
    <>
      <Child ref={childRef} />
      <button onClick={() => {
          childRef.current?.focusInput();
          console.log("值等于", childRef.current?.getValue());
      }}>子组件聚焦</button>
    </>
  );
}

4. useEffect

useEffect 弥补函数式组件没有生命周期的缺陷。接收2个参数:

第1个参数是个函数,函数里支持返回一个函数,它会在销毁时执行,类似于 componentWillUnmount

第2个参数是个依赖数组,依赖项需要是能够触发组件更新的变量,比如 state 或者 props:

  1. 数组有值:当数组里的依赖项发生改变时,触发第一个函数参数。
  2. 传空数组:只会在组件 首次挂载和卸载 时,触发第一个函数参数,相当于 componentDidMount
  3. 参数不传:组件 挂载或更新 时,触发第一个函数参数。相当于componentDidMountcomponentDidUpdate
  useEffect(() => {
    console.log("初始化定时器");
    const timer = setInterval(() => console.log("定时器"), 1000);
    return () => {
      // 类似于componentWillUnmount
      console.log("清除定时器");
      clearInterval(timer);
    };
  }, []);

日常使用中建议传入第二个参数,明确副作用执行的依赖项,以便减少不必要的性能消耗。

5. useLayoutEffect

同样接收2个参数:第1个参数是函数,第2个参数是依赖数组。相比 useEffect 有两个不同之处:

  1. useLayoutEffect同步执行useEffect异步执行
  2. useLayoutEffect 执行时机是 在DOM更新之后,浏览器绘制之前, useEffect 是在 绘制之后

useInsertionEffect 的执行时机是在DOM更新之前

  useLayoutEffect(() => {
    console.log(divRef.current?.getBoundingClientRect());
  }, []);

6. useCallback

同样接收2个参数:第1个参数是函数,第2个参数是依赖数组。返回值是一个函数,执行该函数可以得到回调函数里返回的值。如果依赖项没有发生改变,回调函数会被缓存。

import { useCallback, useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const getValue = useCallback(() => {
    return count + 1;
  }, [count]); //依赖项为[]时,弹窗会一直显示值:1
  return (
    <>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>修改</button>
      <button onClick={() => alert(getValue())}>获取值</button>
    </>
  );
}

useCallBack 的另一个主要作用是配合 memo 缓存子组件,当传递给子组件的props有函数的时候,需要以下2处包装:

import { useState, memo, useCallback } from "react";

export default function RefundOrder() {
  console.log("渲染父组件");
  const [count, setCount] = useState(0);
  // 1.useCallback包装函数
  const getMessage = useCallback((msg: string) => console.log(msg), []);
  return (
    <>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>按钮</button>
      <Child getMessage={getMessage} />
    </>
  );
}

// 2.子组件使用memo包装
const Child = memo((props: { getMessage: (msg: string) => void }) => {
  console.log("渲染子组件");
  return <button onClick={() => props.getMessage("hello")}>打印msg</button>;
});

7. useMemo

同样接收2个参数:第1个参数是函数,第2个参数是依赖数组。

  • useMemouseCallback 类似,不同之处在于前者缓存的是函数的运行结果,类似vue的computedValue。
  • props 是对象、数组的情况,这时候可以使用 useMemo 配合 memo 缓存组件。
import { useState, memo, useMemo } from "react";

const List = memo(function (props: { list: Record<string, any>[] }) {
  console.log("子组件重新渲染");
  return (
    <>
      {props.list.map((item) => (
        <div key={item.id}>{item.content}</div>
      ))}
    </>
  );
});

export default function App() {
  const [title, setTitle] = useState("父组件");
  const [todoList, setTodoList] = useState([
    { id: 1, content: "吃饭", isDone: true },
    { id: 2, content: "睡觉", isDone: false },
    { id: 3, content: "洗澡", isDone: true },
    { id: 4, content: "刷牙", isDone: false },
  ]);

  const changeTitle = () => {
    setTitle("父组件" + Math.random().toFixed(2));
  };

  // useMemo 配合 memo, 实现组件缓存
  const list = useMemo(
    () => todoList.filter((item) => item.isDone),
    [todoList]
  );

  return (
    <>
      <h1 onClick={changeTitle}>{title}</h1>
      <List list={list} />
    </>
  );
}

8. useContext

useContext 用于爷孙组件传值,会从使用它的组件向上查找最近的Provider。

/* 父级组件App.tsx */
import { createContext } from "react";
import Grandson from "./Grandson";
// 创建上下文
export const AppContext = createContext<any>(null);

export default function App() {
  const data = { name: "张三", age: 32 };
  return (
    <AppContext.Provider value={data}>
      <Grandson />
    </AppContext.Provider>
  );
}

/* 孙级组件Grandson.tsx */
import { AppContext } from "./App";
import { useContext } from "react";

export default function Grandson() {
  const data = useContext(AppContext);
  console.log("data", data);
  return <div>{data.name}</div>;
}

9. useReducer

useReducer 提供了类似redux的功能,它可以像useState一样让我们轻松的管理状态。

import { useReducer } from "react";

export default function App() {
  const reducer = (state: string, action: Record<string, any>) => {
    switch (action.type) {
      case "add":
        return state + action.payload;
      case "clear":
        return "";
      default:
        return state;
    }
  };
  const [state, dispatch] = useReducer(
    reducer,
    "李四", //默认值
    (name) => "hello, " + name //覆盖默认值
  );

  return (
    <>
      <div>{state}</div>
      <button onClick={() => dispatch({ type: "add", payload: "a" })}>
        加
      </button>
      <button onClick={() => dispatch({ type: "clear" })}>清除</button>
    </>
  );
}

感谢

感谢 React内置hooks一览 这篇文章的作者,这里的大部分内容都是从那篇文章搬过来的。

但是写这篇文章真心不是抄袭,目的仅仅是为了加深印象做个笔记,把所有代码重新手撸了一遍。