Vue源码分析(Render渲染函数)

Render函数

渲染DOM原理

在前面的内容我们学习到了Vue的响应式,但是只有响应式是不够的,我们要将内容渲染出来。Vue的templates就是通过Render(渲染)函数渲染出来的。

第一阶段(生成虚拟DOM)

在Vue中,如果你把模版直接传入Vue实例,那Vue会执行完整的编译,把传入的template编译为浏览器可运行的DOM,就像你直接在DOM中编写模板一样。

如果你使用vue-cli构建项目,会用到webpack和vue-loader。它会在构建时预编译模板为可以直接解析的DOM代码(h()函数),即纯JavaScript代码。还有另一种编译模式,就是将编译器也打包进去,压缩之后会比第一种体积大一些。

上面两种情况其实都是使用Render函数生成虚拟DOM

第二阶段(生成真实DOM)

Vue会基于第一阶段的虚拟DOM转换成真实DOM。

虚拟DOM更新

第一、二阶段相当于完成了初始化,如果之后DOM需要更新呢?回顾之前讲的autorun函数(用于订阅发布),我们可以将虚拟DOM的代码放在里面,当数据改变时,Render函数会生成新的虚拟DOM,即触发我们的发布,新的虚拟DOM和旧的虚拟DOM进行比较,得出最少需要更新的节点并生成真实DOM完成一次更新。

虚拟DOM

简单来说,就是将真实的DOM转化成JS代码,既然都是JS代码,虚拟DOM产生变化的时候也只需要修改JS的一些内容,最终再转换成真实DOM。

你可能会好奇,我本来直接操作DOM就可以,为什么要在中间加一系列操作呢?

这是由于操作真实DOM中会存在一些问题,当DOM节点非常多的时候:会有资源消耗问题和执行效率问题。

使用JavaScript操作真实DOM会非常消耗资源,因为要修改真实DOM操作的内容很多,但是如果使用虚拟DOM,你无论是如何增删修改节点,都只是在操作JS,这样会很节省资源。同样,这样只操作JS,用JS计算差异,也会比真实DOM比较差异快很多。(当然:如果你能将DOM操作到炉火纯青,保证每次对DOM的操作都是较为节约快捷的方式,那还是要比虚拟DOM快的,毕竟我们多了一步将它转换成了JS,又变回真实DOM的过程)

<div>Hello World</div>

调用Render函数转化为虚拟DOM

import {
    
     openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
    
    
  return (_openBlock(), _createElementBlock("div", null, "Hello World"))
}

Vue整体机制

现在我们来整合一下之前的所有知识

每个组件都有一个渲染函数,它包装在我们第一节实现的autorun函数中,当执行渲染的时候,getters收集它的依赖。同时,每个组件都有一个watcher观察者去监听,一旦数据更新或依赖的渲染属性发生变化,我们就执行收集的依赖(即渲染函数),这样每个组件即可实现自己的自动循环渲染。

请添加图片描述

Render Function API

export default{
    
    
	render(h){
    
    
		return h('div',{
    
    },[...])
	}
}

上面是渲染函数的使用,你需要一个参数h,h是一种简写表示超脚本(HyperScript),这是一种虚拟DOM渲染函数的编写风格。就像超文本叫HTML一样,他没有什么特殊的含义,只是方便书写的表现形式而已。

接下来我们分析一下h()

h()接收三个函数,第一个是元素类型;第二个是参数对象();第三个是子节点

h('div','some text')
h('div',{
    
    class:'foo'},'some text')
h('div',{
    
    ...},[
	'some text',
	h('span','bar')
])

最后一个的渲染效果就像

<div ...>some text <span> bar </span></div>

h()同样也接受一个组件定义,这将会创建一个组件实例

import MyComponent from '...'
h(MyComponent,{
    
    
	props:{
    
    ...}
})

练习Render函数动态渲染标签

前面说了这么多,让我们来做一个demo真正体验一下

传入tags数组

<example :tags="['h1', 'h2', 'h3']"></example>

渲染出标签并显示元素索引值

<div>
  <h1>0</h1>
  <h2>1</h2>
  <h3>2</h3>
</div>

如果经常使用Vue,你的第一反应可能是使用一个构造器,动态渲染

<component is="h1"></component>

但是这个组件设计的初衷是用来在组件间动态切换,而不是渲染真实的标签;如果Vue没有将这个api提供给我们,我们的开发就会受限。这也是模板的一个缺点,不如jsx灵活。开发者总不能等待vue官方提供解决办法,所以在这里我们使用渲染函数

<script src="../node_modules/vue/dist/vue.js"></script>

<div id="app">
  <example :tags="['h1', 'h2', 'h3']"></example>
</div>

<script>
  Vue.component('example', {
    props: ['tags'],
    render(h) {
      return h('div', {
        attrs: { id: 'hello' }
      }, this.tags.map((tag, i) => h(tag, i)))
    },
  })

  new Vue({ el: '#app' })
</script>

我们通过example创建的模板就是这样

<div id="hello">
	<h1>0</h1>
	<h2>1</h2>
	<h3>2</h3>
</div>
函数组件和状态组件

上面我们使用的是Vue的状态组件,而函数组件不包含state和props,你可以理解它就是一个函数。

const foo = {
    
    
	functional: true,
	render: h => h('div','foo')
}

函数组件特点:

  1. 组件不支持实例化。
  2. 优化更优,因为在Vue中它的渲染函数比父级组件更早被调用,但是他并不会占用很多资源,因为它没有保存数据和属性,所以它常用于优化一个有很多节点的组件。
  3. 容易扩展,如果你的组件只是用来接收 prop然后显示数据,或者一个没有状态的按钮,建议使用函数组件。
  4. 函数组件没有this,获取prop可以通过render函数的第二参数得到render(h, context)

我们改写一下上面的demo

Vue.component('example', {
    
    
  functional: true,
  props: {
    
    
    tags: {
    
    
      type: Array,
      validator (arr) {
    
     return !!arr.length }
    }
  },
  render: (h, context) => {
    
    
    const tags = context.props.tags
    return h('div', context.data, tags.map((tag, index) => h(tag, index)))
  }
})

new Vue({
    
     el: '#app' })

练习Render函数动态渲染组件

渲染函数除了可以渲染普通标签外,还可以渲染组件,下面代码有FooBar组件,点击toggle按钮的时候,切换两组件的显示状态。

<script src="../node_modules/vue/dist/vue.js"></script>

<div id="app">
  <example :ok="ok"></example>
  <button @click="ok = !ok"></button>
</div>

<script>
const Foo = {
  functional: true,
  render: h => h('div', 'foo')
}

const Bar = {
  functional: true,
  render: h => h('div', 'bar')
}

Vue.component('example', {
  functional: true,
  props: {
    ok: Boolean
  },
  render: (h, context) => h(context.props.ok ? Foo : Bar)
})

new Vue({
  el: '#app',
  data: {
    ok: true
  }
})
</script>

猜你喜欢

转载自blog.csdn.net/qq_47234456/article/details/126792410