摸爬滚打React
React的历史和应用
应用场景
- 前端应用开发,如
Facebook、Netflix网页版 - 移动端原生应用,如
Android、IOS的app - 结合
Electron,进行桌面应用开发 - 基于
webgl的react three fiber提供了类似于 React 组件的 API用以创建三维场景中的对象
历史
- 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的新工具/新框架开始涌现
React的设计思路
UI编程的痛点

每次用户点击不同的机型,右上角的价格标签都会更新,如果使用原生的JS写:

绑定callback,更新currentValue,进而更新右上角的标签。
总结下来:
- 状态更新的时候,
UI不会自动更新,需要手动调用DOM接口进行更新 - 欠缺基本的代码层面的封装和隔离,代码层面没有组件化
UI之间的数据依赖关系,需要手动维护,如果依赖链路长,则会遇到“Callback Hell”
React的出现,就是为了解决这三大痛点
响应式与转换式
- 转换式系统:给定输入求解输出,比如编译器的实现,数值计算等场景
- 响应式系统:监听事件,由消息驱动,需要有一个监控系统去关注事件,并对事件做出响应,更新
UI界面
前端UI

响应式编程
- 状态更新,
UI也会进行更新————>状态更新的时候,UI不会自动更新,需要手动调用DOM接口进行更新 - 前端代码组件化,可复用,可封装————>欠缺基本的代码层面的封装和隔离,代码层面没有组件化
- 状态之间的互相依赖关系,只需声明即可————>
UI之间的数据依赖关系,需要手动维护,如果依赖链路长,则会遇到“Callback Hell”
组件化

左侧树非DOM树!
总结:
- 组件是组件的组合/原子组件
- 组件内拥有自己的状态,外部不可见
- 父组件可将状态传入组件内部
组件设计
- 组件声明了状态和
UI的映射 - 组件有
Props/State两种状态,前者是父组件传入的数据,后者是组件内部维护的数据 - 组件可由其他组件拼装而成
组件代码会是什么样?
- 组件内部拥有私有状态 State
- 组件接受外部的 Props 状态提供复用性
- 根据当前的 State/Props,返回一个 UI

状态归属问题
右上角的当前价格状态属于谁?
答案是属于根节点Root,因为这个属性会被多个子组件共享

这也导致了一个问题,当多个子组件需要共享数据的时候,就得将共享数据提升到父组件中,这其实是不好的

由于在js中,函数是一等公民,所以可以将函数也作为属性传递给子组件,那么就可以在Root组件中定义一个修改当前价格的函数,然后将这个函数传给子组件,当子组件需要修改当前价格时,就调用该函数即可

思考
-
React 是单向数据流,还是双向数据流 ?
a:单线数据流。在 React 中,数据流是自上而下(从父组件到子组件)的单向流动。父组件可以通过 props 将数据传递给子组件,子组件接收到 props 后进行渲染和展示。子组件无法直接修改父组件传递的 props,它们只能通过回调函数的方式将状态的修改请求传递给父组件,由父组件来决定是否进行状态的更新
-
如何解决状态不合理上升的问题 ?
- 将共享状态提升到最近的共同父组件:如果多个组件需要共享相同的状态,可以将该状态提升到它们的最近共同父组件中。这样,父组件可以管理该状态,并通过 props 将状态传递给子组件。
- 使用回调函数传递状态更新:当子组件需要修改共享状态时,可以通过回调函数的方式将状态更新请求传递给父组件。父组件接收到回调函数后,可以更新状态并将新的状态通过 props 传递给子组件。
- 使用状态管理库:如果应用程序的状态较为复杂或需要在多个组件之间进行共享和管理,可以考虑使用状态管理库,如 Redux、MobX 或 React Context。这些库提供了一种集中式的状态管理机制,可以更方便地管理和共享状态。
- 使用 React Hook:如果使用函数组件开发,可以使用 React Hook 中的 useState 或 useReducer 来管理组件的状态。通过将状态提升到共同的父组件中,并使用 Hook 来管理状态,可以更好地控制状态的一致性。
-
组件的状态改变后,如何更新 DOM ?
a:React 使用虚拟 DOM(Virtual DOM)来进行高效的 DOM 更新。当组件的状态发生变化时,React 会重新渲染组件,并生成一个新的虚拟 DOM 树。然后,React 会将新的虚拟 DOM 树与之前的虚拟 DOM 树进行比较,找出两者之间的差异(Diffing)。最后,React 会根据差异的结果,只更新需要改变的部分,而不是重新渲染整个 DOM 树。
生命周期

