react hook 文档总结

本文是官方文档的总结,主要提取一些重要的概念信息,相比于官方文档,更为简洁,方便记忆。适用于面试前复习,和快速查找相关知识点。刚入门的同学还是从官方文档入手。

Hook 概念:

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 。

Hook 能否覆盖 class 的所有使用场景?

我们给 Hook 设定的目标是尽早覆盖 class 的所有使用场景。目前暂时还没有对应不常用的 getSnapshotBeforeUpdategetDerivedStateFromError 和 componentDidCatch 生命周期的

hook优势:

1、组件是DOM结构复用,hook是为了解决逻辑复用,相比于HOC和render props,避免了嵌套地狱。

2、更好地组织相关代码

3、避免了class 不能很好的压缩,并且会使热重载出现不稳定的情况,以及需要理解this等复杂概念的情况。

Hook使用规则

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。这是由于hook是通过调用顺序来确保每次渲染找到正确的hook的。
  • 只能在React 的函数组件或自定义的 Hook中调用 Hook

hook api  Hook API 索引 – React

各中内置hook的用法和注意点,看上面hook api即可。下面内容只是为了方便记忆。

内置hook

useState

用于定义变量和相关的更新变量的函数

 1:  import React, { useState } from 'react';
 2:
 3:  function Example() {
 4:    const [count, setCount] = useState(初始值);
 5:
 6:    return (
 7:      <div>
 8:        <p>You clicked {count} times</p>
 9:        <button onClick={() => setCount(count + 1)}>
10:         Click me
11:        </button>
12:      </div>
13:    );
14:  }

React 会在重复渲染时保留count这个 state

我应该使用单个还是多个 state 变量?

1、将state按照相关性进行划分,这样有利于独立出自定义hook

function Box() {
  // 将state按照相关性进行划分
  const [position, setPosition] = useState({ left: 0, top: 0 });
  const [size, setSize] = useState({ width: 100, height: 100 });

  useEffect(() => {
    function handleWindowMouseMove(e) {
      setPosition({ left: e.pageX, top: e.pageY });
    }
  )
}

注意: 多次调用相同的hook返回的是不同的state是独立的。

2、如果 state 很复杂, 用 reducer 来管理它,或使用自定义 Hook。

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。下面的计数器组件示例展示了 setState 的两种用法:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

setState(prevState => {
  // 也可以使用 Object.assign
  return {...prevState, ...updatedValues};
});

useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象。

惰性初始 state

initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

跳过 state 更新

调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较算法 来比较 state。)

useEffect

可以让你在函数组件中执行副作用操作

useEffect(() => { 
    // 相当于 componentDidMount 和 componentDidUpdate: 
    
    // 相当于componentWillUnmount 
    return () => { 
        // 清除函数 }; 
    }
});

hook都可以在函数组件中重复调用,区分成多个,把相关的代码写在一起,使代码可维护性更好。

function ExampleWithManyStates() { 
    const [age, setAge] = useState(初始值); 
    const [name, setName] = useState(初始值); 
    
    useEffect(()=>{})) 
    useEffect(()=>{})) 
}

如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除,这是为了避免了在 class 组件中因为没有处理更新逻辑而导致常见的 bug。

这有时会导致性能问题,采用传递第二个参数,判断是否需要重新执行

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值。

大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用.

我可以只在更新时运行 effect 吗?

这是个比较罕见的使用场景。如果你需要的话,你可以 使用一个可变的 ref 手动存储一个布尔值来表示是首次渲染还是后续渲染,然后在你的 effect 中检查这个标识。(如果你发现自己经常在这么做,你可以为之创建一个自定义 Hook。)

在依赖列表中省略函数是否安全?

一般来说,不安全。

