服用5年份筑基丹 - React篇

前言

React,作为前端领域的一股强大势力,以其组件化、声明式的编程范式赢得了众多修仙者的青睐。然而,要想真正掌握 React 的精髓,却需要深入骨髓的修炼与理解。这五年的筑基丹,正是我修炼 React 的得力助手。

服下此丹,我仿佛置身于一个由React构建的奇妙世界。虚拟DOM组件通信状态管理生命周期 等核心概念逐一展现在我眼前。我开始深入研究 React 的源码,探寻其内部运行机制,逐渐掌握 React 的高级特性与用法。

然而,修仙之路从来都不是一帆风顺的。在修炼 React 的过程中,我也遇到了诸多挑战与困惑。有时,我会在复杂的组件关系中迷失方向;有时,我会在状态管理的泥沼中挣扎。但正是这些挑战,让我更加坚定了修炼的决心,也让我更加珍惜每一次的突破与进步。

若你也想深入修炼 React 之道,不妨一同服下这五年份的筑基丹。让我们携手共进 ~

1. React 中的 Fiber 架构 ⭐

Fiber 架构是 React 16 引入的一种新的架构,它允许 React 在渲染过程中进行中断和恢复,从而实现更流畅的用户体验。

解决的痛点:

  • 增量渲染:Fiber 架构可以将渲染工作拆分成多个时间片段执行,使得每个时间片段都有机会插入其他优先级更高的任务,从而在保证页面响应性的同时,尽可能快地完成渲染工作。
  • 优先级调度:Fiber 架构引入了任务优先级的概念,可以根据任务的紧急程度和重要性对任务进行优先级排序,确保优先级较高的任务能够尽早得到处理,提高用户交互的流畅度。
  • 可中断和恢复:Fiber 架构支持任务的中断和恢复,即在渲染过程中,如果有更高优先级的任务需要处理,当前任务可以被暂停并稍后恢复,以确保更及时地响应用户操作。
  • 更好的错误处理:Fiber 架构为错误处理提供了更好的机制。每个 Fiber 都有自己的错误边界,可以捕获并处理组件树中发生的错误,并在不崩溃整个应用程序的情况下进行优雅降级。

2. React 中的 Diff 算法 ⭐

React Diff 算法是 React 虚拟 Dom 的核心。它允许 React 快速确定虚拟 Dom 树上的哪些部分需要更新,从而使得 UI 更新非常的高效。

同级比较

  • React 不会进行跨层级的 DOM 比较。它只比较同一层级的元素。

元素类型

  • 类型不同,删除重建:当两棵树的相同位置找到两个不同类型的元素时,React 会直接销毁旧节点及其子节点,并从头开始创建新节点。
  • 类型相同,复用节点:如果两个元素是相同类型的,React 会保留该 DOM 节点,并仅更新变化的属性。并递归地对其子节点进行比较。

Keys重要性

当在数组中渲染子节点时,必须为每个子节点分配一个稳定的 Key 值。通过为每个节点分配一个 Key,React 可以使用这个 Key 来识别那些在不同的渲染中仍然保持不变的元素。

如果你没有设置 Key,React 内部会默认使用元素的索引作为它们的 Key

若是设置一个随机 Key 会怎样

现象:每次组件渲染,随机 Key 值都会变化,即时实际渲染的数据没有变,这个元素也会被销毁重新创建。

这将导致以下问题出现:

  1. 性能下降
  2. 状态丢失
    • 如果一个组件或元素依赖于本地状态或维护状态(如输入字段)。
  3. 不一致的 DOM 行为
    • 这可能导致不可预测的行为,如焦点丢失、文本选择丢失等。

3. React 中的 Virtual DOM ⭐

虚拟 DOM 本质上是一个 JS 对象,它反映了真实 DOM 的结构和内容。

  • 1.提供一个中间缓存,使得直接操作实际的 DOM(这通常是昂贵的操作)不再那么频繁。
  • 2.通过对比新旧虚拟 DOM 来计算出最小的变动,从而优化实际 DOM 的更新。

虚拟 DOM 可以让 React 更优雅高效的更新真实 DOM,虚拟 DOM 可以让开发者不用关注 DOM 操作,只操作数据。

4. React 中的 Hooks ⭐

Hooks 是 React 16.8 版本引入的新特性,它允许在函数组件中使用状态(state)和生命周期(lifecycle)等 React 特性。