-
挂载阶段(Mounting):
- constructor:组件实例化时调用,用于初始化状态和绑定事件处理程序。
- static getDerivedStateFromProps:在组件实例化和更新阶段调用,用于根据传入的属性计算并返回新的状态。
- render:根据当前的状态和属性,返回要渲染的 JSX 元素。
- componentDidMount:组件首次渲染后调用,可以进行异步操作、订阅事件等。
-
更新阶段(Updating):
- static getDerivedStateFromProps:在组件更新阶段调用,用于根据新的属性计算并返回新的状态。
- shouldComponentUpdate:在组件更新之前调用,用于判断是否需要进行更新,默认返回 true。
- render:根据新的状态和属性,返回要渲染的 JSX 元素。
- componentDidUpdate:组件更新后调用,可以进行 DOM 操作、网络请求等。
-
卸载阶段(Unmounting):
- componentWillUnmount:组件即将卸载前调用,可以进行清理操作,如取消订阅、清除定时器等。
React (hooks)的写法与 React 实现
import React,{ useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
React 实现
Problems
-
JSX 不符合 JS 标准语法

可以使用构建工具(如 Babel)来将 JSX 转译为标准的 JavaScript
-
返回的JSX发生改变时,如何更新 DOM
在 React 中,当 JSX 发生改变时,React 会自动处理 DOM 的更新。React 使用了一种称为 Virtual DOM 的机制来优化 DOM 的更新过程。
当 JSX 发生改变时,React 会重新计算新的 Virtual DOM,并将其与之前的 Virtual DOM 进行比较,找出需要进行实际 DOM 更新的部分。然后,React 会根据这些变化,只更新需要更新的部分,而不是完全重新渲染整个 DOM。
这个过程被称为协调(Reconciliation),它是 React 的核心算法之一。React 通过比较 Virtual DOM 的差异,最小化了对实际 DOM 的操作,从而提高了性能。
当你使用 React 开发应用时,你只需要关注数据的变化和 React 组件的更新。当你更新组件的状态或属性时,React 会自动重新计算 Virtual DOM,并将变化应用到实际 DOM 上。
以下是一个简单的示例,展示了当状态变化时,React 如何更新 DOM:
import React, { useState } from 'react'; import ReactDOM from 'react-dom'; function MyComponent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={handleClick}>增加</button> </div> ); } ReactDOM.render(<MyComponent />, document.getElementById('root'));在上面的示例中,我们使用了
useStateHook 来声明一个状态变量count和一个更新状态的函数setCount。每次点击按钮时,count的值会增加,并触发组件的重新渲染。当组件重新渲染时,React 会计算新的 Virtual DOM,并将变化应用到实际 DOM 上。在这个例子中,只有
<p>Count: {count}</p>这部分的内容会发生变化,React 会更新实际 DOM 中对应的部分,而不会重新渲染整个页面。
完美的最小 Diff 算法,需要 O(n3)的复杂度 牺牲理论最小 Diff,换取时间,得到了 O (n)复杂度的算法 Heuristic O(n) Algorithm
对于一个完美的最小差异算法,其复杂度通常是O(n^3),其中n是比较的元素数量。这种算法会尝试所有可能的组合,以找到最小的差异。
然而,为了在实际应用中获得更好的性能,可以使用启发式算法来进行近似的差异比较。这些启发式算法可以在更短的时间内生成接近最小差异的结果,但并不能保证找到最小的差异。
一种常见的启发式算法是O(n)算法,它在实践中表现良好。该算法的基本思想是根据一些启发式规则和策略来进行差异比较,以尽可能减少比较的次数。
具体而言,O(n)算法通常会遵循以下步骤:
- 遍历源列表和目标列表,同时比较相应位置的元素。如果元素相同,则继续下一个位置的比较。
- 如果元素不同,算法会尝试进行一系列的操作,如插入、删除或替换元素,以使源列表和目标列表的当前位置相匹配。
- 算法会根据一些启发式规则来选择最佳的操作。这些规则可能基于元素的属性、位置信息或其他上下文。
- 算法会继续进行下一个位置的比较和操作,直到遍历完整个列表。
尽管O(n)算法不能保证找到最小的差异,但在实际应用中,它通常能够提供足够准确的结果,并且具有更好的性能。这使得它成为许多差异比较工具和库的常用算法。

