模拟 React 02 组件渲染、更新 DOM 元素

组件渲染

组件类型的 Virtual DOM

Virtual DOM 分为普通 Virtual DOM(Native Element) 和 组件 Virtual DOM(Component)。

Native Element 和 Component 的主要区别就是它们的 type 不同:

  • Native Element 的 type 是字符串
  • Component 的 type 是函数
    • 函数组件:type 存储的就是定义组件的函数
    • 类组件:type存储的是定义组件的 class (JavaScript 中 class 其实就是函数)
function Heart () {
	return <div>&hearts;</div>
}

console.log(<Heart />)
// 组件的 Virtual DOM
{
  type: f Heart(),
  props: {},
  children: []  
}

在渲染组件时,要先将 Component 和 Native Element 区分开,如果是 Native Element 可以直接开始渲染,如果是 Component 需要特别处理。

// src/index.js
import TinyReact from './TinyReact'

// 容器
const root = document.querySelector('#root')

const virtualDOM = (
	...  
)

// TinyReact.render(virtualDOM, root)

function Heart () {
	return <div>&hearts;</div>
}

TinyReact.render(<Heart />, root)

// src/TinyReact/mountElement.js
import mountNativeElement from './mountNativeElement'
import isFunction from './isFunction'
import mountComponent from './mountComponent'
export default function mountElement(virtualDOM, container) {
  // Component VS NativeElement
  if (isFunction(virtualDOM)) {
    // Component
    mountComponent(virtualDOM, container)
  } else {
    // NativeElement
    mountNativeElement(virtualDOM, container)
  }
}

// src/TinyReact/isFunction.js
export default function isFunction(virtualDOM) {
  return virtualDOM && typeof virtualDOM.type === 'function'
}

// src/TinyReact/mountComponent.js
export default function mountComponent(virtualDOM, container) {
  // 判断组件是类组件还是函数组件

}

区分函数组件和类组件

在渲染组件的时候还要区分是函数型组件还是 class 组件,两者的主要区别是,类组件的 Virtual DOM 对象的 type 存储的函数的原型包含一个 render() 方法。

因为定义类组件必须定义render() 方法返回渲染的内容,而函数组件则直接返回:

class Foo {
  render() {
    return <div>Hello Foo</div>
  }
}

function Bar() {
  return <div>Hello Bar</div>
}

继续扩展:

// src/TinyReact/isFunctionComponent.js
import isFunction from './isFunction'
export default function isFunctionComponent(virtualDOM) {
  const type = virtualDOM.type
  return type && isFunction(virtualDOM) && !(type.prototype && type.prototype.render)
}

// src/TinyReact/mountComponent.js
import isFunctionComponent from "./isFunctionComponent";
export default function mountComponent(virtualDOM, container) {
  // 判断组件是类组件还是函数组件
  if (isFunctionComponent(virtualDOM)) {
    console.log('函数组件');
  }
}

函数组件

函数组件的内容就是它所生成的 Virtual DOM 对象的 type 属性存储的函数执行后返回的内容。

// src/TinyReact/mountComponent.js
import isFunctionComponent from './isFunctionComponent'
import mountNativeElement from './mountNativeElement'
export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null
  // 判断组件是类组件还是函数组件
  if (isFunctionComponent(virtualDOM)) {
    nextVirtualDOM = buildFunctionComponent(virtualDOM)
  }
  mountNativeElement(nextVirtualDOM, container)
}

function buildFunctionComponent(virtualDOM) {
  return virtualDOM.type()
}

函数组件返回的也可能是另一个组件:

function Heart () {
  return <App />
}

所以需要增加判断:

// src/TinyReact/mountComponent.js
import isFunction from './isFunction'
import isFunctionComponent from './isFunctionComponent'
import mountNativeElement from './mountNativeElement'
export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null
  // 判断组件是类组件还是函数组件
  if (isFunctionComponent(virtualDOM)) {
    nextVirtualDOM = buildFunctionComponent(virtualDOM)
  }

  // 判断渲染的组件是否直接返回了另一个组件
  if (isFunction(nextVirtualDOM)) {
    mountComponent(nextVirtualDOM, container)
  } else {
    mountNativeElement(nextVirtualDOM, container)
  }
}

