Jadual Kandungan
生命周期
测试生命周期
diff 的实现
对比文本节点
对比非文本节点
对比自定义组件
遍历对比子节点
测试
Rumah hujung hadapan web tutorial js 从 0 到 1 实现 React 系列:生命周期和diff 的实现

从 0 到 1 实现 React 系列:生命周期和diff 的实现

Jul 27, 2018 pm 02:22 PM
javascript react.js

1.jpg

本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/...)

  • 从 0 到 1 实现 React 系列 —— JSX 和 Virtual DOM

  • 从 0 到 1 实现 React 系列 —— 组件和 state|props

生命周期

先来回顾 React 的生命周期,用流程图表示如下:

2.jpg

该流程图比较清晰地呈现了 react 的生命周期。其分为 3 个阶段 —— 生成期,存在期,销毁期。

因为生命周期钩子函数存在于自定义组件中,将之前 _render 函数作些调整如下:

// 原来的 _render 函数,为了将职责拆分得更细,将 virtual dom 转为 real dom 的函数单独抽离出来
function vdomToDom(vdom) {
  if (_.isFunction(vdom.nodeName)) {        // 为了更加方便地书写生命周期逻辑,将解析自定义组件逻辑和一般 html 标签的逻辑分离开
    const component = createComponent(vdom) // 构造组件
    setProps(component)                     // 更改组件 props
    renderComponent(component)              // 渲染组件,将 dom 节点赋值到 component
    return component.base                   // 返回真实 dom
  }
  ...
}
Salin selepas log masuk

我们可以在 setProps 函数内(渲染前)加入 componentWillMountcomponentWillReceiveProps 方法,setProps 函数如下:

function setProps(component) {
  if (component && component.componentWillMount) {
    component.componentWillMount()
  } else if (component.base && component.componentWillReceiveProps) {
    component.componentWillReceiveProps(component.props) // 后面待实现
  }
}
Salin selepas log masuk

而后我们在 renderComponent 函数内加入 componentDidMountshouldComponentUpdatecomponentWillUpdatecomponentDidUpdate 方法

function renderComponent(component) {
  if (component.base && component.shouldComponentUpdate) {
    const bool = component.shouldComponentUpdate(component.props, component.state)
    if (!bool && bool !== undefined) {
      return false // shouldComponentUpdate() 返回 false,则生命周期终止
    }
  }
  if (component.base && component.componentWillUpdate) {
    component.componentWillUpdate()
  }

  const rendered = component.render()
  const base = vdomToDom(rendered)

  if (component.base && component.componentDidUpdate) {
    component.componentDidUpdate()
  } else if (component && component.componentDidMount) {
    component.componentDidMount()
  }

  if (component.base && component.base.parentNode) { // setState 进入此逻辑
    component.base.parentNode.replaceChild(base, component.base)
  }

  component.base = base  // 标志符
}
Salin selepas log masuk

测试生命周期

测试如下用例:

class A extends Component {
  componentWillReceiveProps(props) {
    console.log('componentWillReceiveProps')
  }

  render() {
    return (
      <p>{this.props.count}</p>
    )
  }
}

