Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >React Hook实践总结

React Hook实践总结

原创
作者头像
jadeCarver
修改于 2021-01-08 12:19:55
修改于 2021-01-08 12:19:55
1.1K0
举报
文章被收录于专栏:CS成长之路CS成长之路

最近一年几乎都在使用 TypeScript + Hooks 编写函数式组件,这一篇是我使用 hooks 的一些总结。

开始之前,看一个经典的计数器例子:

代码语言:txt
AI代码解释
复制
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>点击了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>
        Click
      </button>
    </div>
  );
}
每一帧都是独立的

任意一次渲染中的count常量都不会随着时间改变。渲染输出会变是因为我们的组件被一次次调用,而每一次调用引起的渲染中,它包含的count值独立于其他渲染。 —— Dan Abramov

在React组件中,通过改变状态来触发组件的重新 render,每次渲染都可以理解为一。在每一帧中,状态只是一个普通的变量,render的时候,它都是独立不变的。

也就是说,在每次渲染中,所有的 state、props 以及 effects 在组件的任意位置都是固定的,我们无法直接获取过去或者未来渲染周期的状态。

state 变化,引发了视图的更新,从直觉上看来,这里是不是使用了数据绑定或者,观察者之类的高级技巧,实际上不是的,它只是函数的重复调用而已,count 是每次调用都独立的局部变量。

更新 state

在react中,state或者props的改变,都会触发重新渲染。函数式组件以参数的形式接受props,props变化,整个组件都会重新渲染。useState在函数式组件内部创建了状态,并提供了一个改变状态的方法。

代码语言:txt
AI代码解释
复制
const [count, setCount] = useState(0);

几个值得注意的点:useState的初始值可以是一个简单类型,也可以是复杂类型。同时它还可以接收一个函数,将函数的返回值作为该state的初始值。

代码语言:txt
AI代码解释
复制
const a = 1;
const b = 2;
const [count, setCount] = useState(() => a + b);

既然每一帧的渲染中,state 都是独立的,其实就会有一个问题,当我们执行完 setCount 之后,并不会立即拿到最新的 count 的值:

代码语言:txt
AI代码解释
复制
const [count, setCount] = useState(0);
setCount(count + 1);
console.log(count); // 0

也就是说,count 的值在本次渲染周期内是固定不变的,直到下一次渲染,count 才会更新为 1.这也是为什么感觉 state 的改变是异步的原因。

获取未来或者过去的state

如果想要获取到最新的state值,则可以通过给setCount方法传入一个函数来执行。

还有一种方法就是使用 useRef,它是一个所有帧共享的变量,你可以在任何时间改变它,然后在它未来的帧中访问它。也就是说,useRef可以为渲染视图的特定一帧打一个快照进行保存。

什么样的数据需要保存为内部 state

在实际使用中,一个组件可能会出现大量的 useState定义,这个时候,我们需要回头反思,如此多的 state 定义是否有必要?

我们知道,react 状态的变化会引发视图的更新,所以将一个变量定义为 state 的标准是:它的改变需要直接引发视图的更新?如果答案是否定的,那就完全不必定义一个 state 出来,而是通过一般的变量将其缓存起来。或者说,使用 useRef是一种不错的选择。

管理复杂状态的两种选择: useReducer + useContext

对于一些需要全局使用的状态,如果需要在多层组件之间传递更新数据,这很容易造成逻辑混乱且不可追踪,则可以通过 useContext 来简化这一过程,以避免显示地在每一层组件之间传递props,子组件可以在任何地方访问到该 context 的值。在下面的例子中,我们将终端的平台和版本通过context注入:

代码语言:txt
AI代码解释
复制
const client = {
  mobile: {
    system: 'android',
    version: '8.0.0'
  },
  mac: {
    system: 'MacOS',
    version: '11.0.1'
  }
}
const ClientContext = React.CreateContext({});
const App = () => {
  return (
  		<ClientContext.Provider value={client.mac}>
    		<MyComponent />
    </ClientContext.Provider>
  )
}

