<!DOCTYPE html>
<html lang="en">
<head>
<meta
charset="utf-8"
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"/>
<style>
.red{color:red}
</style>
<!--
<script src="https://unpkg.com/vue@3.4.30/dist/vue.global.js"></script>
-->
<script src="./vue/vuetest.global.js"></script>
</head>
<body>
<div id="app">
</div>
</body>
<script type="module">
var person={
name:"shengbinqian",
age:10,
techs:{
name:"java",
desc:{data:"java高手"}
}
};
var refdata03=Vue.reactive(person);
window.refdata=refdata03;
var refdata1=Vue.ref(13);
window.refdata1=refdata1;
const app = Vue.createApp({
name:"root-component",
props: {///这个定义 会赋值给组件实例instance中的propsOptions(属性的定义,数据类型,是否必填等)
rootprops1: String,
},
renderTracked:function(e){
console.log("------------renderTracked,参数e: "+e);
},
data:function(){
return {
msg:'hello vue3',
userlist:[
{"name":"xujingliang","password":"123456"},
{"name":"xujingliang","password":"123456"},
{"name":"xujingliang","password":"123456"}
]
}
},
methods: {
dealChange:function(str){
alert("子组件发射了chanage事件: "+str);
},
dealPutinfo:function(str){
alert("子组件发射了putinfo事件: "+str);
}
},
beforeCreate:function(){console.log("root-beforeCreate");},
setup:function(props,context){
///执行setup函数时, 组件已经实例化了, 此时可以访问到实例对象
// context中有 props, attrs, slots, emit 和 expose
console.log("root-setup");
///返回的对象第一层key 会被扩展到instance.ctx中
return {setupData:{data1:"data1",data2:"data2"}};
},
///用到了template 需要引入的vue中有 编译模块, 或者在构建阶段完成编译
//emit的原理:
//在父组件在使用子组件是:会通过v-on 绑定事件属性,在生成父组件vnode时,碰到子组件使用时,会解析子组件 上的各种属性定义
// 把他们传入到 子组件实例中的 props[change] 或者 动态props[change]中,同时父组件中的定义的dealChange这个函数也会被传入进去(v-on:chanage='dealChange')
//这里就是change函数
//然后当子组件 里面通过emit触发一个事件时: 就会去遍历子组件的props属性中的所有信息,找到onChange 他们对应一个函数dealChange
//这些信息具体是在哪里解析的呢: 在执行父组件的render函数
template:`<div>
<div><span> 发发的方法<div><p>毒贩夫妇</p></div></span></div>
<div id="root-chidren-div-id" class="root-chidren-div-class" style="color: blue;">{{msg}}</div>
<test-component v-on:chanage='dealChange' @putinfo="dealPutinfo">
<template v-slot:default><div>我是父组件的插槽内容<p>测试锻炼</p></div></template>
</test-component>
</div>`,
},{rootprops1:"rootmsg1",rootprops2:"rootmsg2"});
//
var testComponet={
name:"test-component",
props: {
testpmsg1: String,
onPutinfo:Function
},
data:function(){
return {
cmsg:"shengbinqian",
cdata:"zhongguoren"
}
},
methods: {
testdata1:function(str){
console.log("testdata-"+str);
this.$emit("chanage","你好我是子组件信息chanage");
},
testdata2:function(str){
this.$emit("putinfo","你好我是子组件信息putinfo");
}
},
template:`<div class='border:solid 1px red'>
<div>我是子组件testComponent---{{cmsg}}</div>
<div><slot><span>我是默认内容</span></slot></div>
<div>{{cdata}}</div>
<div><button @click="testdata1" >chanageEmit</button></div>
<div><button @click="testdata2" >putinfoEmit</button></div>
</div>`,
setup:function(){
console.log("testComponet-setup");
}
};
///会在app的上下文对象APPContext中的components中保存testComponet
app.component("test-component",testComponet);
app.mount('#app');
</script>
</html>
父组件templdate 编译生成的render函数如下:
(function anonymous(
) {
const _Vue = Vue
const { createVNode: _createVNode, createElementVNode: _createElementVNode, createTextVNode: _createTextVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [
/*#__PURE__*/_createElementVNode("span", null, [
/*#__PURE__*/_createTextVNode(" 发发的方法"),
/*#__PURE__*/_createElementVNode("div", null, [
/*#__PURE__*/_createElementVNode("p", null, "毒贩夫妇")
])
])
], -1 /* HOISTED */)
const _hoisted_2 = {
id: "root-chidren-div-id",
class: "root-chidren-div-class",
style: {"color":"blue"}
}
const _hoisted_3 = /*#__PURE__*/_createElementVNode("div", null, [
/*#__PURE__*/_createTextVNode("我是父组件的插槽内容"),
/*#__PURE__*/_createElementVNode("p", null, "测试锻炼")
], -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
const _component_test_component = _resolveComponent("test-component")
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_createElementVNode("div", _hoisted_2, _toDisplayString(msg), 1 /* TEXT */),
_createVNode(_component_test_component, {
onChanage: dealChange,
onPutinfo: dealPutinfo
}, {
///这个内容是作为子节点参数传入的,也是父组件中传给子组件的slot,就是一个函数,返回一个包括slot中内容的vnode节点对象
default: _withCtx(() => [
_hoisted_3
]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["onChanage", "onPutinfo"])
]))
}
}
})
总结1:组件中几个重要的对象说明
1、父组件的type,subtree、vnode
总结2
组件通过templdate内容生成的渲染函数render 就是把纯字符串全部转化成vnode节点数据,一个标签对应一个vnode,然后vnode也有各种属性对应标签上的内容。 然后挂载时,如果vue 发现单当前的 vnode是一个普通的html标签类型(vnode.type类决定) 就直接容调用渲染器的hostCreateElement等创建dom的方法,得到对应vnode的el对象(真实dom),然后再遍历子节点,如果子节点的type不是普通的html标签类型,是一个对象的话,那就是组件,然后要是根据子节点中的type对象创建 子节点的中间态vnode 和子组件的instance对象,然后也开始调用子节点的render方法,得到子节点对应的可以挂载的vnode(subtree),然后要开始挂载,然后循环工作。
总结3
组件创建的主题过程
1、首先有组件的描述对象。
数据结构是:
{
data:function(){return {}},
beforeCreate:function(){}
props:{
msg:String,
info:String
}
methods:{
dealclick:function(){},
buttonReactive:function(){}
}
setup(){
},
template:"<div>dfdfdf<div><childrenComponent><templdate v-slot:body>我是父组件传给子组件的body</template></childrenComponent></div></div>"
...
}
2、然后vue根据上面的组件描述对象创建组件的中间态vnode对象,也称initVnode
const vnode = createVNode(rootComponent, rootProps);
这个vnode中有个属性type属性对应的就是描述对象
3、然后vue根据中间态的vnode创建组件的实例对象instance,实例对象中有一个属性type 也是指向组件的描述对象
同时instance中也有中间态的vnode引用
4、然后对组件对象instance开始各种初始化,初始化props,slot,调用setup方法,并把数据暴露到instance.ctx中,它是组件渲染的上下文数据对象
5、然后处理setup方法的返回结构,把它的返回结果也暴露到instance.ctx上。以便在组件渲染时使用
6、然后调用组件的渲染方法开始渲染
7、执行挂载任务