常用 Hooks

  • useState:用于在函数组件中定义和更新状态(state)。
  • useEffect:用于在函数组件中执行副作用操作(side effect)。
  • useContext:用于在函数组件中获取上下文(context)的值。
  • useReducer:用于在函数组件中实现状态管理。
  • useRef:用于在函数组件中获取对 DOM 元素的引用。
  • useCallback:用于在函数组件中缓存函数。
  • useMemo:用于在函数组件中缓存计算结果。
  • useLayoutEffect:类似于 useEffect,但会在 DOM 更新后同步调用副作用函数。

5. React 中的 生命周期 ⭐

React 组件的生命周期是指组件从创建到销毁的过程,它包括三个阶段:挂载更新卸载

  • 挂载:组件创建时,会调用 constructor 方法,然后调用 render 方法,最后调用 componentDidMount 方法。
  • 更新:当组件的 propsstate 发生变化时,会调用 render 方法,然后调用 componentDidUpdate 方法。
  • 卸载:当组件从 DOM 中移除时,会调用 componentWillUnmount 方法。

挂载阶段

  • constructor:在组件创建时调用,用于初始化组件的状态。
  • render:在组件创建时调用,用于返回组件的 UI 结构。
  • componentDidMount:在组件挂载后调用,用于执行一些副作用操作,如发送网络请求、订阅事件等。

更新阶段

  • componentWillReceiveProps:在组件接收到新的 props 时调用,用于更新组件的状态。
  • shouldComponentUpdate:在组件接收到新的 propsstate 时调用,用于判断是否需要重新渲染组件。
  • componentWillUpdate:在组件接收到新的 propsstateshouldComponentUpdate 返回 true 时调用,用于更新组件的状态。
  • render:在组件接收到新的 propsstate 时调用,用于返回组件的 UI 结构。
  • componentDidUpdate:在组件更新后调用,用于执行一些副作用操作,如发送网络请求、订阅事件等。

卸载阶段

  • componentWillUnmount:在组件从 DOM 中移除时调用,用于执行一些清理操作,如取消订阅事件、清除定时器等。

6. useMemo、React.memo、useCallback区别 ⭐

useMemoReact.memo 都是 React 中的性能优化工具,但它们的作用和用法上有些区别。

  • useMemo:用于在组件渲染时计算某个值,并将计算结果缓存起来,避免在每次渲染时重新计算。
  • React.memo:用于优化组件的渲染,当组件的 props 没有变化时,避免重新渲染。
  • useCallback:用于在组件渲染时缓存某个函数,避免在每次渲染时重新创建函数。

7. useState 和 useRef 区别 ⭐

  • 触发渲染:useState 更新状态时会触发组件的重新渲染,而 useRef 更新 .current 属性时不会。
  • 用途:useState 用于那些组件状态(通常是用户界面状态),这些状态的改变需要导致组件重新渲染。useRef 用于持久化存储数据,这些数据的改变不应该引起渲染。
  • 返回值:useState 返回一个状态值和一个更新这个状态值的函数。useRef 返回一个具有 .current 属性的对象,你可以在其中存储任何值。

8. useEffect 和 useLayoutEffect ⭐

useEffect

  • 作用:useEffect 允许你执行任何在组件渲染后需要执行的操作。这些副作用包括数据获取、订阅或手动更改 React 组件树之外的 DOM。
  • 执行时机:useEffect 在组件渲染到屏幕之后被调用,因此不会阻塞 DOM 的更新。这对于大多数副作用是合适的,尤其是那些不需要同步到屏幕的操作。
  • 示例用途:数据请求、设置订阅、以及任何需要在组件加载后执行且不需要立即反映在屏幕上的操作。

useLayoutEffect

  • 作用:useLayoutEffect 的使用和 useEffect 相似,但它在 DOM 更新完成后、浏览器绘制之前同步调用。这使得它可以读取 DOM 布局并同步重新渲染。
  • 执行时机:useLayoutEffect 会在 DOM 更新后立即同步执行,但在浏览器进行任何绘制之前,这意味着你可以在浏览器绘制之前读取布局并同步更新它,避免不必要的视觉闪烁。
  • 示例用途:用于需要同步计算和重新渲染的操作,例如测量 DOM 节点的布局、同步动画等。

9. 类组件 和 函数组件 ⭐

React一开始,以类组件作为主体,在React16.8版本(引入React Hooks)后,解决了类组件的部分痛点(Hooks部分再说明),函数组件后来者居上。

类组件

定义特点

  • 组件首字母大写
  • 继承自 React.Component
  • 必须实现render函数

实现方式

  • 使用ES6 Class语法
  • 构造器是可选的,用于初始化数据
jsx 复制代码
import React, { Component } from 'react';
 
