Hook 前言

什么是Hook

自从 16.8 版本开始,hooks 的出现使得你可以在不编写 class 的情况下使用状态管理以及其它 React 的特性。

那么在 React Hooks 出现之前,class 类组件和 function 函数组件有什么区别?Hooks 出现之后,函数组件又是如何满足原来只有类组件才有的功能的?

1.类组件和没有 hooks 加持的函数组件:

函数组件常被称为无状态组件,意思就是它内部没有状态管理,只能做一些展示型的组件或者是完全受控组件。因此差别主要体现在:

  • 函数组件没有内部状态管理
  • 函数组件内部没有生命周期钩子
  • 函数组件不能被获取组件实例 ref,函数组件内也不能获取类组件的 ref

2.类组件和有 hooks 加持的函数组件:

有了 hooks 加持之后,函数组件具备了状态管理,除了可以使用内置的 hooks ,我们还可以自定义 hooks。

  • 类组件有完备的生命周期钩子,而函数组件只能具备:DidMount / WillUnmount / DidUpdate / willUpdate
  • 函数组件内部可以通过内置 hook 获取类组件 ref,也可以通过一些 API 的组合使用达到获取函数组件 ref 的功能
  • 函数组件具备了针对状态变量的 setter 监听(类似于 vue watch),类组件没有这种 API。(useCallback、useEffect、useMemo等)

类组件原本比函数组件更加完整,为什么还需要 hooks?

这要说到 React 的设计理论:

  • React 认为,UI 视图是数据的一种视觉映射,即 UI = F(DATA) ,这里的 F 需要负责对输入的数据进行加工、并对数据的变更做出响应
  • 公式里的 F 在 React 里抽象成组件,React 是以组件为粒度编排应用的,组件是代码复用的最小单元
  • 在设计上,React 采用 props 来接收外部的数据,使用 state 属性来管理组件自身产生的数据(状态),而为了实现(运行时)对数据变更做出响应需要,React 采用基于类 Class 的组件设计
  • 除此之外,React 认为组件是有生命周期的,因此开创性地将生命周期的概念引入到了组件设计,从组件的 create 到 destroy 提供了一系列的 API 共开发者使用

类组件 Class Component 的困局

组件状态逻辑复用困局

对于有状态组件的复用,React 团队和社区尝试过许多方案,早期使用 CreateClass + Mixins,使用 Class Component 后又设计了 Render Props 和 HOC,再到后来的 Hooks设计,React 团队对于组件复用的探索一直没有停止。

HOC 和 Render Props 都有自己的缺点,都不是完美的复用方案(详情了解 React HOC 和 Render Props),官方团队认为应该为共享状态逻辑提供更好的原生途径。在 Hooks 加持后,功能相对独立的部分完全抽离到 hook 实现,例如网络请求、登录状态、用户核验等;也可以将 UI 和功能(状态)分离,功能放到 hook 实现,例如表单验证。

复杂组件变得难以理解

我们经常维护一些组件,它们起初很简单,但是逐渐会被状态逻辑和副作用充斥。在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了挑战。Hook 可将组件中相互关联的部分拆分成更小的函数

JavaScript Class 的缺陷
  • this的指向问题(语言缺陷)
  • 编译后体积和性能的问题

同样功能的类组件和函数组件,在经过 Webpack 编译后体积相差明显,也伴随着一定的性能问题。这是因为 class 在 JavaScript 中本质是函数,在 React 内部也是当做 Function类 来处理的。而函数组件编译后就是一个普通的 function,function 对 JS 引擎是友好的。

内置 Hooks

useState

  1. const [state, setState] = useState(initialState);

用来承担与类组件中的 state 一样的作用,组件内部的状态管理

  1. function () {
  2. const [ count, setCount ] = useState(0);
  3. const onClick = () => {
  4. setCount( count + 1 );
  5. // setCount(count => count + 1);
  6. };
  7.  
  8. return <div onClick={onClick}>{ count }</div>
  9. }

除了直接传入最新的值,还可以函数式更新,这样可以访问到先前的 state。如果你的初始 State 创建比较昂贵时,可以传一个函数给 useState:

  1. function Table(props) {
  2. // ⚠️ createRows() 每次渲染都会被调用
  3. const [rows, setRows] = useState(createRows(props.count));
  4. // ...
  5. }
  6.  
  7. function Table(props) {
  8. // ✅ createRows() 只会被调用一次
  9. const [rows, setRows] = useState(() => createRows(props.count));
  10. // ...
  11. }

