1.vue渲染页面整体流程

13429147-32832d6b08b108c4.jpg

渲染过程解析:

  1. template:静态模板template经过转换解析生成AST.
  2. AST: AST最终会编译为render函数,Vue使用HTML的Parser将HTML模板解析为AST,并且对AST进行一些优化的标记处理,提取最大的静态树,方便Virtual DOM时直接跳过Diff.
  3. render:渲染函数用来生成virtual dom。vue推荐使用template来构建应用界面,在底层实现中vue会将模板编译为渲染函数,当然我们也可以不写模板直接写渲染函数。
  4. virtual DOM:虚拟DOM树,Vue的Virtual DOM Patching算法是基于Snabbdom的实现,并在些基础上作了很多的调整和改进.


2.两种构建过程


  • 独立构建:包含模板编译器,渲染过程HTML字符串 → render函数 → VNode → 真实DOM节点
  • 运行时构建:不包含模板编译器,渲染过程render函数 → VNode → 真实DOM节点

<br />
13429147-7f193c6cb2e36da0.png
由此图可以看出,vue提供了三种渲染方法 ,el template render,这三种渲染模式最终都是要而得到render函数,只不过用户自定义的render函数省去了程序分析的过程,等同于处理过的render函数,而普通的template或者el只是字符串,需要解析成AST,再由AST转换为render函数。

3.render语法

  1. renderfunction(createElement) {
  2. return createElement(arg1, arg2, arg3);
  3. }

createElement函数提供了三个参数,其中第一个参数是必填的,后两个参数是可选的,下文将分别介绍这三个参数的作用。

3.1createElement参数

3.1.1 参数一:{ String| Object | Function }

用于生成在页面上显示的标签元素

  1. //未使用render方式创建h1标签的写法
  2. const vm = new Vue({
  3. el: "#app",
  4. template: "<h1>h1标签</h1>"
  5. })
  6. //使用render - String
  7. const vm = new Vue({
  8. render: function(createElement) {
  9. return createElement("h1")
  10. }
  11. })
  12. //使用render - Object
  13. const vm = new Vue({
  14. render:function(createElement) {
  15. return createElement({
  16. template: '<h1>h1标签</h1>'
  17. })
  18. }
  19. })
  20. //使用render -Function
  21. const vm = new Vue({
  22. render: function(createElement) {
  23. const elFunc = function() {
  24. return {
  25. template: '<h1>h1标签</h1>'
  26. }
  27. }
  28. return createElement(elFunc())
  29. }
  30. })

3.1.2 参数二 { Object }

包含配置信息的数据对象,如html特性,属性,时间侦听器以及要绑定的class及style

  1. <div id="app">
  2. <custom-element></custom-element>
  3. </div>
  4. Vue.component('custom-element', {
  5. render: function (createElement) {
  6. var self = this
  7. // 第一个参数是一个简单的HTML标签字符 “必选”
  8. // 第二个参数是一个包含模板相关属性的数据对象 “可选”
  9. return createElement('div', {
  10. 'class': {
  11. foo: true,
  12. bar: false
  13. },
  14. style: {
  15. color: 'red',
  16. fontSize: '14px'
  17. },
  18. attrs: {
  19. id: 'boo'
  20. },
  21. domProps: {
  22. innerHTML: 'Hello Vue!'
  23. }
  24. })
  25. }
  26. })
  27. let app = new Vue({
  28. el: '#app'
  29. })

3.1.3 参数三 { String | Array }

子节点字符串或包含子节点的数组

  1. <div id="app">
  2. <custom-element></custom-element>
  3. </div>
  4. Vue.component('custom-element', {
  5. render: function (createElement) {
  6. var self = this
  7. return createElement(
  8. 'div', // 第一个参数是一个简单的HTML标签字符 “必选”
  9. {
  10. class: {
  11. title: true
  12. },
  13. style: {
  14. border: '1px solid',
  15. padding: '10px'
  16. }
  17. }, // 第二个参数是一个包含模板相关属性的数据对象 “可选”
  18. [
  19. createElement('h1', 'Hello Vue!'),
  20. createElement('p', '开始学习Vue!')
  21. ] // 第三个参数是传了多个子元素的一个数组 “可选”
  22. )
  23. }
  24. })
  25. let app = new Vue({
  26. el: '#app'
  27. })

4.使用JavaScript代替模板功能

在使用vue模板时,可以在模板中使用v-if v-show v-for等指令,但是使用render渲染函数时,需要通过原生JS实现响应功能。

  1. <ul v-if="items.length">
  2. <li v-for="item in items">{{ item }}</li>
  3. </ul>
  4. <p v-else>No items found.</p>

以上代码通过render函数可以这样实现:

  1. render: function(createElement) {
  2. if(this.items.length) {
  3. return createElement("ul",null,this.items.map( item => {
  4. return createElement("li")
  5. }))
  6. }else {
  7. return createElement("<p>No items found.</p>")
  8. }
  9. }

5.插槽

你可以从this.$slots获取VNodes列表中的静态内容:

  1. render: function (createElement) {
  2. // 相当于 `<div><slot></slot></div>`
  3. return createElement('div', this.$slots.default)
  4. }

还可以从this.$scopedSlots中获得能用作函数的作用域插槽,这个函数返回VNodes:

  1. props: ['message'],
  2. render: function (createElement) {
  3. // `<div><slot :text="message"></slot></div>`
  4. return createElement('div', [
  5. this.$scopedSlots.default({
  6. text: this.message
  7. })
  8. ])
  9. }

如果要用渲染函数向子组件中传递作用域插槽,可以利用VNode数据中的scopedSlots域:

  1. <div id="app">
  2. <custom-ele></custom-ele>
  3. </div>
  4. Vue.component('custom-ele', {
  5. render: function (createElement) {
  6. return createElement('div', [
  7. createElement('child', {
  8. scopedSlots: {
  9. default: function (props) {
  10. return [
  11. createElement('span', 'From Parent Component'),
  12. createElement('span', props.text)
  13. ]
  14. }
  15. }
  16. })
  17. ])
  18. }
  19. })
  20. Vue.component('child', {
  21. render: function (createElement) {
  22. return createElement('strong', this.$scopedSlots.default({
  23. text: 'This is Child Component'
  24. }))
  25. }
  26. })
  27. let app = new Vue({
  28. el: '#app'
  29. })

6. JSX

在Vue中使用JSX可以让我们回到更接近于模板的语法上

  1. import AnchoredHeading from './AnchoredHeading.vue'
  2. new Vue({
  3. el: '#demo',
  4. render: function (h) {
  5. return (
  6. <AnchoredHeading level={1}>
  7. <span>Hello</span> world!
  8. </AnchoredHeading>
  9. )
  10. }
  11. })

原文引自:Vue render函数