const MyComponent = () => {
  const client = useContext(ClientContext);
  return <>当前系统为{client.system},系统版本为{client.version}</>
}

在某一个节点注入 context,该组件及其所有下属组件都会共享这个 context。当该 context 的值发生变化时,其下的所有组件都会重新 render.

useReducer,是改变 state 的另一种方式。顾名思义,就是 reducer 的 hooks 用法。reducer 接受一个改变 state 的方法,以及触发方法的 action,计算之后,返回新的 state.类似于这样 (state, action) => newState.useReducer在某些复杂场景下比 useState更实用,例如一个操作会引发N多个 state 的更新,或者说,state 本身嵌套很多层,更新的逻辑易遗漏,维护起来一片凌乱等等场景。

reducer 是一个纯函数,也就是说,它不包含任何 UI 和副作用操作。也就是说,只要输入的值不变,其输出的值也不会改变。

同样地,reducer 中的数据是 immutable 的,不要直接改变输入的 state,而是应该返回一个新的改变后的 state.

action 是一个用 type 标识的动作,例如对计数器的 increase、decrease等,在 reducer中,可以根据 action type 的不同,采用不同的数据处理。同时,它可以接收第二个参数 payload,传入执行该 action 需要的额外数据。

代码语言:txt
AI代码解释
复制
const [state, dispatch] = useReducer(reducer, initialArg, init);

useReducer接收一个 reducer 函数,以及一个初始的 state 值,暴露出计算之后的新 state,以及一个 dispatch 方法,它接收一个 action 为参数,用来触发相应的 reducer. 下面使用 useReducer重构计数器的例子:

代码语言:txt
AI代码解释
复制
const initialCount = {
  count: 0
};

const countReducer = (state, action) => {
  switch (action.type) {
    case "increase":
      return { count: state.count + 1 };
    case "decrease":
      return { count: state.count - 1 };
    case "reset":
      return { count: action.payload };
    default:
      return initialCount;
  }
};

export default function App() {
  const [state, dispatch] = useReducer(countReducer, initialCount);

  return (
    <div className="App">
      <h1>{state.count}</h1>
      <button onClick={() => dispatch({ type: "increase" })}>+1</button>
      <button onClick={() => dispatch({ type: "decrease" })}>-1</button>
      <button
        onClick={() => dispatch({ type: "reset", payload: initialCount.count })}
      >
        reset
      </button>
    </div>
  );
}

在某些复杂的场景中,reducer 的引入实际上将复杂的 state 更新行为剥离出来,单独在 reducer 之中维护,而组件的核心交互逻辑我们只需要关照 dispatch 了哪个 action,这样使得代码可读性大大提高,组件的核心逻辑也会清晰明了。同时,对于不涉及多层组件交互的状态,并不适合使用 reducer 来维护,这样,反而增加了维护的复杂度。

在一些复杂场景下,结合 useContextuseReducer可以发挥出十分强大的威力。一般的做法是,可以把 state 和 dispatch 方法通过 context 注入,这样,很方便地实现了状态的集中管理和更新。这种方法最好不要滥用,因为集中管理、处处可以变更的方式虽然看起来方便很多,但在 context 的作用范围处处都可以通过 dispatch 来更新 state,这样很容易造成 state 的更新不可追踪。

一般情况下,这种模式适合多层组件状态交互十分密集,且数据具有较好的完整性和共享需要,整个 state 描述的是同一件事,而不是把任意的数据都塞进去维护,这样写起来一时爽,维护起来火葬场~

副作用管理

useEffectuseLayoutEffect都是用来执行视图之外的副作用。前者在每次状态更新且视图也渲染完毕之后执行。后者则是在DOM更新完毕,但页面渲染之前执行,所以会阻塞页面渲染。

