MSIPO技术圈 首页 IT技术 查看内容

React系列之常用ReactHook

2024-03-28

Hook

React Hook 是 React 16.8 版本引入的一项新特性,它可以使函数组件也拥有状态管理机制和一些副作用处理机制。

Hook 一览:

  • 组件状态处理相关:useState、useContext、useReducer
  • 处理副作用:useEffect、useLayoutEffect
  • 性能优化相关:useMemo、useCallback
  • DOM 相关:useRef
  • redux 相关:useSelector、useDispatch、useStore
  • 用户自定义 hook 或 库里面带的 hook

useState

function Test() {
  const [count, setCount] = React.useState(0)
  const change = () => setCount(count + 1)
  return <div>
    {count}
    <button onClick={change}>click</button>
  </div>
}

useState 返回一个 state 和更新 state 的函数。
这个 newState 如果是对象的话,更新就要先拷贝之前的对象,再把要更新的对象合并进去。

const [state, setState] = useState({ name: 'oldName',  age: 18});
setState({ ...state, name: 'newName' });

useEffect

useEffect 是 React Hook 中的一个函数,用于处理函数组件中的副作用。类比生命周期函数包括 componentDidMount、componentDidUpdate 和 componentWillUnmount。

useEffect(setup, dependencies?)

useEffect接受两个参数:

第一个参数函数:需要执行的函数。setup 函数里还可以选择返回一个 cleanup 函数,cleanup 函数会在组件卸载时执行。
第二个参数依赖数组:这是 setup 代码内部引用的所有响应式值的列表。响应式值包括 props、state 以及直接在组件主体中声明的所有变量和函数。依赖数组可以不传递、传空数组和非空数组。

依赖数组的不同情况:

  1. 没有依赖数组:在组件挂载卸载和每次重新渲染的时候都会执行。
  2. 空数组:在组件挂载卸载的时候执行。
  3. 依赖数组:依赖项发生变化时执行。
副作用和纯函数

副作用是和纯函数相关的一个词,纯函数是指函数执行过程中,只有返回值,它的过程中不会让外部环境产生变化:包括但不限于修改外部变量、状态、发送网络请求、操作 DOM 等。它们只依赖于传入的参数,并且仅根据参数的值来计算返回值。
而且给定相同的输入,纯函数总是返回相同的输出,不受外部环境或状态的影响。

而副作用就是函数执行过程中,对外部环境进行的操作,在前端项目中例如数据请求等。

useContext

useContext 用于父组件向子孙组件传递数据,不需要再通过 props 从父组件向子组件逐层传递。

例子:明暗主题覆盖

import { createContext, useContext } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

定义一个 context 变量,用于存放当前上下文对象。将上下文对象的 Provider 作为父组件,通过 value 属性将要传递的值传给子孙组件。在子孙组件中就可以通过 useContext 获取到要传递的值。

https://react.dev/reference/react/useContext

useReducer

Reducer 是处理状态的另一种方式,它是类似 Redux 的写法,将设置状态的逻辑修改成 dispatch 的一个 action,将 state 和 action 传入 reducer 来获得新的状态。

例子:计数器

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

useRef

useRef 主要用于获取组件中的 dom 对象,来操作 DOM。但是实际上它可以保存任何值。

const ref = useRef(initialValue) 

返回一个只有一个属性 current 的对象,current 初始值为 initialValue。之后可以将其设置为其他值。如果将 ref 传递给一个 JSX 节点的 ref 属性,React 将为它设置 current 属性。
改变 ref.current 属性时,React 不会重新渲染组件。

例子:用于操作DOM节点

import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

例子:使用 useRef 来保存不需要变化的值
因为 useRef 的返回值在组件的每次 redner 之后都是同一个,所以它可以用来保存一些在组件整个生命周期都不需要变化的值。最常见的就是定时器的清除场景。
如果 timer 被声明为一个普通的变量,而不是使用 useState 或 useRef 来声明。这意味着每次组件重新渲染时,都会创建一个新的 timer 变量,而之前的定时器的引用会丢失。因此,当点击停止按钮时,clearTimer 函数中使用的 timer 变量与当前定时器不是同一个,所以清除定时器的操作失效。使用 useRef 来声明 timer,以确保在多次渲染之间保持其引用不变。这样,即使组件重新渲染,定时器的引用也会保持不变,从而可以正确地清除定时器。

const App = () => {
  const timer = useRef();
  useEffect(() => {
    timer.current = setInterval(() => {
      console.log('触发了');
    }, 1000);
  },[]);
  const clearTimer = () => {
    clearInterval(timer.current);
  }
  return (
    <>
      <Button onClick={clearTimer}>停止</Button>
    </>)
}

