转载请注明出处,未经同意,不可修改文章内容。🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
            
            
              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,我想实现"数据"变化,页面跟着变化的功能,我们很惯性的会想到以下两种方法。
❌方法一:
- 
定义一个 state 数据; 
- 
定义一个"JSX 模板"; 
- 
将"数据 + JSX 模板"结合,生成"真实的 DOM"来显示; 
- 
当数据 state 发生改变; 
- 
"数据 + JSX 模板"相结合,生成"真实的 DOM",并替换"原始的 DOM"。 
❗️缺陷------非常耗"性能": 第一次生成了一个完整的 DOM 片段 --> 数据变化后,又生成了一个完整的 DOM 片段 --> 然后用第二次的 DOM 替换第一次的 DOM
❎方法二(对"方法一"改良一下):
- 
定义一个 state 数据; 
- 
定义一个"JSX 模板"; 
- 
将"数据 + JSX 模板"结合,生成"真实的 DOM"来显示; 
- 
当数据 state 发生改变; 
- 
🔨 "数据 + JSX 模板"相结合,生成"真实的 DOM",但并不直接替换"原始的 DOM"; 
- 
将"新的 DOM(DocumentFragment)"和"原始的 DOM"作对比,找差异; 
- 
找到某个"节点"(如 <input>元素)发生了变化;
- 
只用"新的 DOM"中的 <input>元素,替换掉"原始的 DOM"中的<input>元素。
❗️优势和缺陷: "方法二"相对于"方法一",虽然步骤增加了,但"性能"上的确有一些提升:"局部替换"比"整体替换"消耗的性能更少。
但同时,其性能又损失了不少:新、旧 DOM 在"作对比,找差异"的过程中,性能又被"消耗"了。
这样"一增一减",性能其实也没多大提升!
2 "虚拟 DOM"带来的新篇章
基于此痛点,React 提出了"虚拟 DOM"方案。
✔️方法三(虚拟 DOM):
- 定义一个 state 数据:
            
            
              javascript
              
              
            
          
          this.state = { 
  content: "hello, Oli."
};- 定义一个"JSX 模板":
            
            
              jsx
              
              
            
          
          render() { 
  return(
    <div id="abc">
      <span>{content}</span>
    </div>
  )
}- "数据 + 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."]]
- 根据上边的"虚拟 DOM"生成"真实 DOM":
            
            
              javascript
              
              
            
          
          <div id="abc">
  <span>hello, Oli.</span>
</div>- 当数据 state 发生变化:
            
            
              javascript
              
              
            
          
          this.state = { 
  content: "hello, qdywxs."
};- 随即"数据 + JSX 模板"结合,生成新的"虚拟 DOM":
["div", {id: "abc"}, ["span", {}, "hello, qdywxs."]]
- 
比较 "原始虚拟 DOM"和"新的虚拟 DOM"的区别,找到区别是 <span>中的内容;
- 
直接操作 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!