看例子学React Hooks

React Hooks 是 React 16.8 引入的一项重要特性,它使函数组件能够拥有类组件的一些特性,例如状态管理和生命周期方法的使用。

React Hooks 是一种函数式组件的增强机制,它允许你在不编写类组件的情况下使用 React 的特性。主要的 Hooks 包括 useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, 和 useImperativeHandle 等。这些 Hooks 提供了访问 React 特性的方式,使得你可以更好地组织和重用你的代码。

下面是几个常用的Hooks

1、useState

允许你在函数组件中使用局部状态。它返回一个状态值和更新该状态值的函数。

import React, { useState } from 'react';

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

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(0);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

它返回一个状态值和一个设置该状态的函数:const [state, setState] = useState(initialState);
每次需要更新状态时,你会调用 setState,并传入新的状态值。

2、useReducer

用于更复杂的 state 逻辑,它接收一个 reducer 函数和初始状态,然后返回当前的状态和派发 action 的 dispatch 函数。

import React, { useReducer } from 'react';

const initialState = { count: 0 };

const counterReducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      return state;
  }
};

const Counter = () => {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <div>
      <h1>Count: {state.count}</h1>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
};

它将状态更新的逻辑封装在一个函数中,并根据动作(action)的类型来决定如何更新状态。
使用 useReducer 钩子时,状态更新逻辑被集中在一个地方,使得代码更具可读性和可维护性。

3、useContext

用于访问 React context 在组件树中传递的数据,而不必通过每个组件传递 props。

//  【1】数据组件
import React, { useState, useContext, createContext } from 'react';

// 创建一个上下文
const CounterContext = createContext();

// 创建一个提供者组件
const CounterProvider = ({ children }) => {
  const [count, setCount] = useState(0);

  // 定义更新状态的函数
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(0);

  // 在上下文中共享状态和更新函数
  return (
    <CounterContext.Provider value={{ count, increment, decrement, reset }}>
      {children}
    </CounterContext.Provider>
  );
};