https://react.docschina.org/reference/react/useRef#referencing-a-value-with-a-ref
https://www.jianshu.com/p/c1240516c44c

useMemo

为了优化组件渲染的性能问题,有时候父组件重新渲染子组件不需要重新渲染,如果子组件的逻辑较复杂,就是无意义的大量计算,浪费资源。
class 解决此问题可以使用 shouldCompnentUpdate(nextProps, nextState) 生命周期,在组件更新之前,判断当前组件是否受某个state或者prop更改的影响。而在函数组件中,也不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。
useMemo和useCallback都是解决上述性能问题的。

useMemo 用于对计算成本较高的值进行记忆化处理,以提高性能。它类似于 useEffect,但主要用于缓存和返回一个计算值,而不是执行副作用操作。

const memoizedValue = useMemo(() => computeExpensiveValue(dependencies), dependencies);

参数:

  • computeExpensiveValue 是一个函数,用于计算成本较高的值。
  • dependencies 是一个依赖数组,用于指定在其中某些值发生变化时才重新计算 computeExpensiveValue。如果依赖数组为空,则表示只在组件的每次渲染周期中都重新计算值。

返回:
useMemo 返回的是一个记忆化的值,当依赖数组中的某些值发生变化时,才会重新计算并返回新的值。否则,它会返回上一次计算得到的值,避免了不必要的重复计算。

import React, { useMemo } from 'react';

function ExpensiveComponent({ a, b }) {
  const expensiveValue = useMemo(() => {
    console.log('Compute expensive value');
    return a + b;
  }, [a, b]); // 只有在 a 或 b 发生变化时才重新计算值

  return <div>{expensiveValue}</div>;
}

在这个示例中,useMemo 缓存了计算结果 a + b,并且只有当 a 或 b 的值发生变化时,才会重新计算结果。这样可以避免在每次组件渲染时都执行成本较高的计算操作。expensiveValue 没有变化的时候,即使父组件重新渲染了,子组件也不会重新渲染。

当需要对大量的数据进行处理和转换时,比如从服务器获取的原始数据需要进行筛选、排序、格式化等操作,这些计算都会耗费一定的时间。通过使用 useMemo 缓存处理后的数据,可以避免在每次组件渲染时都执行这些计算,提高页面性能。

React.memo

React.memo 是一个高阶组件,用于包装函数组件,以实现浅比较 props 的功能。当组件的 props 发生变化时,React.memo 会比较前后两次 props 是否相同,如果相同则跳过重新渲染。示例:

import React from 'react';

const MyComponent = React.memo(({ value }) => {
  return <div>{value}</div>;
});

export default MyComponent;

useCallBack

useMemo 是对数据的记忆,useCallback 是对函数的记忆。

useMemo 用于对函数进行记忆化处理,以避免在每次渲染时都创建新的函数实例。它主要用于优化性能,特别是在处理函数作为 props 传递给子组件时。

const memoizedCallback = useCallback(callback, dependencies);

useCallback 返回的是一个记忆化的函数,当依赖数组中的某些值发生变化时,才会重新创建函数实例。否则,它会返回上一次创建的函数实例,避免了不必要的函数重新创建。

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

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

  // 使用 useCallback 缓存回调函数,只在 count 发生变化时才重新创建函数实例
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, [count]);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

function ChildComponent({ onClick }) {
  return <button onClick={onClick}>Click me</button>;
}

在这个示例中,useCallback 缓存了 handleClick 回调函数,并且只有在 count 的值发生变化时才会重新创建函数实例。这样可以避免在每次父组件渲染时都创建新的函数实例,提高性能。

自定义Hook

自定义 Hook 就是自己封装的函数功能和 react 中内置的 Hook 进行结合,用于组件间共享逻辑,本质是一个函数。自定义 Hook 必须以 use 开头。

import { useEffect, useState } from 'react';

// 自定义 Hook:用于跟踪组件的宽度变化
function useComponentWidth() {
  const [width, setWidth] = useState(0);

  useEffect(() => {
    // 定义函数来更新组件宽度
    function updateWidth() {
      setWidth(window.innerWidth);
    }

    // 添加事件监听器来在窗口大小变化时更新宽度
    window.addEventListener('resize', updateWidth);

    // 初始化时获取一次宽度
    updateWidth();

    // 在组件卸载时移除事件监听器
    return () => {
      window.removeEventListener('resize', updateWidth);
    };
  }, []); // 依赖数组为空,表示只在组件挂载和卸载时执行一次

  return width;
}

// 使用自定义 Hook 来获取组件宽度
function MyComponent() {
  const width = useComponentWidth();

  return <div>The width of the component is: {width}px</div>;
}

相关阅读

热门文章

    手机版|MSIPO技术圈 皖ICP备19022944号-2

    Copyright © 2024, msipo.com

    返回顶部