如果是复杂类型的 state,需要传入修改后的完整的数据,不再像类组件中的 setState 可以自动合并对象,需要手动合并:

setState(prevState => ({...prevState, ...updatedValues}));

此外,useReducer 是另一种可选的方案。

useEffect

  1. useEffect(func, [deps]);

可以用来模拟生命周期,即可以完成某些副作用。什么叫副作用?一般我们认为一个函数不应该对外部产生影响,一旦在函数内部有某些影响外部的操作,将其称之为副作用。例如改变 DOM、改变 Window对象(Global)、设置定时器、使用原生API绑定事件等等,如果处理不好,它们可能会产生 bug 并产生破坏。

如果只传一个参数,每次组件渲染都会执行回调函数(挂载+跟新),相当于 componentDidMount() + componentDidUpdate()

返回值函数:在组件更新前、组件卸载时执行,相当于 componentWillUnmount() + componentWillUpdate()

  1. useEffect(() => { // 每次渲染后执行此函数,获取到的值是最新的
  2. console.log("Effect after render", count);
  3. return () => { // 每次执行useEffect前,先执行此函数,获取到的数据是更新之前的值
  4. console.log("remove last", count);
  5. }
  6. });

第二个参数是依赖列表,当依赖的状态数据发生改变时会执行回调

1.如果是一个空数组,表示没有依赖项

  • 回调函数:只在组件挂载的时候执行一次,相当于 componentDidMount()
  • 返回值函数:只在组件卸载的时候执行一次,相当于 componentWillUnmount()

2.如果有值

  • 回调函数:除了具有 componentDidMount(),还当 数组内的变量发生变化时执行 componentDidUpdate()
  • 返回值函数:除了具有 componentWillUnmount(),还当 数组内的值发生变化时执行 componentWillUpdate()

需要注意的是,

1.第二个参数的比较其实是浅比较,传入引用类型进去是无意义的
    2.一个组件内可以使用多个 useEffect,它们相互之间互不影响
    3.useEffect 第一个参数不能是 async 异步函数,因为它总是返回一个 Promise,这不是我们想要的。你可以在其内部定义 async 函数并调用

useLayoutEffect

它与 useEffect 的用法完全一样,作用也基本相同,唯一的不同在于执行时机,它会在所有的 DOM 变更之后同步调用 effect,可以使用它来

useEffect 不会阻塞浏览器的绘制任务,它会在页面更新之后才执行。而 useLayoutEffect 跟 componentDidMount 和 componentDidUpdate 的执行时机一样,会阻塞页面渲染,如果当中有耗时任务的话,页面就会卡顿。大多数情况下 useEffect 比 class 的生命周期函数性能更好,我们应该优先使用它。

如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件,则需要注意 useLayoutEffect 与 componentDidMount、componentDidUpdate 的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect。

useReducer

  1. const [state, dispatch] = useReducer(reducer, initialArg, init);

useState 的替代方案,它接收一个 (state, action) => newState 的 reducer 处理函数,并返回当前的 state 和 配套的 dispatch 方法。使用方法与 redux 非常相似。

某些场景下,useReducer 比 useState 更加适用:

  • 当状态变量比较复杂且包含多个子值的时候
  • 下一个 state 依赖之前的 state
  1. const initialState = {count: 0};
  2.  
  3. function init(initialCount) {
  4. return {count: initialCount};
  5. }
  6.  
  7. function reducer(state, action) {
  8. switch (action.type) {
  9. case 'increment':
  10. return {count: state.count + 1};
  11. case 'decrement':
  12. return {count: state.count - 1};
  13. default:
  14. throw new Error();
  15. }
  16. }
  17.  
  18. function Counter(props) {
  19. const [state, dispatch] = useReducer(reducer, initialState);
  20. // const [state, dispatch] = useReducer(reducer, props.initialCount, init);
  21.  
  22. return (
  23. <>
  24. Count: {state.count}
  25. <button onClick={() => dispatch({type: 'decrement'})}>-</button>
  26. <button onClick={() => dispatch({type: 'increment'})}>+</button>
  27. </>
  28. );
  29. }