function Example({ someProp }) {
  function doSomething() {
    console.log(someProp);  }

  useEffect(() => {
    doSomething();
  }, []); // 🔴 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)}

要记住 effect 外部的函数使用了哪些 props 和 state 很难。这也是为什么 通常你会想要在 effect 内部 去声明它所需要的函数。 这样就能容易的看出那个 effect 依赖了组件作用域中的哪些值:

function Example({ someProp }) {
  useEffect(() => {
    function doSomething() {
      console.log(someProp);    }

    doSomething();
  }, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)}

如果你指定了一个 依赖列表 作为 useEffectuseLayoutEffectuseMemouseCallback 或 useImperativeHandle 的最后一个参数,它必须包含回调中的所有值,并参与 React 数据流。这就包括 props、state,以及任何由它们衍生而来的东西。

只有 当函数(以及它所调用的函数)不引用 props、state 以及由它们衍生而来的值时,你才能放心地把它们从依赖列表中省略。

如果出于某些原因你 无法 把一个函数移动到 effect 内部,还有一些其他办法:

  • 你可以尝试把那个函数移动到你的组件之外。那样一来,这个函数就肯定不会依赖任何 props 或 state,并且也不用出现在依赖列表中了。
  • 如果你所调用的方法是一个纯计算,并且可以在渲染时调用,你可以 转而在 effect 之外调用它, 并让 effect 依赖于它的返回值。
  • 万不得已的情况下,你可以 把函数加入 effect 的依赖但 把它的定义包裹 进 useCallback Hook。这就确保了它不随渲染而改变,除非 它自身 的依赖发生了改变:

useContext

用于跨组件传递数据

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

// 1、创建context
const ThemeContext = React.createContext(themes.light);

function App() {
  // 2、Provider 提供数据
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // 3、useContext获取数据
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

useReducer

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

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。 一般用于state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等

// 初始化state
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>
    </>
  );
}

惰性初始化

你可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallback(fn, deps) 相当于 useMemo(() => fn, deps) 见下面useMemo

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

返回一个 memoized 值。

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算

传入 useMemo 的函数会在渲染期间执行,不能在里面执行副作用操作,那是useEffect的适用范畴。

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。

useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

一个常见的用例便是命令式地访问子组件:

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

useRef 会在每次渲染时返回同一个 ref 对象,类似于class组件的this,可用于保存变量

function Timer() {
  const intervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      // ...
    });
    intervalRef.current = id;
    return () => {
      clearInterval(intervalRef.current);
    };
  });

  // ...
}

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

我该如何测量 DOM 节点?

获取 DOM 节点的位置或是大小的基本方式是使用 callback ref。每当 ref 被附加到一个另一个节点,React 就会调用 callback。这里有一个 小 demo:

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {    if (node !== null) {      setHeight(node.getBoundingClientRect().height);    }  }, []);
  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

在本例中,渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()

注:这其实是一种子组件对外暴露方法给父组件的方式。

看此处例子:https://www.csdn.net/tags/NtTaUgwsMDg5OTQtYmxvZwO0O0OO0O0O.html

自定义hook

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

hook本质上是函数,他们之间的通讯通过参数进行。

只能在客户端渲染的组件,可以采用下面判断方式

showChild && <Child /> 

useEffect(() => { setShowChild(true); }, [])

React Redux 从 v7.1.0 开始支持 Hook API 并暴露了 useDispatch 和 useSelector 等 hook。

React Router 从 v5.1 开始支持 hook

如何获取上一轮的 props 或 state?

目前,你可以 通过 ref 来手动实现:

function Counter() {
  const [count, setCount] = useState(0);

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;  });
  const prevCount = prevCountRef.current;
  return <h1>Now: {count}, before: {prevCount}</h1>;
}

这或许有一点错综复杂,但你可以把它抽取成一个自定义 Hook:

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);  return <h1>Now: {count}, before: {prevCount}</h1>;
}

function usePrevious(value) {  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

注意看这是如何作用于 props, state,或任何其他计算出来的值的。

function Counter() {
  const [count, setCount] = useState(0);

  const calculation = count + 100;
  const prevCalculation = usePrevious(calculation);  // ...

考虑到这是一个相对常见的使用场景,很可能在未来 React 会自带一个 usePrevious Hook。

为什么我会在我的函数中看到陈旧的 props 和 state ?

组件内部的任何函数,包括事件处理函数和 effect,都是从它被创建的那次渲染中被「看到」的。例如,考虑这样的代码:

function Example() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert('You clicked on: ' + count);
    }, 3000);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <button onClick={handleAlertClick}>
        Show alert
      </button>
    </div>
  );
}

如果你先点击「Show alert」然后增加计数器的计数,那这个 alert 会显示在你点击『Show alert』按钮时的 count 变量。这避免了那些因为假设 props 和 state 没有改变的代码引起问题。

如果你刻意地想要从某些异步回调中读取 最新的 state,你可以用 一个 ref 来保存它,修改它,并从中读取。

最后,你看到陈旧的 props 和 state 的另一个可能的原因,是你使用了「依赖数组」优化但没有正确地指定所有的依赖。举个例子,如果一个 effect 指定了 [] 作为第二个参数,但在内部读取了 someProp,它会一直「看到」 someProp 的初始值。解决办法是要么移除依赖数组,要么修正它。 这里介绍了 你该如何处理函数,而这里介绍了关于如何减少 effect 的运行而不必错误的跳过依赖的 一些常见策略

