(13)React 进阶——③ 虚拟 DOM(上):React 中的“虚拟 DOM” | React 基础理论实操

复制代码
转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。

javascript 复制代码
涉及面试题:
1. 什么是 Virtual DOM?
2. Virtual DOM 如何工作?
3. Shadow DOM 和 Virtual DOM 之间有什么区别?
4. 什么是 React Fiber,它的主要目标是什么?

编号:[react_13]

1 没有"虚拟 DOM"时的痛点

上篇文章,我们清楚地理解了:当组件的 state 或者 props 发生改变的时候, render 函数就会重新执行,页面也随即被"重新渲染"。

在 React 中,实现这种"重新渲染"的操作时,它的"性能"是非常高的,因为它引入了"虚拟 DOM"的概念!

紧接着上一篇的代码,打开 TodoList.js 文件:

jsx 复制代码
import React, { Component, Fragment } from "react"; 

import TodoItem from "./TodoItem"; 

import "./style.css"; 

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = { // 1️⃣当 state 发生改变时,
      inputValue: "",  
      list: []
    };
    
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleBtnClick = this.handleBtnClick.bind(this);
    this.handleItemDelete = this.handleItemDelete.bind(this);
    
  }

  render() { // 2️⃣render 函数下边的函数体会重新执行;
             /*
             ❓❓❓3️⃣但假设没有 React,我们要自己去实现这个功能,应该怎样思考呢?
             请认真阅读下边的各个方法! 
              */
    return(
      <Fragment>
        <div>
          <label htmlFor="insertArea">请输入要进行的事项:</label>    
          <input 
            id="insertArea"
            className="input"
            value={this.state.inputValue}
            onChange={this.handleInputChange}
          />
          <button onClick={this.handleBtnClick}>
            提交
          </button>
        </div>
        <ul>
          {this.getTodoItem()}
        </ul>


      </Fragment>
    )
  }

  getTodoItem() {
    return this.state.list.map((item, index) => { 
      return( 
        <TodoItem 
        	key={index}
        
        	content={item}
        	index={index} 
        	itemDelete={this.handleItemDelete}
        />  
      )  
    })
  }
  
  handleInputChange(e) {
    const value = e.target.value
    
    this.setState(() => ({
      inputValue: value
    }))
  }

  handleBtnClick() {
    this.setState((prevState) => ({
      list: [...prevState.list, prevState.inputValue],  
      inputValue: ""        
    }))
  }

  handleItemDelete(index) { 
    this.setState((prevState) => {
      const list = [...prevState.list]
      list.splice(index, 1)
        
      return {list}
 
    })
  }
}

export default TodoList;

假设没有 React,我想实现"数据"变化,页面跟着变化的功能,我们很惯性的会想到以下两种方法。

❌方法一:

  1. 定义一个 state 数据;

  2. 定义一个"JSX 模板";

  3. 将"数据 + JSX 模板"结合,生成"真实的 DOM"来显示;

  4. 当数据 state 发生改变;

  5. "数据 + JSX 模板"相结合,生成"真实的 DOM",并替换"原始的 DOM"

❗️缺陷------非常耗"性能": 第一次生成了一个完整的 DOM 片段 --> 数据变化后,又生成了一个完整的 DOM 片段 --> 然后用第二次的 DOM 替换第一次的 DOM

❎方法二(对"方法一"改良一下):

  1. 定义一个 state 数据;

  2. 定义一个"JSX 模板";

  3. 将"数据 + JSX 模板"结合,生成"真实的 DOM"来显示;

  4. 当数据 state 发生改变;

  5. 🔨 "数据 + JSX 模板"相结合,生成"真实的 DOM",但并不直接替换"原始的 DOM";

  6. 将"新的 DOM(DocumentFragment)"和"原始的 DOM"作对比,找差异

  7. 找到某个"节点"(如 <input> 元素)发生了变化;

  8. 只用"新的 DOM"中的 <input> 元素,替换掉"原始的 DOM"中的 <input> 元素

❗️优势和缺陷: "方法二"相对于"方法一",虽然步骤增加了,但"性能"上的确有一些提升:"局部替换"比"整体替换"消耗的性能更少。

但同时,其性能又损失了不少:新、旧 DOM 在"作对比,找差异"的过程中,性能又被"消耗"了。

这样"一增一减",性能其实也没多大提升!

2 "虚拟 DOM"带来的新篇章

基于此痛点,React 提出了"虚拟 DOM"方案。

