React Hooks 笔记

目录:

[TOC]

React Hooks

虚拟 DOM 机制,React 会自动优化虚拟 DOM 更新到真实 DOM 节点。

使用 JSX 语法,使代码简洁、直观,提高维护效率。

React 在 2019 年 2 月发布的 16.8 版本中引入了 Hooks 概念。

React 组件开发,有 Class 组件、function 函数组件。Hooks 是一种扩展,可以在这两种组件同时使用。从函数组件方面来讲,Hooks 更多是为函数组件赋能。让函数组件具备 Class 组件的所有能力。两种组件可相互调用。

Hooks 可以轻松实现逻辑复用。将多个组件中重复的逻辑部分,抽离为一个依赖,就是创建自定义 Hooks,进而通过调用自定义 Hooks 实现逻辑复用。

目前 React 项目开发,基本以 function 函数组件 + Hooks 的方式开发。

理解 React

React 的中文含义是“反应”或“响应”,它描述了 React 前端框架的核心原理:当数据发生变化时,UI 能够自动把变化反映出来

用数据驱动 UI 。

React 机制

数据就是状态(State),当数据改变时,也就是状态发生改变,触发 UI(JSX 语法)重新渲染(Render),更新虚拟 DOM 到真实 DOM 节点。

理解 React 三个概念:组件、状态、JSX。

React 组件

在 React 中,UI 是通过组件去描述和组织的。分为两种。

内置组件:映射到 HTML 节点的组件,例如div input等,全部使用小写字母。

自定义组件:顾名思义自己创建的组件,使用大驼峰法命名,必须以大写字母开头,例如Layout Tooltip PageHeader

React 组件以树状结构组织到一起,一个 React 应用通常只有一个根组件。

使用 state 和 props 管理状态

state:函数组件使用useState()保存管理状态。useState 是 React 内部 Hooks 。

  1. import React from "react";
  2. export default function Counter() {
  3. const [count, setCount] = React.useState(0);
  4. return (
  5. <div>
  6. <button onClick={() => setCount(count + 1)}>{count}</button>
  7. </div>
  8. );
  9. }

props:用于父子组件之间传递状态。把属性作为参数传递给子组件。

  1. import React from "react";
  2. // 子组件将 props 对象,解构赋值,取出 count 值。
  3. function CountLabel({ count }) {
  4. // 子组件用于显示颜色
  5. const color = count > 10 ? "red" : "blue";
  6. return <span style={{ color }}>{count}</span>;
  7. }
  8. export default function Counter() {
  9. // 定义了 count 这个 state
  10. const [count, setCount] = React.useState(0);
  11. return (
  12. <div>
  13. <button onClick={() => setCount(count + 1)}>
  14. // 父组件 以 count 属性作为参数传递给子组件。
  15. <CountLabel count={count} />
  16. </button>
  17. </div>
  18. );
  19. }

理解 JSX 语法的本质

从本质上来说,JSX 并不是一个新的模板语言,而是一个语法糖。也就是说,不用 JSX 的写法,其实也是能够写 React 的。

  1. React.createElement(
  2. "div",
  3. null,
  4. React.createElement(
  5. "button",
  6. { onClick: function onClick() {
  7. return setCount(count + 1);
  8. } },
  9. React.createElement(CountLabel, { count: count })
  10. )
  11. );

使用 createElement API 创建组件实例。

  • 第一个参数表示组件的类型;
  • 第二个参数是传给组件的属性,也就是 props ;
  • 第三个以及后续所有的参数则是子组件。

JSX 语法的本质,就是使用 createElement 创建组件实例,而 JSX 让组件的创建变得直观和高效。所以说 JSX 其实是一种语法糖。

React 组件的本质

React 组件的模型就是从 Model 到 View 的映射,这里的 Model 对应到 React 中就是 state 和 props。如下图所示:

React 组件的模型

在过去,我们需要处理当 Model 变化时,DOM 节点应该如何变化的细节问题。而现在,我们只需要通过 JSX,根据 Model 的数据用声明的方式去描述 UI 的最终展现就可以了,因为 React 会帮助你处理所有 DOM 变化的细节。而且,当 Model 中的状态发生变化时,UI 会自动变化,即所谓的数据绑定

React Hooks 介绍

useState:让函数组件具有维持状态的能力

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

state 中永远不要保存可以通过计算得到的值。

  1. 从 props 传递过来的值。(不是绝对的,只是建议)
  2. 从 URL 中读取到的值。(每次需要用的时候从 URL 中读取)
  3. 从 cookie、localStorage 中读取的值。(每次要用的时候直接去读取)

useEffect:执行副作用

副作用是指一段和当前执行结果无关的代码。比如说要修改函数外部的某个变量,要发起一个请求,等。也就是说,在函数组件的当次执行过程中,useEffect 中代码的执行是不影响渲染出来的 UI 的。

  1. useEffect(callback, dependencies)
  • 第一个参数 callback 是要执行的函数。
  • 第二个参数 dependencies 是可选的依赖项数组。

第二参数依赖项,为可选项。

  • 如果不指定依赖项,那么 callback 函数会在每次函数组件执行完毕后执行。
  • 如果指定了,则依赖项的值发生改变时,才会执行 callback 函数。
  • 如果空数组作为依赖项,则只在首次执行时触发。

重点:useEffect 是每次组件 render 完后判断依赖并执行。

useEffect 允许 callback 函数内部 return 返回一个函数,用于在组件销毁的时候做一些清理的操作。比如移除事件的监听。