function buildFunctionComponent(virtualDOM) {
  return virtualDOM.type()
}

而当函数组件返回的内容中包含其他组件时,会在递归创建 DOM 子节点的时候调用 mountElement() ,该方法又会重新判断组件的类型,不用额外处理。

function Heart () {
  return <div>&hearts;<App /></div>
}

函数组件 props 参数处理

函数组件会接受一个 props 作为参数,并在返回的内容中访问其中的属性。

在渲染的时候只需要将 Virtual DOM 对象的 props 属性传递给这个函数即可,注意要考虑 propsnull 的情况。

// src/index.js
/* ... */

function Heart(props) {
  return <div>{props.title}&hearts;</div>
}

TinyReact.render(<Heart title="Hello React" />, root)

// src/TinyReact/mountComponent.js
import isFunction from './isFunction'
import isFunctionComponent from './isFunctionComponent'
import mountNativeElement from './mountNativeElement'
export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null
  // 判断组件是类组件还是函数组件
  if (isFunctionComponent(virtualDOM)) {
    nextVirtualDOM = buildFunctionComponent(virtualDOM)
  }

  // 判断渲染的组件是否直接返回了另一个组件
  if (isFunction(nextVirtualDOM)) {
    mountComponent(nextVirtualDOM, container)
  } else {
    mountNativeElement(nextVirtualDOM, container)
  }
}

function buildFunctionComponent(virtualDOM) {
  return virtualDOM.type(virtualDOM.props || {})
}

类组件

类组件的内容是调用这个类创建的实例对象的 render() 方法返回的内容。

在 React 中,类组件会继承 React.Component,这里先定义一个 Component 类:

// src/TinyReact/Component.js
export default class Component {}

import createElement from './createElement'
import render from './render'
import Component from './Component'

export default {
  createElement,
  render,
  Component
}

编写类组件示例:

// src/index.js
/* ... */

// TinyReact.render(<Heart title="Hello React" />, root)

class Alert extends TinyReact.Component {
  render() {
    return <div>Hello React</div>
  }
}

TinyReact.render(<Alert />, root)

// src/TinyReact/mountComponent.js
import isFunction from './isFunction'
import isFunctionComponent from './isFunctionComponent'
import mountNativeElement from './mountNativeElement'
export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null
  // 判断组件是类组件还是函数组件
  if (isFunctionComponent(virtualDOM)) {
    // 函数组件
    nextVirtualDOM = buildFunctionComponent(virtualDOM)
  } else {
    // 类组件
    nextVirtualDOM = buildClassComponent(virtualDOM)
  }

  // 判断渲染的组件是否直接返回了另一个组件
  if (isFunction(nextVirtualDOM)) {
    mountComponent(nextVirtualDOM, container)
  } else {
    mountNativeElement(nextVirtualDOM, container)
  }
}

function buildFunctionComponent(virtualDOM) {
  return virtualDOM.type(virtualDOM.props || {})
}

function buildClassComponent(virtualDOM) {
  const component = new virtualDOM.type()
  const nextVirtualDOM = component.render()
  return nextVirtualDOM
}

类组件 props 参数处理

React 通过在继承的 Component 组件中定义 props 属性,让继承它的子类可以通过 this.props 访问组件的参数。

// src/TinyReact/Component.js
export default class Component {
  constructor(props) {
    this.props = props
  }
}

给示例添加参数:

// src/index.js
/* ... */

class Alert extends TinyReact.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <div>
        {this.props.name}
        {this.props.age}
      </div>
    )
  }
}

TinyReact.render(<Alert name="张三" age={20} />, root)

JavaScript 的 class 默认会添加一个 constructor() 构造函数,如果是继承其他类的子类,则构造函数内部还会调用 super()

// 子类默认添加的构造函数
constructor(...args) {
  super(...args)
}

所以上面的组件示例可以精简为:

class Alert extends TinyReact.Component {
  // constructor(props) {
  //   super(props)
  // }
  render() {
    return (
      <div>
        {this.props.name}
        {this.props.age}
      </div>
    )
  }
}

注意:如果要显示定义构造函数中的内容,必须要手动调用 super()

更新 DOM 元素

编写示例:

  1. 创建两个 Virtual DOM
  2. 在页面加载时渲染第一个 Virtual DOM
  3. 在延迟 2秒 后渲染第二个 Virtual DOM
  4. 两个 Virtual DOM 有一些修改:
    1. <h2>元素的 data-test
    2. 元素被改变 <h3> to <h6>
    3. <span>的文本内容
    4. <button>点击事件的 alert 内容
// src/index.js
import TinyReact from './TinyReact'

// 容器
const root = document.querySelector('#root')

const virtualDOM = (
  <div className="container">
    <h1>你好 Tiny React</h1>
    <h2 data-test="test">(编码必杀技)</h2>
    <div>
      嵌套1 <div>嵌套 1.1</div>
    </div>
    <h3>(观察:这个将会被改变)</h3>
    {2 == 1 && <div>如果2和1相等渲染当前内容</div>}
    {2 == 2 && <div>2</div>}
    <span>这是一段内容</span>
    <button onClick={() => alert('你好')}>点击我</button>
    <h3>这个将会被删除</h3>
    2, 3
    <input type="text" value="13" />
  </div>
)

const modifyDOM = (
  <div className="container">
    <h1>你好 Tiny React</h1>
    <h2 data-test="test123">(编码必杀技)</h2>
    <div>
      嵌套1 <div>嵌套 1.1</div>
    </div>
    <h6>(观察:这个将会被改变)</h6>
    {2 == 1 && <div>如果2和1相等渲染当前内容</div>}
    {2 == 2 && <div>2</div>}
    <span>这是一段被修改过的内容</span>
    <button onClick={() => alert('你好!!!')}>点击我</button>
    <h3>这个将会被删除</h3>
    2, 3
    <input type="text" value="13" />
  </div>
)

TinyReact.render(virtualDOM, root)

setTimeout(() => {
  TinyReact.render(modifyDOM, root)
}, 2000)

在更新 DOM 的时候,要先对比两个 Virtual DOM 的差异,然后仅重新渲染差异部分,以达到最小更新。

VirtualDOM 对比

获取更新前的 Virtual DOM

在进行 Virtual DOM 对比时,需要用到更新后的 Virtual DOM更新前的 Virtual DOM

更新后的 Virtual DOM 目前可以通过 render 方法进行传递。

问题是如何获取更新前的 Virtual DOM ?

对于更新前的 Virtual DOM,对应的其实就是已经在页面中显示的真实 DOM 对象

既然如此,那么我们在创建真实 DOM 对象时,可以将 Virtual DOM 添加到真实 DOM 对象的属性中。

在进行 Virtual DOM 对比之前,就可以通过真实 DOM 对象获取其对应的 Virtual DOM 对象了。

其实就是通过 render 方法的第三个参数获取的:container.firstChild

为什么是 container.firstChild

因为 JSX 要求所有标签必须包含在一个父标签下,所以通过获取容器下的第一个元素,就可以拿到之前渲染到页面的DOM元素。

// src/TinyReact/render.js
import diff from './diff'
export default function render(virtualDOM, container, oldDOM = container.firstChild) {
  diff(virtualDOM, container, oldDOM)
}

节点类型相同的情况

文本节点
  • 节点类型相同的时候根据节点的类型选择更新方式:
    • 文本节点:更新文本内容
    • 元素节点:更新元素的属性
  • 通过更新前的真实 DOM 元素去执行更新操作
  • 更新后也要同步更新旧的 Virtual DOM 对象
    • 该对象存储在真实 DOM 元素的属性上,将作为每次 DOM 更新对比时的**“更新前的Virtual DOM”**
  • 如果包含子元素还要递归对比
    • 当前暂时使用的**[序号]**进行对比,之后将扩展为使用 key 去对比。
