数据处理相关 Hook 的细节

大家好,我是森木。本文将从平时开发常用的 Hook 入手慢慢将每个 Hook 的细节扒开。宗旨是让大家轻松阅读,收获满满。

前言

数据的处理涉及到我们开发日常的方方面面,在习惯了某些方法后可能容易忽视其他细节;不过,接下来我同大家一起来回顾下 React 最常用的几个 Hook。废话少说,直接开整。

useState

用法:

const [value, setValue] = useState(initValue)
/**
 * 更新 value
 */
setValue(xx);
// 这里还是原来 value
console.log(value);

/**
 * 修改后
 */
setValue((prevValue) => {
    // 返回的就是最新的值
    return prevValue + 1
});
// 这里拿到的就是最新的 value
console.log(value);

useState 我这里就谈两点细节:

  1. 惰性初始化
  2. 重置数据

惰性初始化

基本使用就不再赘述了,这里强调下 initValue,它支持所有类型的数据,但是函数类型会有特殊的逻辑,它有个优雅的名字——惰性状态函数。那么它有什么不同呢?

  • React 会将传入的函数保存下来,并在初始化渲染时执行
  • 修改状态通过 setState

它最大的用处就是性能优化,想象一下有一个需要通过复杂“计算”的初始值,如果你不想在每次重新渲染都去“计算”的话这种方式就是最好的方式~

加餐:既然都是为了不在重新渲染后重复执行那么使用 useEffect 的空依赖数组岂不是一样?(这个放在 useEffect 章节详细讲)

重置数据

重置数据小技巧:给组件设置 key,并且传递 state 值给 key,在需要重置数据时改变 key 值。

useEffect

用法:

useEffect(() => {
  // setup

  return () => {
    // cleanup
  }
}, [/*依赖项*/])

关于 useEffect 这里讲三点细节:

  1. 依赖项
  2. 数据请求
  3. Effect 生命周期

关于依赖项

  • 不填(指不写useEffect第二个参数),那么每次重新渲染都会执行 Effect
  • 为空数组的,只有在 mount 阶段执行 setup 逻辑,unmount 阶段执行 cleanup 逻辑
  • 填写响应式依赖,在 mount 阶段执行 setup 逻辑,每次响应式值改变先执行 cleanup 逻辑,后执行 setup 逻辑,unmount 阶段执行 cleanup 逻辑

数据请求

// 切换 person state 来请求最新数据
const [person, setPerson] = useState('senmu')
const [list, setList] = useState()

// 使用 .then 方式
useEffect(() => {
  let flag = false
  fetchData(person).then(res => {
    if (!flag) {
      setList(res)
    }
  })
  return () => {
    flag = true
  }
}, [person])

// 使用 async await 方式
useEffect(() => {
  async function getData() {
    const res = await fetchData(person)
    if (!flag) {
      setList(res)
    }
  }
  let flag = false
  getData()
  return () => {
    flag = true
  }
}, [person])

很多人看到上面的代码表示不屑(我天天写数据请求难道还用你教我 🙄)。其实这里面有一点细节需要我们注意:

  • useEffect 的第一个参数不允许是 Promise,如果要写 async 需要在内部单独定义函数;
  • 关于为什么要定义 flag 变量,是因为假如在请求还未完成时执行了意外的操作(例如组件卸载),那么数据就不该被设置到 state 中。

Effect 的生命周期

mount 阶段:

  • 初始化变量
  • 渲染 DOM(render
  • 执行 Effect setup 逻辑

update 阶段:

  • 渲染 DOM(render
  • 执行 Effect cleanup 逻辑
  • 执行 Effect setup 逻辑

unmount 阶段:

  • 执行 Effect cleanup 逻辑

下面是代码实例,可以使用控制台查看:

明白了上面的生命周期回头思考下在 useState 中留下来的疑问,我们可以发现 useEffect 的执行时机是在渲染之后,而惰性初始化却是在初始化渲染时执行,这就意味着如果在渲染之后执行赋值操作会造成一次重新渲染,并且这样处理也不够优雅🤵。

useLayoutEffect

只针对客户端

用法/写法上与 useEffect 无异,只不过需要注意它比 useEffect 生命周期更早并且会阻塞浏览器重绘,这也意味着它的性能较差,所以一般建议使用 useEffect,除非特殊场景。

比如鼠标移入弹出提示框功能,需要通过获取位置信息来生成提示框信息,下面可以看看具体对比:

useInsertionEffect

只针对客户端

同样用法/写法上与上述的 Effect 无异,只不过需要注意它比 useLayoutEffect 生命周期更早,设计初心也是为了解决使用 JS 插入 CSS 由于时机问题引发的性能问题。实例:

import { useState, useInsertionEffect } from 'react'
function MyComponent() {
  const [color, setColor] = useState('blue');
  
  useInsertionEffect(() => {
    // 假设 insertStyles 是一个将 CSS 注入到 DOM 的函数
    insertStyles(`.my-component { color: ${color}; }`);
  }, [color]); // 依赖项数组中包含 color,确保颜色变化时样式可以更新

  return <div className="my-component">Hello, World!</div>;
}

useContext

提到 useContext 不可避免的就是 React 的 Context 整个设计

Context 特性

  1. 跨组件传递数据
  2. 避免 Props 逐层传递
  3. 动态性—可以结合 statereducer 动态更改其值
  4. 默认值—如果上下文的 Provider 未提供值,那么便会采用创建 Context 时传递的默认值
  5. 多个 Context—可以使用多个 Provider 嵌套以支持多种 Context,每个 Context 管理不同的数据

注意事项:

  • 使用 Context 的组件由于状态与组件的绑定导致组件的复用性大大降低
  • 由于重新渲染引发的对于性能的考量

关键步骤

  1. 使用 const ThemeContext = createContext(defaultValue) 创建 Context
  2. 使用 <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider> 包裹子组件
  3. 在被 2 中包裹的任意子组件中使用 useContext(ThemeContext) 来获取状态值

以上三点缺一不可~

从设计上来讲,Context 可以包裹多层多次组件,那么使用 useContext() 拿到的值就是距离最近Provider 提供的值。

再啰嗦一句,Context 虽好,但是在使用之前一定要仔细考量是否真的需要使用 Context

useReducer

用法:

const [state, dispatch] = useReducer(reducer, initValue, initFn?)

关于它的用法上有一点值得注意,那就是如果需要传递复杂的或者需要计算得到的初始化值,你可以把 initValue 当作 initFn 的参数,在 initFn 中返回通过计算得到的初始值,这样做的好处就是可以避免重复渲染导致不必要的性能开销。

关于它与 useStateuseReducer 更像是需要提前进行技术设计才去使用的功能,而 useState 更偏向是此时此刻我需要这个状态我就会使用,这也导致它们有各自不同的结果和影响。

它可以和 useContext 结合,封装更加强大的功能,并使交互与状态分离。上实例:

原文链接:https://juejin.cn/post/7332354240479707151 作者:森木阿

(0)
上一篇 2024年2月8日 下午4:32
下一篇 2024年2月8日 下午4:42

相关推荐

发表回复

登录后才能评论