# render渲染函数和$mount挂载
render: h => h(App) 是下面内容的缩写:
render: function (createElement) {
return createElement(App);
}
进一步缩写为(ES6 语法):
render (h){
return h(App);
}
Vue.js 里面的 createElement 函数,这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。
还有另外一种写法效果是一样的:
import App from './App'
new Vue({
el: '#root',
template: '<App></App>',
components: {
App
}
})
# vue createElement函数
createElement 接受的参数:
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
- 参数可以省略,这是他可能的取值
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML attribute
attrs: {
id: 'foo'
},
// 组件 prop
props: {
myProp: 'bar'
},
// DOM property
domProps: {
innerHTML: 'baz'
},
// 事件监听器在 `on` 内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
on: {
click: this.clickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层 property
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
}
Vue.component("myview",{
render(h){
return h('div',{attrs:{id:"kk"}},['测试1',h("a",["ssss"])])
}
})
- 渲染成以下dom
<div id="kk">测试1<a>ssss</a></div>
- h函数甚至可以传的是一个组件自身
let test=Vue.component("test",{
render(h){
return h("span",'-------------------')
}
})
Vue.component("myview",{
render(h){
return h('div',{attrs:{id:"kk"}},['测试1',h("a",["ssss"]),h(test,{style:{color:'red'}},["ssss1"])])
}
})
<div id="kk">
测试1
<a>ssss</a>
<span style="color: red;">-------------------</span>
</div>
# vue用render实现v-model功能操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Render函数实现v-model</title>
</head>
<body>
<div id="app">
<ele></ele>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建ele组件
Vue.component('ele', {
render: function(createElement) { // render函数
return createElement('div', [
createElement('input', {
domProps: {
value: this.value // 类似于使用v-bind将data选项中的value变量绑定到input的value属性上
},
on: {
input:(event)=> {
console.log(this)
this.value = event.target.value;
}
}
}),
createElement('p', 'value: ' + this.value),
// 创建button按钮用来测试
createElement('button', {
attrs: {
type: 'button'
},
domProps: {
innerHTML: 'button'
},
on: {
click: this.handleClick
}
})
])
},
data: function () {
return {
value: ''
}
},
methods: {
handleClick: function () {
this.value = "按下button时会变成我"
}
}
});
var app = new Vue({
el: '#app'
});
</script>
</body>
</html>
# vue渲染
从模板到真实dom节点还需要经过一些步骤
- 把模板编译为render函数
- 实例进行挂载, 根据根节点render函数的调用,递归的生成虚拟dom
- 对比虚拟dom,渲染到真实dom
- 组件内部data发生变化,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3
mainjs
import Vue from 'vue'
import App from './App'
console.log(App)
new Vue({
render: h => h(App)
}).$mount('#app')
# vue虚拟节点VNode
把Vue的实例挂载到#app, 会调用实例里面的render方法,生成虚拟DOM
new Vue({
render: h => {
let root = h(App)
console.log('root:', root)
return root
}
}).$mount('#app')
VNode {tag: "vue-component-5-app", data: {…}, children: undefined, text: undefined, elm: undefined, …}
VNode就是虚拟节点,虚拟节点里面有一个属性elm, 这个属性指向真实的DOM节点。因为VNode指向了真实的DOM节点,那么虚拟节点经过对比后,生成的DOM节点就可以直接进行替换。
一个组件对象,如果内部的data发生变化,触发了render函数,重新生成了VNode节点。那么就可以直接找到所对应的节点,然后直接替换。那么这个过程只会在本组件内发生,不会影响其他的组件。于是组件与组件是隔离的。
// main.js
const root = new Vue({
data: {
state: true
},
mounted() {
setTimeout(() => {
console.log(this)
this.state = false
}, 1000)
},
render: function(h) {
const { state } = this // state 变化重新触发render
let root = h(App)
console.log('root:', root)
return root
}
}).$mount('#app')
// App.vue
<script>
export default {
render: (h) => {
let app = h('h1', ['hello world'])
console.log('app:', app)
return app
}
}
</script>
可以看到,当main.js中重新触发render函数的时候,render方法里面有引用App.vue这个子组件。但是并没有触发App.vue组件的的render函数。
# new Vue的实际应用
create函数用于动态创建指定组件实例并挂载至body
import Vue from "vue";
// 创建函数接收要创建组件定义
function create(Component, props){
// 创建一个Vue新实例
const vm = new Vue({
render(h) {
// render函数将传入组件配置对象转换为虚拟dom
console.log(h(Component, { props }));
return h(Component, { props });
}
}).$mount(); //执行挂载函数,但未指定挂载目标,表示只执行初始化工作
// 将生成dom元素追加至body
document.body.appendChild(vm.$el);
// 给组件实例添加销毁方法
const comp = vm.$children[0];
comp.remove = () => {
document.body.removeChild(vm.$el);
vm.$destroy();
};
return comp;
}
// 暴露调用接口
export default create;
创建通知组件,Notice.vue
<template>
<div id="tk">
<h3 v-if="isShow">{{title}}</h3>
<p>{{message}}</p>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ""
},
message: {
type: String,
default: ""
},
duration: {
type: Number,
default: ""
}
},
data() {
return {
isShow: false
};
},
methods: {
show() {
this.isShow = true;
setTimeout(() => {
this.hide()
}, this.duration);
},
hide() {
this.isShow = false;
this.remove();
}
}
};
</script>
<style lang="scss" scoped>
</style>
测试
<script>
import Notice from "../Notice";
import create from "@/utils/create";
export default {
methods: {
onLogin() {
// 创建弹窗实例
let notice;
this.$refs.loginForm.validate(isValid => {
notice = create( Notice,{
title: "xxx1",
message: isValid ? "登录!!!" : "有错!!!",
duration: 10000
});
notice.show();
});
}
}
};
</script>
# render函数代替template的写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lesson 31</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// render function
// template -> render -> h -> 虚拟DOM(JS对象)-> 真实 DOM -> 展示到页面上
const app = Vue.createApp({
template: `
<my-title :level="2">
hello dell
</my-title>
`
});
app.component('my-title', {
props: ['level'],
render() {
const { h } = Vue;
console.log(h)
return h('h' + this.level, {}, [
this.$slots.default(),
h('h4', {}, 'dell')
])
}
})
const vm = app.mount('#root');
</script>
</html>