React-hooks 基础

108 阅读10分钟

1. hooks-介绍

大致步骤:

  • hooks 解释
  • hooks 作用
  • 有了 hooks 之后组件开发模式

具体内容:

1.hooks 解释

  • Hooks:钩子、钓钩、钩住 ,Hooks 是 React v16.8 中的新增功能

2.hooks 作用

  • 为函数组件提供状态、生命周期等原本 class 组件中提供的 React 功能
  • 可以理解为通过 Hooks 为函数组件钩入 class 组件的特性
  • 注意:Hooks 只能在函数组件中使用,自此,函数组件成为 React 的新宠儿

3.React v16.8 版本前后,组件开发模式的对比:

  • React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)
  • React v16.8 及其以后:
  1. class 组件(提供状态) + 函数组件(展示内容)
  2. Hooks(提供状态) + 函数组件(展示内容)
  3. 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件

总结:

  • 虽然有了 Hooks,但 React 官方并没有计划从 React 库中移除 class
  • 有了 Hooks 以后,不能再把函数组件称为无状态组件了,因为 Hooks 为函数组件提供了状态

2. hooks-解决的问题

大致步骤:

  • 组件的状态逻辑复用问题
  • class 组件自身的问题

具体内容:

1.组件的状态逻辑复用问题

  • 在 Hooks 之前,组件的状态逻辑复用经历了:mixins(混入)、HOCs(高阶组件)、render-props 等模式
  • (早已废弃)mixins 的问题:1. 数据来源不清晰 2. 命名冲突
  • HOCs、render-props 的问题:重构组件结构,导致组件形成 JSX 嵌套地狱问题

2.class 组件自身的问题

  • 选择:函数组件和 class 组件之间的区别以及使用哪种组件更合适
  • 需要理解 class 中的 this 是如何工作的
  • 相互关联且需要对照修改的代码被拆分到不同生命周期函数中
  • 相比于函数组件来说,不利于代码压缩和优化,也不利于 TS 的类型推导

总结:

  • 正是由于 React 原来存在的这些问题,才有了 Hooks 来解决这些问题

3. hooks-渐进策略

大致步骤:

  • 什么是渐进式策略?
  • 在 hooks 中开发会使用那些知识?

具体内容:

1.什么是渐进式策略(项目开发场景)

  • 不推荐直接使用 Hooks 大规模重构现有组件
  • 推荐:新功能用 Hooks,复杂功能实现不了的,也可以继续用 class
  • 找一个功能简单、非核心功能的组件开始使用 hooks

2.在 hooks 中开发会使用那些知识?

  • class 组件相关的 API 在 hooks 中不可用
    • class 自身语法,比如,constructor、static 等
    • 钩子函数,componentDidMount、componentDidUpdate、componentWillUnmount
    • this 相关的用法
  • 原来学习的 React 内容还是要用的
    • JSX:{}、onClick={handleClick}、条件渲染、列表渲染、样式处理等
    • 组件:函数组件、组件通讯
    • React 开发理念:单向数据流、状态提升 等
    • 解决问题的思路、技巧、常见错误的分析等

总结:

  1. react 没有计划从 React 中移除 class
  2. react 将继续为 class 组件提供支持
  3. 可以在项目中同时使用 hooks 和 class

4. useState-基本使用

大致步骤:

  • useState 作用?
  • useState 语法?
  • useState 使用步骤?
  • useState 写法正确姿势

具体内容:

1.useState 作用

  • 为函数组件提供状态
  • 它是一个 hook,就是一个特殊的函数,让你在函数组件中获取状态等 React 特性。
  • 名称上看 hook 都以use开头useXxx

2.useState 语法

// 参数:状态初始值(数值、字符串、数组,对象)
// 返回值:stateArray 是一个数组
const stateArray = useState(0);
// 索引 0 表示:状态值(state)
const state = stateArray[0];
// 索引 1 表示:修改状态的函数(setState)
const setState = stateArray[1];

3.useState 使用步骤

  1. 导入 useState hook
  2. 调用 useState 函数,并传入状态的初始值
  3. 从 useState 函数的返回值中,拿到状态和修改状态的函数
  4. 在 JSX 中展示状态
  5. 在按钮的点击事件中调用修改状态的函数,来更新状态

import { useState } from 'react';

const Count = () => {
  // stateArray 是一个数组
  const stateArray = useState(0);
  const state = stateArray[0];
  const setState = stateArray[1];
 
  return (
    <div>
      {/* 展示状态值 */}
      <h1>状态为:{state}</h1>
      {/* 点击按钮,让状态值 +1 */}
      <button onClick={() => setState(state + 1)}>+1</button>
    </div>
  );
};

