发布时间

学习React的几个hook

作者
  • avatar
    作者名字
    Kavin Wang
    Twitter

React的几个hook,如useContext和useEffect等掌握不好,特别记录在此

useContext

useContext是一个提供了在组件之间共享数据的功能Hook。使用时如以下步骤:

1、需要先生成一个context备用,如:

const userContext = React.createContext();
export default userContext;

2、在应用的某个高层组件中,使用UserContext.Provider来包裹需要访问这个Context的子组件,并提供初始的Context值, 如:

// App.js
import React, { useState } from 'react';
import UserContext from './UserContext';
import Navbar from './Navbar';
import ContentArea from './ContentArea';

function App() {
 const [user, setUser] = useState({ loggedIn: false, name: 'Guest' });

 return (
   <UserContext.Provider value={{ user, setUser }}>
     <div className="app">
       <Navbar />
       <ContentArea />
     </div>
   </UserContext.Provider>
 );
}

export default App;

3、然后就可以在其它任何需要的地方使用这个context了,如:

// Navbar.js
import React, { useContext } from 'react';
import UserContext from './UserContext';

function Navbar() {
 const { user } = useContext(UserContext);

 return (
   <nav>
     {user.loggedIn ? `欢迎,${user.name}!` : '请登录'}
   </nav>
 );
}

export default Navbar;

useEffect

useEffect就是为了在某些特定时候,执行某些操作的hook。如在组件加载后进行初始化操作,或者在组件卸载后做一些清理工作。
useEffet的代码需要返回一个清理函数,当然也可以不返回。
useEffect有两种形式,传入依赖和不传入依赖。

  • 如果不传入依赖,则此useEffect代码会在组件挂接后执行,并在组件解挂时执行它在挂载时返回的清理函数;
  • 如果传入依赖,则在传入的依赖变化时执行,一般先执行上一次执行时返回的清理函数,然后再次执行useEffect代码,同时返回在下一次改变时需要调用的清理函数。 简而言之,useEffect 的清理函数会在下面两个时刻执行: 当依赖项改变,新 effect 执行前。就是说如果有变化,会先执行上一次调用的清理函数,再执行useEffect的内容 组件卸载时会执行前一次执行返回的清理函数

另外,需要记得,如果想在使用useEffect的过程中改变要呈现的数据,最好要使用useState来管理这个要呈现的数据,否则,可能并不能绑定输出的。

还有,useEffect要执行的内容,是个代码块,可以认为类似一个ESModule,返回的函数为清理函数,会在页面最后,或者数据更新时调用,完成清除收尾的工作。

useReducer

useReducer 是React中的一个Hook,它用于管理组件的复杂状态逻辑。与useState类似,它允许你在函数组件中添加状态,但是相比于useState,useReducer更适用于那些包含多个子状态或者状态更新逻辑较复杂的场景。它接受一个reducer函数和初始状态作为参数,并返回当前状态和一个用来更新状态的dispatch函数。

useReducer的工作原理

  • Reducer函数:
    这是一个纯函数,接收两个参数——当前状态和一个动作对象(通常称为action),并根据动作类型返回新的状态。这意味着它没有副作用,给定相同的状态和动作,它总是返回相同的结果。
  • Dispatch函数:
    这是用来触发状态变更的函数。当你调用dispatch函数并传入一个动作对象时,它会执行reducer函数,根据动作来计算新的状态值,并更新组件的状态。

如何使用useReducer

下面是一个简单的useReducer使用的示例:

mport React, { useReducer } from 'react';

// 定义reducer函数
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

// 初始状态
const initialState = { count: 0 };