// src/TinyReact/diff.js
import mountElement from './mountElement'
import updateTextNode from './updateTextNode'
export default function diff(virtualDOM, container, oldDOM) {
  const oldVirtualDOM = oldDOM && oldDOM._virtualDOM
  // 判断 oldDOM 是否存在
  if (!oldDOM) {
    mountElement(virtualDOM, container)
  } else if (virtualDOM.type === oldVirtualDOM.type) {
    // 节点类型相同
    if (virtualDOM.type === 'text') {
      // 文本节点:更新内容
      updateTextNode(virtualDOM, oldVirtualDOM, oldDOM)
    } else {
      // 元素节点:更新元素属性
    }

    // 对比子节点
    virtualDOM.children.forEach((child, i) => {
      diff(child, oldDOM, oldDOM.childNodes[i])
    })
  }
}

// src/TinyReact/updateTextNode.js
export default function updateTextNode(virtualDOM, oldVirtualDOM, oldDOM) {
  if (virtualDOM.props.textContent !== oldVirtualDOM.props.textContent) {
    // 更新文本内容
    oldDOM.textContent = virtualDOM.props.textContent
    // 同步更新 Virtual DOM 对象
    oldDOM._virtualDOM = virtualDOM
  }
}

元素节点

对比新旧元素节点的属性 props 并更新。

更新元素节点的属性使用的是之前定义过的 updateNodeElement() 方法。

更新元素节点有以下几种情况:

  1. 原有属性被修改或添加新的属性
    1. 如果是事件属性,则注册新的事件处理函数,并且删除旧的事件处理函数
    2. 如果是其他属性,则重新设置即可
  2. 属性被删除
    1. 如果是事件属性,则删除旧的事件处理函数
    2. 如果是 children 属性,则忽略
    3. 如果是其他属性,则进行删除
// src/TinyReact/diff.js
import mountElement from './mountElement'
import updateNodeElement from './updateNodeElement'
import updateTextNode from './updateTextNode'
export default function diff(virtualDOM, container, oldDOM) {
  const oldVirtualDOM = oldDOM && oldDOM._virtualDOM
  // 判断 oldDOM 是否存在
  if (!oldDOM) {
    mountElement(virtualDOM, container)
  } else if (virtualDOM.type === oldVirtualDOM.type) {
    // 节点类型相同
    if (virtualDOM.type === 'text') {
      // 文本节点:更新内容
      updateTextNode(virtualDOM, oldVirtualDOM, oldDOM)
    } else {
      // 元素节点:更新元素属性
      updateNodeElement(oldDOM, virtualDOM, oldVirtualDOM)
    }

    // 对比子节点
    virtualDOM.children.forEach((child, i) => {
      diff(child, oldDOM, oldDOM.childNodes[i])
    })
  }
}

// src/TinyReact/updateNodeElement.js
/**
 * @param {*} newElement 要更新的 DOM 元素对象
 * @param {*} virtualDOM 新的 Virtual DOM 对象
 * @param {*} oldVirtualDOM 旧的 Virtual DOM 对象
 */
