Vue 进阶系列(三)之Render函数原理及实现

栏目: JavaScript · 发布时间: 6年前

内容简介:根据第一篇文章介绍的响应式原理,如下图所示。在初始化阶段,本质上发生在在更新阶段,

根据第一篇文章介绍的响应式原理,如下图所示。

Vue 进阶系列(三)之Render函数原理及实现

在初始化阶段,本质上发生在 auto run 函数中,然后通过 render 函数生成 Virtual DOMview 根据 Virtual DOM 生成 Actual DOM 。因为 render 函数依赖于页面上所有的数据 data ,并且这些数据是响应式的,所有的数据作为组件 render 函数的依赖。一旦这些数据有所改变,那么 render 函数会被重新调用。

在更新阶段, render 函数会重新调用并且返回一个新的 Virtual Dom ,新旧 Virtual DOM 之间会进行比较,把diff之后的最小改动应用到 Actual DOM 中。

Watcher负责收集依赖,清除依赖和通知依赖。在大型复杂的组件树结构下,由于采用了精确的依赖追踪系统,所以会避免组件的过度渲染。

Vue 进阶系列(三)之Render函数原理及实现

Actual DOM 和 Virtual DOM

Actual DOM 通过document.createElement('div')生成一个DOM节点。

document.createElement('div')

// 浏览器原生对象(开销大)
"[object HTMLDivElement]"
复制代码

Virtual DOM 通过 vm.$createElement('div')生成一个JS对象,VDOM对象有一个表示div的tag属性,有一个包含了所有可能特性的data属性,可能还有一个包含更多虚拟节点的children列表。

vm.$createElement('div')

// 纯JS对象(轻量)
{ tag: 'div', data: { attrs: {}, ...}, children: [] }
复制代码

因为Virtual DOM的渲染逻辑和Actual DOM解耦了,所以有能力运行在的非浏览器环境中,这就是为什么Virtual DOM出现之后混合开发开始流行的原因,React Native 和 Weex能够实现的原理就是这个。

JSX和Template

JSX和Template都是用于声明DOM和state之间关系的一种方式,在Vue中,Template是默认推荐的方式,但是也可以使用JSX来做更灵活的事。

JSX更加动态化,对于使用编程语言是很有帮助的,可以做任何事,但是动态化使得编译优化更加复杂和困难。

Template更加静态化并且对于表达式有更多约束,但是可以快速复用已经存在的模板,模板约束意味着可以在编译时做更多的性能优化,相对于JSX在编译时间上有着更多优势。

实例1:实现example组件

要求使用如下

<example :tags="['h1', 'h2', 'h3']"></example>
复制代码

要求输出如下

<div>
  <h1>0</h1>
  <h2>1</h2>
  <h3>2</h3>
</div>
复制代码

上面这个需求可以通过 render 函数来做,官方提供了 createElement 函数用来生成模板。 createElement('div', {}, [...]) 可接受的参数如下。

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签字符串,组件选项对象,或者
  // 解析上述任何一种的一个 async 异步函数。必需参数。
  'div',

  // {Object}
  // 一个包含模板相关属性的数据对象
  // 你可以在 template 中使用这些特性。可选参数。
  {
    
  },

  // {String | Array}
  // 子虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选参数。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)
复制代码

知道了用法之后,就可以在 render 中返回 createElement 生成的虚拟节点,外层是 div ,内层是三个锚点标题 h1 h2 h3 ,所以内层需要遍历下,使用两个 createElement 就可以完成了。

通常使用 h 作为 createElement 的别名,这是 Vue 的通用惯例,也是 JSX 的要求。

实现如下

<!--引用-->
<script src="../node_modules/vue/dist/vue.js"></script>

<!--定义template -->
<div id="app">
  <example :tags="['h1', 'h2', 'h3']"></example>
</div>

<script>
    // 定义example组件
    Vue.component('example', {
      props: ['tags'],
      render (h) {
        
        // 第二个参数是一个包含模板相关属性的数据对象,可选参数
        
        // 子虚拟节点(VNodes)参数可以传入字符串或者数字,
        // 通过createElement生成,可选参数
        return h('div', this.tags.map((tag, i) => h(tag, i)))
      }
    })
    
    // 实例化
    new Vue({ el: '#app' })
</script>
复制代码

实例2:实现动态的 <example> 组件