export default class App extends Component {
  constructor() {
    super();
    this.state = {};
  }
 
  render() {
    return (
      <div>
        <h1>我是类组件</h1>
      </div>
    );
  }
}

函数组件

函数组件,字面意思,就是JavaSript函数作为React组件。

定义特点

  • 组件首字母大写
  • 无生命周期函数,可用useEffect替代
  • 无状态管理,可用useState替代
jsx 复制代码
// 定义一个函数组件  
function Greeting({ name }) {  
  // 使用JSX来渲染组件的UI  
  return (  
    <div>  
      Hello, {name}!  
    </div>  
  );  
}  
  
// 在另一个组件或应用中使用Greeting组件  
function App() {  
  return (  
    <div className="App">  
      <Greeting name="Alice" />  
      <Greeting name="Bob" />  
    </div>  
  );  
}  
  
export default App;

10. 高阶组件 ⭐

  • 定义:高阶组件(HOC)是一个函数,接收一个组件作为参数,返回一个新的组件。
  • 应用场景:
    • 复用逻辑:高阶组件可以帮助我们在组件之间复用逻辑,避免重复代码。如果你现在多个组件有相同的逻辑代码,你可以将这个逻辑抽离出来封装一个高阶组件,实现代码逻辑的复用。
    • 加工 props:高阶组件可以加工传递给组件的 props,从而改变组件的行为。
    • 条件渲染:高阶组件可以根据特定的条件来决定是否渲染组件。
    • 提供额外功能:高阶组件可以增强现有组件的功能,如错误处理,数据获取等等。

11. 受控组件 和 非受控组件 ⭐

受控组件

  • 定义 :受控组件是指通过 Reactstate 来控制组件的输入。每当表单数据改变时,都会通过一个事件处理函数来更新 state,然后组件会根据这个新的 state 来重新渲染。

  • 特点

    • 数据驱动:组件的状态完全由 Reactstateprops 控制。
    • 即时更新:每个状态的改变都伴随着组件的重新渲染。
    jsx 复制代码
    import React, { useState } from 'react';
    
    function ControlledComponent() {
        const [value, setValue] = useState('');
    
        const handleChange = (event) => {
            setValue(event.target.value);
        };
    
        return <input type="text" value={value} onChange={handleChange} />;
    }

非受控组件

  • 定义 :非受控组件是指通过 DOMref 来控制组件的输入。非受控组件通过 DOMref 来获取组件的输入,而不是通过 Reactstate 来控制组件的输入。

  • 特点

    • DOM 驱动:表单数据由 DOM 节点自己管理,React 不负责维护状态。
    • 使用 ref 访问:通常通过 ref 来从 DOM 节点获取表单数据。
    jsx 复制代码
    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 (
            <form onSubmit={handleSubmit}>
                <input type="text" ref={inputRef} />
                <button type="submit">Submit</button>
            </form>
        );
    }

对比

组件类型 数据管理 更新方式 使用场景
受控 组件 React 管理 stateprops 实时更新 实时验证,控制 输入
非受控 组件 DOM 管理 依赖 ref 获取当前值 表单提交,取值

总结:React 推荐使用受控组件来实现表单,因为需要控制组件的状态和行为。但非受控组件在一些场景下(例如性能要求极高)可能更为简单。

12. React 18 的新特性 ⭐

官方文档:React v18.0 的新特性

Concurrent Mode(并发模式)

Concurrent Mode 是一种新的渲染模式,它允许 React 在主线程上执行渲染操作,从而提高应用程序的性能和响应能力。

React18 之前,所有任务都被视为急迫任务,react18 引入了并发模式,在这个模式下,渲染是可以中断的,高优先级的任务可以优先渲染更新。开启并发模式只需要将之前的 ReactDom.render 换成 ReactDom.createRoot 这个新的 api 即可。

自动批量处理

React18 之前,我们只在 React 事件处理函数 中进行批处理更新。默认情况下,在 promisesetTimeout、原生事件处理函数中、或任何其它事件内的更新都不会进行批处理。React18 之后,默认自动执行批处理,多次更新合并为一次更新。

其他

  • 新的 Suspense 特性:允许在组件中使用异步组件,从而实现组件的懒加载和错误边界处理。
  • 新的 Hooks:useIduseTransitionuseDeferredValueuseSyncExternalStoreuseInsertionEffect
  • 新的 根节点挂载方式:使用新的 API createRoot 来替代旧的 render 方法。

13. React Vue 异同处 ⭐