-
State/Props更新时要重新触发render函数
-
既然所有的前端框架都是声明式,为何不把声明式语法植入浏览器?
浏览器是一个通用的平台,需要支持各种不同的编程语言和框架。如果将特定的声明式语法直接植入浏览器,那么其他编程语言和框架可能无法充分利用这些功能。这可能导致开发者在选择框架时受到限制,而无法根据自己的偏好和需求选择最合适的工具。
其次,浏览器的标准化过程相对较慢,引入新的语法和功能需要经过广泛的讨论、设计和实施。这可能需要很长时间才能在浏览器中实现和广泛支持。在等待新的声明式语法被标准化和普及的过程中,开发者可能会错过使用其他框架和工具所提供的先进功能。
另外,将声明式语法直接植入浏览器还可能引入复杂性和安全性的问题。浏览器是一个公共平台,需要考虑到各种不同的使用场景和安全风险。引入新的语法和功能可能会增加浏览器的复杂性,并带来潜在的安全漏洞。
相反,前端框架通常通过自己的运行时或编译器来实现声明式语法。这样,框架可以在自己的生态系统中灵活地实现和演进这些语法,而不受浏览器的限制。这也为开发者提供了更多的选择和灵活性,可以根据自己的需求选择适合的框架和工具。
React状态管理库
将状态抽离到UI外部进行统一管理
- Redux:Redux 是一个广泛使用的状态管理库,它提供了一个可预测的状态容器。它使用单一的全局状态存储(store)来管理应用程序的状态,并通过使用纯函数的方式来处理状态的变化。Redux 使用了一个称为 "action" 的概念来描述状态的变化,并通过 "reducer" 函数来处理这些变化。它与 React 的结合非常紧密,可以与 React 组件无缝配合使用。
- MobX:MobX 是另一个流行的状态管理库,它采用了观察者模式来实现状态的响应式更新。它允许您将状态标记为可观察的,并在状态发生变化时自动更新相关的组件。相对于 Redux,MobX 更加简单和直观,它的设计目标是使状态管理变得简单且易于理解。
- Zustand:Zustand 是一个轻量级的状态管理库,它专注于简化状态管理的复杂性。它提供了一个小巧的 API,使您可以在 React 组件中定义和使用状态,并自动处理状态的订阅和更新。Zustand 的设计目标是尽可能地减少样板代码,并提供简单而强大的状态管理功能。
- Recoil:Recoil 是由 Facebook 开发的状态管理库,专为 React 应用程序设计。它提供了一个基于原子(atom)的状态模型,使您可以在组件之间共享和访问状态。Recoil 的一个主要特点是它支持异步状态和跨组件的依赖关系追踪,这使得处理复杂的状态逻辑变得更加简单。
- XState:XState 是一个基于有限状态机(FSM)的概念的库,用于管理应用程序的状态和行为。XState 提供了一种声明式的方式来定义状态和状态之间的转换。它的核心概念包括状态(States)、转换(Transitions)、行为(Actions)、守卫(Guards)和上下文(Context)。XState 提供了丰富的工具和 API,用于创建、可视化、调试和测试状态机。它适用于各种应用程序,从简单的表单到复杂的应用程序工作流都可以使用 XState 进行状态管理。
状态机

当前状态,收到外部事件,迁移到下一个状态。
状态机(State Machine)是一种数学模型,用于描述对象或系统在不同状态之间的转换和行为。它由一组状态、转换和动作组成,可以用于建模和控制各种实际问题。
在状态机中,对象或系统可以处于不同的状态,每个状态代表一种特定的情况或条件。状态之间的转换表示对象或系统从一个状态切换到另一个状态的过程。转换可以由事件、条件或其他触发机制触发。当状态发生变化时,可以执行相应的动作或行为。
状态机的核心概念包括:
- 状态(States):状态是对象或系统可能处于的不同情况或条件。例如,一个简单的开关可以有两个状态:打开和关闭。
- 转换(Transitions):转换定义了状态之间的变化。它表示对象或系统从一个状态切换到另一个状态的过程。转换可以由事件、条件或其他触发机制触发。
- 动作(Actions):动作是在状态转换发生时执行的操作。它表示在状态变化时要执行的任务或行为。例如,在从关闭状态切换到打开状态时,可以执行打开开关的动作。
- 事件(Events):事件是触发状态转换的信号或输入。它可以是外部事件(如用户操作)或内部事件(如定时器触发)。事件触发状态转换并可能引发相应的动作。
核心思想
什么时候将状态存储到状态管理库中取决于这个状态是被整个APP所有用or被某个组件所拥有
ep:用户头像——>store,我觉得用户头像可能在整个页面中经常用到,属于整个APP,所以存入状态管理库
通常情况下,将状态存储到全局的存储中的时机可以有以下考虑:
- 多个组件需要共享同一份状态数据。当多个组件需要访问和修改同一份数据时,将数据存储到全局的存储中可以方便地实现状态的共享和同步。
- 跨组件通信和数据传递。当需要在不同的组件之间传递数据或进行通信时,将数据存储到全局的存储中可以简化数据传递的过程。
- 状态需要在组件之间保持同步。当状态的变化需要在多个组件之间保持同步时,将数据存储到全局的存储中可以确保状态的一致性。
- 状态需要被持久化或在页面刷新后恢复。如果需要将状态进行持久化,或者在页面刷新后恢复之前的状态,将数据存储到全局的存储中可以方便地实现这些功能。
需要注意的是,并不是所有的应用程序都需要使用全局的状态管理库。对于简单的应用程序或组件层级较浅的应用程序,使用组件的本地状态可能已经足够。只有在应用程序变得复杂、状态共享和同步成为问题时,考虑使用全局的状态管理库来管理应用程序的状态。