4.useState 写法正确姿势

  • 数据函数需要符合业务语义,修改状态的函数名称以 set 开头,后面跟上状态的名称。
import { useState } from 'react';
 
const Count = () => {
  // 解构:
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <h1>计数器:{state}</h1>
      <button onClick={() => setState(state + 1)}>+1</button>
    </div>
  );
};

总结:

  • 给 useState 提供初始化值,返回数组。
    • 数组[0] 状态数据
    • 数组[1] 修改状态函数
  • 参考写法:const [count, setCount] = useState(0)

5. useState-读取和修改状态

大致步骤:

  • 读取状态
  • 修改状态

具体内容:

1.读取状态

  • 读取状态:useState 提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用
const UserCom = () => {
  const [user, setUser] = useState({ name: 'jack', age: 18 });
  return (
    <div>
      <p>姓名:{user.name}</p>
      <p>年龄:{user.age}</p>
    </div>
  );
};

2.修改状态

  • setUser(newValue) 是一个函数,参数表示:新的状态值
  • 调用该函数后,将使用新的状态值替换旧值
  • 修改状态后,因为状态发生了改变,所以该组件会重新渲染
const UserCom = () => {
  const [user, setUser] = useState({ name: 'jack', age: 18 });
  const onAgeAdd = () => {
    setUser({
      ...user,
      age: user.age + 1,
    });
  };
  return (
    <div>
      <p>姓名:{user.name}</p>
      <p>年龄:{user.age}</p>
      <button onClick={onAgeAdd}>年龄+1</button>
    </div>
  );
};

总结:

  • 修改状态的时候,一定要使用新的状态替换旧的状态

6. useState-组件更新过程

大致步骤:

  • 组件初始化时候的事情
  • setState 后发生的事情

具体内容:

1. 组件初始化时候的事情

  1. 从头开始执行该组件中的代码逻辑
  2. 调用 useState(0) 将传入的参数作为状态初始值,即:0
  3. 渲染组件,此时,获取到的状态 count 值为: 0

2. setState 后发生的事情

  1. 点击按钮,调用 setCount(count + 1) 修改状态,因为状态发生改变,所以,该组件会重新渲染
  2. 组件重新渲染时,会再次执行该组件中的代码逻辑
  3. 再次调用 useState(0),此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1
  4. 再次渲染组件,此时,获取到的状态 count 值为:1
import { useState } from 'react';
const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
};

总结:

useState 的初始值(参数)只会在组件第一次渲染时生效,以后的每次渲染,useState 获取到都是最新的状态值。

7. useState-使用原则

大致步骤:

  • 定义多个状态的原则
  • hook 函数书写位置原则

具体内容:

1. 定义多个状态的原则

  1. 调用 useState Hook 多次即可,每调用一次 useState Hook 可以提供一个状态
  2. useState Hook 多次调用返回的 [state, setState],相互之间,互不影响
  3. 尽量按照业务来定义数据,不要全部定义在一起,因为是替换,不是合并

2. hook 函数书写位置原则

  1. React Hooks 只能直接出现在 函数组件 中
  2. React Hooks 不能嵌套在 if/for/其他函数 中
  3. 原理:React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook

3. 可以通过开发者工具进行查看组件的 hooks

总结:

  • 只能在函数组件中使用,不能嵌套在 分支循环语句 中,react 存储 hooks 状态按顺序存储。

8. useEffect-副作用

大致步骤:

  • side effect 副作用专业解释
  • 通过生活例子,理解副作用
  • 使用函数组件常见的副作用

具体内容:

1.side effect 副作用专业解释

  1. 在计算机科学中,如果一个函数或其他操作修改了其局部环境之外的状态变量值,那么它就被称为有副作用
  2. 在函数组件中:职责就行根据状态渲染 UI,其他的事情都是副作用

2.使用函数组件常见的副作用

  1. 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
  2. 常见的副作用(side effect):数据(Ajax)请求、手动修改 DOM、localStorage、console.log 操作等
  3. 当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了

总结:

  • 对于 react 组件来说,除了渲染 UI 之外的其他操作,都可以称之为副作用

9. useEffect-基本使用

大致步骤:

  • 语法介绍
  • 随堂练习

具体内容:

1.语法介绍

  • 参数:回调函数(称为 effect),就是在该函数中写副作用代码
  • 执行时机:该 effect 会在组件第一次渲染以及每次组件更新后执行
  • 相当于 componentDidMount + componentDidUpdate

2.随堂练习

  • count 更新的时候显示到标题
import { useEffect } from 'react';
 
