Render Function

Render Function

渲染顺序

1. Template->render function   (compiled into)    在这一步, 相当于第一章里说的autorun, 当数据变化的时候我们会重新执行这的函数
- new virtual dom(return)  数据更新后返回新的虚拟dom树
- dom updates(diff against old virtual dom)   旧树和新树进行比较, patch最小差异部分得到即将渲染的虚拟dom树
- actual dom(applied to) 
2. virtual dom   (returns)
3. actual dom (generates)

解释:

​ 我们首先编译temlate转换为render函数

什么是虚拟dom树

先做个类比
浏览器的domAPI:

​ - 语法: document.createElement(‘div’)

​ - 返回: “[object HTMLDivElement]”

​ 虚拟dom: 本质上是一个轻量级的描述dom的一个对象.

​ 语法: vm.$createElement(‘div’)

​ 返回: { tag: ‘div’, data: { attrs: {}…..}, child: []}

dom是一个很大的对象, 所以在vue中(平常也是),我们只要操作了dom那么性能就会存在开销。

but, 和浏览器的dom对象比起来,我们创建多个虚拟dom对象, 很简单,而创建多个浏览器dom实体性能损耗更大.

我们平常使用dom的API对页面进行更新,实际操作是扔掉所有的旧节点,再次生成新dom节点.

虚拟dom最棒的一点是, 解耦了渲染步骤。我们以前渲染是跟着页面的逻辑去一部分一部分的渲染, 在这个过程中很可能出现重复和不必要的渲染。这影响了性能。但是虚拟dom, 是这样做的, 生成最初的虚拟dom树,此时并没有开始渲染,而是先进行逻辑运算,在运算完成得到最后的dom树,完成渲染(渲染过程在最后一步)。

render在响应式做了什么工作

data

我们的每一个组件都有自己的render/Data/watcher

render 往data里面取数据,此时发生了两件事:

1. 触发getter, 由于我们在getter里做了dependency collect,依赖被保存在watcher.当setter数据时,从watcher获取依赖,重新进行render(因为render在getter的时候,把依赖传入了watcher.)
2. 得到数据,进行render

JSX与模板比较

jsx与模板语法本质上都是为了描述我们的state与dom之间的关系.

1
2
3
4
5
export default {
render (h) {
return h('h', {}, [...])
}
}

区别

  • 模板: 模板语法相对于jsx更加的静态, 更多的约束的表达, 但是模板语法更易迁移。模板语法因为存在限制,我们可以对这些限制进行更深一层的优化。
  • jsx: 更加动态,更加灵活, 可以完全体现出fp编程的优势。

render function API

1
2
3
4
5
6
export default {
render (h) {
// 为什么是h, 首先解释一下h的意思, 就是createElement, 为啥不是c是因为设计的时候参照了hyperscript的API, 表示是通过js写html的方法
return h('h', {}, [...]) // arg1: 标签类型 arg2: 数据对象 arg3:
}
}
1
2
3
4
5
6
7
// exmple
h('div', 'some text')
h('div', { class: 'foo' }, 'some text')
h('div', {}, [
'some text',
h('span', 'bar')
])

使用render函数可以直接渲染组件(在router中直接使用render语法渲染组件)

1
2
3
4
5
import component from ''

h(component, {
props: {... }
})
1
2
3
4
5
6
练习1: 使用render语法渲染以下html
<div>
<h1>0</h1>
<h2>1</h2>
<h3>2</h3>
</div>

函数式render

函数式组件是指, 无状态组件,相同输入,固定输出, 不依赖外部变量。像一个处理管道。

组件需要的一切都是通过context传递

1
2
3
4
5
6
7
8
9
10
11
12
Vue.component('my-component', {
functional: true,
// Props 可选
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})

高阶组件

记忆: 高阶组件就像是高阶函数, 都是通过原子组合复用得到更抽象,功能更强的东西。

smartAvatar, 基于Avatar组件组合的高阶组件。

功能, 根据smartAvatar的props属性来决定Avatar的渲染结果

  1. 原子: Avatar低阶组件
  2. 在app这一层准备数据, props/data.url/created()
  3. 在render进行组件的渲染(计算出最后的组件应该是啥样)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function fetchURL (username, cb) {
setTimeout(() => {
// hard coded, bonus: exercise: make it fetch from gravatar!
cb('https://avatars3.githubusercontent.com/u/6128107?v=4&s=200')
}, 500)
}

const Avatar = {
props: ['src'],
template: `<img :src="src">`
}

function withAvatarURL (InnerComponent) {
// Implement this!

return {
props: ['username'],
data(){
return { // 配置默认值
url: 'https://avatars3.githubusercontent.com/u/6128107?v=4&s=200'
}
},
created () {
// 在created阶段拿到数据, 为渲染做准备
fetchURL(this.username, url => {
this.url = url
})
},
render(h) {
// render Avatar
return h(InnerComponent, {
props: { // 父级组件向子级组件传递props
src: this.url
}
})
},
}
}

const SmartAvatar = withAvatarURL(Avatar)

new Vue({
el: '#app',
components: { SmartAvatar }
})

总结一下render函数的用法啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render (h) {
// render标签
return h('div', {
id: '',
class: ''
}, 'contentText')

// render复合标签
return h('div', {}, ['someText', h('div', {}, 'someText')])

// render组件
return h(component, {
props: {
name: 'name'
}
})
}

// 所以常见的main.js, 路由中常见的语法
render: h => h(app)