React Hooks 笔记
目录:
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 。
数据就是状态(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 。
import React from "react";
export default function Counter() {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
);
}
props:用于父子组件之间传递状态。把属性作为参数传递给子组件。
import React from "react";
// 子组件将 props 对象,解构赋值,取出 count 值。
function CountLabel({ count }) {
// 子组件用于显示颜色
const color = count > 10 ? "red" : "blue";
return <span style={{ color }}>{count}</span>;
}
export default function Counter() {
// 定义了 count 这个 state
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
// 父组件 以 count 属性作为参数传递给子组件。
<CountLabel count={count} />
</button>
</div>
);
}
理解 JSX 语法的本质
从本质上来说,JSX 并不是一个新的模板语言,而是一个语法糖。也就是说,不用 JSX 的写法,其实也是能够写 React 的。
React.createElement(
"div",
null,
React.createElement(
"button",
{ onClick: function onClick() {
return setCount(count + 1);
} },
React.createElement(CountLabel, { count: count })
)
);
使用 createElement API 创建组件实例。
- 第一个参数表示组件的类型;
- 第二个参数是传给组件的属性,也就是 props ;
- 第三个以及后续所有的参数则是子组件。
JSX 语法的本质,就是使用 createElement 创建组件实例,而 JSX 让组件的创建变得直观和高效。所以说 JSX 其实是一种语法糖。
React 组件的本质
React 组件的模型就是从 Model 到 View 的映射,这里的 Model 对应到 React 中就是 state 和 props。如下图所示:
在过去,我们需要处理当 Model 变化时,DOM 节点应该如何变化的细节问题。而现在,我们只需要通过 JSX,根据 Model 的数据用声明的方式去描述 UI 的最终展现就可以了,因为 React 会帮助你处理所有 DOM 变化的细节。而且,当 Model 中的状态发生变化时,UI 会自动变化,即所谓的数据绑定。
React Hooks 介绍
useState:让函数组件具有维持状态的能力
const [state, setState] = useState(initialState);
state 中永远不要保存可以通过计算得到的值。
- 从 props 传递过来的值。(不是绝对的,只是建议)
- 从 URL 中读取到的值。(每次需要用的时候从 URL 中读取)
- 从 cookie、localStorage 中读取的值。(每次要用的时候直接去读取)
useEffect:执行副作用
副作用是指一段和当前执行结果无关的代码。比如说要修改函数外部的某个变量,要发起一个请求,等。也就是说,在函数组件的当次执行过程中,useEffect 中代码的执行是不影响渲染出来的 UI 的。
useEffect(callback, dependencies)
- 第一个参数 callback 是要执行的函数。
- 第二个参数 dependencies 是可选的依赖项数组。
第二参数依赖项,为可选项。
- 如果不指定依赖项,那么 callback 函数会在每次函数组件执行完毕后执行。
- 如果指定了,则依赖项的值发生改变时,才会执行 callback 函数。
- 如果空数组作为依赖项,则只在首次执行时触发。
重点:useEffect 是每次组件 render 完后判断依赖并执行。
useEffect 允许 callback 函数内部 return 返回一个函数,用于在组件销毁的时候做一些清理的操作。比如移除事件的监听。
总结 useEffect 四种时机执行回调函数产生副作用:
- 每次 render 后执行:不提供第二个依赖项参数。比如
useEffect(() => {})
。 - 仅第一次 render 后执行:提供一个空数组作为依赖项。比如
useEffect(() => {}, [])
。 - 第一次以及依赖项发生变化后执行:提供依赖项数组。比如
useEffect(() => {}, [deps])
。 - 组件 unmount 后执行:返回一个回调函数。比如
useEffect() => { return () => {} }, [])
。
理解 Hooks 的依赖
定义依赖项时,需要注意以下三点:
- 依赖项中定义的变量一定是会在回调函数中用到的,否则声明依赖项其实是没有意义的。
- 依赖项一般是一个常量数组,而不是一个变量。
- React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型。如果你是每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。例如在函数组件内声明变量,每次组件刷新时都会创建新的变量,多以会导致每次都会执行回调函数,导致 Bug 。
Hooks 的使用规则
Hooks 的使用规则包括以下两个:
- 只能在函数组件的顶级作用域使用;
- 只能在函数组件或者其他 Hooks 中使用。
Hooks 只能在函数组件的顶级作用域使用
所谓顶层作用域,就是 Hooks 不能在循环、条件判断或者嵌套函数内执行,而必须是在顶层。同时 Hooks 在组件的多次渲染之间,必须按顺序被执行。因为在 React 组件内部,其实是维护了一个对应组件的固定 Hooks 执行列表的,以便在多次渲染之间保持 Hooks 的状态,并做对比。
总结为两点:
- 第一,所有 Hook 必须要被执行到。
- 第二,必须按顺序执行。
Hooks 只能在函数组件或者其它 Hooks 中使用
Hooks 作为专门为函数组件设计的机制,使用的情况只有两种,一种是在函数组件内,另外一种则是在自定义的 Hooks 里面。
useCallback:缓存回调函数
function Counter() {
const [count, setCount] = useState(0);
// 当组件刷新时,会重复 创建 新函数。
const handleIncrement = () => setCount(count + 1);
// ...
return <button onClick={handleIncrement}>+</button>
}
重点:每次创建新函数,会让接收事件处理函数的组件,触发重新渲染。
使用useCallback(fn, deps)
函数,缓存回调函数,优化组件性能。
- 参数 fn 是定义的回调函数。
- 参数 deps 是依赖的变量数组。
import React, { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = useCallback(
() => setCount(count + 1),
[count], // 只有当 count 发生变化时,才会重新创建回调函数
);
// ...
return <button onClick={handleIncrement}>+</button>
}
useMemo:缓存计算的结果
const value = useMemo(fn, deps);
- 参数 fn 是一个计算函数,return 一个计算结果。
- 参数 deps 是一个数组依赖。
与 useCallback 相比,一个是缓存函数,一个是缓存结果。另外,useMemo 的计算函数 fn 也可以 return 一个函数,做到与 useCallback 同样的缓存函数。useCallback 的功能其实是可以用 useMemo 来实现的。
useRef:在多次渲染之间共享数据
const myRefContainer = useRef(initialValue);
在 current 属性设置值,在函数组件多次渲染之间共享这个值。
使用 useRef 保存的数据,一般是和 UI 渲染无关的,因此当 ref 的值发生变化时,不会触发组件的重新渲染,这也是 useRef 区别于 useState 的地方。
useRef 还可以保存某个 DOM 节点的引用。结合 React 的 ref 属性,可以获得真实 DOM 节点的引用,并对这个节点进行操作。
useContext:定义全局状态
const value = useContext(MyContext);
React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。
使用 useContext 可以在函数组件中管理 Context 。
使用 React.createContext API 创建一个 Context 并使用 Provider 属性作为组件树的根组件。代码如下:
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
// 整个应用使用 ThemeContext.Provider 作为根组件
return (
// 使用 themes.dark 作为当前 Context
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 在 Theme Button 中使用 useContext 来获取当前的主题
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{
background: theme.background,
color: theme.foreground
}}>
I am styled by theme context!
</button>
);
}
使用 Context 是为了能够进行数据的绑定。当 Context 的数据发生变化时,使用这个数据的组件就会自动刷新。如果没有 Context,而是使用一个简单的全局变量,就很难去实现了。
创建自定义 Hooks
声明一个名字以 use 开头的函数,例如 useCounter 。
这个函数与 JavaScript 普通函数没有区别,可传参数,也可返回任意值。需要注意的是,自定义 Hooks 函数中必须用到其它 Hooks 才能让组件刷新,或者产生副作用。
(未完待更新…)