这是我参与「第四届青训营 」笔记创作活动的的第10天
React 的历史和应用
历史
一个东西的出现往往是因为在某个历史阶段人们产生了这个需求。
- 2010年, Facebook 在其 php 生态中,引入了 xhp 框架,首次引入了组合式组件的思想,启发了后来的 React 的设计。
- 2011年, Jordan Walke 创造了 FaxJS ,也就是后来的 React 原型:
- 2012年,在 Facebook 收购 Instagram 后,该 FaxJS 项目在内部得到使用, Jordan Walke 基于 FaxJS 的经验,创造了 React 。
- 2013年, React 正式开源,在2013 JSConf 上 Jordan Walke 介绍了这款全新的框架。
- 2014年至今,生态大爆发,各种围绕 React 的新工具/新框架开始涌现。
应用
- 前端应用开发,如 Facebook , Instagram , Netflix 网页版。
- 移动原生应用开发,如 Instagram , Discord , Oculus 。
- 结合 Electron ,进行桌面应用开发。
- React Three Fiber 用于写 3D 图形的库。
React 的设计思路
UI 编程痛点
- 状态更新, UI 不会自动更新,需要手动地调用 DOM 进行更新。
- 欠缺基本的代码层面的封装和隔离,代码层面没有组件化。
- UI 之间的数据依赖关系,需要手动维护,如果依赖链路过长,则会遇到 “Callback Hell”。
转换式与响应式
编译器可能是最复杂的转换式系统。
前端 界面/代码 的模式:前端界面本身不需要那么多计算,它需要监听事件,每当事件发生时它需要做一些事情,这个事情可能会改变系统里面的某些变量,这些变量的改变可能会影响其它变量(一旦事件发生了,这些事件会扩散,然后会影响一些事情)。
响应式注重点(为什么 JavaScript 是异步编程):
- 我要监听事件;
- 当事件发生之后我要做什么事情。
响应式编程
对 React 的期望:
- 状态更新, UI 自动更新。
- 前端代码组件化,可复用,可封装。
- 状态之间的互相依赖关系,只需声明即可。
组件划分准则
- 组件是 组件的组合/原子组件。
- 组件内拥有状态,外部不可见。
- 父组件可将状态传入组件内部。
状态归属问题
状态归属于两个节点向上寻找到最近的祖宗节点。(单个组件用就把状态定义在当前组件,多个组件用就把状态定义在共同的父组件。)
React 是单向数据流(永远都是父组件给子组件传东西),这不代表子组件不能改变父组件的状态。
组件化
- 组件声明了状态和 UI 的映射。
- 组件有 Props/State 两种状态(组件内部有自己的状态,父组件可以给子组件传状态)。
- 组件可由其他组件拼装而成。
组件代码会是什么样子
- 组件内部拥有私有状态 State 。
- 组件接受外部的 Props 状态提供复用性。
- 根据当前的 State/Props ,返回一个 UI 。
生命周期
Mounting : 组件挂载到 DOM 上; Updating :组件状态改变的时候; Unmounting :组件卸载的时候。
setState() :每次状态改变时都要重新执行 render 函数。
React (hooks) 的写法
const [count, setCount] = useState(0);声明新状态需要手动调用 useState 函数,这个函数传入状态的初始值,返回一个数组,第一个是状态本身,第二个是 setCount 函数。要改变状态不是直接去改变,需要手动的调用 setCount 函数(React 封装了一层,方便 React 接管和控制,例如:代码中没有手动的去更新 UI)。
useEffect 函数作用:副作用(不是纯函数):一个组件内部单纯的执行语句会对组件外部造成影响(比如:发起一个网络请求和外部系统做了一个交互)。
useEffect 执行时机:在图中代码中, useEffect 在组件 mount 的时候执行一次,只执行一次。
React 的实现
问题:
- JSX 不符合 JS 标准语法。
- 返回的 JSX 发生改变时,如何更新 DOM 。
- State/Props 更新时,要重新触发 render 函数(组件本身就是函数, render 函数就是这个函数)。
问题1:
React.createElement() 参数一是标签,参数二是属性,参数三是它的 children 。
问题2:
Virtual DOM (虚拟 DOM)
- Virtual DOM 是一种用于和真实 DOM 同步而在 JS 内存中维护的一个对象,它具有和 DOM 类似的树状结构并和 DOM 可以建立一一对应的关系。
- 它赋予了 React 声明式的 API : 您告诉 React 希望让 UI 是什么状态, React 就确保 DOM 匹配该状态。这使您可以从属性操作、事件处理和手动 DOM 更新这些在构建应用程序时必要的操作中解放出来。
每次组件状态(红色)发生更新,然后开始 Diffing ,计算它的扩散。因为一个组件状态更新之后,它返回的 JSX 和它返回的那个 Virtual DOM 片段,会重新计算一次生成新的 Virtual DOM ,然后新的和旧的做一次对比,这就是 Diffing 的过程, Diffing 的过程是一个递归的过程。因为父组件可以嵌套子组件,所以在 React 中当父组件状态发生改变时,它所有的子组件包括子组件的子组件,它会递归的发生重新执行 render 函数。
React 性能问题:位置很高的父组件状态发生改变了,它的子树全部都重新一遍 render 函数
How to Diff?
render 函数本身的执行速度不能太慢, DOM 的更新次数足够少, TradeOff 权衡。
问题描述:
给你两棵树,这两棵树上的节点都有自己的属性,提供一些操作几何:更改某个节点的属性、删除某个节点的 children 、增加某个节点的 children 、删除整个子树,求解让其中一颗树变成另外一棵树所需要操作的最小步骤的次数。
完美的最小 Diff 算法,需要 O(n^3) 的复杂度。
牺牲理论最小 Diff ,换取事件,得到了 O(n) 复杂度的算法: Heuristic O(n) Algorithm 。
React 状态管理库
第二种方式最大缺点:降低组件的复用性,相当于这个组件和外部数据强耦合了,一般都出现在实际的业务代码中。
推荐
状态机 —— 交通灯
应用级框架科普
NEXT.JS 内置的能力:
MODERN.JS :