Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术随笔

    • git教程
    • 前端模块化:CommonJS,AMD,CMD,ES6
    • 如何动态加载本地图片
    • GitHub自动部署脚本
    • React生命周期详解
    • 将父组件的props作为组件的初始state的几种方案
    • webpack基础入门
    • StackOver记录:如何在function中使用redux
    • React中使用onClick时的三种性能优化
    • 思考React状态管理
    • forEach/for..in/of
    • styled-components使用记录
    • 深入理解forEach与forof
    • Node技术架构
    • Redux、Mobx状态管理杂谈
    • JS中的类型检测
    • 如何判断图片是否在可视区域
    • 如何使用图片的懒加载操作
    • JavaScript模块化:从CommonJS到AMD
    • git查看:修改用户名、密码
    • 保持数据Immutable的几种写法
    • 如何实现一个Promise.all
    • resolve(Promise对象)会多出两个微任务
    • todo_基于Mocha的测试驱动开发
    • 浏览器页面渲染机制
    • todo-响应式编程之数据劫持
    • todo-immer库的介绍
    • React-Hooks 基础原理解析
      • 0.前言
      • 1.useState 原理解析
        • 使用说明
        • useState原理分析
        • 使用 useRef 保证一份数据地址
      • 2. 核心 Hooks 整理
        • 1. useState
        • 2.useReducer
        • 3.useContext
        • 4.useEffect | useLayoutEffect
        • 5.Memo | useMemo | useCallback
        • 6. useRef
        • 7.forwardRef
        • 8.自定义 Hook
      • 3.在Hooks 中实现setState的第二个参数
    • JS基本功
    • 函数闭包与this指针
    • 收集 DUMI 配置过程中遇到的问题
    • typescript 类型工具
    • 统一接口的导入导出
    • 网络安全:如何预防 XSS 攻击
    • 浏览器页面渲染机制【2023】
    • 跨域资源请求
    • 浏览器事件循环机制
    • 如何理解浏览器的 user agent 用户代理的含义?
  • 转载文章

  • Mac相关

  • 前端学习周报

  • 包管理工具

  • 技术随笔
  • 技术随笔
wangjiasheng
2022-04-09
目录

React-Hooks 基础原理解析

# 0.前言

由于在工作中并没有使用过Hooks,但考虑目前使用函数式编程已是大势所趋。

所以本篇博客就来好好的梳理这部分的知识点,属于边学边写,尽量将每个 Hooks 钩子函数的使用特性给梳理清楚。

# 1.useState 原理解析

# 使用说明

const [n, setN] = React.useState(0)  // 简单数据类型
1

使用 setXXX 方法更新变量时,有几个特别注意的点,新生一般容易犯的错误:

  1. 当对 复杂数据 类型修改状态时,需始终保持数据的 immutable 特性。

    const [user, setUser] = React.useState({name: 'Jack', age: 18})
    const onClick = () =>{
      setUser({
        ...user, // 
        name: 'Frank' //这里的name覆盖之前的name
      })
    }
    
    1
    2
    3
    4
    5
    6
    7

    否则,在 obj 引用地址不发生变化的话,React 就认为数据没有发生改变。

  2. ⭐️在使用 set 函数对变量进行更新时,更推荐使用函数写法,如下:

    const [n, setN] = React.useState(0)  // 简单数据类型
    const onClick = ()=>{
      setN(n+1); // 新手写法
      setN(x => x + 1); // 推荐写法
    }
    
    1
    2
    3
    4
    5

    以上两者的区别是,setN 接受的是一个n+1 的变量,而后者的写法 setN 接受的是一个表达式,其中 x 始终能获取到最新的 n 。

    更推荐表达式的原因:

    1. 避免可能出现的 stale closure (陈旧闭包)问题。
    2. 懒计算,如 setN(()=>( 1+2+3+4+5 )),只有执行时才会去进行运算。

# useState原理分析

在刚接触Hooks 组件的时候一直有个疑问,在真实的项目中,往往需要使用多个 useState 时,见伪代码如下:

function App(){
  const [n1, setN1] = React.useState(0)  
  const [n2, setN2] = React.useState(0)  
  console.log(n1);
  console.log(n2);
  const onClick1 = ()=>{
    setN1(x=>x+1);
  }
  const onClick2 = ()=>{
    setN2(x=>x+1);
  }
  return (<div>.....</div>)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

以上,是 useState 的基本操作,但是在使用的时候一定会问两个问题:

  1. 在多次调用React.useState 是如何对应具体的变量的,为何不冲突呢?
  2. 将变量 n1 打印后,n1 的变量每次都会发生改变,而最近的 n1 数值保存在什么地方呢?

下面这段代码是模拟实现setState,非实际 React 源码:

let _state = [] ;// 使用 数组 顺序地存储变量的最新值,如[n1,n2]
let index = 0;
const myUseState = initialValue => {
  _state[index] = _state[index] === undefined ? initialValue : _state[index];
  const setState = newValue =>{
    render(); // 触发 render 函数
  }
  index += 1; // 更新 数组 索引
  return [_state[index], setState]
}

// 封装渲染
conset render = ()=>{
  let index = 0;
  ReactDom.render(<App/> ,rootElement);
  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

在上述代码中,核心思路是:使用 _state 数组 + index 的形式,"顺序" 缓存了变量,可通过 index 找到变量的位置并对其进行修改。

而这一切都源于数组的有序性,这也解释了,为何 useState 不允许卸载if 语句中,因为这样会破坏useState 的调用顺序。

需要说明的是:

  • 在 React 源码中,在使用 setN 时,实际上是生成一个新的组件,在这个组件对应的vNode 中挂载着上述代码中的 _state 数组以及 index ,而非全局作用域。通过DOM diff 算法最终完成视图层的更新。

  • 组件的更新过程如下图所示:

    由上图可知,同一个时间一个变量会存在新旧之分,在两个<App/> 组件中,同时挂载这新旧两个_state 以及 index 。

注:在源码中,React 节点应该是 FiberNode ,_state 的真实变量名为memoizedState,index 的实现则使用到的是链表结构。

# 使用 useRef 保证一份数据地址

在 React 的设计思路中最最核心的一点保持数据的immutable 特性,因此在设计 useState 这个api 时在值的保存以及更新方面,始终会创建一个 New State ,即上述新旧两个_state 挂载到虚拟节点上。

但若要保存数据独一份,并且做到视图的更新。在 Vue 中采用的ref 引用的方式实现的。而这边我们也可以通过 React.useRef + 构造 update 函数模拟出相同的效果。

function App(){
  const nRef = React.useRef(0);
  const [,update] = React.useState(null); // 仅是为了实现更新
  return(
  	<div className="App">
    	<button onClick={()=>{
    		nRef.current +=1; // Ref 的改变无法导致视图层的更新
    		update(nRef.current); // 当 nRef 变化时,则手动触发视图更新,
  		}}>
    </div>
  )
}
ReactDOM.render(<App/>, rootElement);
1
2
3
4
5
6
7
8
9
10
11
12
13

除了上述的做法外,我们也可以采用 useContext 以及 redux 中的store 达到相同的效果,这两者在维护数据地址的作用上基本相同,类似于维护了一个局部的全局作用域 ,由于篇幅原因就不再赘述了。

# 2. 核心 Hooks 整理

# 1. useState

上述已经对 useState 这个 api 做了比较深入的分析,这里就总结核心结论:

  1. useState使用时建议推荐接受函数的形式,避免陷入stale closure 。

    const [n,setN] = React.useState(0)
    setN(x=>x+1); // 函数形式
    
    1
    2
  2. 在进行状态更新时,始终返回 newValue ,对于复杂数据类型可以使用扩展运算符的形式,也可以使用各类api 库,如facebook 的 Immutable.js 库,或者 immer.js 库。

# 2.useReducer

该函数主要的特性如下:

  1. 迷你版的 redux 状态库。

    此 api 在 Hooks 函数中支持较晚,主要是仿造 redux 进行的状态管理库,简化了 redux 的功能以及api 的使用,相当于小型的 redux 库。

  2. 增强版的 useState

    useState在状态管理方面,细粒度太细。如表单提交案例中,同一表单类型的数据完全可以维护一个复杂数据对象即可。

  3. 不推荐使用,使用自定义 Hooks 可以达到更好的状态拆分形态,即使 useReduer 对redux 再简化,使用起来还是略微显得有些重。

# 3.useContext

核心功能:创造一个局部的全局变量(上下文)。

使用方法:

  1. 使用 C = createContext(initial) 。
  2. 使用<C.provider> 圈定作用域。
  3. 使用时直接通过 useContext(C) 来获取"全局变量"。

在使用 useContext 时,注意对函数引用地址的缓存,有时间这里补一个案例。

# 4.useEffect | useLayoutEffect

这个钩子函数的名字起的不好,此钩子并非只能将 副作用 操作放在此钩子中,实际上称为 afterRender 更好,因为每次在 render 后运行,可以用于替代以下生命周期函数:

  1. componentDidMount : 初始
  2. componentUpdate : 更新
  3. componentWillUnmount:卸载

这里第二个钩子useLayoutEffect ,需要对React中的渲染机制有一定了解,主要由以下链条:

render →\rightarrow→ 转化为vNode →\rightarrow→ Dom diff 操作 →\rightarrow→ 经过DOM 树解析等操作→\rightarrow→ useLayEffect() →\rightarrow→ 将 DOM 转化为屏幕上真实的pixels 像素点( render 结束) →\rightarrow→ useEffect()

主要有以下特点:

  • useLayoutEffect 总是比 useEffect 先执行。
  • 推荐使用 useEffect 优先渲染,原因是为了用户体验,即先让页面加载出来再说。
  • 什么使用用?当useEffect 中存在 DOM 操作时。
  • 什么时候不用? 据说 SSR 中不存在useLayoutEffect() 钩子函数,若需要做到同构效果的话,不推荐使用。

# 5.Memo | useMemo | useCallback

这三个函数均是优化React 重复渲染的问题的

  • Memo

    const App = React.memo((props)=>{ ... })
    
    1

    等价于Class extends PureComponent ,一层浅比较。

  • useMemo : 缓存引用地址。

  • useCallback:属于useMemo 的语法糖,当使用useMemo缓存一个function 时

    useMemo(()=>{
      return (x)=>{
        console.log(x)
      }
    },[m])
    
    1
    2
    3
    4
    5

    如果使用callback 时,则为:

    useCallback((x)=>{console.log(x)},[m])
    
    1

# 6. useRef

该 api的使用目的:当希望维护一个引用地址保持不变的变量时,可以使用useRef()

使用方法:

const count = useRef(0);
// 从 current 上读取数值
count.current +=1;
1
2
3

注意此时,count.value 变化时,在React 中是不会触发视图更新的,此时我们需要构造一个update 函数,这一点在前文已经叙述过了。

在 Vue3 中同样存在 ref ,不同之处在于 Vue3 会自动触发 render 操作。

# 7.forwardRef

在Class 组件中有两个特殊的关键字是不能被占用的,即 key 和 ref,而在 Hooks 中,是不存在 ref 属性的,如果我们希望使用 ref 字段的话,希望在原有组件上包一层 forwardRef。

function App(){
  const buttonRef = useRef(null); // 最好还是要传下 null
  return(
  	<div>
    		<Button ref={buttonRef}>按钮</Button>
    </div>
  )
}

// 可以从 forwarRef 包裹的组件中获取 props 以及 ref 属性
const Button = React.forwardRef((props,ref)=>{
  return <button ref={ref} {...props} />
})

const rootElement = document.getElementById("root");
ReactDOM.render(<App/>, rootElement)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 8.自定义 Hook

# 3.在Hooks 中实现setState的第二个参数

编辑 (opens new window)
上次更新: 2022/06/26, 23:06:00
todo-immer库的介绍
JS基本功

← todo-immer库的介绍 JS基本功→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式