总结 useEffect 四种时机执行回调函数产生副作用:

  1. 每次 render 后执行:不提供第二个依赖项参数。比如 useEffect(() => {})
  2. 仅第一次 render 后执行:提供一个空数组作为依赖项。比如 useEffect(() => {}, [])
  3. 第一次以及依赖项发生变化后执行:提供依赖项数组。比如 useEffect(() => {}, [deps])
  4. 组件 unmount 后执行:返回一个回调函数。比如 useEffect() => { return () => {} }, [])

理解 Hooks 的依赖

定义依赖项时,需要注意以下三点:

  1. 依赖项中定义的变量一定是会在回调函数中用到的,否则声明依赖项其实是没有意义的。
  2. 依赖项一般是一个常量数组,而不是一个变量。
  3. React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型。如果你是每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。例如在函数组件内声明变量,每次组件刷新时都会创建新的变量,多以会导致每次都会执行回调函数,导致 Bug 。

Hooks 的使用规则

Hooks 的使用规则包括以下两个:

  1. 只能在函数组件的顶级作用域使用;
  2. 只能在函数组件或者其他 Hooks 中使用。

Hooks 只能在函数组件的顶级作用域使用

所谓顶层作用域,就是 Hooks 不能在循环、条件判断或者嵌套函数内执行,而必须是在顶层。同时 Hooks 在组件的多次渲染之间,必须按顺序被执行。因为在 React 组件内部,其实是维护了一个对应组件的固定 Hooks 执行列表的,以便在多次渲染之间保持 Hooks 的状态,并做对比。

总结为两点:

  • 第一,所有 Hook 必须要被执行到。
  • 第二,必须按顺序执行。

Hooks 只能在函数组件或者其它 Hooks 中使用

Hooks 作为专门为函数组件设计的机制,使用的情况只有两种,一种是在函数组件内,另外一种则是在自定义的 Hooks 里面

useCallback:缓存回调函数

  1. function Counter() {
  2. const [count, setCount] = useState(0);
  3. // 当组件刷新时,会重复 创建 新函数。
  4. const handleIncrement = () => setCount(count + 1);
  5. // ...
  6. return <button onClick={handleIncrement}>+</button>
  7. }

重点:每次创建新函数,会让接收事件处理函数的组件,触发重新渲染。

使用useCallback(fn, deps)函数,缓存回调函数,优化组件性能。

  • 参数 fn 是定义的回调函数。
  • 参数 deps 是依赖的变量数组。
  1. import React, { useState, useCallback } from 'react';
  2. function Counter() {
  3. const [count, setCount] = useState(0);
  4. const handleIncrement = useCallback(
  5. () => setCount(count + 1),
  6. [count], // 只有当 count 发生变化时,才会重新创建回调函数
  7. );
  8. // ...
  9. return <button onClick={handleIncrement}>+</button>
  10. }

useMemo:缓存计算的结果

  1. const value = useMemo(fn, deps);
  • 参数 fn 是一个计算函数,return 一个计算结果。
  • 参数 deps 是一个数组依赖。

与 useCallback 相比,一个是缓存函数,一个是缓存结果。另外,useMemo 的计算函数 fn 也可以 return 一个函数,做到与 useCallback 同样的缓存函数。useCallback 的功能其实是可以用 useMemo 来实现的。

useRef:在多次渲染之间共享数据

  1. const myRefContainer = useRef(initialValue);

在 current 属性设置值,在函数组件多次渲染之间共享这个值

使用 useRef 保存的数据,一般是和 UI 渲染无关的,因此当 ref 的值发生变化时,不会触发组件的重新渲染,这也是 useRef 区别于 useState 的地方。

useRef 还可以保存某个 DOM 节点的引用。结合 React 的 ref 属性,可以获得真实 DOM 节点的引用,并对这个节点进行操作。

useContext:定义全局状态

  1. const value = useContext(MyContext);

React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。

使用 useContext 可以在函数组件中管理 Context 。

使用 React.createContext API 创建一个 Context 并使用 Provider 属性作为组件树的根组件。代码如下:

  1. const themes = {
  2. light: {
  3. foreground: "#000000",
  4. background: "#eeeeee"
  5. },
  6. dark: {
  7. foreground: "#ffffff",
  8. background: "#222222"
  9. }
  10. };
  11. const ThemeContext = React.createContext(themes.light);
  12. function App() {
  13. // 整个应用使用 ThemeContext.Provider 作为根组件
  14. return (
  15. // 使用 themes.dark 作为当前 Context
  16. <ThemeContext.Provider value={themes.dark}>
  17. <Toolbar />
  18. </ThemeContext.Provider>
  19. );
  20. }
  21. // 在 Theme Button 中使用 useContext 来获取当前的主题
  22. function ThemedButton() {
  23. const theme = useContext(ThemeContext);
  24. return (
  25. <button style={{
  26. background: theme.background,
  27. color: theme.foreground
  28. }}>
  29. I am styled by theme context!
  30. </button>
  31. );
  32. }

使用 Context 是为了能够进行数据的绑定。当 Context 的数据发生变化时,使用这个数据的组件就会自动刷新。如果没有 Context,而是使用一个简单的全局变量,就很难去实现了。

创建自定义 Hooks

声明一个名字以 use 开头的函数,例如 useCounter 。

这个函数与 JavaScript 普通函数没有区别,可传参数,也可返回任意值。需要注意的是,自定义 Hooks 函数中必须用到其它 Hooks 才能让组件刷新,或者产生副作用

(未完待更新…)