相同点

  • 都有 虚拟 Dom + Diff 算法 的渲染机制,用于提升渲染速度
  • 都使用 组件化开发,通过 props 传参等方式进行父子数据通信
  • 都具备 状态管理( VueVuex/PiniaReactRedux/Mobx
  • 都支持 跨平台开发( VueUniappReactReact Native

不同点

1.框架层面

Vue

  • 本质属于 MVVM 模式(由 MVC 发展出来的)
  • 由于 MVVM 模式,双向数据流

React

  • 严格意义上,只能算是 MVC 中的 View 层
  • 由于 MVC 模式,单向数据流

2.数据层面

Vue

  • vue 推崇 响应式,实现了数据的 双向绑定
  • 由于数据可变,当数据发生变化,可通过 getter/setter 以及一些 函数的 劫持监听数据的变化
  • 并且当数据变化时,可直接更新到对应的虚拟 Dom(VM 的理念)

React:

  • React 推崇 不可变(Immutable),为 单向数据流
  • 由于数据不可变,所以 React 无需监听数据的变化,React 只对 setState 之后会有 重新渲染 的流程
  • 当数据发生变化( setState 之后),React 默认是通过 比较引用的方式(Diff) 进行的,所以若是不优化,则会导致大量非必要渲染,从而影响性能(代码要求更高)

3.渲染层面

Vue

  • Vue 可以很快的计算出虚拟 DOM 的差异,这由于它监听了每一个组件的依赖关系,不需要重新渲染整个组件树

React

  • React 数据变化时,会将全部子组件重新渲染。但可 shouldComponentUpdate 这个生命周期函数进行控制。

4.Diff 算法

Vue

  • 同层比较新老:新的不存在旧的存在就删除,新的存在旧的不存在就创建
  • 基于 Snabbdom 库,使用双向链表,边对比,边更新 DOM

React

  • 递归同层比较,标识差异点保存到 Diff 队列,从而得到 patch 树,再 统一操作批量更新 DOM。

5.其他层面

  • 模板语法不同:Vue 是 指令+模板语法,react 是 函数式编程
  • 性能优化可控:Vue 性能优化(自动的)相对 React 可控性低,因此大型应用,数据量庞大,无需过量非必要的 Watcher,出于性能方面,推荐使用可控的 React。
  • 社区生态差异:Vue 国内受众人群多,React 国际受众人群多(更为成熟↑)

14. React 中的 组件通信 ⭐

Props

最基本的通信方式,适用于简单的父子关系,单向传递。

State

使用 React 的内置 Hook useStateuseReducer,来更复杂的状态管理。

Context

跨层级组件通信,创建 Context 对象,在父组件中使用 <Context.Provider> 包裹需要共享数据的子组件,然后在子组件中使用 <Context.Consumer>useContext Hook 来访问这些数据。

事件回调函数

  • 子组件可以通过回调函数(作为 props 传递)来通知父组件某些事件或状态的变化。
  • 父组件可以定义这些回调函数,并在子组件触发事件时执行相应的逻辑

Redux

  • 对于更复杂的应用,可能需要使用 Redux 这样的状态管理库。
  • Redux 提供了一种集中的方式来存储和管理应用的状态,并提供了一套可预测的状态更新机制。
  • 通过 Redux,组件可以通过 action 来触发状态的更新,并通过 selector 来获取状态数据。

其他库和框架

  • MobX、Zustand 等其他状态管理的插件。

15. Redux 状态管理库 ⭐

Reduxreact 的一个状态管理库。

Redux 的三大原则:

  • 1.单一数据源:应用的状态存储在唯一的 store 中。
  • 2.状态是只读的:唯一改变状态的方式是触发 action
  • 3.使用纯函数执行修改:为了指定 action 如何改变状态,你需要编写 reducers

常用概念:

  • Actions:描述发生了什么的对象。
  • Reducers:指定每个 action 如何改变应用状态的函数。
  • Store:将 ActionsReducers 绑定在一起的对象。

Redux 通过解耦状态和 UI,使得状态管理更加明确和可预测。但也因为其模式和约束,对于一些简单的应用,使用 Redux 可能会显得过于复杂。不过,在大型应用和复杂状态管理场景下,Redux 的优势就显现出来了。

持续更新中...

React 部分 | 路漫漫其修远兮 - Xaivor

相关推荐
Myli_ing9 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风11 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
软件小伟20 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾41 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧1 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep7011 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm1 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
疯狂的沙粒2 小时前
对 TypeScript 中高级类型的理解?应该在哪些方面可以更好的使用!
前端·javascript·typescript