const Counter = () => {
  const [count, setCount] = useState(0);
 
  useEffect(() => {
    document.title = `当前已点击 ${count} 次`;
  });
 
  return (
    <div>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
};

总结:

  • 在函数组件处理副作用 useEffect(()=>{}) 组件初始化、更新的时候执行

10. useEffect-依赖项

大致步骤:

  • 默认使用 useEffect 的问题
  • useEffect 依赖项的使用

具体内容:

1.默认使用 useEffect 的问题

  • useEffect(()=>{}) 只要状态发生更新 useEffect 的 effect 回调就会执行
  • 如果组件中有另外一个状态,另一个状态更新时,刚刚的 effect 回调也会执行

2.useEffect 依赖项的使用

  • 跳过不必要的执行,只在 count 变化时,才执行相应的 effect
  • useEffect(()=>{},[依赖项]) 依赖项的值变化才会执行 effect
import { useEffect } from 'react';
 
const Counter = () => {
  const [count, setCount] = useState(0);
  const [loading, setLoading] = useState(false);
 
  useEffect(() => {
    document.title = `当前已点击 ${count} 次`;
  }, [count]);
 
  return (
    <div>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(!loading)}>切换 loading</button>
    </div>
  );
};

总结:

  • useEffect(()=>{},[依赖项]) 依赖项可以指定某些状态变化再去执行副作用

11. useEffect-不要对依赖项撒谎

具体内容:

  • useEffect 完全指南:useEffect 完整指南 — Overreacted
  • useEffect 回调函数(effect)中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中
  • 如果 useEffect 回调函数中用到了某个数据,但是,没有出现在依赖项数组中,就会导致一些 Bug 出现
  • 所以,不要对 useEffect 的依赖撒谎
const App = () => {
  const [count, setCount] = useState(0);
 
  // 错误演示:
  useEffect(() => {
    document.title = '点击了' + count + '次';
  }, []);
 
  return (
    <div>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
};

总结:

  • 副作用中使用的状态,需要写在依赖项中

12. useEffect-依赖是一个空数组

能够设置 useEffect 的依赖,让组件只有在第一次渲染后会执行

大致步骤:

  • 知道 useEffect 的依赖是一个空数组何时执行
  • 一般在这样的 effect 中做些什么事

具体内容:

1.useEffect 的第二个参数,还可以是一个空数组([])

  • 表示只在组件第一次渲染后执行 effect
  • 该 effect 只会在组件第一次渲染后执行,因此可以执行像事件绑定等只需要执行一次的操作。
  • 相当于 class 组件的 componentDidMount 钩子函数的作用
useEffect(() => {
  const handleResize = () => {};
  window.addEventListener('resize', handleResize);
}, []);

注意:

  • 跟 useState Hook 一样,一个组件中也可以调用 useEffect Hook 多次
  • 推荐:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect

13. useEffect-清除副作用

能够在组件卸载的时候清除副作用

具体内容:

  • effect 的返回值是可选的,可省略。也可以返回一个清理函数,用来执行事件解绑等清理操作
  • 清理函数的执行时机:
    • 清理函数会在组件卸载时以及下一次副作用回调函数调用的时候执行,用于清除上一次的副作用。
    • 如果依赖项为空数组,那么会在组件卸载时会执行。相当于组件的componetWillUnmount
useEffect(() => {
  const handleResize = () => {};
  window.addEventListener('resize', handleResize);
 
  // 这个返回的函数,会在该组件卸载时来执行
  // 因此,可以去执行一些清理操作,比如,解绑 window 的事件、清理定时器 等
  return () => window.removeEventListener('resize', handleResize);
}, []);

14. useEffect-使用总结

说出 useEffect 的 4 种使用使用方式

具体内容:

// 1
// 触发时机:1 第一次渲染会执行 2 每次组件重新渲染都会再次执行
// componentDidMount + ComponentDidUpdate
useEffect(() => {});
 
// 2(使用频率最高)
// 触发时机:只在组件第一次渲染时执行
// componentDidMount
useEffect(() => {}, []);
 
// 3(使用频率最高)
// 触发时机:1 第一次渲染会执行 2 当 count 变化时会再次执行
// componentDidMount + componentDidUpdate(判断 count 有没有改变)
useEffect(() => {}, [count]);
 
// 4
useEffect(() => {
  // 返回值函数的执行时机:组件卸载时
  // 在返回的函数中,清理工作
  return () => {
    // 相当于 componentWillUnmount
  };
}, []);
 
useEffect(() => {
  // 返回值函数的执行时机:1 组件卸载时 2 count 变化时
  // 在返回的函数中,清理工作
  return () => {};
}, [count]);