如果做完这个笔记,还需要回头看原文,那么讲毫无意义。

这是修言在拉钩的一门课程

深入浅出搞定 React

基础知识

JSX

  • JSX 的本质是什么,它和 JS 之间到底是什么关系?
  • 为什么要用 JSX?不用会有什么后果?
  • JSX 背后的功能模块是什么,这个功能模块都做了哪些事情?

Facebook 公司给 JSX 的定位是 JavaScript 的“扩展”,他在 js 编译后,会变成 React.createElement,这个编译过程使用的就是 Babel

JSX 语法糖允许前端开发者使用我们最为熟悉的类 HTML 标签语法来创建虚拟 DOM,在降低学习成本的同时,也提升了研发效率与研发体验。

Drawing 3.png

在实际的面试场景下,许多候选人由于缺乏对源码的了解,谈及 createElement 时总会倾向于去夸大它的“工作量”。但其实,相信你也已经发现了,createElement 中并没有十分复杂的涉及算法或真实 DOM 的逻辑,它的每一个步骤几乎都是在格式化数据

可以联想到字节码和机器码之间的转换

Drawing 7.png

ReactElement 就是我们常说的虚拟 DOM, 他和真实 DOM 之间还有个 ReactDOM.render()

React 生命周期

React 数据通信

所谓单向数据流,指的就是当前组件的 state 以 props 的形式流动时,只能流向组件树中比自己层级更低的组件。

  • 父 - 子 props
  • 子 - 父 传递方法
  • 兄弟组件 通过连接同一个属性值 进行并且传递方法
  • 跨层级 事件中心 发布中心

Context

 
const AppContext = React.createContext()
const { Provider, Consumer } = AppContext
 
//Provider
 
<Provider value={title: this.state.title, content: this.state.content}>
<Title />
<Content />
</Provider>
 
 
 
//Consumer
 
<Consumer>
{value => <div>{value.title}</div>}
</Consumer>
 

过时的 Context

  • 代码不够优雅
  • 如果组件提供的一个 Context 发生了变化,而中间父组件的 shouldComponentUpdate 返回 false,那么使用到该值的后代组件不会进行更新。使用了 Context 的组件则完全失控,所以基本上没有办法能够可靠的更新 Context。这篇博客文章很好地解释了为何会出现此类问题,以及你该如何规避它。 ——React 官方

Redux

Hooks

React-Hooks 自 React 16.8 以来才真正被推而广之

函数组件与类组件

函数组件与类组件的对比:无关“优劣”,只谈“不同”

  • 类组件需要继承 class,函数组件不需要;
  • 类组件可以访问生命周期方法,函数组件不能;
  • 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以;
  • 类组件中可以定义并维护 state(状态),而函数组件不可以;
  • ……

类组件

React 类组件内部预置了相当多的“现成的东西”等着你去调度/定制,state 和生命周期就是这些“现成东西”中的典型。要想得到这些东西,难度也不大,你只需要轻轻地继承一个 React.Component 即可。

但是 多就是好吗

他增加了我们的学习成本,这时就显得类组件太重了。

函数组件

函数组件肉眼可见的特质自然包括轻量、灵活、易于组织和维护、较低的学习成本等。这些要素毫无疑问是重要的,它们也确实驱动着 React 团队做出改变。但是除此之外,还有一个非常容易被大家忽视、也极少有人能真正理解到的知识点,我在这里要着重讲一下。这个知识点缘起于 React 作者 Dan 早期特意为类组件和函数组件写过的一篇非常棒的对比文章,这篇文章很长,但是通篇都在论证这一句话:

函数组件会捕获 render 内部的状态,这是两类组件最大的不同。

函数组件更加契合 React 框架的设计理念

UI = render(data)

为什么需要 React-Hooks”

  1. 告别难以理解的 Class:把握 Class 的两大“痛点”

this 和生命周期两个痛点

  1. Hooks 如何实现更好的逻辑拆分

**我们可以有专门管理订阅的函数组件、专门处理 DOM 的函数组件、专门获取数据的函数组件等。Hooks 能够帮助我们**实现业务逻辑的聚合,避免复杂的组件和冗余的代码

  1. 状态复用:Hooks 将复杂的问题变简单

使用原则

