青训营伴学笔记第六篇
React历史与应用
React 是一个由 Facebook 开发的 JavaScript 库,主要用于构建用户界面,特别是单页应用。它允许开发者创建可复用的 UI 组件,并且通过虚拟 DOM(Document Object Model)来高效地更新和渲染页面。
- 前端应用开发:Facebook、Instagram、Netflix 网页版等作为前端应用开发的例子。
- 移动原生应用开发:Instagram、Discord、Oculus 等,这些应用是基于 React 进行移动原生应用开发的例子。
- 结合 Electron 进行桌面应用开发:通过结合 Electron 进行桌面应用开发。
React发展历史
-
2010 年,Facebook 在其 php 生态中引入了 xhp 框架,首次引入了组合式组件的思想,启发了后来 React 的设计。
-
2011 年,Jordan Walke 创造了 FaxJS,也就是后来 React 的原型,它具有以下特点:
- Seamless Client Server Rendering(无缝的客户端 - 服务器渲染):一次编写,随处渲染 - 客户端或服务器。
- Reactive(响应式):视图会根据状态变化自动更新 - 无需绑定。
- Performant(高性能):使用字符串拼接实现快速渲染,代码量小。
- Structural(结构化):高级组件,功能定义明确,声明式视图。
-
2012 年,在 Facebook 收购 Instagram 后,FaxJS 项目在内部得到使用,Jordan Walke 基于 FaxJS 的经验,创造了 React。
-
2013 年正式开源,并在 2013 年的 JSConf 上由 Jordan Walke 进行了介绍。
-
2014 年至今,生态大爆发,各种围绕React的新工具/新框架开始涌现。
React的设计思路
UI编程的痛点
- 状态更新时,UI 不会自动更新,需要手动调用 DOM 进行更新。
- 欠缺基本的代码层面的封装和隔离,代码层面没有组件化。
- UI 之间的数据依赖关系需要手动维护,如果依赖链长,则会遇到 “Callback Hell”。
响应式和转换式
-
转换式系统
- 特点:给定 “输入” 求解 “输出”。
- 示例应用:编译器、数值计算。
- 解释:转换式系统通常是基于明确的输入数据,通过一系列预定义的规则和算法来产生对应的输出结果。例如编译器将源代码(输入)转换为机器可执行的代码(输出),数值计算则是基于输入的数值和操作来得出计算结果(输出)。
-
响应式系统
- 特点:监听事件,消息驱动。
- 示例应用:监控系统、UI 界面。
- 解释:响应式系统是基于事件和消息来进行操作的。系统会不断监听外部事件或消息,当有事件发生或消息传入时,系统会做出相应的反应。例如监控系统会监听环境中的变化(如温度、湿度、入侵等事件),并根据这些事件做出报警或记录等响应;UI 界面会监听用户的操作(如点击、滑动等事件),并根据这些操作来更新界面或执行相应功能.
响应式编程
- 状态更新,UI 自动更新。文中对比提到在其他情况下,状态更新后,UI 不会自动更新,需要手动地调用 DOM 进行更新。
- 前端代码组件化,可复用,可封装。文中对比提到在其他情况下,欠缺基本的代码层面的封装和隔离,代码层面没有组件化。
- 状态之间的互相依赖关系,只需声明即可。文中对比提到在其他情况下,UI 之间的数据依赖关系需要手动维护,如果依赖链路长,则会遇到 “Callback Hell”。
组件化
- 组件是 组件的组合 / 原子组件
- 组件是构建用户界面的独立、可复用的代码片段。它们可以是简单的原子组件(如一个按钮),也可以是由多个组件组合而成的复杂组件(如一个包含多个按钮和输入框的表单)。
- 组件内拥有状态,外部不可见
- 组件内部可以有自己的状态(state),这些状态通常用于存储和管理组件内部的数据。这些状态是私有的,外部组件无法直接访问。例如,一个计数器组件内部有一个状态来存储当前的计数值,外部组件不能直接修改这个计数值。
- 父组件可将状态传入组件内部
- 父组件可以通过属性(props)将数据传递给子组件。这种机制允许在组件树中进行数据的传递和共享。例如,父组件有一个用户列表,它可以将用户数据作为属性传递给子组件,子组件根据接收到的数据进行渲染。
组件设计的三个要点
- 组件声明了状态和 UI 的映射。这意味着在 React 中,组件的状态(数据)与用户界面(UI)是相互关联的,状态的改变会引起 UI 的相应更新。
- 组件有 Props/State 两种状态。Props(属性)是从父组件传递给子组件的数据,是不可变的;State(状态)是组件内部的数据,是可变的,并且状态的改变会触发组件的重新渲染。
- “组件” 可由其他组件拼装而成。这体现了 React 的组件复用性,一个复杂的 UI 界面可以由多个简单的组件组合而成,提高了开发效率和代码的可维护性。
组件代码会是什么样子
-
组件内部拥有私有状态 State。
- 解释:在 React 中,组件可以有自己的内部状态。这个状态只在组件内部可用,并且可以随着用户交互或其他操作而改变。例如,一个按钮组件可能有一个 “按下” 状态,这个状态只有按钮组件自己知道并处理。
-
组件接受外部的 Props 状态提供复用性。
- 解释:Props(Properties 的缩写)是从父组件传递给子组件的数据。这种机制允许组件在不同的场景下被复用,因为可以通过传递不同的 Props 来改变组件的行为和外观。例如,一个显示文本的组件可以通过 Props 接收不同的文本内容。
-
根据当前的 State/Props,返回一个 UI。
- 解释:React 组件根据其内部状态(State)和外部传入的属性(Props)来渲染用户界面(UI)。也就是说,组件的外观和行为取决于它的 State 和 Props。例如,一个列表组件可能根据传入的数组(Props)来显示不同的项目。
状态归属问题
状态归属于两个节点向上寻找到最近的祖宗节点
状态归属相关问题思考
-
React 是单向数据流,还是双向数据流?
- React 是单向数据流。数据从父组件流向子组件,通过 props 传递。当子组件需要更新数据时,通常是通过回调函数将数据传递回父组件,再由父组件更新数据后重新传递给子组件,而不是子组件直接修改父组件的数据。
-
如何解决状态不合理上升的问题?
- 解决状态不合理上升的问题可以采用状态提升(Lifting State Up)的方法。当多个子组件需要共享某些状态时,将这些状态提升到它们的最近公共父组件中,由父组件来管理和传递这些状态。这样可以避免状态在组件树中不合理地分布,使数据流向更加清晰。
-
组件的状态改变后,如何更新 DOM?
- React 使用虚拟 DOM(Virtual DOM)来管理 DOM 更新。当组件的状态改变时,React 会创建一个新的虚拟 DOM 树,并将其与旧的虚拟 DOM 树进行比较(这个过程称为 Diffing),找出需要更新的部分,然后只对实际 DOM 中需要改变的部分进行更新,这样可以提高性能,减少不必要的 DOM 操作。
生命周期
React 的生命周期方法是一系列在组件不同阶段自动调用的方法,这些方法可以帮助开发者管理组件的状态和行为。
-
Mounting(挂载)
- constructor:组件的构造函数,用于初始化状态(state)和绑定方法。
- render:这是 React 组件中最重要的方法,用于渲染 DOM 节点。
- componentDidMount:在组件挂载后立即调用,通常用于执行数据获取等操作。
-
Updating(更新)
- New props:当组件接收到新的属性(props)时触发更新。
- setState() :用于更新组件的状态(state),触发重新渲染。
- forceUpdate() :强制组件重新渲染。
- componentDidUpdate:在组件更新后立即调用,通常用于执行与更新相关的操作。
-
Unmounting(卸载)
- componentWillUnmount:在组件卸载和销毁之前调用,通常用于清理操作,如取消定时器、取消网络请求等。
此外,图中还提到 “React updates DOM and refs”,这表明 React 在这些生命周期方法中会更新 DOM 和引用(refs)。
通过这些生命周期方法,开发者可以更好地控制组件的行为,实现复杂的用户界面和交互逻辑。
React (hooks) 的写法
React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发并开源。Hooks 是 React 16.8 版本引入的新特性,它允许你在不编写类组件的情况下使用 React 的特性,如状态管理和生命周期方法。
常见的 React Hooks 写法
-
useState
- 用途:用于在函数组件中添加状态。
- 示例:
import React, { useState } from 'react';
function Example() {
// 声明一个新的状态变量,叫做 "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
-
useEffect
- 用途:用于处理副作用,如数据获取、订阅和手动 DOM 操作。
- 示例:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate
useEffect(() => {
// 更新文档标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
-
useContext
- 用途:用于在组件树中传递数据,而不必通过 props 层层传递。
- 示例:
import React, { useContext } from 'react';
const MyContext = React.createContext();
function ComponentA() {
return (
<MyContext.Provider value="Hello from Context">
<ComponentB />
</MyContext.Provider>
);
}
function ComponentB() {
const contextValue = useContext(MyContext);
return <div>{contextValue}</div>;
}
这些只是 React Hooks 的基本用法,实际应用中还有更多的 Hooks 和更复杂的用法,例如useReducer、useCallback、useMemo等。
React实现
Problem1
-
JSX 不符合 JS 标准语法:
- 解析:JSX(JavaScript XML)是 React 中用于描述 UI 的一种语法扩展。它看起来像 HTML,但实际上是 JavaScript 的语法扩展。由于它不是 JavaScript 的标准语法,在使用时需要进行转换(例如通过 Babel)才能在浏览器中运行。
- 解决方法:使用 Babel 等工具将 JSX 转换为标准的 JavaScript 语法,通常是通过将 JSX 转换为
React.createElement()函数调用。
-
返回的 JSX 发生改变时,如何更新 DOM:
- 解析:在 React 中,当组件的状态或属性发生变化时,组件的 JSX 可能会发生改变。React 需要高效地更新 DOM 来反映这些变化。
- 解决方法:React 使用虚拟 DOM(Virtual DOM)来解决这个问题。当组件的状态或属性发生变化时,React 会重新渲染组件的虚拟 DOM,然后通过比较新旧虚拟 DOM 的差异(Diffing 算法),只更新实际 DOM 中发生变化的部分。
-
State/Props 更新时,要重新触发 render 函数:
- 解析:在 React 中,当组件的状态(State)或属性(Props)发生变化时,组件需要重新渲染以反映这些变化。这涉及到 React 的生命周期方法和渲染机制。
- 解决方法:React 会自动检测到状态或属性的变化,并重新调用组件的
render函数。在render函数中,组件会根据新的状态和属性生成新的 JSX。为了优化性能,可以使用shouldComponentUpdate生命周期方法或React.PureComponent来避免不必要的重新渲染。
Problem2 Virtual DOM(虚拟 DOM)
Virtual DOM 是一种用于和真实 DOM 同步,并在 JS 内存中维护的一个对象。它具有和 DOM 类似的树状结构,并和 DOM 可以建立一一对应的关系。
它赋予了 React 声明式的 API:用户告诉 React 希望让 UI 是什么状态,React 就确保 DOM 匹配该状态。这使得用户可以从属性操作、事件处理和手动 DOM 更新这些在构建应用程序时必要的操作中解放出来。
Problem3 How to Diff
在 React 中,Diff 算法用于比较新旧虚拟 DOM(Virtual DOM)树,以确定哪些部分需要更新。理想的最小 Diff 算法可以找到最少的 DOM 操作来更新页面,但这种算法的复杂度非常高(O (n^3)),在实际应用中效率很低。为了提高效率,React 采用了启发式(Heuristic)的 O (n) 复杂度算法。这种算法虽然不能保证找到最少的 DOM 操作,但在大多数情况下可以高效地更新 DOM,并且复杂度大大降低。
- 完美的最小 Diff 算法需要 O (n^3) 的复杂度。
- 通过牺牲理论最小 Diff,换取时间,得到了 O (n) 复杂度的算法:Heuristic O (n) Algorithm。
React 实现中的 Diff 操作
-
不同类型的元素
- 当新旧元素类型不同时,React 会直接替换整个 DOM 树。例如,从
<div>变成<span>,React 会销毁<div>及其所有子节点,并创建新的<span>及其子节点。
- 当新旧元素类型不同时,React 会直接替换整个 DOM 树。例如,从
-
同类型的 DOM 元素
- 当元素类型相同(例如都是
<div>),React 会比较它们的属性和子元素。如果属性或子元素不同,React 会更新对应的 DOM 属性或子元素。
- 当元素类型相同(例如都是
-
同类型的组件元素
- 当组件类型相同时,React 会递归地比较组件的属性和子元素。这意味着会深入到组件内部去比较它们的状态和子元素,然后进行最小化的更新操作。
React状态管理库
核心思想
将状态抽离到 UI 外部进行统一管理
推荐几个 React 状态管理库
- Redux:它是一个用于 JavaScript 应用的可预测状态容器,适合用于管理应用的状态。
- Xstate:这是一个用于现代网络的状态机和状态图,能够帮助开发者更好地处理复杂的应用状态。
- Mobx:提供简单且可扩展的状态管理功能,使状态管理变得更加容易。
- Recoil:这是一个实验性的状态管理库,具有 React 的最新特性,适用于尝试新技术的开发者。
这些库都可以将状态抽离到 UI 外部进行统一管理,帮助开发者更好地组织和维护代码。
状态机
在 React 应用中,状态管理是非常重要的一部分。状态机是一种设计模式,用于管理和处理应用中的状态变化。图中的状态机展示了如何通过计时器来控制交通信号灯的状态转换,这在实际应用中可以用于模拟和控制各种状态变化的场景。
这种状态机的实现通常涉及到以下几个关键部分:
- 状态定义:图中定义了三种状态:green、yellow 和 red。
- 事件触发:通过 TIMER 事件来触发状态的转换。
- 状态转换逻辑:当收到 TIMER 事件时,状态从 green 转换到 yellow,再从 yellow 转换到 red。
这种设计模式有助于保持代码的清晰和可维护性,特别是在处理复杂的状态变化时。
状态管理库Modern.js
Modern.js 由字节跳动开发,它集成了 React 状态管理库(如 Redux)等功能,旨在帮助开发者更方便地构建现代 Web 应用。它提供了一系列的工具和最佳实践,用于处理前端开发中的常见问题,如状态管理、路由、性能优化等。
应用级框架科普
-
NEXT.js
- 由硅谷明星创业公司 Vercel 的 React 开发框架,稳定,开发体验好,支持 Unbundled Dev、SWC 等,其同样有 Serverless 一键部署平台帮助开发者快速完成部署。口号是 “Let's Make Web Faster”。
-
MODERN.js
- 字节跳动 Web Infra 团队研发的全栈开发框架,内置了很多开箱即用的能力与最佳实践,可以减少很多调研选择工具的时间。
-
Blitz
- 无 API 思想的全栈开发框架,开发过程中无需写 API 调用与 CRUD 逻辑,适合前后端紧密结合的小团队项目。
纯科普
- Image Optimization:
和 Automatic Image Optimization with instant builds。
- Internationalization:Built - in Domain & Subdomain Routing and Automatic Language detection。
- Next.js Analytics:A true lighthouse score based on real visitor data & page - by - page insights。
- Hybrid: SSG and SSR:Pre - render pages at build time (SSG) or request time (SSR) in a single project。
- Incremental Static Regeneration:Add and update static pre - rendered pages incrementally after build time。
- Zero Config:Automatic compilation and bundling. Optimized for production from the start。
- Fast Refresh:Fast, reliable live - editing experience, as proven at Facebook scale。
- File - system Routing:Every component in the ‘pages‘ directory becomes a route。
- TypeScript Support:Automatic TypeScript configuration and compilation。
- API Routes:Optionally create API endpoints to provide backend functionality。
- Built - in CSS Support:Create component - level styles with CSS modules. Built - in Sass support。
- Code - splitting and Bundling:Optimized bundle splitting algorithm created by the Google Chrome team。
都有官方文档
技术相关的术语和功能模块
- Post-Webpack Era(后 Webpack 时代)
- CSS 解决方案
- 默认零配置、模板文件最简
- 单元测试、集成测试
- Eslint 全量规则集、IDE 支持
- 模块构建产物规范
- Visual Testing(视觉测试)
- Monopose
- 自带 Web Server(自带网络服务器)
- 一体化 SSR/SPR/SSG
- 一体化 BFF
- 为 Serverless 优化
- 多框架
- 定制 Web Server(定制网络服务器)
- 三位一体
这些术语大多与前端开发和网络技术相关。例如:
- Eslint 全量规则集、IDE 支持:Eslint 是一种用于识别和报告在 ECMAScript/JavaScript 代码中发现的模式的工具,确保代码风格的一致性。IDE 支持意味着可以在集成开发环境中更好地使用这些规则。
- 单元测试、集成测试:单元测试是对软件中的最小可测试单元进行检查和验证,而集成测试是在单元测试的基础上,将所有模块按照设计要求组装成为子系统或系统,进行测试。
- Serverless 优化:Serverless 是一种云计算执行模型,其中云提供商动态地管理机器资源的分配。为 Serverless 优化意味着在这种架构下进行性能和资源利用的优化。
- 一体化 SSR/SPR/SSG:SSR(服务器端渲染)、SPR(可能是笔误,可能是指同构渲染 Isomorphic Rendering)、SSG(静态站点生成)是现代前端开发中用于优化页面加载速度和 SEO 的技术。