class B extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }

  componentWillMount() {
    console.log(&#39;componentWillMount&#39;)
  }

  componentDidMount() {
    console.log(&#39;componentDidMount&#39;)
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log(&#39;shouldComponentUpdate&#39;, nextProps, nextState)
    return true
  }

  componentWillUpdate() {
    console.log(&#39;componentWillUpdate&#39;)
  }

  componentDidUpdate() {
    console.log(&#39;componentDidUpdate&#39;)
  }

  click() {
    this.setState({
      count: ++this.state.count
    })
  }

  render() {
    console.log(&#39;render&#39;)
    return (
      <p>
        <button onClick={this.click.bind(this)}>Click Me!</button>
        <A count={this.state.count} />
      </p>
    )
  }
}

ReactDOM.render(
  <B />,
  document.getElementById(&#39;root&#39;)
)
Salin selepas log masuk

页面加载时输出结果如下:

componentWillMount
render
componentDidMount
Salin selepas log masuk

点击按钮时输出结果如下:

shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
Salin selepas log masuk

diff 的实现

在 react 中,diff 实现的思路是将新老 virtual dom 进行比较,将比较后的 patch(补丁)渲染到页面上,从而实现局部刷新;本文借鉴了 preact 和 simple-react 中的 diff 实现,总体思路是将旧的 dom 节点和新的 virtual dom 节点进行了比较,根据不同的比较类型(文本节点、非文本节点、自定义组件)调用相应的逻辑,从而实现页面的局部渲染。代码总体结构如下:

/**
 * 比较旧的 dom 节点和新的 virtual dom 节点:
 * @param {*} oldDom  旧的 dom 节点
 * @param {*} newVdom 新的 virtual dom 节点
 */
function diff(oldDom, newVdom) {
  ...
  if (_.isString(newVdom)) {
    return diffTextDom(oldDom, newVdom)   // 对比文本 dom 节点
  }

  if (oldDom.nodeName.toLowerCase() !== newVdom.nodeName) {
    diffNotTextDom(oldDom, newVdom)       // 对比非文本 dom 节点
  }

  if (_.isFunction(newVdom.nodeName)) {
    return diffComponent(oldDom, newVdom) // 对比自定义组件
  }

  diffAttribute(oldDom, newVdom)          // 对比属性

  if (newVdom.children.length > 0) {
    diffChild(oldDom, newVdom)            // 遍历对比子节点
  }

  return oldDom
}
Salin selepas log masuk

下面根据不同比较类型实现相应逻辑。

对比文本节点

首先进行较为简单的文本节点的比较,代码如下:

// 对比文本节点
function diffTextDom(oldDom, newVdom) {
  let dom = oldDom
  if (oldDom && oldDom.nodeType === 3) {  // 如果老节点是文本节点
    if (oldDom.textContent !== newVdom) { // 这里一个细节:textContent/innerHTML/innerText 的区别
      oldDom.textContent = newVdom
    }
  } else {                                // 如果旧 dom 元素不为文本节点
    dom = document.createTextNode(newVdom)
    if (oldDom && oldDom.parentNode) {
      oldDom.parentNode.replaceChild(dom, oldDom)
    }
  }
  return dom
}
Salin selepas log masuk

对比非文本节点

对比非文本节点,其思路为将同层级的旧节点替换为新节点,代码如下:

// 对比非文本节点
function diffNotTextDom(oldDom, newVdom) {
  const newDom = document.createElement(newVdom.nodeName);
  [...oldDom.childNodes].map(newDom.appendChild) // 将旧节点下的元素添加到新节点下
  if (oldDom && oldDom.parentNode) {
    oldDom.parentNode.replaceChild(oldDom, newDom)
  }
}
Salin selepas log masuk

对比自定义组件

对比自定义组件的思路为:如果新老组件不同,则直接将新组件替换老组件;如果新老组件相同,则将新组件的 props 赋到老组件上,然后再对获得新 props 前后的老组件做 diff 比较。代码如下:

// 对比自定义组件
function diffComponent(oldDom, newVdom) {
  if (oldDom._component && (oldDom._component.constructor !== newVdom.nodeName)) { // 如果新老组件不同,则直接将新组件替换老组件
    const newDom = vdomToDom(newVdom)
    oldDom._component.parentNode.insertBefore(newDom, oldDom._component)
    oldDom._component.parentNode.removeChild(oldDom._component)
  } else {
    setProps(oldDom._component, newVdom.attributes) // 如果新老组件相同,则将新组件的 props 赋到老组件上
    renderComponent(oldDom._component)              // 对获得新 props 前后的老组件做 diff 比较(renderComponent 中调用了 diff)
  }
}
Salin selepas log masuk

遍历对比子节点

遍历对比子节点的策略有两个:一是只比较同层级的节点,二是给节点加上 key 属性。它们的目的都是降低空间复杂度。代码如下:

// 对比子节点
function diffChild(oldDom, newVdom) {
  const keyed = {}
  const children = []
  const oldChildNodes = oldDom.childNodes
  for (let i = 0; i < oldChildNodes.length; i++) {
    if (oldChildNodes[i].key) { // 将含有 key 的节点存进对象 keyed
      keyed[oldChildNodes[i].key] = oldChildNodes[i]
    } else {                    // 将不含有 key 的节点存进数组 children
      children.push(oldChildNodes[i])
    }
  }

  const newChildNodes = newVdom.children
  let child
  for (let i = 0; i < newChildNodes.length; i++) {
    if (keyed[newChildNodes[i].key]) {  // 对应上面存在 key 的情形
      child = keyed[newChildNodes[i].key]
      keyed[newChildNodes[i].key] = undefined
    } else {                            // 对应上面不存在 key 的情形
      for (let j = 0; j < children.length; j++) {
        if (isSameNodeType(children[i], newChildNodes[i])) { // 如果不存在 key,则优先找到节点类型相同的元素
          child = children[i]
          children[i] = undefined
          break
        }
      }
    }
    diff(child, newChildNodes[i]) // 递归比较
  }
}
Salin selepas log masuk

测试

在生命周期的小节中,componentWillReceiveProps 方法还未跑通,稍加修改 setProps 函数即可:

/**
 * 更改属性,componentWillMount 和 componentWillReceiveProps 方法
 */
function setProps(component, attributes) {
  if (attributes) {
    component.props = attributes // 这段逻辑对应上文自定义组件比较中新老组件相同时 setProps 的逻辑
  }

  if (component && component.base && component.componentWillReceiveProps) {
    component.componentWillReceiveProps(component.props)
  } else if (component && component.componentWillMount) {
    component.componentWillMount()
  }
}
Salin selepas log masuk

来测试下生命周期小节中最后的测试用例:

  • 生命周期测试

a.gif

  • diff 测试

b.gif

项目地址,关于如何 pr

相关文章:

React组件生命周期实例分析

React组件生命周期详解

相关视频:

虚拟dom-React框架视频教程

Atas ialah kandungan terperinci 从 0 到 1 实现 React 系列:生命周期和diff 的实现. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Artikel Panas

R.E.P.O. Kristal tenaga dijelaskan dan apa yang mereka lakukan (kristal kuning)
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Tetapan grafik terbaik
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Cara Memperbaiki Audio Jika anda tidak dapat mendengar sesiapa
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Bagaimana untuk melaksanakan sistem pengecaman pertuturan dalam talian menggunakan WebSocket dan JavaScript Bagaimana untuk melaksanakan sistem pengecaman pertuturan dalam talian menggunakan WebSocket dan JavaScript Dec 17, 2023 pm 02:54 PM

Cara menggunakan WebSocket dan JavaScript untuk melaksanakan sistem pengecaman pertuturan dalam talian Pengenalan: Dengan perkembangan teknologi yang berterusan, teknologi pengecaman pertuturan telah menjadi bahagian penting dalam bidang kecerdasan buatan. Sistem pengecaman pertuturan dalam talian berdasarkan WebSocket dan JavaScript mempunyai ciri kependaman rendah, masa nyata dan platform merentas, dan telah menjadi penyelesaian yang digunakan secara meluas. Artikel ini akan memperkenalkan cara menggunakan WebSocket dan JavaScript untuk melaksanakan sistem pengecaman pertuturan dalam talian.

WebSocket dan JavaScript: teknologi utama untuk melaksanakan sistem pemantauan masa nyata WebSocket dan JavaScript: teknologi utama untuk melaksanakan sistem pemantauan masa nyata Dec 17, 2023 pm 05:30 PM

WebSocket dan JavaScript: Teknologi utama untuk merealisasikan sistem pemantauan masa nyata Pengenalan: Dengan perkembangan pesat teknologi Internet, sistem pemantauan masa nyata telah digunakan secara meluas dalam pelbagai bidang. Salah satu teknologi utama untuk mencapai pemantauan masa nyata ialah gabungan WebSocket dan JavaScript. Artikel ini akan memperkenalkan aplikasi WebSocket dan JavaScript dalam sistem pemantauan masa nyata, memberikan contoh kod dan menerangkan prinsip pelaksanaannya secara terperinci. 1. Teknologi WebSocket

Bagaimana untuk melaksanakan sistem tempahan dalam talian menggunakan WebSocket dan JavaScript Bagaimana untuk melaksanakan sistem tempahan dalam talian menggunakan WebSocket dan JavaScript Dec 17, 2023 am 09:39 AM

Cara menggunakan WebSocket dan JavaScript untuk melaksanakan sistem tempahan dalam talian Dalam era digital hari ini, semakin banyak perniagaan dan perkhidmatan perlu menyediakan fungsi tempahan dalam talian. Adalah penting untuk melaksanakan sistem tempahan dalam talian yang cekap dan masa nyata. Artikel ini akan memperkenalkan cara menggunakan WebSocket dan JavaScript untuk melaksanakan sistem tempahan dalam talian dan memberikan contoh kod khusus. 1. Apakah itu WebSocket? WebSocket ialah kaedah dupleks penuh pada sambungan TCP tunggal.

Cara menggunakan JavaScript dan WebSocket untuk melaksanakan sistem pesanan dalam talian masa nyata Cara menggunakan JavaScript dan WebSocket untuk melaksanakan sistem pesanan dalam talian masa nyata Dec 17, 2023 pm 12:09 PM

Pengenalan kepada cara menggunakan JavaScript dan WebSocket untuk melaksanakan sistem pesanan dalam talian masa nyata: Dengan populariti Internet dan kemajuan teknologi, semakin banyak restoran telah mula menyediakan perkhidmatan pesanan dalam talian. Untuk melaksanakan sistem pesanan dalam talian masa nyata, kami boleh menggunakan teknologi JavaScript dan WebSocket. WebSocket ialah protokol komunikasi dupleks penuh berdasarkan protokol TCP, yang boleh merealisasikan komunikasi dua hala masa nyata antara pelanggan dan pelayan. Dalam sistem pesanan dalam talian masa nyata, apabila pengguna memilih hidangan dan membuat pesanan

JavaScript dan WebSocket: Membina sistem ramalan cuaca masa nyata yang cekap JavaScript dan WebSocket: Membina sistem ramalan cuaca masa nyata yang cekap Dec 17, 2023 pm 05:13 PM

JavaScript dan WebSocket: Membina sistem ramalan cuaca masa nyata yang cekap Pengenalan: Hari ini, ketepatan ramalan cuaca sangat penting kepada kehidupan harian dan membuat keputusan. Apabila teknologi berkembang, kami boleh menyediakan ramalan cuaca yang lebih tepat dan boleh dipercayai dengan mendapatkan data cuaca dalam masa nyata. Dalam artikel ini, kita akan mempelajari cara menggunakan teknologi JavaScript dan WebSocket untuk membina sistem ramalan cuaca masa nyata yang cekap. Artikel ini akan menunjukkan proses pelaksanaan melalui contoh kod tertentu. Kami

Tutorial JavaScript Mudah: Cara Mendapatkan Kod Status HTTP Tutorial JavaScript Mudah: Cara Mendapatkan Kod Status HTTP Jan 05, 2024 pm 06:08 PM

Tutorial JavaScript: Bagaimana untuk mendapatkan kod status HTTP, contoh kod khusus diperlukan: Dalam pembangunan web, interaksi data dengan pelayan sering terlibat. Apabila berkomunikasi dengan pelayan, kami selalunya perlu mendapatkan kod status HTTP yang dikembalikan untuk menentukan sama ada operasi itu berjaya dan melaksanakan pemprosesan yang sepadan berdasarkan kod status yang berbeza. Artikel ini akan mengajar anda cara menggunakan JavaScript untuk mendapatkan kod status HTTP dan menyediakan beberapa contoh kod praktikal. Menggunakan XMLHttpRequest

Bagaimana untuk menggunakan insertBefore dalam javascript Bagaimana untuk menggunakan insertBefore dalam javascript Nov 24, 2023 am 11:56 AM

Penggunaan: Dalam JavaScript, kaedah insertBefore() digunakan untuk memasukkan nod baharu dalam pepohon DOM. Kaedah ini memerlukan dua parameter: nod baharu untuk dimasukkan dan nod rujukan (iaitu nod di mana nod baharu akan dimasukkan).

Bagaimana untuk mendapatkan kod status HTTP dalam JavaScript dengan cara yang mudah Bagaimana untuk mendapatkan kod status HTTP dalam JavaScript dengan cara yang mudah Jan 05, 2024 pm 01:37 PM

Pengenalan kepada kaedah mendapatkan kod status HTTP dalam JavaScript: Dalam pembangunan bahagian hadapan, kita selalunya perlu berurusan dengan interaksi dengan antara muka bahagian belakang, dan kod status HTTP adalah bahagian yang sangat penting daripadanya. Memahami dan mendapatkan kod status HTTP membantu kami mengendalikan data yang dikembalikan oleh antara muka dengan lebih baik. Artikel ini akan memperkenalkan cara menggunakan JavaScript untuk mendapatkan kod status HTTP dan memberikan contoh kod khusus. 1. Apakah kod status HTTP bermakna kod status HTTP apabila penyemak imbas memulakan permintaan kepada pelayan, perkhidmatan tersebut

See all articles