✔️方法三(虚拟 DOM):

  1. 定义一个 state 数据:
javascript 复制代码
this.state = { 
  content: "hello, Oli."
};
  1. 定义一个"JSX 模板":
jsx 复制代码
render() { 
  return(
    <div id="abc">
      <span>{content}</span>
    </div>
  )
}
  1. "数据 + JSX 模板"结合,React 底层会调用 React.createElement() 接口,将 JSX 模板变成一个原始"虚拟 DOM" (定义:"虚拟 DOM"就是一个数组结构的 JS 对象,用它来描述"真实 DOM")。

"虚拟 DOM"的格式为:

1️⃣------第一项表示"最外层元素的标签名";

2️⃣------第二项表示"最外层标签的属性";

3️⃣------第三项表示"children 子节点",若"子节点"又是一个完整的 DOM,那么又需要以 [1️⃣, 2️⃣, 3️⃣] 的形式来写。

※本例中,"JSX 模板"转变为"JS 对象"的底层实现为(注意看 createElement 的用法)

jsx 复制代码
render() { 
  /*
  🚀-①:我们将"JSX 模板"用 craeteElement 方法来改写,其接收 3 个参数:
  第一个参数为"最外层元素的标签名";
  第二个参数为"最外层标签的属性";
  第三个参数为"children 子节点"。
   */
  return React.createElement("div", {id: "abc"}, React.createElement("span", {}, "hello, Oli."))
  
  
  /*
  🚀-②:我们发现,即使没有"JSX 模板",
  我们依然能通过 React.createElement() 来实现 JSX 的功能。
  但,会很繁杂!
  这也正是 JSX 存在的原因,因为它看着真的很简单!
   */
  
  /*
  return(
  	<div id="abc">
    	<span>{content}</span>
    </div>
  )
   */
}

本例生成的原始"虚拟 DOM" 为: ["div", {id: "abc"}, ["span", {}, "hello, Oli."]]

  1. 根据上边的"虚拟 DOM"生成"真实 DOM":
javascript 复制代码
<div id="abc">
  <span>hello, Oli.</span>
</div>
  1. 当数据 state 发生变化:
javascript 复制代码
this.state = { 
  content: "hello, qdywxs."
};
  1. 随即"数据 + JSX 模板"结合,生成新的"虚拟 DOM"

["div", {id: "abc"}, ["span", {}, "hello, qdywxs."]]

  1. 比较 "原始虚拟 DOM"和"新的虚拟 DOM"的区别,找到区别是 <span> 中的内容;

  2. 直接操作 DOM,改变 <span> 中的内容。

🏆优势(性能大幅提升):

  • 用 JS 生成一个"JS 对象",其性能消耗是非常小的。而"方法二"中,用 JS 去生成一个 DOM 元素,它的代价极高,其底层会调用 Web Application 这个级别的 API,性能损耗很大!

  • 第 7 步中,两个"虚拟 DOM"之间作对比(即两个"JS 对象"作对比),"Diff 算法"是不怎么消耗性能的。而"方法二"中,把两个"真实 DOM"作对比,其性能消耗极大。

3 "虚拟 DOM"之于"跨端应用"

有了"虚拟 DOM",我们可以用 React(React Native)去写"原生的应用"了。

其原理为: 在上边的第 3 步中,"数据 + JSX 模板"生成了一个"原始虚拟 DOM",这个"虚拟 DOM"是一个"JS 对象"。

"JS 对象 "不仅在"浏览器"里可以被识别,它在"原生应用------Android、iOS 机器上的代码"里也可以被很好地识别"原生应用"里是不存在 DOM 这个概念的,故"真实 DOM"在"原生应用"里不被识别,无法使用!)。

在"原生应用"里,有了生成的"虚拟 DOM"后,我们可以让它生成一些原生的组件。

这样的话,在复用"state"、"JSX 模板"等代码的优势下,让"虚拟 DOM"在不同"端"进行转化:

  • 网页端:转化为"真实 DOM";
  • 原生应用:转化为原生的"组件"。

这样的话,在"原生应用"里,也可以把相应页面展示出来了。

祝好,qdywxs ♥ you!

相关推荐
Pedantic11 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘11 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆11 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
YFF菲菲兔12 小时前
调度系统和调和系统的桥梁
react.js
浏览器工程师12 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆12 小时前
VSCode自动格式化三要素
前端
爱勇宝13 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen13 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user205855615181316 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端