//  【2】创建一个消费组件
const Counter = () => {
  const { count, increment, decrement, reset } = useContext(CounterContext);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

// 【3】在应用中使用提供者组件
const App = () => {
  return (
    <CounterProvider>
      <Counter />
      {/* 你可以在这里添加其他组件,它们也能访问 CounterContext */}
    </CounterProvider>
  );
};

export default App;

CounterContext:使用 createContext 创建一个上下文。这是共享状态的方式。

CounterProvider:这是一个负责提供状态的组件。它使用 useState 来管理状态,并将状态和更新函数通过上下文提供给子组件。

useContext(CounterContext):在 Counter 组件中,通过 useContext 钩子访问上下文中的状态和更新函数。

App 组件:在应用中使用 CounterProvider 包裹住需要使用共享状态的组件。这样,Counter 组件及其子组件都可以访问 CounterContext 中的状态。

4、useCallback

用于返回一个 memoized 版本的回调函数,防止不必要的渲染。

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

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

  // 使用 useCallback 来优化函数的创建
  const increment = useCallback(() => setCount(count + 1), [count]);
  const decrement = useCallback(() => setCount(count - 1), [count]);
  const reset = useCallback(() => setCount(0), []);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

它接收两个参数:一个是要记忆的函数,另一个是依赖数组。

只有当依赖数组中的某个依赖项发生变化时,useCallback 才会返回一个新的函数实例。否则,它会返回之前记忆的函数实例。

使用 useCallback 可以避免在每次组件重渲染时重新创建这些函数。这在某些情况下(例如,当这些函数被传递给子组件时)可以减少不必要的子组件重渲染。

5、useEffect

允许你在函数组件中执行副作用操作(如数据获取、订阅管理、DOM 操作等)。它在每次渲染后都会执行。

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

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

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(0);

  // 使用 useEffect 来处理副作用
  useEffect(() => {
    console.log(`Count has changed to: ${count}`);
    // 可以在这里添加清理逻辑,比如取消订阅
    // return () => {
    //   console.log('Cleanup logic here');
    // };
  }, [count]); // 依赖数组,指定副作用何时执行

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

依赖数组:

  • 在这个例子中,useEffect 的依赖数组是 [count],这意味着每次 count 发生变化时,副作用函数都会执行。
  • 如果省略依赖数组(即不传递第二个参数),副作用函数将在每次组件渲染时都执行。
  • 如果传递一个空的依赖数组([]),副作用函数将只在组件初次渲染时执行一次。

useCallback 和 useEffect 是用于不同场景的钩子。useCallback 主要用于优化性能,而 useEffect 主要用于处理副作用。

6、useMemo

用于对计算结果进行记忆,避免在每次渲染时重复计算。

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

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

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(0);

  // 使用 useMemo 来记忆计算值
  const doubleCount = useMemo(() => {
    console.log('Calculating double count...');
    return count * 2;
  }, [count]); // 依赖数组,指定何时重新计算

  return (
    <div>
      <h1>Count: {count}</h1>
      <h2>Double Count: {doubleCount}</h2>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

在这个例子中,useMemo 的依赖数组是 [count],这意味着每次 count 发生变化时,doubleCount 都会被重新计算。

如果省略依赖数组,useMemo 将在每次组件渲染时都重新计算值。

如果传递一个空的依赖数组([]),useMemo 将只在组件初次渲染时计算一次值。

useMemo 用于记忆计算结果,适用于需要避免重复计算的场景。

区别:

  • useEffect 用于处理副作用,适用于需要执行数据获取、订阅、DOM 操作等操作的场景。

  • 两者都通过依赖数组来管理何时重新执行相关逻辑,但 useMemo 返回计算结果,而 useEffect 不返回值。

7、useRef

用于创建对 DOM 元素或值的引用,可以在渲染之间保持状态。

使用 useRef 可以持久化存储一个可变值,这个值在组件的整个生命周期内保持不变,而不会导致组件重新渲染。useRef 通常用于访问 DOM 元素或存储组件内部的可变状态。

import React, { forwardRef } from 'react';

const ChildComponent = forwardRef((props, ref) => {
  return <input type="text" ref={ref} />;
});
import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const inputRef = useRef(null);

  const focusInput = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <ChildComponent ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
};

解释:

  • 在 ParentComponent 中,使用 useRef 创建一个 ref(inputRef)。
  • 将 inputRef 传递给 ChildComponent 的 ref 属性。
  • 在 focusInput 函数中,通过 inputRef.current 访问input 元素,并调用其 focus() 方法。

如果你希望在父组件中直接访问 ChildComponent 内部的input元素的 ref,你需要使用 React.forwardRef 来包裹你的子组件。这是因为函数组件默认情况下不支持接收 ref 属性,而 forwardRef 可以让你在函数组件中接收并处理 ref。

使用场景:

  • DOM 操作:访问和操作 DOM 元素,如获取焦点、测量尺寸等。
  • 持有可变状态:持有组件内的某个可变状态,但不需要该状态触发组件重渲染。
  • 避免不必要的重渲染:在某些情况下,使用 useRef 可以避免不必要的组件重渲染,提高性能。

8、useImperativeHandle

用于使用 ref 时暴露 DOM 元素的方法。

通常在以下场景中使用:

  • 你想控制或操作一个函数组件内部的某些方法或属性。
  • 你需要将一些内部的 DOM 操作暴露给父组件。
import React, { useImperativeHandle, useRef, forwardRef } from 'react';

// 创建一个函数组件,并使用 forwardRef 以便能够接收 ref
const ChildComponent = forwardRef((props, ref) => {
  const inputRef = useRef();

  // 使用 useImperativeHandle 自定义暴露出来的值
  useImperativeHandle(ref, () => ({
    focusInput: () => {
      inputRef.current.focus();
    },
    getValue: () => {
      return inputRef.current.value;
    }
  }));

  return (
    <input ref={inputRef} type="text" />
  );
});

const ParentComponent = () => {
  const childRef = useRef();

  const handleFocusClick = () => {
    // 调用子组件中暴露的方法
    if (childRef.current) {
      childRef.current.focusInput();
    }
  };

  const handleGetValueClick = () => {
    if (childRef.current) {
      alert(`Input value: ${childRef.current.getValue()}`);
    }
  };

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={handleFocusClick}>Focus Input</button>
      <button onClick={handleGetValueClick}>Get Input Value</button>
    </div>
  );
};

export default ParentComponent;

子组件中useImperativeHandle 接受三个参数:

  • 第一个参数是传递给组件的 ref
  • 第二个参数是一个回调函数,该函数返回一个对象,其中包含你希望暴露给父组件的方法和属性。
  • 第三个参数(可选)是依赖项数组,类似于 useEffect 中的依赖项数组,用于决定何时更新暴露的值。

