单文件组件
<script setup>
</script>
<template>
</template>
<style scoped>
</style>
API
1. 选项式API(this指向当前组件的实例)
<script>
export default {
// data() 响应式状态属性
data() {
return {
count: 0
}
},
// methods 函数
methods: {
increment() {
this.count++
}
},
// mounted()生命周期钩子,在组件生命周期的各个不同阶段被调用
mounted() {
console.log(``)
}
}
</script>
2. 组合式API
<script setup> //setup不要漏,其作用就是暴露响应式状态和函数
import {ref,onMounted} from 'vue'
const count = ref(0)
function increment(){
count.value++
}
onMounted(()=>{
console.log("")
})
</script>
构建Vue
1. 安装脚手架
npm create vue@latest
2. 创建vue项目
npm install
3. 运行vue项目
npm run dev
4. 发布vue项目
npm run bulid
创建vue应用
<script>
// 创建应用(根组件)
import {createApp} from 'vue'
const app = createApp({}) // 可以创建多个,每个独享全局资源作用域
// app.mount(挂载应用-->渲染)
app.mount('#app')
// app.config(配置)
app.config.errorHandler = (err) = >{}
// app.component(注册组件)
app.component('name',component)
</script>
<template>
<div id="app"></div>
</template>
模板语法 {{}} v-xxx
1. 文本插值
<span>{{ msg }}</span>
2. 响应式绑定(将属性的值变为响应式的)
<div v-bind:id="name" ></div>
<div :id="name"></div>
<div v-bind="obj" ></div> // 绑定多个属性
const name = ref('muggle')
const obj = {
id:'muggle',
class:'student',
style:''
}
3. 模板内支持的语句
支持JS、单一表达式、函数调用、有限全局对象、
{{}}
v-xxx:xxx
4. 动态参数
// arrtributeName同样支持上述所有,但必须是字符串
v-bind:[attributeName]="value"
5. 修饰符
为.开头的特殊后缀,表明指定需要以特殊的方式绑定
.prevent会告知v-on触发时调用event.preventDefault
<form @submit.prevent="onSubmit"></form>
响应式 ref reactive
(一) ref
1. 基本语法
<script setup>
import {ref} from 'vue'
// ref返回一个ref对象,对象中有value属性,就是其值
const count = ref(0)
function add(){
count.value++
}
</script>
<tamplate>
<button @click="add()">
{{count}} // 模板中使用ref不需加.value
</button>
</tamplate>
2. ref伪代码
<script>
// 其内部的get set实际执行了JS的getter setter方法
const myRef = {
_value:0,
get value(){
track()
return this._value
},
set value(newValue){
this.value=newValue
trigger()
}
}
</script>
3. ref支持JS中任何类型的值
且具有深层响应性,即嵌套数组里的值改变了也能检测到
<script>
const obj = ref({
name:"muggle",
hobby:['paino','camera']
firend:{a:'A',b:'B'}
})
function person(){
obj.value.hobby.push('piture')
}
</script>
(二)reactive
返回响应式对象proxy
局限性:仅支持对象类型,不能替换对象,不能解构
<script>
import {reactive} from 'vue'
const person = {
name:"muggle",
hobby:['paino','camera']
firend:{a:'A',b:'B'
}
// proxy != person
const proxy = reactive(person)
// 对同一个对象调用reactive(),返回相同的代理
reactive(person) === proxy ture
// 对同一个代理调用reactive(),返回其本身
reactive(proxy) === proxy ture
</script>
<template>
<button>
{{proxy.name}}
</button>
</template>
(三)ref和reactive的区别
1. ref可用于所有数据类型,reactive只能用于对象类(Object,Array,Map,Set)
2. ref返回一个包含.value的对象,访问时需要使用.value,模板使用时不需要;reactive直接返回一个响应式对象,访问和模板使用时为proxy.name模式
3. ref对于复杂对象需要手动解包,reactive深度响应不需要
(四)DOM更新时机
当修改了响应式状态,DOM会自动更新,但DOM更新不是同步的,Vue会在next tick更新周期中缓冲所有状态,确保组件只更新一次。如果需要等待DOM更新完成后才能执行代码,可以使用nextTick()全局API
<script>
import {nextTick} from 'vue'
async function add(){
count.value++
await nextTick()
}
</script>
计算属性 computed()
1. 基本语法
<script setup>
import {ref,computed} from 'vue'
const person = reactive({
name:'muggle',
hobby:['piano','camera','piture']
})
// 接收getter函数,返回ref对象,因此script中记得value,模板不用
const hobbyCount = computed(()=>{
return hobby.length //hobbyCount.value = hobby.length
})
</script>
<template>
<span>{{hobbyCount}}</span>
</template>
2. computed()和普通函数的区别
computed()基于其响应式依赖被缓存,只有响应式依赖更新时才会重新计算重新调用getter();而函数会重复执行很多次
3. 可写计算属性(避免修改)
计算属性默认只读,如果想要修改,可以通过创建getter和setter实现
<script setup>
import {ref,computed} from 'vue'
const fistName = ref('muggle')
const lastName = ref('wizard')
const fullName = computed({
get(){
return firstNmae.value + ' ' + lastName.value
},
set(newValue){
[firstName.value,lastName.value] = newValue.split(' ')
}
})
</script>
样式绑定 :class :style
(一) :class="{}"
1. 绑定对象
:class=""传递一个对象(键:值 = 类名 : true/flase)动态绑定class,键为CSS类名,值为true/false,决定该元素是否绑定这个类
<script setup>
import{ref,reactive,computed} from 'vue'
const isActive = ref(true)
const hasError = ref(false)
// 也可以直接设为一个对象,:class传入该对象
const classObject = reactive({
active:true,
'text-danger':false // 键中有-类字符需用''
})
// 也可以计算属性返回一个对象,关键点就是:class传入的是对象(键+值=类名+true/false),此处computed返回的是ref对象
const classObject = computed(()=>{
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal'
})
</script>
<template>
// :class可以与原始属性class共存
<div :class = "{active:isActive,'text-danger':hasError}"></div>
<span :class = "classObject"></span>
</template>
2. 绑定数组
简单的为元素添加一组class
<script>
// activeClass.value = 'active'
const activeClass = ref('active')
const errorClass = ref('text-danger')
</script>
// ['active','text-danger']
<div :class = "[activeClass,errorClass]"></div>
3. 组件上使用
自定义组件只有一个根元素,自动继承组件的class
<p class="foo bar"></p> // 子组件模板
<myComponent :Class="{active:isActive}"></myComponent>
则p实际为<p class="foo bar active"></p>
自定义组件有多个根元素,通过$attrs.class指定哪个根元素继承组件的class
<p :class="{$attrs.class}"></p>
(二) :style="{}" 内联样式
1. 绑定对象
<script>
const color = ref('red')
const fontSize = ref(30)
const styleObject = reactive({
color:'red',
fontSize:'30px'
})
</script>
<div :style="{color:color,fontSize:fontSize + 'px'}"></div>
<p :style="styleObject"></p>
2. 绑定数组
<div :style = "[baseStyle, unqieStyle]"></div>
条件渲染 v-if v-show
1. v-if v-else-if v-else 返回真值时渲染
<h1 v-if="type === 'A'">A</h1>
<h1 v-else-if="type === 'B'">B</h1>
<h1 v-else>Not A B</h1>
// 如果想要切换不止一个元素,可用tempalte包裹,该元素渲染不可见
<template v-if="ok">
<h1>A</h1>
<h1>B</h1>
</template>
2. v-show
<h1 v-if="ok">A</h1>
3. v-if与v-show的区别
v-if是真正的按条件渲染,是惰性的,返回真值时才渲染,适用切换少
v-show始终渲染,只是改变CSS中的display属性,适用切换多
v-if可以用template,v-show不可
4. v-if不建议与v-for一块使用
列表渲染 v-for
1. in 操作符
(1) key in Object
key字符串或Symbol,表示检查的键(obj的键为属性,arr的键为索引)
Object是对象或数组
in检查对象自身和原型链,Object.hasOwmProperty只检查自身
返回true/false
(2) key in obj
const obj = {name:'muggle', age:23}
console.log("name" in obj) // true
(3) key in arr
const arr = [3,4,5]
console.log(0 in arr) //true
console.log(5 in arr) //false
2. for
(1) forEach((value,key)=>{} 遍历arr,map,set的键和值,不能遍历对象
arr.forEach
map.forEach
set.forEach // 无键
(2)for(let key in obj){} 遍历对象的键(obj的属性,arr的索引)
for(let key in obj){
console.log(key,obj.key)
}
for(let key in arr){
console.log(key) // "0" "1" "3" // 返回的是索引的字符串
}
(3)for(let value of obj){} 遍历可迭代器的值(obj,arr,map,set,string)
for(let value of arr){
console.log(value) // 3 4 5
}
3. v-for="item in items" 与forEach类似,of可以替代in
(1)遍历数组
const arr = ref([{message:'A'},{message:'B'}])
<li v-for="(value,key) in arr">{{value.message}} - {{key}}</li>
<li v-for="{message} in arr">{{message}}</li> //解构赋值
(2)遍历对象
const obj = reactive({name:'muggle',age:23})
<li v-for="(value,key,index) in obj">{{index}}. {{key}}:{{value}}</li>
(3)遍历整数值(1...n)
const count = 100
<span v-for="n in count">{{n}}</span> // 1-100
(4)template中使用,同时渲染多个模块
<template v-for='(value,key,index) in obj'>
<li>{{index}}.{key}:{{value}}</li>
<li>空一行</li>
</template>
(5)不能在用一个标签上用v-if和v-for,v-if的优先级比v-for高,类比一般先写for再写if
const obj=[{name:'muggle',age:23},{name:'wizar',age:25}]
<template v-for="value in obj">
<li v-if="value.age===23">{{value.name}}</li>
</template>
(6)v-for中内置的特殊属性key
应为每一个v-for标签绑定一个key,key的值只能是数字或字符串,不能是对象,绑定key的原因是,v-for渲染策略是就地更新,如果数据项的顺序变化,vue不会随之移动Dom元素的顺序,而添加key会重新排序
<template v-for="value in obj" :key='value.name'>
<li>{{value.name}}</li>
</template>
(7)自定义组件上使用v-for
// 和普通标签一样,但是不会传递任何数据给组件,因为组件有自己独立的作用域,所以我们需要传递props!
<myComponent v-for="value in obj" :key="value.name"></myComponent>
(8)v-for中数组方法的注意事项
改变自身:push pop shift unshift splice sort reverse
不改变自身,返回新数组:filter slice concat
改变自身的会响应式更新,不改变自身的可以利用computed存储新数组
const arr=ref([1,2,3,4,5])
const filterArr = computed(()=>{
return arr.value.filter((n)=>n%2)
})
<li v-for="value in filterArr">{{value}}</li>
若,有多层嵌套数组,可以通过嵌套v-for来拆解出数组的值
const set = ref([[1,2,3,4,5],[6,7,8,9,10]])
<ul v-for="arr in set">
<li v-for="value in arr">{{value}}</li>
</ul>
事件处理 v-on(@)
v-on:click = "handler" @click = "handler"
1. 内联事件处理器 count++ add()
const count = ref(0)
<button @click="count++"></button>
//在内联事件处理器中调用方法,可以传参替换原生event
function say(msg){
alert(msg)
}
<button @click="say('hello')"></button>
//在内联事件处理器中使用event
function say(msg,event){
alert(msg)
console.log(event.target.name)
}
<button @click="say('hello',$event)"></button>
2. 方法事件处理器 add
// 自动接收原生DOM事件event
const count = ref(0)
function add(event){
count.value++
event.preventDefault()
}
<button @click="add"></button>
3. 事件修饰符
常用事件:click keypress change mouseover submit
.stop -- event.stopPropagation -- 阻止事件传播(停留在子元素)
.prevent -- event.preventDefault -- 阻止默认行为(阻止浏览器跳转/页面刷新)
.capture -- addEventListener-option-capture -- true捕获触发,false冒泡触发+默认
.once -- addEventListener-option-once -- true触发一次后移除
.passive -- addEventListener-option-passive -- true禁止调用preventDefault()
.self -- 非原生JS -- 仅事件元素本身触发
<a @click.stop.prevent='doThar'></a> // 注意顺序
@click.prevent.self 阻止元素及其子元素的默认行为
@click.self.prevent 阻止对元素本身的默认行为
4. 按键修饰符
常用键盘事件:keydown keyup keypress
.enter .tap .delete(捕获Delete和Backspace) .esc .space .up .down .left .right
.ctrl .alt .shift .meta
.exact(未额外按下其他按键才触发)
5. 鼠标修饰符
.left .right .middle
表单输入绑定 v-model
1. 表单元素
<input type = "text,password,email,number,checkbox,radio,file"/>
<textarea></textarea>
<button></button>
<lable></lable>
<select>
<option></option>
</select>
2. 基本用法 v-model绑定input标签的value并传入event事件处理
// 文本
const msg = ref('')
<input v-model="msg">
<span>{{msg}}</span>
// 复选框
const checked = ref(false)
<input type="checkbox" id="check" v-model="checked">
<label for="check">{{checked}}</label>
// 多个复选框,将其值绑定到同一个数组/集合
const names = ref([])
<input type="checkbox" id="A" value="abby" v-model="names">
<label for="A">abby</label>
<input type="checkbox" id="B" value="bubby" v-model="names">
<label for="B">bubby</label>
// 单选框
const pick = ref('One')
<input type="radio" id="A" value="One" v-model="pick">
<label for="A">One</label>
<input type="radio" id="B" value="Two" v-model="pick">
<label for="B">Two</label>
// 下拉列表
const selected = ref('')
<select v-model="selected">
<option disable value="">设置空值禁用选项</option> //IOS问题
<option>A</option> // 没有指定value时,option标签内的文本就是value
<option>B</option>
<option>C</option>
</select>
<span>{{selected}}</span>
// 下拉列表的option使用v-for
const selected = ref('muggle')
const options = ref([{name:'muggle',age:23},{name:'wizar',age:25}])
<select v-model="selected">
<option v-for="option in options" :value="option.age">Name: {{option.name}}</option>
</select>
<span>Age: {{selected}}</span>
// v-model="" 和:value="" 都支持非字符串类型的值绑定,比如对象数组
3. 修饰符
.lazy v-model会在每次input事件后更新数据,.lazy改为在change事件后更新
.number 输入自动转化为数字
.trim 自动去除输入两端的空格
<input v-model.lazy="msg" />
模板引用 ref
ref属性用于标记DOM元素和组件实例,可以通过this.$refs访问所有被标记的DOM元素和组件实例,this.$refs是一个对象。
1. 选项式api
<button ref="myButton"></button>
<myComponent ref="myComponent"></myComponent>
this.$ref.myButton.focus() // 获取DOM元素并执行操作
this.$ref.myComponent.someMethod() // 获取组件并调用其方法
2. 组合式api
<script>
import {useTemplateRed,onMounted} from 'vue'
const btn = useTemplateRed('myButton')
// 必须在组件挂载之后才能使用
onMounted(()=>{
btn.focus()
})
</script>
<button ref="myButton"></button>
3. v-for中使用ref
<script>
import {ref,useTemplateRef,onMounted} from 'vue'
const list = [1,2,3]
const lis = useTemplateRef('lis')
onMounted(()=>{
cosole.log(lis.textContent) // [1,2,3]
})
</script>
<ul>
<li v-for="value in list" ref="lis">{{value}}</li>
</ul>
生命周期钩子 onMounted() onUpdated() onUnmounted()
每个vue组件实例在创建时,都经历一系列的初始化步骤:设置数据侦听、编译模板、挂在实例到DOM、数据改变时更新DOM。在执行这些步骤的时候会运行生命周期钩子函数。所有钩子都不能异步注册,应与组件实例同步注册,例如放在setup下。调用钩子时,其回调函数会自动注册到当前正在初始化的组件实例上。
1. onMounted()
组件挂载完成后执行:所有同步子组件已被挂载(不含异步),自身DOM树构建完成并插入父容器,不会在服务端渲染期间执行。
<script>
import {ref,onMounted} from 'vue'
const myButton = ref()
onMounted(()=>{
myButton.value // <button></button> 证明DOM树已构建
})
</script>
<button ref="myButton"></button>
2. onUpdated()
响应式状态变更而更新DOM树后调用:组件内任意DOM更新后调用,不会在服务端渲染期间执行。如果想要特定状态导致的DOM更新,需要使用nextTick(),Vue在nextTick更新周期中缓冲所有状态,确保组件只更新一次。不能在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环。
<script>
import {reft,onUpdated} from 'vue'
const count=ref(0)
onUpdated(()=>{
console.log(document.getElementById("myButton").textContent) // count.value DOM树更新完毕
})
</script>
<button id="myButton" @click="count++">{{count}}</button>
3. onUnmounted()
组件实例被卸载之后调用:所有子组件已卸载,所有响应式作用(渲染、计算属性、侦听器)都停止,通常用于清理一些副作用,例如定时器,事件监听器,与服务器的连接等。不会在服务端渲染期间执行。
<script>
import {onMounted,onUnmounted} from 'vue'
const intervalId
onMounted(()=>{
intervalId = setInterval(()=>{})
})
onUnmounted(()=>{
clearInterval(intervalId)
})
</script>
4. onBeforeMount()
组件挂载之前调用:完成响应式状态设置,但没有创建DOM节点,即将进行DOM的首次渲染。
5. onBeforeUpdate()
组件响应式状态更新,但还没更新DOM树。在这个钩子更改状态是安全的。
6. onBeforeUnmout()
组件卸载前调用,组件实例依然保有全部功能
7. onErrorCatured()
捕获组件传递错误时调用
8. onRenderTracked()
调试钩子,组件渲染时追踪到响应式依赖时调用,仅开发模式下可用
9. onRendTriggerd()
调试钩子,响应式依赖变更触发了组件渲染时调用,仅开发模式下可用
10. onActive()
组件实例是<KeepAlive></KeepAlive>缓存树的一部分,当组件插入DOM树时调用
11. onDeactivated()
组件实例是<KeepAlive></KeepAlive>缓存树的一部分,当组件移除DOM树时调用
12. onServerPrefetch()
注册一个异步函数,服务器渲染之前调用,仅会在服务端渲染中执行,用于执行一些仅存在于服务端的数据抓取过程。若此钩子返回一个promise,服务端渲染会等待该promise完成

侦听器 watch()
用于每次响应式状态发生变化时触发回调函数
1. watch()
watch (数据源,回调函数,{deep:true},{imediate:true},{once:true})
数据源:ref reactive getter函数(return某个变量的函数) 多个数据源的数组
回调函数:(newValue,oldValue)=>{},接收两个参数,在父组件更新之后,所属组件DOM更新之前调用
{deep:true}:自定义深层响应
{imediate:true}:watch默认懒执行,仅当数据源变化时才执行回调,若想初始化时执行一次,可以使用这个
{once:true}: 仅执行一次回调
<script>
const obj = reactive({name:'muggle'})
// 错误,因为obj.name = 'muggle',传入的是字符串,不是变量
watch(obj.name,(name)=>{
console.log(name)
})
// 正确,应使用一个箭头函数来return其变量
watch(()=>obj.name,(name)=>{ // name = obj.name
console.log(name)
}
</script>
2. 深层侦听器
如果数据源是响应式对象,则对象里的嵌套变更都会触发侦听器
<script>
const obj = reactive({name:'muggle'})
watch(obj,()=>{})
watch(()=>obj) // 这样返回的没有深层响应
</script>
3. watchEffect
不需要数据源,自动侦听回调函数内使用到的所有响应式数据,只要其中一个发生变化,就会触发回调函数。并且watchEffet在执行时会立即触发一次回调函数。
<script>
const name = ref("muggle")
watchEffect(()=>{
console.log(name)
})
</script>
4. 副作用清理 vue3.5+有效
使用onWatcherCleanup()清理过时请求,即请求还没完成id已发生变化
<script>
watch(id,(newId)=>{
const controller = new AbortController()
fetch(`/api/${newId}`).then(()=>{})
onWatchCleanup(()=>{
controller.abort()
})
})
</script>
5. 停止侦听器
watch()要用同步语句创建,即setup下,可以自动停止。如果在异步函数内创建了watch(),需要手动停止,否则会内存泄漏
<script>
setTimeout(()=>{
watch(()=>{})
},2000)
const unwatch = watchEfferct(()=>{})
unwatch() // 手动停止
</script>
6. watch和watchEffect的区别
watch需要指定数据源,数据源只能是ref对象、reactive对象、getter返回的响应式数据,这些才能启动响应式数据的依赖追踪。若传入的是单纯的响应式数据,不会触发响应式依赖。响应式数据变更时才触发回调。
watchEffect不需要指定数据源,回调函数自动追踪所有响应式数据的依赖。立即触发一次回调
组合式函数
利用vue组合式API封装和复用有状态逻辑的函数
1. 鼠标位置例子
exprot将组合式函数传递出去
// mouse.js
<script setup>
import {ref,onMounted,onUnmounted} from 'vue'
export default function useMouse(){
const x = ref(0)
const y = ref(0)
function update(event){
x.value=event.pageX
y.value=event.pageY
}
onMounted(()=>{
window.addEventListener('mousemove',update)
})
onUnMounted(()=>{
window.removeEventListener('mousemove',update)
})
return {x,y}
}
</script>
组件内使用函数
<script setup>
import {useMouse} from 'vue'
const {x,y} = useMouse()
</script>
<span>{{x}}{{y}}</span>
2. 异步状态例子
// fetch.js
<script setup>
import {ref} from 'vue'
export default function useFetch(url){
const data = ref()
const error = ref()
fetch(toValue(url))
.then((res)=>res.json())
.then((json))=>(data.value=json))
.cathc((error)=>(error.value=error))
return {data,error}
}
</script>
// 组件使用
<script setup>
import {useFetch} from './fetch.js'
const url = ref('./initial-url')
const {data,error} = useFetch(url)
</script>
3. 组合式函数约定规则
命名:驼峰命名+use开头
输入参数:使用toValue,对传入函数的参数化为值,对ref和getter函数均有效
返回值:返回一个包含多个ref的对象return {x,y} ,这样解构的时候仍保持响应式
自定义指令
1. 自定义指令在script setup下构建,任何以v开头的驼峰式命名变量都可以作为自定义指令
<script setup>
const vFocus = {
mounted:(el)=>el.focus()
}
</script>
// 与自定义属性(props) 自定义事件(emits)一样,自定义指令也用-这种方式命名,vue会自动解析成vFocus
<input v-focus/>
2. 指令钩子
// 具体可看文档
3. 不建议在组件上使用自定义指令
插件
1. 定义插件
插件是一个包含install(app,option)方法的对象,或者是install(app,option)函数本身
const myPlugin = {
install:(app,option)=>{
app.component()
app.provide()
}
}
export {myPlugin}
2. 使用插件
import {myPlugin} from './myPlugin.js'
import {createApp} from 'vue'
const app = createApp(App)
app.use(myPlugin,option)
vue项目结构
├── node_modules/
├── public/
│ └── index.html
├── src/
│ ├── assets/
│ ├── components/
│ ├── views/
│ ├── App.vue
│ ├── main.js
├── package.json
├── .gitignore
├── vue.config.js
└── README.md
1. index.html
作为容器放置根组件,通过script标签引入vue应用的入口文件main.ts,同时main.ts使用app.mount('#app')将vue应用挂载到div DOM元素上
<body>
<div id="app"></div>
</body>
<script type="module" src="./main.ts"></script>
2. main.js
vue应用的入口文件,用于创建vue应用
import {crateApp} from 'vue'
import App from './App.vue' // 根组件
const app = create(App)
app.mount('#app') // 将根组件挂载到id='app'的dom元素上
3. App.vue
Vue应用的根组件,不需要export defualt name,script setup自动将组件暴露到全局
defineOtions
直接在script setup下定义属性选项,不再需要单独的script
<script setup>
defineOptions({
name: 'TButton',
inheritAttrs: false,
customOptions: {
}
})
</script>
vue的渲染机制
Vue组件挂载时
1. 编译:模板被编译为渲染函数,即用于返回虚拟dom的函数(render)
2. 挂载:调用渲染函数,遍历返回虚拟DOM树,基于虚拟DOM树创建实际节点,在这个阶段会追踪所有响应式依赖
3. 更新:当一个依赖发生变化,会诚信创建一个更新后的虚拟DOM树,遍历这颗新的虚拟DOM树,与旧树对比,将必要的更新到真实的DOM
其中render函数和创建虚拟DOM节点的h函数都可以由用户直接调用
模板vs渲染函数
模板会预编译为虚拟DOM渲染函数,可以之直接手写渲染函数render,创建想要的虚拟节点vnode
渲染函数基本用法
1. 创建vnode
import {h} from 'vue'
const vnode = h(
'div',
{id:'foo',class:'bar'},
[
]
)
2. 使用renderAPI将虚拟vnode渲染到指定容器中
render(VNode, container)