🧐🧐十五个 React Hooks 简单总结

654 阅读5分钟

useState

useState主要用于给组件添加状态变量

基本使用

const [age, setAge] = useState(42);

直接更新

setAge(newState);

函数更新

避免闭包陷阱

setState(pre => pre + 1);

对象与数组的更新

对象和数组的更新需要创建新的引用

setForm({
  ...form,
  name: e.target.value // 更新这个属性
});

// 错误示例:
// form.name = e.target.value

useReducer

useReducer允许我们使用 action 和 reducer 的方式来组织复杂的状态逻辑,使其变得更加清晰和模块化,弥补了useState的局限性。

基础用法

useReducer接收三个参数

  • reducer 函数:指定如何更新状态的还原函数,它必须是纯函数,以 state 和 dispatch 为参数,并返回下一个状态。
  • 初始状态:初始状态的计算值。

useReducer返回两个参数

  • 当前的状态:当前状态。在第一次渲染时,它会被设置为init(initialArg)或 initialArg(如果没有 init 的情况下)。
  • dispatch:调度函数,用于调用 reducer 函数,以更新状态并触发重新渲染。
const [state, dispatch] = useReducer(reducer, initialArg, init?)
const initialState = { count: 0 };

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

dispatch状态更新是异步的

useContext

通过context实现跨层级共享状态

简单使用

使用React.createContext创建一个context对象:

const MyContext = React.createContext(defaultValue);

defaultValue是当组件不在任何 Context Provider 内部时的默认值

import { createContext, useContext } from 'react';
const countContext = createContext(111);
function Aaa() {
  return <div>
      <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>
}
export default Aaa;

性能问题

Providervalue属性值发生变化时,所有使用了useContext的组件都将重新渲染。

拆分 context

useMemouseCallback优化value

import { useCallback, useMemo } from 'react';
function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);
  const login = useCallback((response) => {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }, []);
  const contextValue = useMemo(() => ({
    currentUser,
    login
  }), [currentUser, login]);
  return (
    <AuthContext.Provider value={contextValue}>
      <Page />
    </AuthContext.Provider>
  );
}

useRef

  1. 持久性useRef的返回对象在组件的整个生命周期中都是持久的,而不是每次渲染都重新创建。
  2. 不会触发渲染:当useState中的状态改变时,组件会重新渲染。而当useRef.current属性改变时,组件不会重新渲染。

基本使用

// 定义
const inputRef = useRef(null);

// 使用
console.log(inputRef.current)

访问DOM

function TextInput() {
  const inputRef = useRef(null);
  function focusInput() {
    inputRef.current.focus();
  }
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus the input</button>
    </div>
  );
}

保存状态但不触发渲染

function Timer() {
  const count = useRef(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      count.current += 1;
      console.log(`Elapsed time: ${count.current} seconds`);
    }, 1000);

    return () => clearInterval(intervalId);
  }, []);

  return <div>Check the console to see the elapsed time!</div>;
}

保存上一次的 props 或 state

function DisplayValue({ value }) {
  const [prevValue, setPrevValue] = useState(null); // 初始时,没有前一个值
  const previousValue = useRef(value);

  useEffect(() => {
    setPrevValue(currentRef.current);
    previousValue.current = value;
  }, [value]);

  return (
    <div>
      Current Value: {value} <br />
      Previous Value: {prevValue}
    </div>
  );
}

useEffect

useEffect出现的目的为处理React中的副作用

模拟生命周期

  • componentDidMount()

在组件被挂载到 DOM 后调用,通常用于发送网络请求、订阅事件等初始化操作。 使用useEffect: 依赖项为空数组,初次渲染执行

useEffect(() => {
  console.log('Component mounted');
  // 执行数据获取操作
  fetchData();
}, []);
  • componentDidUpdate

在组件更新后调用

useEffect(() => {
  console.log('Component updated');
  // 执行其他副作用操作
  document.title = `You clicked ${count} times`;
}, [count]);

使用useEffect: 依赖项为 state,state 变化时执行

  • componentWillUnmount 在组件即将从 DOM 中移除时调用,用于清理工作,比如取消订阅、清除定时器等。