要求如下

  • 实现一个 Foo 组件渲染 <div>foo</div> ,实现一个 Bar 组件渲染 <div>bar</div>
  • 实现一个 <example> 组件,根据属性 ok 动态渲染 Foo 组件或者 Bar 组件。如果属性 oktrue ,那么最终的渲染应该是 <div>foo</div>
  • 实现一个按钮控制属性 ok ,通过这个属性让 <example>Foo 或者 Bar 之间切换。

根据上面的要求,在模板中调用 <example> 组件,然后定义 <button> 组件,同时绑定属性 ok

实现如下

<!--引用-->
<script src="../node_modules/vue/dist/vue.js"></script>

<!--定义template -->
<div id="app">

  <!--绑定属性ok-->
  <example :ok="ok"></example>
  
  <!--绑定点击事件-->
  <button @click="ok = !ok">toggle</button>
</div>

<script>
    // 定义Foo
    const Foo = {
      render (h) {
        return h('div', 'foo')
      }
    }
    
    // 定义Bar
    const Bar = {
      render (h) {
        return h('div', 'bar')
      }
    }
    
    // 定义example组件
    // 根据ok属性动态切换
    Vue.component('example', {
      props: ['ok'],
      render (h) {
        return h(this.ok ? Foo : Bar)
      }
    })
    
    // 实例化
    new Vue({
      el: '#app',
      data: { ok: true }
    })
</script>
复制代码

实例3:实现组件

要求如下

  • 实现一个 withAvatarURL 函数,要求传入一个带有 url 属性的组件,返回一个接收 username 属性的高阶组件,这个高阶组件主要负责获取相应的头像URL。
  • 在API返回之前,高阶组件将占位符URL http://via.placeholder.com/200x200 传递给内部组件。

例子如下

const SmartAvatar = withAvatarURL(Avatar)

// 使用这个方式
<smart-avatar username="vuejs"></smart-avatar>

// 替换下面的方式
<avatar url="/path/to/image.png"></avatar>
复制代码

withAvatarURL 函数返回一个对象,接收 username 属性,在生命周期 created 获取头像URL。 Avatar 对象接收 src 属性, src 的内容从 withAvatarURL 中获取,然后展示在上。实例化的时候,传入新定义的组件名 SmartAvatar

实现如下

<!--引用-->
<script src="../node_modules/vue/dist/vue.js"></script>

<!--定义template-->
<div id="app">
  <smart-avatar username="vuejs"></smart-avatar>
</div>

<script>
    // 获取头像URL
    function fetchURL (username, cb) {
      setTimeout(() => {
        // 获取头像并回传
        cb('https://avatars3.githubusercontent.com/u/6128107?v=4&s=200')
      }, 500)
    }
    
    // 传递的InnerComponent
    const Avatar = {
      props: ['src'],
      template: `<img :src="src">`
    }
    
    function withAvatarURL (InnerComponent) {
      return {
        props: ['username'],
        inheritAttrs: false, // 2.4 only,组件将不会把未被注册的props呈现为普通的HTML属性
        data () {
          return { url: null }
        },
        created () {
          // 获取头像URL并回传给this.url
          fetchURL(this.username, url => {
            this.url = url
          })
        },
        render (h) {
          return h(InnerComponent, {
            attrs: this.$attrs, // 2.4 only,获取到没有使用的注册属性
            props: {
              src: this.url || 'http://via.placeholder.com/200x200'
            }
          })
        }
      }
    }
    
    const SmartAvatar = withAvatarURL(Avatar)
    
    // 实例化,新构造组件名为SmartAvatar或smart-avatar
    new Vue({
      el: '#app',
      components: { SmartAvatar }
    })
</script>

复制代码

本文内容参考自VUE作者尤大的付费视频

Vue官网之渲染函数 & JSX

交流

本人Github链接如下,欢迎各位Star

github.com/yygmind/blo…

我是木易杨,现在是网易高级前端工程师,目前维护了一个高级前端进阶群,欢迎加入。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!

Vue 进阶系列(三)之Render函数原理及实现

以上所述就是小编给大家介绍的《Vue 进阶系列(三)之Render函数原理及实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

ME2.0

ME2.0

丹·斯柯伯尔 / 2011-11 / 36.00元

《Me2.0个人品牌崛起E时代》,本书介绍在信息技术飞速发展的今天,如何使用网络来营建个人形象,建立关系网,谋求理想的工作,完成商务交易。成功学与今天的网络通讯相结合,smart 原则与SWOT分析,视频网站、博客、社交网站、搜索引擎如何使用才能让你以直线方式走向成功等内容。一起来看看 《ME2.0》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码