(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!

相关推荐
吞掉星星的鲸鱼42 分钟前
使用高德api实现天气查询
前端·javascript·css
lilye661 小时前
程序化广告行业(55/89):DMP与DSP对接及数据统计原理剖析
java·服务器·前端
....4921 小时前
Vue3 + Element Plus + AntV X6 实现拖拽树组件
javascript·vue.js·elementui·antvx6
zhougl9963 小时前
html处理Base文件流
linux·前端·html
花花鱼3 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_3 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo4 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)5 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之6 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端6 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端