此外,它还可以模拟 forceUpdate()

  1. const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
  2.  
  3. function handleClick() {
  4. forceUpdate();
  5. }

useCallback

  1. const memoizedCallback = useCallback(func, [deps]);

useCallback 缓存了方法的引用。它有的作用:性能优化,父组件更新,传递给子组件的函数指针不会每次都改变,只有当依赖项发生改变的时候才会改变指针。避免了子组件的无谓渲染

它的本质是对函数依赖进行分析,依赖变更时才重新执行。

useMemo & React.memo

useMemo 用于缓存一些耗时的计算结果(返回值),只有当依赖项改变时才重新进行计算。

  1. useCallback(func, [deps]) 等同于 useMemo(() => func, [deps])

useCallback 缓存的是方法的引用,useMemo 缓存的是方法的返回值,适用场景都是避免不必要的子组件渲染。

在类组件中有 React.PureComponent,与之对应的函数组件可以使用 React.memo,它们都会在自身 re-render 时,对每一个 props 项进行浅对比,如果引用没有发生改变,就不会触发渲染。

那么,useMemo 和 React.memo 有什么共同点呢?前者可以在组件内部使用,可以拥有比后者更细粒度的依赖控制。它们两个与 useCallback 的本质一样,都是进行依赖控制。

useContext

专门为函数组件提供的 context hook API,可以更加方便地获取 context 的值。

  1. const value = useContext(MyContext);

useContext(MyContext) 接收一个 context 对象,当前获取到的值由上层组件中距离最近的 <MyContext.Provider> 的 value 决定。

useContext(MyContext) 相当于之前的 static contextType = MyContext 或者 <MyContext.Consumer>

useRef

  1. const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变。

注意:此 hook 可以获取 DOM 元素、类组件示例,但无法获取函数组件实例,因为函数组件根本没有实例。如果想让函数组件被获取到 ref,可以使用 useImperativeHandle 来达到这样的效果

另外,useRef 获取到的“ref”对象是一个 current 属性可变且可以容纳任意值的通用容器。可以实现如下功能:

  • 模拟实例变量
  • 获取 prevProps、prevState
  1. // 当做 class 实例变量
  2. function Timer() {
  3. const intervalRef = useRef();
  4.  
  5. useEffect(() => {
  6. const id = setInterval(() => {
  7. // ...
  8. });
  9. intervalRef.current = id;
  10. return () => {
  11. clearInterval(intervalRef.current);
  12. };
  13. });
  14.  
  15. // ...
  16. }
  17.  
  18. // 获取prevProps,prevState
  19. function Counter(props) {
  20. const [count, setCount] = useState(0);
  21. const prevProps = useRef(props);
  22. const prevCount = useRef(count);
  23.  
  24. useEffect(() => {
  25. prevCount.current = count;
  26. prevProps.current = props;
  27. });
  28.  
  29. return <h1>Now: {count} - {props}, before: {prevCount.current} - {prevProps.current}</h1>;
  30. }

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义对外暴露的属性。官方指出,它应当与 forwardRef 一起使用。

示例:

  1. function FancyInput(props, ref) {
  2. const inputRef = useRef();
  3. useImperativeHandle(ref, () => ({
  4. focus: () => {
  5. inputRef.current.focus();
  6. }
  7. }));
  8. return <input ref={inputRef} ... />;
  9. }
  10. FancyInput = forwardRef(FancyInput);

此时,通过 ref 获取到 FancyInput 的"实例",其 current 属性内只有 foucs 属性可供访问

