基础内容
通过 data()
提供数据
vue 中可以在 export default{}
里通过 data()
提供数据,data
必须是一个函数,return
一个对象
export default {
data () {
return {
uname: 'JuziYou',
age: 18
}
}
}
使用插值表达式显示数据
插值表达式使用两对大括号包裹,使用的数据需要在 data()
、methods
、computed
等地方存在,可以使用表达式,但是不能使用在标签属性
{{ uname }}
{{ obj.uname }}
{{ obj.age > 18 ? '成年' : '未成年' }}
v-bind
插值表达式不能使用在标签属性,需要使用 v-bind
需要设置动态 html 属性上,简写为 :
举例:
<a :href="url">戳戳</a>
<a v-bind:href="url">戳戳</a>
v-on
v-on
语法为 v-on:事件名='需要执行的代码/函数名/函数名(实参)'
需要在 methods
中提供事件处理函数,简写 @
举例:
<button @click='num = bum + 1'>Click</button>
<button @click='functionA'>Click</button>
<button @click='functionB('JuziYou')'>Click</button>
<button v-on:click='num = bum + 1'>Click</button>
<button v-on:click='functionA'>Click</button>
<button v-on:click='functionB('JuziYou')'>Click</button>
事件修饰符
.prevent
阻止默认行为.stop
阻止冒泡.once
程序运行期间只触发一次事件处理函数.native
在组件上调用事件
v-model
v-model
是一个语法糖 @input
和 :value
组合到了一起,它吧属性和数据双向绑定到一起。双向绑定即数据变化视图同步变化,视图变化数据同步变化。
Vue3 中 v-model
是 :modelValue
和 @update:modelValue
的组合,Vue3官方文档
语法:v-model="数据变量"
<button v-model="数据变量"> 戳戳 </button>
在父子组件通信时,如果 :value
传参给子组件 $emit('input', 值)
可以使用 v-model
代替
v-model
修饰符
.number
和 .parseFloat
转换为数字类型.trim
去除首尾空白(空格).lazy
使用 change
事件改变数据(默认为 imput
事件改变数据)
v-text
和 v-html
v-text
和 v-html
会更新 DOM 对象的 innerText 或者 innerHTML,并且会覆盖差值表达式 v-text
不会识别 HTML 标签,v-html
会识别 HTML 标签
语法:
<p v-text="数据变量"></p>
<p v-html="数据变量"></p>
v-show
和 v-if
及 v-else
v-show
和 v-if
控制标签的显示或隐藏,true
或 false
v-show
使用 CSS 的 display:none
来隐藏v-if
将会直接从 DOM 树上移除
频繁切换显示的情况下使用 v-show
因为使用 display:none
来切换显示隐藏不会频繁创建元素节省性能,v-if
是惰性的在 false
时不会创建元素节省初期渲染开销
语法:
<p v-show="true"></p>
<p v-if="false"></p>
v-if
与 v-else
和 v-else-if
同时使用,方便通过变量控制一套标签出现或者隐藏
<template>
<div>
<h1 v-if="yukari>= 60">紫老ffhfhewufuiefefrwgfuktyr</h1>
<h1 v-else-if="yukari>= 30">紫妈</h1>
<h1 v-else>紫</h1>
</div>
</template>
<script>
export default {
data(){
return {
yukari: 16
}
}
}
</script>
v-for
v-for
常用于列表渲染,按照数据循环生成,可以遍历数组、对象、数字、可遍历解构的字符串
每一项唯一标识符作为 :key="索引"
,可以最大限度的复用你的 DOM,:key="索引"
的索引只能是数字或者字符串
语法:v-for="(值, 索引) in 目标" :key="索引"
v-for="值 in 目标" :key="值下的可用于索引的值"
<template>
<div>
<ul>
<li v-for="(item,index) in list" :key="index">
{{ item }} > {{ index }}
</li>
</ul>
</div>
</template>
<script>
export default {
data(){
return {
list:['JuziYou','MikanpaperYuzu','橘纸柚']
}
}
}
</script>
<template>
<div>
<ul>
<li v-for="(value, key) in preson" :key="key">
{{ value }} · {{ key }}
</li>
</ul>
</div>
</template>
<script>
export default {
data(){
return {
preson:{
uname: '八云紫',
skill: '操纵界线的能力',
age: 18
}
}
}
}
</script>
<template>
<div>
<ul>
<li v-for="(item,index) in personList" :key="index">
>>>{{item.uname}}
<p>{{item.skill}}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
data(){
return {
personList:[
{uname:'博丽灵梦',home:'博丽神社'},
{uname:'琪露诺',home:'雾之胡'},
{uname:'蕾米莉亚',home:'红魔馆'},
{uname:'帕秋莉',home:'红魔馆'}
]
}
}
}
</script>
<template>
<div>
<ul>
<li v-for="(item,i) in 5" :key="i">
{{item}} · {{i}}
</li>
</ul>
</div>
</template>
数组的数据更新监测
数组方法中能引起页面更新的方法有:push
、pop
、shift
、unshift
、splice
、sort
、reverse
其他无法引起页面更新的方法可以通过赋值来完成更新
通过下标来更新值不会触发页面更新需要使用 $set
来更新数组
语法:this.$ste(要改变的对象, 要改变的位置, 要改变的值)
<template>
<div>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}}</li>
</ul>
<button @click="changeFn">changeFn</button>
</div>
</template>
<script>
export default {
data() {
return {
list: ['博丽灵梦', '琪露诺', '八云紫']
}
},
methods:
changeFn(){
this.$set(this.list, 1, "魔理沙")
}
}
}
</script>
动态绑定 class
使用 v-bind:class
设置动态属性一般使用简写 :class
允许使用对象或者数组:class
不会影响到原有的 class 属性
使用对象动态绑定 :class="{类名:布尔值}"
如果布尔值为 true
就有这个类名,false
则没有
使用数组动态绑定 :class="数组"
标签会有数组里面的所有类名
<template>
<div>
<div :class="str">Nya</div>
</div>
</template>
<script>
export default {
data(){
return {
str: 'green'
}
}
}
</script>
<template>
<div>
<div :class="isRed ? 'red' : ''"></div>
</div>
</template>
<script>
export default {
data(){
return {
isRed: true
}
}
}
</script>
<template>
<div>
<div :class="{yukari: isRed, suka: !isRed}"></div>
</div>
</template>
<script>
export default {
data(){
return {
isRed: true
}
}
}
</script>
<template>
<div>
<div :class="arr"></div>
</div>
</template>
<script>
export default {
data(){
return {
arr: ['show', 'mask', 'red', 'paper']
}
}
}
</script>
动态绑定 style
使用 v-bind:style
设置动态属性一般使用简写 :style
语法 :style="{样式名:样式值}"
样式名为 CSS 属性的驼峰命名法
<template>
<div>
<h1 :style="{ fontSize: '50px', color: fontColor}">黄色的</h1>
</div>
</template>
<script>
export default {
data(){
return{
fontColor: 'yellow'
}
}
}
</script>
computed
计算属性
计算属性必须在 computed
节点中,多用于多个数据影响一个数据的场景,根据现有的数据区计算出新的数据并且有缓存,在数据不发生变化的情况下只会执行一次,数据有变化时会重新计算并缓存,写法是一个函数,一定需要返回值是最终值
示例翻转字符串:
<template>
<div>
<h1>{{reverseStr}}</h1>
</div>
</template>
<script>
export default {
data(){
return{
uname: 'MikanpaperYuzu'
}
},
computed: {
reverseStr(){
return this.uname.split('').reverse().join('')
}
}
}
</script>
计算属性默认情况下只能获取不能修改,如果需要修改需要用到完整写法
完整写法示例:
<template>
<div>
<h1>计算属性的完整写法</h1>
<h1>{{fullName}}</h1>
<button @click="clickFn">点我</button>
</div>
</template>
<script>
export default {
data(){
return{
firstName:'Juzi',
lastName:'You'
}
},
computed: {
fullName: {
get(){
return this.firstName + '-' + this.lastName
},
set(val){
this.firstName = val.split('-')[0]
this.lastName = val.split('-')[1]
}
}
},
methods: {
clickFn(){
this.fullName = 'MikanPaper-Yuzu'
}
}
}
</script>
watch
属性监听
属性监听必须写在 watch
节点中,只要监听的属性值发生了变化就会执行,多用于一个属性影响多个属性的场景,没有缓存但是可以异步,简单写法无法监听复杂类型的写法,需要使用完整写法监听复杂数据类型
watch: {
监听的属性 (变化后的值, 变化前的值) {
}
}
示例:
<template>
<div>
<h2>{{ uname }}</h2>
<button @click="uname = 'MikanpaperYuzu'">给我变</button>
</div>
</template>
<script>
export default {
data(){
return{
uname: 'JuziYou'
}
},
watch:{
uname(newVal, oldVal){
console.log('魔法少女 ' + oldVal + ' 变 ' + newVal);
}
}
}
</script>
immediate: true
代表一进入页面立即执行deep: true
代表升读监听可以监听到复杂数据类型内部的变化handler(){}
每次变化时触发的函数watch: {
接听的属性:{
immediate: true, //代表一进入页面立即执行
deep: true, // 代表升读监听可以监听到复杂数据类型内部的变化
handler(){ // handler 是每次变化时触发的函数
}
}
}
示例:
<template>
<div>
<h2>{{ obj.uname }}</h2>
<button @click="mya">给我变</button>
</div>
</template>
<script>
export default {
data(){
return{
obj: {
uname : 'JuziYou'
}
}
},
watch: {
obj:{
immediate: true,
deep: true,
handler(){
console.log("变了喵");
}
}
},
methods: {
mya(){
this.obj.uname = "MikanpaperYuzu"
}
}
}
</script>
组件基础
引入组件
组件分为全局引入和局部引入,一般使用大驼峰命名法,除了驼峰, 如果使用小写还可以使用 -
转换链接不同的单词,如一个注册组件名为 <JuziYou />
可以直接使用转换连接 <juzi-you />
也能解析到父组件。如果标签没有内容可以直接使用自结束标签
局部引入
局部引入将组件注册在当前 Vue 文件,只可以用在这个 Vue 文件中。
使用 import 组件对象 from 'Vue文件路径'
引入组件
然后 components: { 组件名: 组件对象 }
注册组件
示例:
<template>
<div id="app">
<Pannel />
</div>
</template>
<script>
import PannelLoc from './components/Pannel.vue'
export default {
components: { Pannel: PannelLoc }
}
</script>
<template>
<div>
<h1>Nya</h1>
</div>
</template>
全局引入
全局引入在 main.js
引入组件并使用,注册在全局整个项目中都可以使用
使用 import 组件对象 from 'Vue文件路径'
引入组件
然后 Vue.component("组件名", 组件对象)
注册组件
示例:
import Vue from 'vue'
import App from './App.vue'
// 引入组件
import Pannel from "./components/Pannel";
// 全局注册
Vue.component('PannelGol', Pannel)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
<template>
<div>
<h1>Nya</h1>
</div>
</template>
<template>
<div>
<PannelGol></PannelGol>
</div>
</template>
组件样式 scoped
吧样式只应用于当前组件,给组件的 <style>
加上 scoped
会给这个组件的所有元素添加自定义属性,以及当前组件的 CSS 添加交集选择器,只有这个自定义属性的标签才能被样式选中
示例:
<template>
<div>
<h1>MikanpaperYuzu</h1>
<ChildOne />
</div>
</template>
<script>
import ChildOne from './components/ChildOne.vue'
export default {
components: {
ChildOne
}
}
</script>
<style>
</style>
<template>
<div>
<h1>JuziYou</h1>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
h1{
color: orange
}
</style>
组件 name
属性
一般使用一些框架会用到,以保证框架的命名风格所以懒得写太清楚(((
示例:
<template>
<div>
<JuziYou />
</div>
</template>
<script>
import ChildCom from './components/ChildCom.vue'
export default {
components:{
[ChildCom.name]: ChildCom
}
}
</script>
<template>
<div>
<p>JuziYou</p>
</div>
</template>
<script>
export default {
name:'JuziYou'
}
</script>
父向子通讯
在父组件的子组件标签上添加自定义属性,然后在子组件使用 props
接收
注: 子组件不能修改父组件传来的 props
示例:
<template>
<div>
<MyProduct title="勾勾果" price="200" intro="蒙德产勾勾果" />
<MyProduct title="甜甜花" price="100" intro="蒙德产甜甜花" />
</div>
</template>
<script>
// 定义局部变量
import MyProduct from './components/MyProduct.vue'
export default {
components: {
MyProduct,
}
}
</script>
<template>
<div>
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
</div>
</template>
<script>
export default
props: ['title','price','intro']
}
</script>
子向父通讯
在父组件的标签上设置自定义事件组件 @事件名="方法名"
,然后在子组件中使用 this.$emit(事件名, 传给父元素的值)
触发自定义事件,$emit
第二个值之后为返回给父元素的值,在父亲元素接收的方法中对应形参
示例:
<template>
<div>
<MyProduct
:title="list.title"
:price="list.price"
:intro="list.intro"
@upMora="upFn"
/>
</div>
</template>
<script>
// 定义局部变量
import MyProduct from './components/MyProduct.vue'
export default {
components: {
MyProduct,
}
data() {
return {
list:{
title: '勾勾果'
price: '200'
intro: '蒙德产勾勾果'
}
}
},
methods: {
upFn(num){
this.list.price = num
}
}
}
</script>
<template>
<div>
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<button @click="$emit('upMora', 1000)">价格改为1000元</button>
</div>
</template>
<script>
export default
props: ['title','price','intro']
}
</script>
props
校验
校验父模块向子模块传来的数据是否符合需求
key
是接收值名称,value
是接收的类型export default {
props:{
user:String
}
}
value
设置为接收的类型数组export default {
props:{
age: [String, Number]
}
}
type
表示接收的类型,required: true
来打开必传export default {
props:{
gender:{
type: String,
required: true
}
}
}
type
表示接收的类型,default: 值
来设置默认值export default {
props:{
salary:{
type: String,
default: 'JuziYou'
}
}
}
type
表示接收的类型,使用 validator(传过来的值){}
它 return
出来的值 true
和 false
来校验通过或不通过,如果 return
了 false
将会在控制台打印错误export default {
props:{
// 检测传入 list 是否 length 大于 3
list: {
type: Array,
validator(val){
if(val.length >= 3){
return true
}
return false
}
}
}
}
.sync
修饰符
可以让繁琐的父子通讯变得简单在使用.sync
修饰符后在子组价使用 update:自定义变量 来更改父组件变量绑定的值就可以完成父子组件的通讯,相当于在父组件的标签上添加了@update:属性名=" 变量名 = $event"
,$event
是默认参数
<template>
<div>
<SyncUpdate :text.sync="name"/>
</div>
</template>
<script>
import SyncUpdate from '@/components/sync-update.vue'
export default {
components:{
SyncUpdate
},
data () {
return {
name: 'Mikanpapre'
}
}
}
</script>
<template>
<div>
<div>{{ text }}</div>
<!-- 使用 update:自定义变量 来更改父组件变量绑定的值 -->
<button @click="$emit('update:text', 'Nya')">戳戳</button>
</div>
</template>
<script>
export default {
props: ['text']
}
</script>
$parent
$parent
可以在子组件获取到父组件的实例
事件总线 EventBus
如果两个子组件通过父组件进行通讯会非常复杂,通过一个空的 Vue 对象当作事件总线常用于跨组件通讯的通用方案,在组件 A 使用 EventBus.$emit('事件名', 值)
触发事件,在组件 B 使用 EventBus.$on('事件名', 函数体)
监听事件
import Vue from 'vue'
引入 Vue使用
export default new Vue()
对外暴露一个 Vue 实例import Vue from 'vue'
export default new Vue()
<template>
<div>我是A
<button @click='clickfn'>戳</button>
</div>
</template>
<script>
import EventBus from '../EventBus'
export default {
methods:{
clickfn(){
EventBus.$emit('mikan')
}
}
}
</script>
created()
在事件创建时就执行<template>
<div>我是B</div>
</template>
<script>
import EventBus from '../EventBus'
export default {
created(){
EventBus.$on('mikan', ()=>{
console.log('喵');
})
}
}
</script>
APP.vue
导入两个组件并使用<template>
<div>
<h1>EventBus</h1>
<ChildA/>
<ChildB/>
</div>
</template>
<script>
import ChildA from './components/ChildA.vue'
import ChildB from './components/ChildB.vue'
export default {
components: {
ChildA,
ChildB
}
}
</script>
ref
和 $refs
ref
和 $refs
通常用于获取元素 DOM 或组件示例,将会挂载到 $refs
的组件对象内
获取 DOM
在需要获取的 DOM 元素上 ref="值"
然后在 $refs.值
使用
<template>
<div>
<div ref="juziyou">JuziYou</div>
<button @click="clickFn">Click</button>
</div>
</template>
<script>
export default {
methods:{
clickFn(){
console.log(this.$refs.juziyou)
}
}
}
</script>
获取组件实例
在需要获取的组件自定义标签上 ref="值"
然后在 $refs.值
使用
<template>
<div>
<ChildB ref="childRef"/>
<button @click="childRefFn">Click</button>
</div>
</template>
<script>
import ChildB from './components/ChildB.vue'
export default {
components:{
ChildB
},
methods:{
childRefFn(){
console.log(this.$refs.childRef.uname)
this.$refs.childRef.logName()
}
}
}
</script>
<template>
<div>
ChildB
</div>
</template>
<script>
export default {
data(){
return{
uname: 'JuziYou',
}
},
methods:{
logName(){
console.log(this.uname)
}
}
}
</script>
$nextTick
由于 Vue 更新 DOM 是异步的,会导致需要在 DOM 更新之后的代码会被先执行,所以需要使用 $nextTick
$nextTick
接收一个函数作为参数,接收的函数将会在 DOM 更新后执行
如在点击按钮后显示输入框然后获取输入框的 DOM:
<template>
<div>
<input type="text" ref="ipt" v-if="isShowInput">
<button @click="fn" v-else>Click</button>
</div>
</template>
<script>
export default {
data () {
return {
isShowInput: false
}
},
methods: {
fn () {
this.isShowInput = true
this.$nextTick(()=>{
console.log(this.$refs.ipt);
})
}
}
}
</script>
component
动态渲染组件
component
是 Vue 内置的一个组件用于动态的渲染组件,:is="值"
的值是什么就会渲染哪个子组件
<template>
<div>
<button @click="comName = 'ChildA'">ChildA</button>
<button @click="comName = 'ChildB'">ChildB</button>
<component :is="comName"></component>
</div>
</template>
<script>
import ChildA from './components/ChildA.vue'
import ChildB from './components/ChildB.vue'
export default {
components:{
ChildA,
ChildB
},
data(){
return{
comName: 'ChildA'
}
}
}
</script>
<template>
<div>
<h1>ChildA</h1>
</div>
</template>
<template>
<div>
<h1>ChildB</h1>
</div>
</template>
自定义指令
自定义指令分为全局和局部注册,在标签上 v-自定义指令名
进行使用
常用的:inserted
方法绑定此指令的元素插入到 DOM 时执行
语法为 inserted(形参){}
inserted(形参){}
第一个形参是绑定此指令的元素,第二个形参为当前自定义指令传入的值
update
方法绑定此指令的元素值发生变化时执行
语法为 update(形参){}
update(形参){}
第一个形参是绑定此指令的元素,第二个形参为当前自定义指令传入的值
main.js
内添加 Vue.directive
进行全局注册语法为
Vue.directive('指令名',{方法})
示例:在 input 标签创建时自动落焦
Vue.directive('focusglo', {
inserted(el){
el.focus()
}
})
export default
内使用 directives{对象}
进行局部注册,注册的自定义指令只能在当前组件使用示例:如 color
的值发生变化时重新设置使用了此自定义指令的元素的颜色
export default {
data () {
return {
color: 'orange'
}
}
directives: {
zy:{
inserted(el,binding){
el.style.color = binding.value
},
update(el,binding){
el.style.color = binding.value
}
}
}
}
插槽
插槽插在父组件的两个起始标签和结束标签之间,默认会放到默认 <slot>
插槽内<template>
中使用指令 v-slot:插槽名
来使用指定的插槽,也可以用 #插槽名来简写
,在子组件内的 <slot>
通过 name="插槽名"
变量的方式来设置插槽名
默认插槽可以使用 <template v-slot="变量名"></template >"
来接收传过来的参数,具名插槽可以之间加上等号变量名来接收传过来的的参数 <template v-slot:插槽名="变量名"></template >"
,在子组件内的 <slot>
通过添加自定义变量名的方式来传参
<template>
<div>
<!-- 使用組件時在父組件的兩個標簽之間插入 -->
<MyDialog>
<img src="https://lovemen.cc/usr/hotlink-ok/avatar.jpg" alt="">
</MyDialog>
<MyDialog>
<!-- <template v-slot:footer> 指定插入footer插槽 v-slot:插槽name -->
<!-- 在具名插槽下直接加上等號就可以接收,可以在等號内直接解構賦值 -->
<template v-slot:footer="{yes:okay,no}">
<button>{{ okay }}</button>
<button>{{ no }}</button>
</template>
<!-- 可以使用#簡寫 -->
<template #header>
<h3>溫馨提示</h3>
</template>
<!-- 默認插槽透過v-slot="變量名"接收傳過來的,將以對象形式呈現,只能在template内使用 -->
<template v-slot="obj">
{{obj}}
<p>你好,買?</p>
</template>
</MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
components:{
MyDialog
}
}
</script>
<template>
<div class="my-dialog">
<div class="header">
<slot name="header"></slot>
</div>
<div class="content">
<!-- slot表示此處有一個插槽 -->
<!-- 作用域插槽:通過插槽設置自定義參數傳數據 -->
<slot uname="MikanpapreYuzu" age="18"></slot>
</div>
<div class="footer">
<!-- name給插槽起名 -->
<slot name="footer" yes="好捏" no="打咩"></slot>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-dialog {
width: 400px;
padding: 10px 20px;
border: 3px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
路由
npm 默认获取的 vue-router
版本为 4,在 Vue2 中需要 vue-router
版本为 3 时才可以使用,所以在 Vue2 中需要 @3
指定 vue-router
安装版本
npm i vue-router@3
路由就是路径和组件间的关系,创建路由的方式有在 main.js
创建路由和创建 router
文件夹在 index.js
中创建路由
在 main.js
创建路由
先在 main.js
内使用 import VueRouter from 'vue-router'
引入 vue-router
,然后使用 Vue.use(VueRouter)
让 Vue 使用 vue-router
插件 const router = new VueRouter(对象)
创建一个路由实例,在对象内添加一个 routes
数组,这个数组内每个对象都是一个匹配规则,import RouterPage from '@/views/RouterPage'
引入组件,router 内的对象 path
为设置路由的路径 component
为设置路由指向的组件,最后在 new Vue
内添加 const 出来的变量名关联到 Vue 实例,匹配到的组件会在 <router-view></router-view>
渲染
import Vue from 'vue'
import App from './App.vue'
import My from '@/views/My'
import Part from '@/views/Part'
// 5 引入组件
import RouterPage from '@/views/RouterPage'
// 1引入vue-router
import VueRouter from 'vue-router'
// 2让Vue使用vue-router插件
Vue.use(VueRouter)
// 3创建路由实例
const router = new VueRouter({
// 6 routes 是一个数组,此数组里面的每一个对象都是一个匹配规则
routes:[
{
// 7设置路由路径
path:'/RouterPage',
// 8 给路径指向组件
component: RouterPage
}
]
})
Vue.config.productionTip = false
//4 吧路由实例关联router到vue实例
new Vue({
router,
render: h => h(App),
}).$mount('#app')
<template>
<div>
<h1>RouterPage</h1>
</div>
</template>
<template>
<div>
<a href="#/RouterPage"></a>
<div>
<router-view></router-view>
</div>
</div>
</template>
在 router
文件夹中的 index.js
创建路由
在 main.js
中创建路由不太好,应该每个模块干每个模块的事情
在 src
中创建 router
文件夹然后在文件夹中创建 index.js
,首先在 index.js
中使用 import Vue from 'vue'
引入 Vue,然后使用 import VueRouter from 'vue-router'
引入 vue-router
,接着使用 Vue.use(VueRouter)
让 Vue 使用 vue-router
插件,使用 const router = new VueRouter(对象)
创建一个路由实例,在对象内添加一个 routes
数组,这个数组内每个对象都是一个匹配规则,import RouterPage from '@/views/RouterPage'
引入组件,router 内的对象 path
为设置路由的路径 component
为设置路由指向的组件,export default router
让 index.js
对外暴露 router
实例,最后在 main.js
中使用 import router from '@/router/index.js'
引入路由实例再在 new Vue
中关联引入的实例 router
到 Vue 实例,匹配到的组件会在 <router-view></router-view>
渲染
// 1 引入Vue
import Vue from 'vue'
// 5 引入组件
import RouterPage from '@/views/RouterPage'
// 2 引入vue-router
import VueRouter from 'vue-router'
// 3 让Vue使用vue-router插件
Vue.use(VueRouter)
// 4 创建路由实例
const router = new VueRouter({
// 6 routes 是一个数组,此数组里面的每一个对象都是一个匹配规则
routes:[
{
// 7设置路由路径
path:'/RouterPage',
// 8 给路径指向组件
component: RouterPage,
}
]
})
// 对外暴露路由实例对象
export default router
import Vue from 'vue'
import App from './App.vue'
// 引入路由实例
import router from '@/router/index.js'
Vue.config.productionTip = false
//4 吧路由实例关联router到vue实例
new Vue({
router,
render: h => h(App),
}).$mount('#app')
<template>
<div>
<h1>RouterPage</h1>
</div>
</template>
<template>
<div>
<a href="#/RouterPage"></a>
<div>
<router-view></router-view>
</div>
</div>
</template>
查询字符串传参
在 to="path"
的 path 后面加 ?
查询字符串的方式传参,然后在组件内使用 $route.query
接收参数会以一个对象的形式接收
比如 <router-link to="/part?uname=JuziYou&ocname=MikanpaperYuzi">橘纸柚</router-link>
,在组件内 $route.query
将会收到一个对象 {uname:'JuziYou', ocname'MikanpaperYuzi'}
动态路由传参
首先在对应的路径复制一份然后在 path 中的路由加上 /:参数名
,然后在组件内使用 $route.params
接收参数会以一个对象的形式接收
比如:
一个路由为 /user
需要接收一个参数名为 uname
(.....省略后面或者前面代码)
..... = new VueRouter({
routes:[
.....
.....
{
path:'/user',
redirect: 'User'
},
{
path:'/user/:uname',
redirect: 'User'
},
.....
然后访问比如 localhost:8080/#/user/juziyou
,在组件内使用 $route.params
将会获取到一个对象 {uname:'juziyou'}
路由重定向以及默认页(404)
在路由实例的 routes
中起始位子添加一个匹配规则,匹配规则中 redirect
为重定向的路径
如从 /
重定向到 /juziyou
(.....省略后面或者前面代码)
..... = new VueRouter({
routes:[
{
path:'/',
redirect: '/juziyou'
},
.....
默认页则在路由实例的 routes
中的末尾添加一个 path
为通配符 *
的一个规则
如前面一个规则也没匹配上返回一个 NotFund
页面(.....省略后面或者前面代码)
..... = new VueRouter({
routes:[
.....
.....
{
path:'*',
redirect: 'NotFund'
},
.....
mode 与 base
mode
写在路由的实例中有个常用的参数 hash
和 history
,hash
会在 URL 中添加#号,history
则不会但是需要后端配合因为会向服务器发送真实路径。
以及可以通过 base
设置基础地址,比如要访问 /user
页面在 hash
模式下是 /#/user
如果在 history
模式下设置了 base
为 /juzi/
地址就是 /juzi/user
如修改 mode
到 history
(.....省略后面或者前面代码)
..... = new VueRouter({
mode: 'history',
base: '/juzi/'
.....
router-link
声明式导航
声明式导航 router-link
是 vue-router
提供的一个全局组件,使用 to="path"
来跳转路由路径
会给选中的路由自动的加上 class 类名,其分为模糊匹配类名和精确匹配类名,可以在路由实例内通过 linkActiveClas
修改模糊匹配类名以及通过 linkExactActiveClass
修改精确匹配类名
如通过 router-link
转到 path /routerpage
<router-link to="/routerpage"></router-link>
如需要修改精确匹配类名为:juzi,模糊匹配类名为:you 在路由实例内设置的方式(.....省略后面或者前面代码)
...... = new VueRouter({
linkActiveClass:'you',
linkExactActiveClass:'juzi',
......
编程式传参
通过调用函数的方式使用方法 $router.push
,使用 path
无法使用 params
传参所以推荐使用 name
来进行跳转
name
在路由配置中添加 name
属性,在编程式导航中如果需要使用 params
就得用 name
来跳转
路由守卫
路由守卫分为全局路由守卫、路由内独享守卫、组件内守卫
全局路由守卫:对所有路由守卫生效
- beforeEach 路由前置守卫:token 判断
- arterEach 路由后置守卫:进度条关闭
路由独享守卫:对某一条路由进行单向控制
- beforeEnter 路由独享守卫:写在路由配置项内,也有
to
from
next
只对某条路由规则生效
组件内守卫:针对组件内
- beforeRouterEnter 只要通过路由就会触发
- beforeRouteUpdate: 再次复用了页面,比如主页跳到带 params 的主页,解决 created 只会触发一次的问题
- beforeRouterLeave: 代表离开了页面
全局守卫
所有的路由一旦匹配到规则在真正渲染解析前都会经过全局守卫,只有在全局守卫放行后才能真正渲染页面
在创建实例前先声明一个白名单变量或常量,吧路由(path)以字符串的形式储存在数组中,然后使用router.beforeEach(回调函数)
创建路由守卫,在路由解析访问之前都会经过回调函数,回调函数中有三个形参from
从哪里来的路由信息对象、to
要到那里去的路由信息对象、next
是否放行这是一个方法,如果next()
表示放行去的页面、next(路径)
则表示拦截到的页面,使用whiteList.includes
来匹配是否处于白名单
如验证用户是否登录
// 设置白名单
const whiteList = ['/login', '/regisrer']
router.beforeEach((to, from, next) => {
// 如果有token就放行
if (this.token) {
next()
} else {
// 如果没有token检查是不是去白名单页面
if (whiteList.includes(to.path)) {
// 如果是去白名单直接放行
next()
} else {
// 如果不是去白名单跳转到登录
next('/login')
}
}
})
生命周期
生命周期是 Vue 组件创建到销毁的过程
在特定的时间点执行特定的操作
分四大阶段,八个方法
官方文档
初始化
beforeCreate
在组件创建之前会被调用,此状态下无法获取自定义的数据,比如 data
中的数据created
会在初始化数据并当前实例代理了数据后执行,一般会在此生命周期钩子中请求服务器数据
挂载
beforeMount
无法获取到 DOM 元素因为这个时候还是虚拟 DOMmounted
中就可以获取到真实 DOM 了
更新
beforeUpdate
在页面使用中的数据发生变时虚拟 DOM 更新之前运行updated
在使用中的数据发生变化时虚拟 DOM 更新到页面后运行
销毁
beforeDestroy
在销毁前运行,一般用来解绑一些事件,比如说定时器destroyed
咋子销毁后运行
keep-alive
缓存
<keep-alive>
是 Vue 内置的全局组件可以吧组件缓存到内存中,使用<keep-alive>
被缓存的组件有两个生命周期activated
组件被激活deactivated
组件被隐藏
vuex
vuex 是 vue 的一个状态(数据)管理工具(也是插件),方便的解决多组件的共享状态,拥有响应式、操作简洁的优势
一般情况下多组件的数据共享会存在 vuex 中
创建仓库
一般为了维护项目的目录整洁会在src
目录下新建store
目录放置index.js
首先在创建 Vuex 实例然后再在main.js
中导入挂载到 Vue 实例上Vuex.Store()
括号内是一个对象
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 注册vuex插件
Vue.use(Vuex)
// 创建仓库实例
const store = new Vuex.Store()
// 导出仓库
export default store
import Vue from 'vue'
import App from './App.vue'
// 引入store
import store from '@/store'
Vue.config.productionTip = false
new Vue({
// 挂载到 Vue 实例
store,
render: h => h(App)
}).$mount('#app')
state
state 提供唯一的公共数据源,所有的共享数据需要放到 state 中储存,可以在任何组件中使用$store.state.键
来获取 state 中的数据也可以通过mapState()
映射到computed
中
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
// 设置 State
state:{
uname: 'JuziYou',
age: 18
}
})
export default store
<template>
<div>
<!-- 直接使用 $store.state.uname 获取 uname 的值 -->
<p>{{ $store.state.uname }}</p>
<!-- 直接使用映射计算属性得到的值 -->
<p>{{ age }}</p>
</div>
</template>
<script>
// 引入 mapState
import { mapState } from 'vuex'
export default {
computed:{
// mapState() 得到的是一个数组中对应的对象可以使用 ... 展开运算符展开到计算属性中
...mapState(['age'])
}
}
</script>
mutation
state 中的数据只能通过 mutation 修改,用于父组件需要修改仓库中的数据
在 vuex 实例中定义mutation
,它是一个对象里面存储修改 state 的方法,每个方法的第一个形参是当前仓库的 state,第二个是传入的参数
可以通过$store.commit('方法', 传参)
来调用 mutation 中的方法,传参只能传一个参数如果需要传多个使用复杂数据类型来完成,比如说对象
还可以通过辅助函数mapMutations(数组)
的方式使用展开运算符映射到methods
中,需要使用this.方法
来调用,使用起来和mapState()
很像
actions
actions 负责仓库内的异步操作,mutation 只能用于同步更新数据
在 vuex 实例中定义actions
,它是一个对象里面存储异步的方法,每个方法的第一个形参拥有仓库实例的所有属性和方法,第二个是传入的参数传参只能传一个参数如果需要传多个使用复杂数据类型来完成,比如说对象
可以通过$store.dispatch('方法', 传参)
来调用
也可以通过辅助函数mapActions(数组)
来调用使用展开运算符映射到methods
getters
getters 使用仓库 state 中现有的数据计算出新的数据
在 vuex 实例中定义getters
,每个方法的第一个形参是当前仓库的 state,必须要有返回值
可以通过$store.getters.方法
来调用
也可以通过辅助函数mapGetters(数组)
的方式使用展开运算符映射到computed
中
module 模块
如果使用单一的index.js
项目大后应用会变得非常复杂相对臃肿
在store
文件夹中创建一个用于存放模块的文件夹,export default
向外暴露键state
、mutations
、actions
、getters
值就是它们所需要的值,访问模块中的数据可以直接通过$store.state.模块名.数据键
来访问mutations
、actions
、getters
默认情况下全局可以调用,然后在
index.js中引入模块并在
modules中注册,组件的
state以对象的形式挂载到
state` 下
export default {
state: {},
mutations: {},
actions: {},
getters: {}
}
import Vue from 'vue'
import Vuex from 'vuex'
// 引入子模块
import data from '@/store/data'
Vue.use(Vuex)
const store = new Vuex.Store({
// 使用modules来引入子模块
// 会将挂载组件的state以对象的形式挂载到state下
modules:{
data
},
})
namespaced 命名空间
开启命名空间后子模块的 mutations、actions、getters 就不会注册在仓库全局,在子模块的export default
中添加namespaced: true
,开启后$store
访问模块内的方法需要模块名/函数名
辅助函数需要在数组前增加模块名如mapState(模块名, 数组)
,然后通过this.方法
来调用
辅助函数还可以通过加/
的方式来使用(如mapState(['模块名/方法名']
),在调用方法时得this[模块名/方法名]()
来调用
export default {
namespaced: true,
}
createNamespacedHelpers
createNamespacedHelpers
辅助函数会返回一个对象里有mapActions
、mapGetters
、mapMutations
、mapState
通过解构赋值和重命名的方式来提取映射
export default {
namespaced: true,
state: {
uname: 'JuziYou'
},
mutations: {
updateName (state, payload) {
state.uname = payload
}
}
}
import Vue from 'vue'
import Vuex from 'vuex'
import datas from '@/store/modules/datas'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
getters: {
},
modules: {
datas
}
})
export default store
<template>
<div>
<div>{{ $store.state.datas.uname }}</div>
<button @click="updateUname">Mikanpapre</button>
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
const { mapMutations: userMapMutations } = createNamespacedHelpers('datas')
export default {
methods: {
...userMapMutations(['updateName']),
updateUname () {
this.updateName('Mikanpapre')
}
}
}
Vue3 内容
相比 Vue2 它首次渲染更快、内存占用更少、diff 算法更快、打包体积更小、更好的支持 Typescript 拥有 Composition API 组合式 API,允许 template
存在多个根标签
vite 构建工具
vite 使用原生 ESModule 通过 script 标签动态导入,访问页面的时候加载到对应模块编译并响应,比 Webpack 查找依赖打包所有模块速度更快。在项目打包的时候还是得用打包工具 Rollup 打包成静态资源
运行创建项目:
# 使用 npm
npm create vite@latest
# 使用 yarn
yarn create vite
快速创建
# 使用 npm
npm init vite-app 项目名称
# 使用 yarn
yarn create vite-app 项目名称
Composition API
官方文档
Vue2 通过 data
、methods
、watch
等配置项编写代码逻辑是选项式 API 写法
Vue3 吧所有逻辑卸载 setup
函数中,使用 ref()
、computed()
等阻止代码是组合式 API 写法
组合式 API 可复用可维护
setup
setup
是组合式 API 的入口钩子,作为组合式 API 的起点,它在组件的生命周期 beforeCreate
之前执行,函数中的 this
不是组件实例而是 undefined
如果数据或者函数在 template
中使用需要 return
对象返回
export default {
setup() {
const uname = 'JuziYou'
return { uname }
}
}
setup
语法糖
Vue3.2 后在 <script>
标签上添加 setup
属性会可以省去默认暴露 export default
和 return
步骤会自动完成,的顶层变量都可以在模板使用数据、函数、组件
在 setup
语法糖中 defineProps
、 defineEmits
、 defineExpose
不需要再引入
setup
语法糖 name
问题
因为使用了 setup
的语法糖后无法使用选项式 API 所以可以使用 vite-plugin-vue-setup-extend
插件来给 <script>
标签上添加属性 name
来解决这个问题,<script>
标签内有内容才能生效
安装插件:
npm i vite-plugin-vue-setup-extend-plus -D
在 vite.config.ts
使用插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 引入 vite-plugin-vue-setup-extend-plus
import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus'
export default defineConfig({
// plugins 中添加 vueSetupExtend() 使用插件
plugins: [vue(), vueSetupExtend()],
})
然后在 <script>
标签添加 name
属性
<!-- script 标签添加 name 属性 -->
<script setup lang="ts" name="Nya">
console.log('script 标签内有内容才能生效');
</script>
<template>
</template>
<style scoped>
</style>
ref
从 vue
中导出在 setup
钩子中使用一般传入一个简单数据类型或复杂数据类型,返回一个响应式数据,template
外使用 ref
中的数据时需要 .value
调用,它可以定义任意数据的响应式
获取 DOM
从 vue
中导出在 setup
钩子中使用,ref
会在onMounted
生命周期后获取页面上绑定了与变量名相同 ref 属性的的 DOM
存在 .value
中,第一个参数是如果没有成功获取到 DOM
时的返回值
<template>
<div ref="Nya">JuziYou</div>
</template>
<script setup>
import { onBeforeMount, onMounted, ref } from 'vue'
const Nya = ref(null)
onBeforeMount(() => {
console.log(Nya.value)
})
onMounted(() => {
console.log(Nya.value)
})
</script>
defineExpose
因为使用 <script setup>
的组件是默认关闭的,组件实例无法使用到顶层的数据和函数,需要配合 defineExpose
暴露给组件实例使用,暴露的响应式数据会自动解除响应式。
从 vue
中导出 defineExpose
传入一个对象对象中写入需要导出的数据和函数
<template>
<h1>{{ count }}</h1>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
const count = ref(0)
const countPlus = () => {
count.value++
}
defineExpose({countPlus, count})
</script>
<template>
<MyCount ref="count" />
<button @click="addCount">Count++</button>
</template>
<script setup>
import { ref } from 'vue';
import MyCount from './components/MyCount.vue';
const count = ref(null)
const addCount = () => {
count.value.countPlus()
}
</script>
reactive
从 vue
中导出在 setup
钩子中使用, reactive
一般用来定义对象类型的响应式数据不支持简单数据类型,reactive
定义的数据修改不用加 .value
调用,不可以直接对它重新赋值,如果涉及到重新赋值应该使用 ref
toRefs
对 reactive
的响应式数据解构或者展开会失去响应式,使用 toRefs
函数让数据保持响应式, toRefs
会吧对象的每一个属性包装成响应式
<template>
<div>{{ uname }} · {{ age }}</div>
<button @click="age++">age++</button>
</template>
<script setup>
import { reactive, toRefs } from 'vue'
const person = reactive({
uname: 'harry',
age: 28
})
const {uname, age} = toRefs(person)
</script>
computed
从 vue
中导出在 setup
钩子中使用,computed
计算函数接收一个函数返回一个计算好的数据,并且和 Vue2 一样有缓存
<template>
<div>{{ arr }}</div>
<div>{{ sum }}</div>
<button @click="pushNum">Push</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const arr = ref([1, 1, 4, 5, 1, 4])
const sum = computed(() => arr.value.reduce((pre, item) => pre + item, 0))
const pushNum = () => {
arr.value.push(1)
}
</script>
watch
从 vue
中导出在 setup
钩子中使用,第三个值是配置项可以省略
单个数据监听
第一个参数是它接听的数据,第二个参数是函数数据发生变化执行的函数,函数中第一个接收值是新值第二个是旧值
<template>
<div>{{ count }}</div>
<button @click="numPlus">Plus</button>
</template>
<script setup>
import { ref, watch } from 'vue'
const count = ref(1)
const numPlus = () => {
count.value++
}
watch(count, (newVal, lodVal) => {
console.log(newVal, lodVal);
})
</script>
多个数据监听
在 Vue3 中 watch
支持一次监听多个数据,只需要第一个参数传入一个数组。第二个参数是函数数据发生变化执行的函数,函数中第一个接收值是新值第二个是旧值,返回值会是数组的形式
<template>
<div>{{ count }} · {{ uname }}</div>
<button @click="numPlus">Plus</button>
<button @click="newName">NewName</button>
</template>
<script setup>
import { ref, watch } from 'vue'
const count = ref(1)
const uname = ref('JuziYou')
const numPlus = () => {
count.value++
}
const newName = () => {
uname.value = prompt('NewName')
}
watch([count, uname], (newVal, lodVal) => {
console.log(newVal, lodVal);
})
</script>
监听对象的单个属性
只需要第一个参数传入一个对象返回需要监听的属性。第二个参数是函数数据发生变化执行的函数,函数中第一个接收值是新值第二个是旧值
<template>
<div>{{ user }}</div>
<button @click="newName">NewName</button>
<button @click="likePlus">LikePlus</button>
</template>
<script setup>
import { watch , reactive } from 'vue'
const user = reactive({
uname: 'JuziYou',
like: 0
})
const newName = () => {
user.uname = prompt('NewName')
}
const likePlus = () => {
user.like++
}
watch(()=>user.uname, (newVal, lodVal) => {
console.log(newVal, lodVal);
})
</script>
深度监听
和 Vue2 相同如果监听的是一个对象中的对象,发生变化后并不会触发,需要开启深度监听,在传入的第三个参数是配置项,在配置项中添加 deep: true
参数
<template>
<div>{{ user }}</div>
<button @click="likePlus">LikePlus</button>
</template>
<script setup>
import { watch , reactive } from 'vue'
const user = reactive({
uname: 'JuziYou',
info: {
like: 0
}
})
const likePlus = () => {
user.info.like++
}
watch(()=>user.info, (newVal, lodVal) => {
console.log(newVal, lodVal);
}, {
deep: true
})
</script>
直接监听响应式对象
如果直接监听一个响应式对象时会自动启用深度监听
<template>
<div>{{ user }}</div>
<button @click="likePlus">LikePlus</button>
</template>
<script setup>
import { watch , reactive } from 'vue'
const user = reactive({
uname: 'JuziYou',
info: {
like: 0
}
})
const likePlus = () => {
user.info.like++
}
watch(user, (newVal, lodVal) => {
console.log(newVal, lodVal);
})
</script>
生命周期
Vue3 的组合式 API 中与 Vue2 生命周期相比基本相同多了 on 打头 beforeCreate
与 created
不再需要直接写入 setup
中即可,beforeDestroyed
和 destroyed
变成了 onBeforeUnmount
和 onUnmounted
Vue3 中组合式 API 的生命周期函数可以在 setup
中多次调用,谁写在前面执行谁
组件通讯
父向子通讯
与 Vue2 基本相同,不同在子组件在 setup
钩子中使用 defineProps
传入一个对象来接收父组件传来的值,如果需要在 script 中使用就接收返回值,如果不需要只在 template 中使用就不用接收
子向父通讯
与 Vue2 基本相同,不同在子组件在 setup
钩子中使用 defineEmits
传入的第一个值是一个数组里面每一项是要触发的父元素绑定的自定义事件名然后接收它的返回值,然后调用接收返回值的常量第一项是父元素绑定的自定义事件名第二项是传参
跨级组件通讯
通过通过 provide
和 inject
函数实习便捷的跨级组件通讯,遵循谁提供由谁修改的原则provide()
提供给后代的组件依赖或者数据,接收两个属性,第一个值为字符串第二个值为函数或要传递的数据inject()
接收 provide()
提供给后代的组件依赖或者数据, 第一个值为 provide()
的第一个值(字符串)
详见官方文档
v-model
Vue3 中 v-model
是 :modelValue
和 @update:modelValue
的组合,Vue3官方文档
Vue3 中 v-model:name
和 .sync
相同,相当于绑定了 :name
和 @update:name
Pinia
Pinia 与 Vuex 一样是一个为 Vue 提供状态管理的工具,它与 Vue3 一样支持 选项式API
与 组合式API
,它也支持 Vue2 和 Devtools 和 TypeScript
可以创建多个全局仓库,不像 Vuex 一个仓库中嵌套模块解构复杂,而且数据管理比 Vuex 更简单只需要写提供数据和修改数据的逻辑即可,不需要像 Vuex 那样记忆大量 APIstoreToRefs
可以解决在解构仓库里的数据后失去响应式的问题
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 1 引入 createPinia
import { createPinia } from 'pinia'
// 2 调用 createPinia()
const pinia = createPinia()
const app = createApp(App)
// 3 注册 createPinia()
app.use(pinia)
app.mount('#app')
<script setup>
// 10 引入 storeToRefs
import { storeToRefs } from 'pinia'
// 7 引入存储仓库的js文件中的存储
import { useCountStore } from './store'
// 8 使用一个变量存储调用按需引入的存储的返回值
const store = useCountStore()
// 11 storeToRefs 解决解构仓库里的数据失去响应式的问题
const { count } = storeToRefs(store)
</script>
<template>
<!-- 9 使用它 -->
<div>{{ store.count }}</div>
<div>{{ store.squCount }}</div>
<div>{{ count }}</div>
<button @click="store.addCont">count++</button>
<button @click="store.asyncAddCount">asycCount++</button>
<button @click="store.payloadCount(10)">asycCount+10</button>
</template>
// 4 引入 defineStore 来创建存储库
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
// 5 defineStore 接收两个参数第一个是名字第二个是函数,函数中 return 出来一个对象里面是你的数据
export const useCountStore = defineStore('count', ()=>{
// 5.1 定义响应式数据,相当于定义vuex中的state
const count = ref(0)
// 5.2 相当于定义了vuex中的getters
const squCount = computed(()=> Math.pow(count.value, 2))
// 5.3 定义修改响应式数据的方法,相当于定义vuex中的mutations
const addCont = () => {
count.value++
}
// 5.4 定义修改响应式数据异步加的方法,相当于定义vuex中的actions
const asyncAddCount = () => {
setTimeout(()=>{
count.value++
}, 1000)
}
// 5.5 接收参数
const payloadCount = (num = 1) => {
count.value += num
}
// 6 对外暴露它们
return { count, squCount, addCont, asyncAddCount, payloadCount }
})
Use "CC BY-NC-SA 3.0 CN" for licensing