export default function updateNodeElement(newElement, virtualDOM = {}, oldVirtualDOM = {}) {
  // 获取节点对应的属性对象
  const newProps = virtualDOM.props
  const oldProps = oldVirtualDOM.props || {}

  // 属性被修改或添加属性的情况
  Object.keys(newProps).forEach(propName => {
    // 获取属性值
    const newPropsValue = newProps[propName]
    const oldPropsValue = oldProps[propName]

    if (newPropsValue !== oldPropsValue) {
      if (propName.startsWith('on')) {
        // 判断属性是否是事件属性
        // 事件名称 onClick -> click
        const eventName = propName.toLowerCase().slice(2)
        // 为元素添加事件
        newElement.addEventListener(eventName, newPropsValue)

        // 删除原有事件的事件处理函数
        if (oldPropsValue) {
          newElement.removeEventListener(eventName, oldPropsValue)
        }
      } else if (propName === 'value' || propName === 'checked') {
        // 判断是否是不能用 setAttribute() 设置的属性
        newElement[propName] = newPropsValue
      } else if (propName !== 'children') {
        // 过滤 children 属性
        if (propName === 'className') {
          newElement.setAttribute('class', newPropsValue)
        } else {
          newElement.setAttribute(propName, newPropsValue)
        }
      }
    }
  })

  // 判断属性被删除的情况
  Object.keys(oldProps).forEach(propName => {
    const newPropsValue = newProps[propName]
    const oldPropsValue = oldProps[propName]
    if (!newPropsValue) {
      // 属性被删除
      if (propName.startsWith('on')) {
        // 判断属性是否是事件属性
        const eventName = propName.toLowerCase().slice(2)
        // 为元素添加事件
        newElement.removeEventListener(eventName, oldPropsValue)
      } else if (propName !== 'children') {
        newElement.removeAttribute(propName)
      }
    }
  })
}

节点类型不同的情况

当两个节点类型不相同的时候,就没有对比的必要了。

只需要用新的 Virtual DOM 生成新的真实 DOM 对象,然后使用新的 DOM 对象替换旧的 DOM 对象即可。

组件需要特殊处理,这里仅处理了普通的 Virtual DOM。

// src/TinyReact/diff.js
import createDOMElement from './createDOMElement'
import mountElement from './mountElement'
import updateNodeElement from './updateNodeElement'
import updateTextNode from './updateTextNode'
import isFunction from './isFunction'
export default function diff(virtualDOM, container, oldDOM) {
  const oldVirtualDOM = oldDOM && oldDOM._virtualDOM
  // 判断 oldDOM 是否存在
  if (!oldDOM) {
    mountElement(virtualDOM, container)
  } else if (
    // 对比的两个节点类型不相同
    virtualDOM.type !== oldVirtualDOM.type &&
    // 并且节点的类型不是组件,因为组件要单独处理
    !isFunction(virtualDOM)
  ) {
    // 节点类型不相同
    const newElement = createDOMElement(virtualDOM)
    oldDOM.parentNode.replaceChild(newElement, oldDOM)
  } else if (virtualDOM.type === oldVirtualDOM.type) {
    // 节点类型相同
    if (virtualDOM.type === 'text') {
      // 文本节点:更新内容
      updateTextNode(virtualDOM, oldVirtualDOM, oldDOM)
    } else {
      // 元素节点:更新元素属性
      updateNodeElement(oldDOM, virtualDOM, oldVirtualDOM)
    }

    // 对比子节点
    virtualDOM.children.forEach((child, i) => {
      diff(child, oldDOM, oldDOM.childNodes[i])
    })
  }
}

Diff 对比

Virtual DOM 对比(Diff)的算法有两个原则:

  • 同级节点对比
  • 深度优先顺序

同级对比

在这里插入图片描述

Virtual DOM 在对比的时候是同级对比。即父元素和父元素对比,子元素和子元素对比,不会发生跨级对比的。

  • 如果对比的节点类型相同
    • 如果是文本节点,则对比文本内容,如果内容不同,则替换为新的内容
    • 如果是元素节点,则对比元素属性
      • 如果属性相同,则不作处理
      • 如果属性值不同(包括新添加属性的情况),则替换为新节点属性值
      • 如果属性被删除(新节点不包含该属性),则删除属性
  • 如果对比的节点类型不同
    • 则直接用新的 Virtual DOM 生成新的真实 DOM 对象,替换旧的 DOM 对象

深度优先

在这里插入图片描述

Virtual DOM 对比的顺序是深度优先。即子节点对比优先于同级节点对比。

例如图例:

  1. 首先对比最外层的节点 ul
  2. 接着对比 ul 节点下的第一个子节点 li
  3. 第一个li节点对比完后,发现它包含子节点,所以继续对比该节点下的第一个子节点 p
  4. 当第一个 li 节点下的 p 节点对比完成后,接着对比 li 的兄弟节点

