vue3中新增了组合式API,本文讲解组合式API setup 的使用
关于setup 的出现和 vue3 js setup 的使用,笔者已经在2022年的文章中说明,这里不再赘述,需要的朋友可以阅读:《vue3 setup 使用教程》
目录
1、新建vue3 ts 项目
在电脑上的空白文件目录下打开 cmd 窗口,执行下面命令
npm create vue@latest
输入y 按回车
输入项目名 vue3-ts-project
是否使用 ts 语法,选择 是
是否启用 JSX 支持,这个不影响学习,是或否都行,笔者选择 是
是否引入vue router,选择 是
是否使用 Pinia 用于状态管理,选择 是
是否引入 Vitest 用于单元测试,选择 是
是否要引入一款端到端测试工具,选择 不需要
是否引入 ESLint 用于代码质量检测,选择 是
是否引入 Prettier 用于代码格式化,选择 否
创建完成
进入项目目录,安装依赖
cd vue3-ts-project
安装依赖
npm install
依赖安装完成
依赖安装完成后,使用 VS Code 打开项目
执行下面命令运行项目
npm run dev
浏览器访问:http://localhost:5173/
出现这个页面说明项目创建成功
2、响应式变量
先将 main.css 中的样式替换为下面代码
@import './base.css';
#app {
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
再App.vue 原来的内容全部删除,替换为下面代码
<template>
<div>
<p>{{name}}</p>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
let name = '年少相逢意气豪,千金买醉度良宵'
function change() {
name = '调筝人去秋风冷,一院梧桐影自摇'
}
</script>
<style scoped>
</style>
运行效果
发现点击按钮,变量不能修改,这是因为默认的 name 不再像vue2 一样默认就是响应式变量,需要使用 ref 或 reactive 函数转换一下,看下面代码
使用 ref
<template>
<div>
<p>{{name}}</p>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref , reactive } from 'vue'
let name = ref('年少相逢意气豪,千金买醉度良宵')
function change() {
name.value = '调筝人去秋风冷,一院梧桐影自摇'
}
</script>
<style scoped>
</style>
运行效果
使用 reactive
<template>
<div>
<p>{{nameObj.name}}</p>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref , reactive } from 'vue'
let nameObj = reactive({name:'一客若蜀士,相逢意气豪'})
function change() {
nameObj.name = '偶谈唐夹寨,遂及楚成皋'
}
</script>
<style scoped>
</style>
运行效果
注意 ref 和 reactive 的区别
ref 一般处理基本类型;reactive 处理复杂的数据类型
3、v-model 双向数据绑定
<template>
<div>
<p>{{name}}</p>
<input type="text" v-model="name" />
</div>
</template>
<script setup lang="ts">
import { ref , reactive } from 'vue'
let name = ref('顿洗风尘恶,都忘箠辔劳')
</script>
<style scoped>
</style>
运行效果
4、计算属性 computed
使用 computed 可实现计算
4.1、基本使用
<template>
<div>
<p>{{numberOfOnlineUsers}}</p>
<button @click="add">添加在线人数</button>
</div>
</template>
<script setup lang="ts">
import { ref , reactive, computed } from 'vue'
let name = ref('顿洗风尘恶,都忘箠辔劳')
let users = ref([])
const numberOfOnlineUsers = computed(()=>{
return users.value.length > 0 ? '当前在线人数'+ users.value.length : '无人在线'
})
const add = ()=>{
let date = new Date()
users.value.push(date.getTime())
}
</script>
<style scoped>
</style>
运行效果
4.2、可写的计算属性
计算属性默认是只读的,可以通过同时提供 getter 和 setter 来创建
<template>
<div>
<p>{{bookInfo}}</p>
<button @click="add">修改书籍信息</button>
</div>
</template>
<script setup lang="ts">
import { ref , reactive, computed } from 'vue'
const bookName = ref('三国演义')
const authorName = ref('罗贯中')
const bookInfo = computed({
// getter
get() {
return bookName.value + ' ' + authorName.value
},
// setter
set(newValue) {
let tmp = newValue.split(' ')
bookName.value = tmp[0]
authorName.value = tmp[1]
}
})
const add = ()=>{
bookInfo.value = '红楼梦 曹雪芹'
}
</script>
<style scoped>
</style>
运行效果
5、侦听器 watch
5.1、基本使用
<template>
<div>
<input type="text" v-model="name">
</div>
</template>
<script setup lang="ts">
import { ref , reactive, watch } from 'vue'
let name = ref('三国演义')
watch(name, (newValue, oldValue)=>{
console.log(oldValue);
console.log(newValue);
})
</script>
<style scoped>
</style>
运行效果
5.2、深层侦听
深层侦听器需要添加 deep: true 属性。默认直接给 watch()
传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发,相比之下,一个返回响应式对象的函数,只有在返回不同的对象时,才会触发回调
看下面代码
<template>
<div>
<p>{{book.name}}</p>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref , reactive, watch } from 'vue'
let book = reactive({name:'西游记', author: '施耐庵'})
watch(()=>book, (newValue)=>{
console.log(newValue);
}
)
const change = ()=> {
book.name = '道德经'
}
</script>
<style scoped>
</style>
运行效果
可以看到没有触发侦听
添加 深层侦听 后看下面代码
<template>
<div>
<p>{{book.name}}</p>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref , reactive, watch } from 'vue'
let book = reactive({name:'西游记', author: '施耐庵'})
watch(()=>book, (newValue)=>{
console.log(newValue);
},
{ deep: true }
)
const change = ()=> {
book.name = '道德经'
}
</script>
<style scoped>
</style>
运行效果
5.3、即时回调
watch
默认是懒执行的,只有当数据源变化时,才会执行回调 。如果想在创建侦听器时,立即执行一遍回调,可以通过传入 immediate: true
选项来强制侦听器的回调立即执行
<template>
<div>
<input type="text" v-model="name">
</div>
</template>
<script setup lang="ts">
import { ref , reactive, watch } from 'vue'
let name = ref('三国演义')
watch(name, (newValue, oldValue)=>{
console.log(oldValue);
console.log(newValue);
},
{ immediate: true }
)
</script>
<style scoped>
</style>
运行效果
5.4、一次性侦听
默认侦听器是每当被侦听源发生变化时,侦听器的回调就会执行。如果想让回调只在源变化时触发一次,可以使用 once: true
选项
<template>
<div>
<input type="text" v-model="name">
</div>
</template>
<script setup lang="ts">
import { ref , reactive, watch } from 'vue'
let name = ref('三国演义')
watch(name, (newValue, oldValue)=>{
console.log(oldValue);
console.log(newValue);
},
{ once: true }
)
</script>
<style scoped>
</style>
运行效果
5.5、watchEffect()
当侦听器的回调使用与源完全相同的响应式状态时,可以使用 watchEffect 简化代码
先看 watch 的代码
<template>
<div>
<input type="text" v-model="name">
</div>
</template>
<script setup lang="ts">
import { ref , reactive, watch, watchEffect } from 'vue'
let name = ref('三国演义')
watch(name, ()=>{
httpGetRequest(name.value)
},
{ immediate: true }
)
//模拟发送请求
function httpGetRequest(username:string) {
console.log('发送请求:' + username);
}
</script>
<style scoped>
</style>
运行效果
使用 watchEffect 简化上面 watch 代码
<template>
<div>
<input type="text" v-model="name">
</div>
</template>
<script setup lang="ts">
import { ref , reactive, watch, watchEffect } from 'vue'
let name = ref('水浒传')
// watch(name, ()=>{
// httpGetRequest(name.value)
// },
// { immediate: true }
// )
watchEffect(()=>{
httpGetRequest(name.value)
})
//模拟发送请求
function httpGetRequest(username:string) {
console.log('发送请求:' + username);
}
</script>
<style scoped>
</style>
运行效果
6、模板引用 ref
ref
是一个特殊的 attribute,它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用
<template>
<div>
<input ref="inputRef" type="text">
<button @click="get">获取</button>
</div>
</template>
<script setup lang="ts">
import { ref , reactive, watch } from 'vue'
const inputRef = ref(null)
const get = ()=> {
console.log(inputRef.value);
inputRef.value.focus()
}
</script>
<style scoped>
</style>
运行效果
7、类与样式绑定
7.1、绑定对象
<template>
<div>
<div :class="{ active: isActive }">
<p>忽匆匆,三月桃花随水转。</p>
<p>飘零零,二月风筝线儿断。</p>
<p>噫,郎呀郎,</p>
<p>巴不得下一世,你为女来我做男。</p>
</div>
<br>
<div :class="classObject">
一朝别后,二地相悬。
</div>
<button @click="change">改变</button>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
let isActive = ref(true)
const classObject = reactive({
active: true,
'text-primary': true
})
const change = ()=> {
isActive.value = false
classObject.active = false
classObject['text-primary'] = false
}
</script>
<style scoped>
.active {
background: #f56c6c;
}
.text-primary {
color: #ffff;
}
</style>
运行效果
7.2、绑定数组
<template>
<div>
<div :class="[activeClass, primaryClass]">
万语千言说不完,百无聊赖,十依栏杆。
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
const activeClass = ref('active')
const primaryClass = ref('text-primary')
</script>
<style scoped>
.active {
background: #f56c6c;
}
.text-primary {
color: #ffff;
}
</style>
运行效果
7.3、三元表达式
<template>
<div>
<!-- 三目表达式单独使用 -->
<div :class="[isActive ? activeClass : primaryClass]">
<p>六月三伏天,人人摇扇我心寒。</p>
<p>五月石榴红似火,偏遇阵阵冷雨浇花端。</p>
</div>
<br>
<!-- 三目表达式和其他样式一起使用 -->
<div :class="[isActive ? successBackgroundClass : primaryBackgroundClass, textClass]">
<p>六月三伏天,人人摇扇我心寒。</p>
<p>五月石榴红似火,偏遇阵阵冷雨浇花端。</p>
</div>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
let isActive = ref(true)
const activeClass = ref('active')
const primaryClass = ref('text-primary')
const textClass = ref('text-class')
const successBackgroundClass = ref('success-background')
const primaryBackgroundClass = ref('primary-background')
const change = ()=> {
isActive.value = false
}
</script>
<style scoped>
.active {
color: #67c23a;
}
.text-primary {
color: #409eff;
}
.text-class {
color: #ffff;
}
.success-background {
background: #67c23a;
}
.primary-background {
background: #409eff;
}
</style>
运行效果
7.4、绑定内联样式
<template>
<div>
<!-- 绑定对象 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
十里长亭望眼欲穿。百思想,千系念,万般无奈把郎怨。
</div>
<br>
<div :style="styleObject">
七弦琴无心弹,八行书无可传。
</div>
<br>
<!-- 绑定数组 -->
<div :style="[styleObject, backStyles]">
四月枇杷未黄,我欲对镜心意乱。
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
const activeColor = ref('#67c23a')
const fontSize = ref(30)
const styleObject = reactive({
color: '#409eff',
fontSize: '18px'
})
const backStyles = reactive({
background: 'black'
})
</script>
<style scoped>
</style>
运行效果
8、生命周期钩子
官网 实例生命周期的图表
在setup 中引入生命周期函数使用
<template>
<div>
<p>{{name}}</p>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted,onUpdated } from 'vue'
let name = ref('阅尽天涯离别苦,不道归来,零落花如许。')
const change = ()=> {
name.value = '花底相看无一语,绿窗春与天俱莫。'
}
onMounted(() => {
console.log('挂载完成')
})
onUpdated(()=>{
console.log('更新完成');
})
</script>
<style scoped>
</style>
运行效果
更多生命周期函数可以看官网文档:https://cn.vuejs.org/api/composition-api-lifecycle.html
9、父子组件通信
9.1、基本使用
在 components 目录下定义子组件 Book.vue
代码如下
<template>
<div>
<p>书名:{{bookName}}</p>
<p>作者:{{author}}</p>
<p>价格:{{price}}</p>
</div>
<button @click="buy">下单</button>
<button @click="cart">加入购物车</button>
</template>
<script setup lang="ts">
//父传子定义props
const props = defineProps({
bookName: String,
author: {
type: String,
//必传
required: true
},
price: Number
})
//定义子传父事件
const emit = defineEmits(['buyEmit', 'cartEmit'])
const buy = ()=> {
emit('buyEmit', props.bookName)
}
const cart = ()=> {
emit('cartEmit')
}
</script>
在 App.vue 中引入 Book.vue
<template>
<div>
<Book :bookName="name" :author="author" :price="price" @buyEmit="buy" @cartEmit="cart" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Book from '@/components/Book.vue'
let name = ref('稼轩长短句')
let author = ref('辛弃疾')
let price = ref(50)
const buy = (bookName)=> {
alert(bookName)
}
const cart = ()=> {
alert('点击购物车')
}
</script>
<style scoped>
</style>
运行效果
9.2、搭配 TypeScript 使用
<template>
<div>
<p>书名:{{bookName}}</p>
<p>作者:{{author}}</p>
<p>价格:{{price}}</p>
</div>
<button @click="buy">下单</button>
<button @click="cart">加入购物车</button>
</template>
<script setup lang="ts">
//父传子定义props
const props = defineProps({
bookName: String,
author: {
type: String,
//必传
required: true
},
price: Number
})
//定义子传父事件
const emit = defineEmits<{
(e: 'buyEmit', bookName:String):void
(e: 'cartEmit'):void
}>()
const buy = ()=> {
emit('buyEmit', props.bookName)
}
const cart = ()=> {
emit('cartEmit')
}
</script>
9.3、事件校验
<template>
<div>
<p>书名:{{bookName}}</p>
<p>作者:{{author}}</p>
<p>价格:{{price}}</p>
</div>
<button @click="buy">下单</button>
<button @click="cart">加入购物车</button>
</template>
<script setup lang="ts">
//父传子定义props
const props = defineProps({
bookName: String,
author: {
type: String,
//必传
required: true
},
price: Number
})
//定义子传父事件
const emit = defineEmits({
//校验 buyEmit 事件
buyEmit:(bookName:String) => {
if(bookName.length > 1) {
console.log('buyEmit error');
return false
} else {
return true
}
},
//没有校验
cartEmit: null
})
const buy = ()=> {
emit('buyEmit', props.bookName)
}
const cart = ()=> {
emit('cartEmit')
}
</script>
运行效果
10、依赖注入
在 App.vue 中提供使用 provide
<template>
<div>
<Book :bookName="name" :author="author" :price="price" />
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import Book from '@/components/Book.vue'
let name = ref('史记')
let author = ref('司马迁')
let price = ref(399)
provide(/* 注入名 */ 'bookShop', /* 值 */ '开心图书商店')
</script>
<style scoped>
</style>
在子组件 Book.vue 中注入使用 inject
<template>
<div>
<h1>{{bookShop}}</h1>
<p>书名:{{bookName}}</p>
<p>作者:{{author}}</p>
<p>价格:{{price}}</p>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
const bookShop = inject('bookShop')
//父传子定义props
const props = defineProps({
bookName: String,
author: {
type: String,
//必传
required: true
},
price: Number
})
</script>
运行效果
依赖注入更详细讲解请阅读笔者文章《vue 依赖注入使用教程》
至此完