function Counter() {
  // 使用useReducer Hook
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

export default Counter;

在这个例子中,我们定义了一个reducer函数来处理两种动作类型:increment和decrement,并使用useReducer来管理计数器的状态。组件内的按钮点击事件会触发dispatch函数,进而更新状态并重新渲染组件。

useReducer在处理涉及多个子状态或复杂的业务逻辑时特别有用,因为它可以让你的状态管理逻辑更加模块化和易于理解。此外,它还便于测试,因为你可以在测试中直接调用reducer函数来验证状态变化是否符合预期。

useMemo

useMemo是React中的另一个Hook,用于优化性能,避免在每次渲染时都进行复杂的计算。它让你能够 memoize(记忆化)一个值,只有当其依赖项改变时才会重新计算该值。这对于提升性能非常有帮助,尤其是在处理大数据处理、昂贵的计算或者避免不必要的DOM更新等场景下。

useMemo的工作原理

  • 记忆化:
    useMemo会记住一个函数的返回值,并在依赖项列表(作为第二个参数传递)改变时再次计算。如果依赖项没有变化,它将直接返回上一次计算的值,从而跳过不必要的计算。
  • 依赖项数组:
    类似于useEffect,你需要提供一个依赖项数组来告诉React哪些变量的变化会影响到你的计算结果。当这些依赖项中的任何一个发生变化时,useMemo内部的函数会被重新调用。

如何使用useMemo

下面是一个简单的使用useMemo的例子,假设我们有一个计算密集型的函数,我们希望只在输入值改变时才重新计算结果:

import React, { useMemo } from 'react';

function ComputeExpensiveValue({ value }) {
  // 使用useMemo来缓存计算结果
  const computedValue = useMemo(() => {
    console.log('Computing...');
    // 假设这是个复杂的计算过程
    for (let i = 0; i < 1000000000; i++) {} // 模拟耗时操作
    return value * value;
  }, [value]); // 依赖项数组,当value变化时,才会重新计算computedValue

  return <div>The squared value of {value} is {computedValue}</div>;
}

export default ComputeExpensiveValue;

在这个例子中,computedValue只有在value改变时才会重新计算。如果不使用useMemo,每次组件渲染时都会执行计算逻辑,即使value没有变化。通过useMemo,我们确保了不必要的计算被跳过,从而提高了应用程序的效率。

总之,useMemo是一个有用的工具,可以帮助你优化React应用的性能,减少不必要的计算,特别是在处理大量数据或复杂计算的场景下。正确使用它,可以使用户界面更加流畅。

useCallback

useCallback是React中的另一个性能优化Hook,它主要用于记忆化函数。与useMemo相似,useCallback也是为了避免在每次渲染时创建新的函数引用,但它的主要关注点在于函数本身,尤其是作为props传递给子组件的函数。当子组件因为这些函数引用的变化而触发不必要的渲染时,useCallback可以帮助优化这一情况。

useCallback的工作原理

  • 记忆化函数:
    useCallback会返回一个记忆化的函数引用,该函数仅在某个依赖项改变时才会重新创建。这确保了当函数作为prop传递时,不会导致不必要的子组件渲染。
  • 依赖项数组:
    同样,你需要提供一个依赖项数组来决定何时应该重新计算记忆化的函数。如果依赖项未变化,返回的函数引用就是相同的,从而避免了子组件因接收新函数引用而重新渲染。

如何使用useCallback

假设你有一个父组件,它传递一个函数给子组件作为prop,而这个函数的创建比较昂贵或者不希望它无意义地改变导致子组件渲染:

import React, { useCallback, useState } from 'react';

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

  // 使用useCallback记忆化increment函数
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count, setCount]); // 注意:这里将count和setCount作为依赖项,实际情况中应避免直接依赖状态值,以防止无限循环

  return <ChildComponent onClick={increment} />;
}

function ChildComponent({ onClick }) {
  // 子组件可能有它自己的状态和逻辑...
  return <button onClick={onClick}>Click me</button>;
}

在这个例子中,如果没有使用useCallback,每次ParentComponent渲染时都会创建一个新的increment函数实例,即使count状态没有改变,这也可能会导致ChildComponent不必要的重新渲染。通过使用useCallback并恰当地设置依赖项数组,我们可以确保只有当count或setCount(理论上不应直接依赖状态值,这里仅为示例说明)改变时,才会生成新的increment函数,从而优化了子组件的渲染性能。

总结来说,useCallback是用来优化那些作为props传递给子组件的函数,确保它们在依赖项不变时维持同样的引用,减少不必要的子组件重渲染。

useRef

就是如果你想在js代码中直接访问dom元素,就可以给dom元素指定一个ref去使用。比如在加载完成后,对某个dom元素指定获得焦点,设置初始值(useState也可以)等。 记着,用useRef改变的内容的值,不会引起页面刷新。

参考下面的例子:

mport React, { useRef, useEffect } from 'react';

function TextInputWithFocusButton() {
  // 创建一个ref来存储文本输入框的DOM元素
  const inputEl = useRef(null);
  // 创建一个ref来保存一个可变的计数器,这个计数器的变化不会引起重新渲染
  const count = useRef(0);

  useEffect(() => {
    // 当组件挂载后自动聚焦到输入框
    inputEl.current.focus();
  }, []); // 空依赖数组意味着这个effect只在组件挂载和卸载时运行

  const handleClick = () => {
    // 每次点击按钮,计数器加一,但不会触发组件渲染
    count.current++;
    alert(`The button has been clicked ${count.current} times.`);
  };

  return (
    <>
      {/* 将ref附加到input元素上 */}
      <input ref={inputEl} type="text" placeholder="I can be focused..." />
      <button onClick={handleClick}>Click me</button>
    </>
  );
}

export default TextInputWithFocusButton;

在这个例子中,inputEl ref被用来在组件挂载后立即给文本输入框设置焦点,而count ref则用来记录按钮被点击的次数,这个计数过程不会导致组件重新渲染。