useEffect(() => {
  return () => {
    console.log('Component unmounted');
    // 执行清理操作
    clearSubscription();
  };
}, []);

使用useEffect: 在组件卸载时执行 return,清理副作用

执行时机

effect 函数会在操作 dom 之后异步执行

useLayoutEffect

页面渲染前操作effect

useMemo

通过缓存计算结果,避免在组件渲染时进行不必要的重复计算

const allUsers = useMemo(() => {
        let list = [];
        for (let i = 1; i <= 500; i++) {
            list.push({ id: i, name: `User${i}` });
        }
        return list;
    }, []);

useuseCallback

避免props不必要的变化

const bbbCallback = useCallback(function () {
    // xxx
}, []);

useTransition

useTransition 是 React 18 中引入的一个 Hook,允许将某些更新标记为“过渡”状态,这样 React 可以优先处理更重要的更新

const [isPending, startTransition] = useTransition()
  • isPending:是一个布尔值,当过渡状态仍在进行中时,其值为true;否则为false
  • startTransition:是一个函数,当你希望启动一个新的过渡状态时调用它。
import React, { useState, useEffect, useTransition } from 'react';
const App: React.FC = () => {
  const [list, setList] = useState<any[]>([]);
  const [isPending, startTransition] = useTransition();
  useEffect(() => {
    // 使用了并发特性,开启并发更新
    startTransition(() => {
      setList(new Array(10000).fill(null));
    });
  }, []);
  return (
    <>
      {list.map((_, i) => (
        <div key={i}>{i}</div>
      ))}
    </>
  );
};
export default App;

useDeferredValue

延迟某个值的更新,使高优先级的任务可以先行完成。

const deferredValue = useDeferredValue(someValue);

其中someValue是你想要延迟的值,它可以是任何类型。

deferredValue的渲染有两种情况:

  1. 在初始渲染时deferredValue的值将与someValue的值相同。
  2. 在UI更新期间,因为deferredValue的优先级较低,即使并发模式下deferredValue已在后台更新,React也会先使用旧值渲染,当其它高优先级的状态更新完成,才会把deferredValue新值渲染出来。

import { useState, useDeferredValue, memo } from 'react';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      {/* 输入框的值直接与 text 绑定,所以输入框会实时显示用户的输入 */}
      <input value={text} onChange={e => setText(e.target.value)} />
      {/* SlowList 组件接受 deferredText 作为属性,渲染优先级会被降低 */}
      {/* 在 UI 真正更新前,如果 deferredText 被更新多次,也只会保留最后一次的结果 */}
      <SlowList text={deferredText} />
    </>
  );
}

useInsertionEffect

为CSS-in-JS赋能

useEffect注入 :重复布局

useLayoutEffect注入:阻塞渲染

useInsertionEffect优点:

  1. 动态性:允许在运行时动态地注入样式,这使得基于组件的状态、道具或上下文的样式变化变得容易。
  2. 及时注入:保证了在任何布局效果触发之前插入样式,减少了样式的重复计算和布局抖动。
import { useInsertionEffect } from 'react';
function useDynamicStyle(styleObj) {
  const cssString = convertStyleObjToCSS(styleObj); // 将样式对象转换为 CSS 字符串的辅助函数
  useInsertionEffect(() => {
    const styleElement = document.createElement('style');
    styleElement.innerHTML = cssString;
    document.head.appendChild(styleElement);
    return () => {
      document.head.removeChild(styleElement);
    };
  }, [cssString]);
}

useInsertionEffect里面,我们可以动态注入<style>

forwardRef

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

const Guang= (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;

useImperativeHanlde

自定义暴露给父组件的实例方法或属性

useSyncExternalStore

useSyncExternalStore 解决的是并发模式下数据管理的问题。

useId

const id = useId()

useDebugValue

useDebugValue 是一个专为开发者调试自定义 hook 而设计的 React hook。

use

简化Promise用法

简化 Suspense 的使用

  const [post, setPost] = useState(null);
  useEffect(() => {
    fetchPost(postId).then(data => setPost(data));
  }, [postId]);
  const postPromise = fetchPost(postId);
  const post = use(postPromise);

image.png