如前所述,在每一帧的渲染中,useEffect 中使用的 state 和 props 也是独立不变的。

可以通过给它传入第二个参数,一个依赖数组,来跳过不必要的副作用操作,React 通过对比依赖数组的值是否发生变化来决定是否执行副作用函数。当第二个参数为一个空数组的时候,意味着这个 Effects 只会执行一次。

对于依赖数组,使用不当经常会遇到各种各样的重复渲染的情况。不要添加不必要的依赖在数组中,因为依赖项越多,意味着该 Effects 被多次执行的概览越大。例如,在下面的场景中,如果需要在 Effects 中更新 state,不必将该 state 传入依赖数组,而是通过给 setCount 传入回调的方式去获得当前 state:

代码语言:txt
AI代码解释
复制
useEffect(() => {
  // setCount(count + 1); 这种方式会引入一个 state 的依赖项。
  setCount(count => count + 1);
}, [])

在React官方的文档中,还提到了两种需要避免重复渲染的情况及处理方式:

  1. 当依赖项中传入一个函数时,通过使用 useCallback来包裹函数避免函数反复被创建;
  2. 当依赖项中传入数组或者对象等引用类型,通过使用 useMemo来缓存处理它。
使用useMemo和useCallback

如上所述,合理地使用 useMemouseCallback能够避免不必要的渲染。当对象或者数组作为 props 传入的时候,可以使用 useMemo来缓存对象,使其在必要的时候更新:

代码语言:txt
AI代码解释
复制
const data = useMemo(() => ({ id}), [id]);

<ComponentA data={data} />

只要 id不变,Component 就不会重新渲染。

useMemo 同样可以用来缓存组件内部的部分 React Element ,以避免重复渲染并没有变化的部分。

使用 useMemo 或者 useCallback 并不是绝对会提升性能。如果你缓存的数据永远不会改变或者说,每一次都会改变,那大可不必使用这两个 hooks,毕竟它们需要额外的计算成本以及存储空间,有的时候得不偿失。