React Hooks总结的更多相关文章

  1. 通过 React Hooks 声明式地使用 setInterval

    本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: setInterval 用起来没你想的简单. Ryan Florence 在他 ...

  2. 初探React Hooks & SSR改造

    Hooks React v16.8 发布了 Hooks,其主要是解决跨组件.组件复用的状态管理问题. 在 class 中组件的状态封装在对象中,然后通过单向数据流来组织组件间的状态交互.这种模式下,跨 ...

  3. React hooks实践

    前言 最近要对旧的项目进行重构,统一使用全新的react技术栈.同时,我们也决定尝试使用React hooks来进行开发,但是,由于React hooks崇尚的是使用(也只能使用)function c ...

  4. 理解 React Hooks

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由志航发表于云+社区专栏 TL;DR 一句话总结 React Hooks 就是在 react 函数组件中,也可以使用类组件(classe ...

  5. React Hooks新特性学习随笔

    React Hooks 是 React 16.8 的新增特性.它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性. 前言 本篇主要以讲几个常用的api为主. 1.u ...

  6. 关于React Hooks,你不得不知的事

    React Hooks是React 16.8发布以来最吸引人的特性之一.在开始介绍React Hooks之前,让咱们先来理解一下什么是hooks.wikipedia是这样给hook下定义的: In c ...

  7. react hooks 全面转换攻略(三) 全局存储解决方案

    针对 react hooks 的新版本解决方案 一.redux维持原方案 若想要无缝使用原来的 redux,和其配套的中间件 promise,thunk,saga 等等的话 可以使用 redux-re ...

  8. react新特性 react hooks

    本文介绍的是react新特性react hooks,本文面向的是有一定react开发经验的小伙伴,如果你对react还不是很熟悉的话我建议你先学习react并多多联系. 首先我们都知道react有3种 ...

  9. 30分钟精通React今年最劲爆的新特性——React Hooks

    你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? --拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function. 你还在为搞不清使用哪个生命周 ...

  10. React Hooks 深入系列 —— 设计模式

    本文是 React Hooks 深入系列的后续.此篇详细介绍了 Hooks 相对 class 的优势所在, 并介绍了相关 api 的设计思想, 同时对 Hooks 如何对齐 class 的生命周期钩子 ...

随机推荐

  1. 武汉加油!(Python版)

    #武汉加油!import turtle as tt.pensize(20)t.pencolor("blue")t.setup(1700, 600) t.penup()#-t.got ...

  2. lr具体使用步骤概述

    lr具体使用 1 无工具情况下的性能测试 2性能测试工具LoadRunner的工作原理 3 VuGen应用介绍 4 协议的类型及选择方法 5 脚本的创建过程 6 脚本的参数化 7 调试技术 8 Con ...

  3. Powershell操作MySQL

    最近再用Python写一些监控脚本,并将监控数据输出到MySQL中,最后通过Python抓取MySQL中的数据进行监控汇总告警 考虑到一些微软产品使用Powershell更为方便,于是找了些资料,尝试 ...

  4. CSS躬行记(6)——滤镜

    滤镜(filter)可改造元素的视觉呈现,CSS内置的滤镜有10种,通过SVG文件还能自定义滤镜. 一.调色滤镜 调色滤镜可控制元素的模糊.颜色.亮度等变化,并且多个滤镜可组合在一起使用.这些滤镜大部 ...

  5. JuiceSSH:安卓平台免费好用的 SSH 客户端

    为了解决上下班路上或者没带电脑时,查看 Linux 服务器日志或者紧急运维的需求,最终找到了 JuiceSSH 这款软件,强烈推荐给大家. 简介 JuiceSSH 是一个为 Android 打造的全功 ...

  6. 基于 Njmon + InfluxDB + Grafana 实现性能指标实时可视监控

    引言 最近逛 nmon 官网时,发现了一个新工具 njmon,功能与 nmon 类似,但输出为 JSON 格式,可以用于服务器性能统计. 可以使用 njmon 来向 InfluxDB 存储服务器性能统 ...

  7. 结束基础,开始MVC之旅!

    今天终于把前端Extjs和基础折腾完,虽然每一个都只是实现一个小的实例,但是也算是把.NET基础和前端基础顺了一遍.也算是提升.不足就是高级的知识点并没有吃透,比如委托,lamda之类的,还得在后面的 ...

  8. J - Recommendations CodeForces - 1315D

    https://blog.csdn.net/w_udixixi/article/details/104479288 大意:n个数,每个数只能向上加,a[i]+1需要的时间是t[i],求使这n个数无重复 ...

  9. Largest Rectangle in a Histogram 杭电1506

    题目链接 :http://acm.hdu.edu.cn/showproblem.php?pid=1506 Problem Description A histogram is a polygon co ...

  10. v&n赛 内存取证题解(已更新)

    题目是一个raw的镜像文件 用volatility搜索一下进程 有正常的notepad,msprint,还有dumpit和truecrypt volatility -f mem.raw --profi ...