React,作为前端领域的一股强大势力,以其组件化、声明式的编程范式赢得了众多修仙者的青睐。然而,要想真正掌握 React 的精髓,却需要深入骨髓的修炼与理解。这五年的筑基丹,正是我修炼 React 的得力助手。
服下此丹,我仿佛置身于一个由React构建的奇妙世界。虚拟DOM
、组件通信
、状态管理
、生命周期
等核心概念逐一展现在我眼前。我开始深入研究 React 的源码,探寻其内部运行机制,逐渐掌握 React 的高级特性与用法。
然而,修仙之路从来都不是一帆风顺的。在修炼 React 的过程中,我也遇到了诸多挑战与困惑。有时,我会在复杂的组件关系中迷失方向;有时,我会在状态管理的泥沼中挣扎。但正是这些挑战,让我更加坚定了修炼的决心,也让我更加珍惜每一次的突破与进步。
若你也想深入修炼 React 之道,不妨一同服下这五年份的筑基丹。让我们携手共进 ~
Fiber
架构是 React
16 引入的一种新的架构,它允许 React
在渲染过程中进行中断和恢复,从而实现更流畅的用户体验。
解决的痛点:
增量渲染
:Fiber 架构可以将渲染工作拆分成多个时间片段执行,使得每个时间片段都有机会插入其他优先级更高的任务,从而在保证页面响应性的同时,尽可能快地完成渲染工作。优先级调度
:Fiber 架构引入了任务优先级的概念,可以根据任务的紧急程度和重要性对任务进行优先级排序,确保优先级较高的任务能够尽早得到处理,提高用户交互的流畅度。可中断和恢复
:Fiber 架构支持任务的中断和恢复,即在渲染过程中,如果有更高优先级的任务需要处理,当前任务可以被暂停并稍后恢复,以确保更及时地响应用户操作。更好的错误处理
:Fiber 架构为错误处理提供了更好的机制。每个 Fiber 都有自己的错误边界,可以捕获并处理组件树中发生的错误,并在不崩溃整个应用程序的情况下进行优雅降级。React Diff
算法是 React 虚拟 Dom 的核心。它允许 React 快速确定虚拟 Dom 树上的哪些部分需要更新,从而使得 UI 更新非常的高效。
当在数组中渲染子节点时,必须为每个子节点分配一个稳定的 Key 值。通过为每个节点分配一个 Key,React 可以使用这个 Key 来识别那些在不同的渲染中仍然保持不变的元素。
如果你没有设置 Key,React 内部会默认使用元素的索引作为它们的 Key
若是设置一个随机 Key 会怎样
?
现象:每次组件渲染,随机 Key 值都会变化,即时实际渲染的数据没有变,这个元素也会被销毁重新创建。
这将导致以下问题出现:
虚拟 DOM 本质上是一个 JS 对象,它反映了真实 DOM 的结构和内容。
虚拟 DOM 可以让 React 更优雅高效的更新真实 DOM,虚拟 DOM 可以让开发者不用关注 DOM 操作,只操作数据。
Hooks
是 React 16.8 版本引入的新特性,它允许在函数组件中使用状态(state)和生命周期(lifecycle)等 React 特性。
useState
:用于在函数组件中定义和更新状态(state)。useEffect
:用于在函数组件中执行副作用操作(side effect)。useContext
:用于在函数组件中获取上下文(context)的值。useReducer
:用于在函数组件中实现状态管理。useRef
:用于在函数组件中获取对 DOM 元素的引用。useCallback
:用于在函数组件中缓存函数。useMemo
:用于在函数组件中缓存计算结果。useLayoutEffect
:类似于 useEffect
,但会在 DOM 更新后同步调用副作用函数。React 组件的生命周期是指组件从创建到销毁的过程,它包括三个阶段:挂载
、更新
和卸载
。
挂载
:组件创建时,会调用 constructor
方法,然后调用 render
方法,最后调用 componentDidMount
方法。更新
:当组件的 props
或 state
发生变化时,会调用 render
方法,然后调用 componentDidUpdate
方法。卸载
:当组件从 DOM 中移除时,会调用 componentWillUnmount
方法。constructor
:在组件创建时调用,用于初始化组件的状态。render
:在组件创建时调用,用于返回组件的 UI 结构。componentDidMount
:在组件挂载后调用,用于执行一些副作用操作,如发送网络请求、订阅事件等。componentWillReceiveProps
:在组件接收到新的 props
时调用,用于更新组件的状态。shouldComponentUpdate
:在组件接收到新的 props
或 state
时调用,用于判断是否需要重新渲染组件。componentWillUpdate
:在组件接收到新的 props
或 state
且 shouldComponentUpdate
返回 true
时调用,用于更新组件的状态。render
:在组件接收到新的 props
或 state
时调用,用于返回组件的 UI 结构。componentDidUpdate
:在组件更新后调用,用于执行一些副作用操作,如发送网络请求、订阅事件等。componentWillUnmount
:在组件从 DOM 中移除时调用,用于执行一些清理操作,如取消订阅事件、清除定时器等。useMemo
和 React.memo
都是 React 中的性能优化工具,但它们的作用和用法上有些区别。
useMemo
:用于在组件渲染时计算某个值,并将计算结果缓存起来,避免在每次渲染时重新计算。React.memo
:用于优化组件的渲染,当组件的 props 没有变化时,避免重新渲染。useCallback
:用于在组件渲染时缓存某个函数,避免在每次渲染时重新创建函数。useState
更新状态时会触发组件的重新渲染,而 useRef
更新 .current
属性时不会。useState
用于那些组件状态(通常是用户界面状态),这些状态的改变需要导致组件重新渲染。useRef
用于持久化存储数据,这些数据的改变不应该引起渲染。useState
返回一个状态值和一个更新这个状态值的函数。useRef
返回一个具有 .current
属性的对象,你可以在其中存储任何值。useEffect
允许你执行任何在组件渲染后需要执行的操作。这些副作用包括数据获取、订阅或手动更改 React 组件树之外的 DOM。useEffect
在组件渲染到屏幕之后被调用,因此不会阻塞 DOM 的更新。这对于大多数副作用是合适的,尤其是那些不需要同步到屏幕的操作。useLayoutEffect
的使用和 useEffect
相似,但它在 DOM 更新完成后、浏览器绘制之前同步调用。这使得它可以读取 DOM 布局并同步重新渲染。useLayoutEffect
会在 DOM 更新后立即同步执行,但在浏览器进行任何绘制之前,这意味着你可以在浏览器绘制之前读取布局并同步更新它,避免不必要的视觉闪烁。React一开始,以类组件作为主体,在React16.8版本(引入React Hooks)后,解决了类组件的部分痛点(Hooks部分再说明),函数组件后来者居上。
定义特点:
实现方式:
import React, { Component } from 'react';
export default class App extends Component {
constructor() {
super();
this.state = {};
}
render() {
return (
我是类组件
);
}
}
函数组件,字面意思,就是JavaSript函数作为React组件。
定义特点:
// 定义一个函数组件
function Greeting({ name }) {
// 使用JSX来渲染组件的UI
return (
Hello, {name}!
);
}
// 在另一个组件或应用中使用Greeting组件
function App() {
return (
);
}
export default App;
定义:受控组件是指通过 React
的 state
来控制组件的输入。每当表单数据改变时,都会通过一个事件处理函数来更新 state
,然后组件会根据这个新的 state
来重新渲染。
特点:
数据驱动
:组件的状态完全由 React
的 state
和 props
控制。即时更新
:每个状态的改变都伴随着组件的重新渲染。import React, { useState } from 'react';
function ControlledComponent() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
return ;
}
定义:非受控组件是指通过 DOM
的 ref
来控制组件的输入。非受控组件通过 DOM
的 ref
来获取组件的输入,而不是通过 React
的 state
来控制组件的输入。
特点:
DOM
驱动:表单数据由 DOM
节点自己管理,React
不负责维护状态。ref
访问:通常通过 ref
来从 DOM
节点获取表单数据。import React, { useRef } from 'react';
function UncontrolledComponent() {
const inputRef = useRef(null);
const handleSubmit = (event) => {
alert('A name was submitted: ' + inputRef.current.value);
event.preventDefault();
};
return (
);
}
组件类型 | 数据管理 | 更新方式 | 使用场景 |
---|---|---|---|
受控 组件 | 由 React 管理 | state 和 props 实时更新 | 实时验证,控制 输入 |
非受控 组件 | 由 DOM 管理 | 依赖 ref 获取当前值 | 表单提交,取值 |
总结:
React
推荐使用受控组件来实现表单,因为需要控制组件的状态和行为。但非受控组件在一些场景下(例如性能要求极高)可能更为简单。
Concurrent Mode
是一种新的渲染模式,它允许 React
在主线程上执行渲染操作,从而提高应用程序的性能和响应能力。
在 React18
之前,所有任务都被视为急迫任务,react18
引入了并发模式,在这个模式下,渲染是可以中断的,高优先级的任务可以优先渲染更新。开启并发模式只需要将之前的 ReactDom.render
换成 ReactDom.createRoot
这个新的 api
即可。
在 React18
之前,我们只在 React
事件处理函数 中进行批处理更新。默认情况下,在 promise
、setTimeout
、原生事件处理函数中、或任何其它事件内的更新都不会进行批处理。React18
之后,默认自动执行批处理,多次更新合并为一次更新。
useId
、 useTransition
、 useDeferredValue
、 useSyncExternalStore
、 useInsertionEffect
API createRoot
来替代旧的 render
方法。Dom + Diff
算法 的渲染机制,用于提升渲染速度props
传参等方式进行父子数据通信Vue
的 Vuex/Pinia
,React
的 Redux/Mobx
)Vue
的 Uniapp
, React
的 React Native
)1.框架层面:
Vue
:
双向数据流
React
:
单向数据流
2.数据层面:
Vue
:
响应式
,实现了数据的 双向绑定
。劫持监听数据的变化
React:
不可变(Immutable)
,为 单向数据流
。setState
之后会有 重新渲染
的流程比较引用的方式(Diff) 进行的
,所以若是不优化,则会导致大量非必要渲染,从而影响性能(代码要求更高)3.渲染层面:
Vue
:
不需要重新渲染整个组件树
React
:
会将全部子组件重新渲染
。但可 shouldComponentUpdate 这个生命周期函数进行控制。4.Diff 算法:
Vue
:
同层比较新老
:新的不存在旧的存在就删除,新的存在旧的不存在就创建双向链表,边对比,边更新 DOM
。React
:
递归同层比较
,标识差异点保存到 Diff 队列,从而得到 patch 树,再 统一操作批量更新
DOM。5.其他层面:
指令+模板语法
,react 是 函数式编程
最基本的通信方式,适用于简单的父子关系,单向传递。
使用 React 的内置 Hook useState
或 useReducer
,来更复杂的状态管理。
跨层级组件通信,创建 Context 对象,在父组件中使用
包裹需要共享数据的子组件,然后在子组件中使用
或 useContext
Hook 来访问这些数据。
Redux
这样的状态管理库。Redux
提供了一种集中的方式来存储和管理应用的状态,并提供了一套可预测的状态更新机制。Redux
,组件可以通过 action
来触发状态的更新,并通过 selector
来获取状态数据。Redux
是 react
的一个状态管理库。
Redux
的三大原则:
store
中。action
。action
如何改变状态,你需要编写 reducers
。常用概念:
Actions
:描述发生了什么的对象。Reducers
:指定每个 action
如何改变应用状态的函数。Store
:将 Actions
和 Reducers
绑定在一起的对象。Redux 通过解耦状态和 UI,使得状态管理更加明确和可预测。但也因为其模式和约束,对于一些简单的应用,使用 Redux 可能会显得过于复杂。不过,在大型应用和复杂状态管理场景下,Redux 的优势就显现出来了。