最后,在React哲学一文中,官方给出了一种使用 React 来构建应用的思路,我觉得十分赞。这篇文章中提到,开始的时候,找出应用所需的最小集合,其他数组均有它们计算而出。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
[OHIF-Viewers]医疗数字阅片-医学影像-REACT-Hook API索引
如果你刚开始接触 Hook,那么可能需要先查阅 Hook 概览。你也可以在 Hooks FAQ 章节中获取有用的信息。
landv
2020/07/09
2.2K0
React-hooks面试考察知识点汇总
React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)。有一些解决此类问题的方案,比如 render props 和 高阶组件。但是这类方案需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。
beifeng1996
2022/10/06
1.3K0
快速上手 React Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。本篇文章将介绍React Hook相关知识。
用户8921923
2022/10/24
5.1K0
Note·React Hook
React Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
数媒派
2022/12/01
2.2K0
React Hook
在传统的 class 中,会使用 componentDidMount 和 componentDidUpdate 获取数据。同时 componentDidMount 中也会处理一些其他的事务,例如事件监听,定时器等等。而后还需要在 componentWillUnmount 中取消。万一忘记其中某一个部分或者处理的时间过多,很可能导致一些可怕的bug。
踏浪
2020/01/17
2K0
宝啊~来聊聊 9 种 React Hook
文章会为你讲述 React 9种 Hook 的日常用法以及进阶操作,从浅入深彻底掌握 React Hook!
19组清风
2022/02/28
1.1K0
宝啊~来聊聊 9 种 React Hook
React Hooks 中的属性详解
React Hooks 是 React 16.8 版本中新增的特性,允许我们在不编写 class 的情况下使用 state 和其他的 React 特性。Hooks 是一种可以让你在函数组件中“钩入” React 特性的函数。以下是一些常用的 React Hooks,并附有详细的用法和代码示例。
世间万物皆对象
2024/03/20
2490
React Hooks 解析(下):进阶
React Hooks 是从 v16.8 引入的又一开创性的新特性。第一次了解这项特性的时候,真的有一种豁然开朗,发现新大陆的感觉。我深深的为 React 团队天马行空的创造力和精益求精的钻研精神所折服。本文除了介绍具体的用法外,还会分析背后的逻辑和使用时候的注意事项,力求做到知其然也知其所以然。
Dickensl
2022/06/14
4490
React 设计模式 0x3:Ract Hooks
React Hooks 是在函数式组件中使用的生命周期方法,React Hooks 在 React 16.8 中被引入。在类组件中的生命周期方法已被合并成 React Hooks,React Hooks 无法在类组件中使用。
Cellinlab
2023/05/17
1.7K0
React Hook丨用好这9个钩子,所向披靡
[raect] React Hook 指南 什么是 Hook ? Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。 Hook 本质上就是一个函数,它简洁了组件,有自己的状态管理,生命周期管理,状态共享。 useState useEffect useContext useReducer Hook 出现解决了什么 ? 组件之间状态复用, 例如:使用useContext 可以很好的解决状态复用问题,或者自定义Hook 来定制
程序员海军
2022/02/15
3.2K0
React Hook丨用好这9个钩子,所向披靡
React Hooks教程之基础篇
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
乐圣
2022/11/19
3.1K0
React Hook实践指南
在React为什么需要Hook这篇文章中我们探讨了React开发团队为什么要为Function Component添加Hook的原因,在本篇文章中我将会为大家提供一份较为全面的React Hook实践指南,其中包括以下方面的内容:
进击的大葱
2022/08/22
2.6K0
React-Hook最佳实践
Hooks 是 React 16.8 新增的特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性,无需转化成类组件
xiaofeng123aa
2022/10/17
4K0
超详细preact hook源码逐行解析
preact hook 作为一个单独的包preact/hook引入的,它的总代码包含注释区区 300 行。
ACK
2020/01/14
2.7K0
React Hook实战
在React Hook出现之前的版本中,组件主要分为两种:函数式组件和类组件。其中,函数式组件通常只考虑负责UI的渲染,没有自身的状态也没有业务逻辑代码,是一个纯函数。而类组件则不同,类组件有自己的内部状态,界面的显示结果通常由props 和 state 决定,因此它也不再那么纯洁。函数式组件,类组件有如下一些缺点:
xiangzhihong
2020/12/21
2.2K0
接着上篇讲 react hook
Hook 是一个特殊的函数,使用了 JavaScript 的闭包机制,可以让你在函数组件里“钩入” React state 及生命周期等特性。Hook 不能在 class 组件中使用。这也就是我开篇说的函数式组件一把索的原因
sunseekers
2020/06/03
2.6K0
在React项目中全量使用 Hooks
此方法会返回两个值:当期状态和更新状态的函数。效果同 this.state 与this.setState,区别是 useState 传入的值并不一定要对象,并且在更新的时候不会把当前的 state 与旧的 state 合并。
江拥羡橙
2022/11/17
3.2K0
在React项目中全量使用 Hooks
超实用的 React Hooks 常用场景总结
React 在 v16.8 的版本中推出了 React Hooks 新特性。在我看来,使用 React Hooks 相比于从前的类组件有以下几点好处:
前端达人
2021/05/11
4.8K0
【react】203-十个案例学会 React Hooks
原文地址:https://github.com/happylindz/blog/issues/19
pingan8787
2019/07/23
3.2K0
React Hooks 万字总结
每个 hook 都会有一个 next 指针,hook 对象之间以单向链表的形式相互串联, 同时也能发现 useState 底层依然是 useReducer 再看看更新阶段发生了什么
ConardLi
2021/04/23
9680
相关推荐
[OHIF-Viewers]医疗数字阅片-医学影像-REACT-Hook API索引
更多 >
LV.1
腾讯云前端开发工程师
作者相关精选
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档