父组件:

  • ParentComponent 创建了一个 ref 并将其传递给 ChildComponent
  • 通过调用 childRef.current.focusInput()childRef.current.getValue(),父组件可以访问 ChildComponent 中暴露的方法。

9、useLayoutEffect

与 useEffect 类似,但它在所有的 DOM 变更之后同步执行。这在需要读取 DOM 布局并同步触发重渲染时非常有用。

import React, { useLayoutEffect, useRef, useState } from 'react';

const MyComponent = () => {
  const divRef = useRef(null);
  const [size, setSize] = useState({ width: 0, height: 0 });

  useLayoutEffect(() => {
    // 在此测量 DOM 元素的尺寸
    if (divRef.current) {
      const { offsetWidth, offsetHeight } = divRef.current;
      setSize({ width: offsetWidth, height: offsetHeight });
    }
    
    // 可以在这里进行 DOM 操作,比如调整样式
    // divRef.current.style.backgroundColor = 'lightblue';

    // 清理函数可选
    return () => {
      // 例如,清除一些事件监听器
      // window.removeEventListener('resize', handleResize);
    };
  }, []); // 空数组表示仅在组件首次渲染后执行

  return (
    <div>
      <div ref={divRef} style={{ width: '100px', height: '50px', backgroundColor: 'lightcoral' }}>
        Measure me!
      </div>
      <p>Width: {size.width}px, Height: {size.height}px</p>
    </div>
  );
};

export default MyComponent;

解释:

  • divRef 用于获取目标 DOM 元素的引用,size 状态用于存储元素的尺寸信息。
  • 在组件完成渲染并且 DOM 更新后立即执行。
  • 通过 divRef.current 获取元素的尺寸,并更新 size 状态。
  • 可以选择返回一个清理函数,用于清除副作用,比如事件监听器。

注意事项

  • 性能影响:由于 useLayoutEffect 会在浏览器进一步呈现之前同步执行,因此它可能会对性能产生一定影响,尤其是在Effect内容复杂或执行时间较长时。
  • 避免不必要的使用:如果不需要在绘制前同步更新 DOM,应优先使用 useEffect
  • 服务器端渲染(SSR) :useLayoutEffect 不会在服务器端渲染时执行,因为它依赖于浏览器的 DOM。确保在 SSR 场景下使用它时考虑到这一点。

10、useDebugValue

用于在 React 开发者工具中显示自定义 hook 的标签。

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

// 自定义 Hook 示例
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  // 使用 useDebugValue 提供调试信息
  useDebugValue(count);

  // 也可以传递一个格式化函数来显示更详细的信息
  // useDebugValue(() => `Count: ${count}`);

  return { count, increment, decrement };
}

const CounterComponent = () => {
  const counter = useCounter(10);

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

export default CounterComponent;

使用场景:

  • 自定义 Hook:当你创建自定义 Hook 时,希望在 React 开发者工具中显示更有意义的信息。
  • 复杂状态:当 Hook 的状态较为复杂,且默认显示的信息不足以帮助调试时。
  • 调试性能问题:帮助识别和分析性能问题,通过显示与性能相关的信息。

代码解释

  1. 自定义 Hook (useCounter):
  • 这是一个简单的计数器 Hook,提供了 count 状态以及 incrementdecrement 函数。
  1. useDebugValue
  • useDebugValue(count);:这行代码告诉 React 开发者工具,当查看这个 Hook 的状态时,显示 count 的值。
  • 你也可以传递一个函数给 useDebugValue,用于返回更复杂的调试信息,例如:useDebugValue(() =>Count: ${count});
  1. React 开发者工具:
  • 当你在 React 开发者工具中检查使用 useCounter 的组件时,会看到 count 的值作为调试信息。
  • 这对于理解组件的状态和行为非常有帮助,尤其是在处理复杂状态时。

注意事项

  • 仅用于开发:useDebugValue 仅在开发模式下有效,在生产环境中不会有任何影响。
  • 不影响组件逻辑:useDebugValue 不会改变组件的行为或状态,仅用于提供调试信息。
  • 适当使用:避免用 useDebugValue 来替代清晰的代码逻辑或适当的命名,它仅作为一种辅助调试工具。

原文链接:看例子学React Hooks | 1Z5K

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容