使用原则,原则的内容如下:

  1. 只在 React 函数中调用 Hook;
  2. 不要在循环、条件或嵌套函数中调用 Hook。

原理

从源码调用流程看原理:Hooks 的正常运作,在底层依赖于顺序链表

function mountWorkInProgressHook() {
	// 注意,单个 hook 是以对象的形式存在的
 
	var hook = {
		memoizedState: null,
		baseState: null,
		baseQueue: null,
		queue: null,
		next: null,
	};
 
	if (workInProgressHook === null) {
		// 这行代码每个 React 版本不太一样,但做的都是同一件事:将 hook 作为链表的头节点处理
		firstWorkInProgressHook = workInProgressHook = hook;
	} else {
		// 若链表不为空,则将 hook 追加到链表尾部
		workInProgressHook = workInProgressHook.next = hook;
	}
 
	// 返回当前的 hook
	return workInProgressHook;
}

到这里可以看出,hook 相关的所有信息收敛在一个 hook 对象里,而 hook 对象之间以单向链表的形式相互串联

虚拟 DOM

发展

  1. 原生 JS 支配下的“人肉 DOM” 时期
  2. 解放生产力的先导阶段:jQuery 时期

对 DOM API 进行封装

  1. 民智初启:早期模板引擎方案

它更新 DOM 的方式是将已经渲染出 DOM 整体注销后再整体重渲染,并且不存在更新缓冲这一说。所以有性能上的问题

  1. 全自动:虚拟 DOM

虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能

误区:虚拟 DOM 性能上具有优势,虚拟 DOM 的劣势主要在于 JS 计算的耗时,而 DOM 操作的能耗和 JS 计算的能耗根本不在一个量级。但是在实际使用中,频繁的 setState 这种修改少量信息的情况下,虚拟 DOM 具有性能上的优势。

价值

虚拟 DOM 解决的关键问题有以下两个。

  1. 研发体验/研发效率的问题
  2. 跨平台的问题

栈调和

Diff 确实是调和过程中最具代表性的一环,但是 调和 !== Diff

setState 是同步还是异步的

结论:并不是 setTimeout 改变了 setState,而是 setTimeout 帮助 setState “逃脱”了 React 对它的管控。只要是在 React 管控下的 setState,一定是异步的

主流程:

3.png

锁结构

reduce = () => {
	// 进来先锁上
 
	isBatchingUpdates = true;
	setTimeout(() => {
		console.log("reduce setState前的count", this.state.count);
		this.setState({
			count: this.state.count - 1,
		});
 
		console.log("reduce setState后的count", this.state.count);
	}, 0);
 
	// 执行完函数再放开
 
	isBatchingUpdates = false;
};

咱们开头锁上的那个 isBatchingUpdates,对 setTimeout 内部的执行逻辑完全没有约束力。因为 isBatchingUpdates 是在同步代码中变化的,而 setTimeout 的逻辑是异步执行的。当 this.setState 调用真正发生的时候,isBatchingUpdates 早已经被重置为了 false,这就使得当前场景下的 setState 具备了立刻发起同步更新的能力。所以咱们前面说的没错——setState 并不是具备同步这种特性,只是在特定的情境下,它会从 React 的异步管控中“逃脱”掉

Fiber

React 团队在用户体验方面最为要紧的一个追求。 快速响应

实现增量渲染的目的,是为了实现任务的可中断、可恢复,并给不同的任务赋予不同的优先级,最终达成更加顺滑的用户体验

React 16 前,Reconciler(找不同) Renderer(渲染不同) ,从 Reconciler 到 Renderer 这个过程是严格同步的。

React 16 以后,增加了一层 Scheduler(调度器),首先,每个更新任务都会被赋予一个优先级。当更新任务抵达调度器时,高优先级的更新任务(记为 A)会更快地被调度进 Reconciler 层;此时若有新的更新任务(记为 B)抵达调度器,调度器会检查它的优先级,若发现 B 的优先级高于当前任务 A,那么当前处于 Reconciler 层的 A 任务就会被中断,调度器会将 B 任务推入 Reconciler 层。当 B 任务完成渲染后,新一轮的调度开始,之前被中断的 A 任务将会被重新推入 Reconciler 层,继续它的渲染之旅,这便是所谓“可恢复”