性能优化

我可以在更新时跳过 effect 吗?

可以的。参见 条件式的发起 effect。注意,忘记处理更新常会 导致 bug,这也正是我们没有默认使用条件式 effect 的原因。

如果我的 effect 的依赖频繁变化,我该怎么办?

有时候,你的 effect 可能会使用一些频繁变化的值。你可能会忽略依赖列表中 state,但这通常会引起 Bug:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // 这个 effect 依赖于 `count` state    }, 1000);
    return () => clearInterval(id);
  }, []); // 🔴 Bug: `count` 没有被指定为依赖
  return <h1>{count}</h1>;
}

传入空的依赖数组 [],意味着该 hook 只在组件挂载时运行一次,并非重新渲染时。但如此会有问题,在 setInterval 的回调中,count 的值不会发生变化。因为当 effect 执行时,我们会创建一个闭包,并将 count 的值被保存在该闭包当中,且初值为 0。每隔一秒,回调就会执行 setCount(0 + 1),因此,count 永远不会超过 1。

指定 [count] 作为依赖列表就能修复这个 Bug,但会导致每次改变发生时定时器都被重置。事实上,每个 setInterval 在被清除前(类似于 setTimeout)都会调用一次。但这并不是我们想要的。要解决这个问题,我们可以使用 setState 的函数式更新形式。它允许我们指定 state 该 如何 改变而不用引用 当前 state:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量    }, 1000);
    return () => clearInterval(id);
  }, []); // ✅ 我们的 effect 不使用组件作用域中的任何变量
  return <h1>{count}</h1>;
}

setCount 函数的身份是被确保稳定的,所以可以放心的省略掉)

此时,setInterval 的回调依旧每秒调用一次,但每次 setCount 内部的回调取到的 count 是最新值(在回调中变量命名为 c)。

在一些更加复杂的场景中(比如一个 state 依赖于另一个 state),尝试用 useReducer Hook 把 state 更新逻辑移到 effect 之外。这篇文章 提供了一个你该如何做到这一点的案例。 useReducer 的 dispatch 的身份永远是稳定的 —— 即使 reducer 函数是定义在组件内部并且依赖 props。

万不得已的情况下,如果你想要类似 class 中的 this 的功能,你可以 使用一个 ref 来保存一个可变的变量。然后你就可以对它进行读写了。举个例子:

function Example(props) {
  // 把最新的 props 保存在一个 ref 中
  const latestProps = useRef(props);
  useEffect(() => {
    latestProps.current = props;
  });

  useEffect(() => {
    function tick() {
      // 在任何时候读取最新的 props
      console.log(latestProps.current);
    }

    const id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []); // 这个 effect 从不会重新执行
}

仅当你实在找不到更好办法的时候才这么做,因为依赖于变更会使得组件更难以预测。

我该如何实现 shouldComponentUpdate?

你可以用 React.memo 包裹一个组件来对它的 props 进行浅比较:

const Button = React.memo((props) => {
  // 你的组件
});

这不是一个 Hook 因为它的写法和 Hook 不同。React.memo 等效于 PureComponent,但它只比较 props。(你也可以通过第二个参数指定一个自定义的比较函数来比较新旧 props。如果函数返回 true,就会跳过更新。)

React.memo 不比较 state,因为没有单一的 state 对象可供比较。但你也可以让子节点变为纯组件,或者 用 useMemo 优化每一个具体的子节点

如何记忆计算结果?

useMemo Hook 允许你通过「记住」上一次计算结果的方式在多次渲染的之间缓存计算结果:

useMemo 也允许你跳过一次子节点的昂贵的重新渲染:

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

注意这种方式在循环中是无效的,因为 Hook 调用 不能 被放在循环中。但你可以为列表项抽取一个单独的组件,并在其中调用 useMemo

如何惰性创建昂贵的对象?

如果依赖数组的值相同,useMemo 允许你 记住一次昂贵的计算。但是,这仅作为一种提示,并不 保证 计算不会重新运行。但有时候需要确保一个对象仅被创建一次。

第一个常见的使用场景是当创建初始 state 很昂贵时:

function Table(props) {
  // ⚠️ createRows() 每次渲染都会被调用
  const [rows, setRows] = useState(createRows(props.count));
  // ...
}

为避免重新创建被忽略的初始 state,我们可以传一个 函数 给 useState

function Table(props) {
  // ✅ createRows() 只会被调用一次
  const [rows, setRows] = useState(() => createRows(props.count));
  // ...
}

React 只会在首次渲染时调用这个函数。参见 useState API 参考

你或许也会偶尔想要避免重新创建 useRef() 的初始值。举个例子,或许你想确保某些命令式的 class 实例只被创建一次:

function Image(props) {
  // ⚠️ IntersectionObserver 在每次渲染都会被创建
  const ref = useRef(new IntersectionObserver(onIntersect));
  // ...
}

useRef 不会 像 useState 那样接受一个特殊的函数重载。相反,你可以编写你自己的函数来创建并将其设为惰性的:

function Image(props) {
  const ref = useRef(null);

  // ✅ IntersectionObserver 只会被惰性创建一次
  function getObserver() {
    if (ref.current === null) {
      ref.current = new IntersectionObserver(onIntersect);
    }
    return ref.current;
  }

  // 当你需要时,调用 getObserver()
  // ...
}

这避免了我们在一个对象被首次真正需要之前就创建它。

Hook 会因为在渲染时创建函数而变慢吗?

不会。在现代浏览器中,闭包和类的原始性能只有在极端场景下才会有明显的差别。

除此之外,可以认为 Hook 的设计在某些方面更加高效:

  • Hook 避免了 class 需要的额外开支,像是创建类实例和在构造函数中绑定事件处理器的成本。
  • 符合语言习惯的代码在使用 Hook 时不需要很深的组件树嵌套。这个现象在使用高阶组件、render props、和 context 的代码库中非常普遍。组件树小了,React 的工作量也随之减少。

传统上认为,在 React 中使用内联函数对性能的影响,与每次渲染都传递新的回调会如何破坏子组件的 shouldComponentUpdate 优化有关。Hook 从三个方面解决了这个问题。

  • useCallback Hook 允许你在重新渲染之间保持对相同的回调引用以使得 shouldComponentUpdate 继续工作:

    // 除非 `a` 或 `b` 改变,否则不会变
    const memoizedCallback = useCallback(() => {  doSomething(a, b);
    }, [a, b]);
  • useMemo Hook 使得控制具体子节点何时更新变得更容易,减少了对纯组件的需要。
  • 最后,useReducer Hook 减少了对深层传递回调的依赖,正如下面解释的那样。

如何避免向下传递回调?

我们已经发现大部分人并不喜欢在组件树的每一层手动传递回调。尽管这种写法更明确,但这给人感觉像错综复杂的管道工程一样麻烦。

在大型的组件树中,我们推荐的替代方案是通过 context 用 useReducer 往下传一个 dispatch 函数:

const TodosDispatch = React.createContext(null);

function TodosApp() {
  // 提示:`dispatch` 不会在重新渲染之间变化  const [todos, dispatch] = useReducer(todosReducer);
  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}

TodosApp 内部组件树里的任何子节点都可以使用 dispatch 函数来向上传递 actions 到 TodosApp

function DeepChild(props) {
  // 如果我们想要执行一个 action,我们可以从 context 中获取 dispatch。  const dispatch = useContext(TodosDispatch);
  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}

useDeferredValue

const deferredValue = useDeferredValue(value);

useDeferredValue接受一个值并返回该值的新副本,该副本将延迟到更紧急的更新完成后更新。如果当前渲染是紧急的更新(如用户输入),React将返回以前的值,然后在紧急渲染完成后渲染新值。

这个hook类似于节流和防抖,用于延迟更新. 使用useDeferredValue的好处是,其他工作完成后,React将立即处理更新(而不是等待任意时间), 和 startTransition 一样, 延迟值可以暂停触发 fallback.

延迟子组件

useDeferredValue 只能验证值. 如果你想在一个紧急更新中延迟子组件, 你需要在组件中使用 React.memo 或 React.useMemo:

function Typeahead() {
  const query = useSearchQuery('');
  const deferredQuery = useDeferredValue(query);

  // Memoizing tells React to only re-render when deferredQuery changes,
  // not when query changes.
  const suggestions = useMemo(() =>
    <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );

  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading results...">
        {suggestions}
      </Suspense>
    </>
  );
}

记忆子组件告诉react仅需在 deferredQuery 改变的时候重新渲染,而不是 query 改变的时候. 这个模式不是 useDeferredValue 特有的, 你在使用节流防抖之类的钩子时,也有相同的模式.

另附函数组件类型校验、默认值参考:

React:Props类型校验&默认值_前端卡卡西呀的博客-CSDN博客_react的props设置默认值

hook 获取更新后的值,参考

自定义hooks实现在useState改变值之后立刻获取到最新的值

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值