对应到代码中,就是在对比当前节点的过程中,递归对比它的子节点,当全部对比完成后,继续对比下一个兄弟节点。

删除节点

  • 时机:在节点更新完成之后才能分析哪些节点应该被删除
  • 范围:发生在同一个父节点下的所有子节点身上

如何判断是否有节点需要被删除:在节点更新完成后,如果旧节点对象的数量多于新 Virtual DOM 节点的数量,就说明有节点需要被删除。

在这里插入图片描述

// src/TinyReact/diff.js
import createDOMElement from './createDOMElement'
import mountElement from './mountElement'
import updateNodeElement from './updateNodeElement'
import updateTextNode from './updateTextNode'
import isFunction from './isFunction'
import unmountNode from './unmountNode'
export default function diff(virtualDOM, container, oldDOM) {
  const oldVirtualDOM = oldDOM && oldDOM._virtualDOM
  // 判断 oldDOM 是否存在
  if (!oldDOM) {
    mountElement(virtualDOM, container)
  } else if (
    // 对比的两个节点类型不相同
    virtualDOM.type !== oldVirtualDOM.type &&
    // 并且节点的类型不是组件,因为组件要单独处理
    !isFunction(virtualDOM)
  ) {
    // 节点类型不相同
    const newElement = createDOMElement(virtualDOM)
    oldDOM.parentNode.replaceChild(newElement, oldDOM)
  } else if (virtualDOM.type === oldVirtualDOM.type) {
    // 节点类型相同
    if (virtualDOM.type === 'text') {
      // 文本节点:更新内容
      updateTextNode(virtualDOM, oldVirtualDOM, oldDOM)
    } else {
      // 元素节点:更新元素属性
      updateNodeElement(oldDOM, virtualDOM, oldVirtualDOM)
    }

    // 对比子节点
    virtualDOM.children.forEach((child, i) => {
      diff(child, oldDOM, oldDOM.childNodes[i])
    })

    // 删除节点
    // 获取旧节点
    const oldChildNodes = oldDOM.childNodes
    // 判断旧节点的数量
    if (oldChildNodes.length > virtualDOM.children.length) {
      // 有节点需要被删除
      for (let i = oldChildNodes.length - 1; i > virtualDOM.children.length; i--) {
        unmountNode(oldChildNodes[i])
      }
    }
  }
}

// src/TinyReact/unmountNode.js
export default function unmountNode(node) {
  node.remove()
}

setState 方法实现类组件状态更新

更新 state

要更新类组件的状态,要用到setState()方法。

setState 也是父类 (Component)定义的方法。

setState方法是组件实例调用的,所以内部的 this 指向实例对象。

该方法可以接受一个对象,调用结果会用传递的对象浅合并类组件的 state 属性。

示例:

class Alert extends TinyReact.Component {
  constructor(props) {
    super(props)
    this.state = {
      title: 'Default Title'
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    this.setState({
      title: 'Changed Title'
    })
  }
  render() {
    console.log(this.state)
    return (
      <div>
        {this.props.name}
        {this.props.age}
        <div>
          {this.state.title}
          <button onClick={this.handleClick}>改变Title</button>
        </div>
      </div>
    )
  }
}

TinyReact.render(<Alert name="张三" age={20} />, root)
// src/TinyReact/Component.js
export default class Component {
  constructor(props) {
    this.props = props
  }
  setState(state) {
    this.state = Object.assign({}, this.state, state)
  }
}

对比新旧 VirtualDOM

state 发生变化后要重新生成新的 Virtual DOM 对象与旧的进行比对,并将差异更新到 旧的 DOM 中。

  • 获取新的 Virtual DOM 对象:调用 render() 方法
  • 获取旧的 DOM 对象:
    • 添加用于存储/获取 DOM 对象的方法,在挂载真实 DOM 时进行存储,之后可以通过 DOM 对象的 _virtualDOM 属性获取它的 virtual DOM。
    • mountNativeElement() 中调用存储 DOM 的方法,将 DOM 存储到组件实例对象上

mountNativeElement() 方法中如何访问组件实例对象:

  • 在挂载类组件的时候调用了 buildClassComponent() 方法
  • 内部实例化了组件实例对象
  • 然后生成了 Virtual DOM 对象(render()),并返回
  • 这个 Virtual DOM 又传递给 mountNativeElement()

所以可以在 buildClassComponent() 方法内部,将实例对象存储在 Virtual DOM 对象中进行传递。

// src/TinyReact/Component.js
export default class Component {
  constructor(props) {
    this.props = props
  }
  setState(state) {
    this.state = Object.assign({}, this.state, state)
    // 获取最新的要渲染的 VirtualDOM 对象
    const virtualDOM = this.render()
    // 获取旧的 VirtualDOM 对象进行比对
    const oldDOM = this.getDOM()

    const container = oldDOM.parentNode
    diff(virtualDOM, oldDOM.parentNode, oldDOM)
  }
  setDOM(dom) {
    this._dom = dom
  }
  getDOM() {
    return this._dom
  }
}

// src/TinyReact/mountComponent.js
import isFunction from './isFunction'
import isFunctionComponent from './isFunctionComponent'
import mountNativeElement from './mountNativeElement'
export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null
  // 判断组件是类组件还是函数组件
  if (isFunctionComponent(virtualDOM)) {
    // 函数组件
    nextVirtualDOM = buildFunctionComponent(virtualDOM)
  } else {
    // 类组件
    nextVirtualDOM = buildClassComponent(virtualDOM)
  }

  // 判断渲染的组件是否直接返回了另一个组件
  if (isFunction(nextVirtualDOM)) {
    mountComponent(nextVirtualDOM, container)
  } else {
    mountNativeElement(nextVirtualDOM, container)
  }
}

function buildFunctionComponent(virtualDOM) {
  return virtualDOM.type(virtualDOM.props || {})
}

function buildClassComponent(virtualDOM) {
  const component = new virtualDOM.type(virtualDOM.props || {})
  const nextVirtualDOM = component.render()
  // 存储组件实例对象
  nextVirtualDOM.component = component
  return nextVirtualDOM
}

// src/TinyReact/mountElement.js
import createDOMElement from './createDOMElement'
export default function mountNativeElement(virtualDOM, container) {
  const newElement = createDOMElement(virtualDOM)

  // 将转换之后的 DOM 对象放置到页面中
  container.appendChild(newElement)

  const component = virtualDOM.component

  // 判断是否是类组件返回的 VirtualDOM
  if (component) {
    component.setDOM(newElement)
  }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React 中,组件的卸载是指组件DOM 中移除并销毁的过程。当组件不再需要渲染或不再在页面上显示时,React 会自动处理组件的卸载。 要在组件卸载时执行特定的操作,你可以使用 `componentWillUnmount` 生命周期方法。在这个方法中,你可以清理定时器、取消订阅、取消网络请求或执行其他必要的清理操作。 在最新版本的 React 中,`componentWillUnmount` 生命周期方法已被废弃,取而代之的是 `componentDidUnmount` 生命周期方法。你可以在这个方法中执行卸载时的清理操作。 下面是一个示例代码,展示了如何使用 `componentDidUnmount` 方法来执行清理操作: ```javascript import React, { Component } from 'react'; class MyComponent extends Component { componentDidMount() { // 在组件挂载后执行一些操作 } componentDidUnmount() { // 在组件卸载时执行一些清理操作 } render() { return ( <div> {/* 组件渲染内容 */} </div> ); } } export default MyComponent; ``` 请注意,在函数式组件中,你可以使用 `useEffect` 钩子来模拟 `componentDidUnmount` 生命周期方法。使用 `useEffect` 钩子的返回函数即可在组件卸载时执行清理操作。 ```javascript import React, { useEffect } from 'react'; const MyComponent = () => { useEffect(() => { // 在组件挂载后执行一些操作 return () => { // 在组件卸载时执行一些清理操作 }; }, []); return ( <div> {/* 组件渲染内容 */} </div> ); }; export default MyComponent; ``` 希望对你有所帮助!如果你有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值