前言
在2021年,vue3
发布了正式版本,经过一年的维护已经越来越稳定,在对TS
的支持、Composition API清晰的语法结构、利用Proxy API对数据代理实现等特点的加持下,vue3
的语法也逐渐被越来越多的开发者所接受。本篇文章就是介绍vue3 setup
语法糖在具体使用过程中所涉及的api语法。
一、Vue setup 定义变量的写法
1、使用ref定义变量
<script lang="ts" setup>
import { ref, Ref } from 'vue'
// 可以用来定义简单数据类型
const strValue = ref('')
// 赋值
strValue.value = '张三'
// 使用ref定义复杂数据类型
// 定义对象类型
let activeMenu: Ref<any> = ref({
menuColor: '#ffcc80',
icon: 'icon-home',
path: '/index',
})
// 赋值
activeMenu.value = {
menuColor: '#ce93d8',
icon: 'icon-wode',
path: '/verify',
}
// 定义any[]类型
let menuList: Ref<any[]> = ref([
{
menuColor: '#ffcc80',
icon: 'icon-home',
path: '/index',
},
{
menuColor: '#81d4fa',
icon: 'icon-xiaoxi1',
path: '/verify',
},
])
// 赋值
menuList.value = [
{
menuColor: '#ffcc80',
icon: 'icon-home',
path: '/index',
},
{
menuColor: '#81d4fa',
icon: 'icon-xiaoxi1',
path: '/verify',
},
]
// ts 声明变量类型
interface routerItme {
name: string // 路由名称
router: string // 路由跳转路径
icon: string // 导航栏图标
menuStatus: boolean // 该路由是否在导航栏显示
parentId: any // 该路由父级id
sort: string // 该路由的排序
sign: string // 路由标识
componentPath: string // 组件在文件中的路径
children: routerItme[] // 子路由列表
id: string // 路由ID
}
// 使用ts变量创建相关类型
const routeList: Ref<routerItme[]> = ref([])
</script>
2、使用reactive 定义变量
<template>
<div>{{ pageObj.pageNum }} {{ pageObj.pageStr }}</div>
</template>
<script setup lang="ts">
import { onMounted, reactive } from 'vue'
let pageObj = reactive({
pageNum: 0,
pageStr: '默认',
})
onMounted(() => {
// (1)错误写法
// pageObj = {
// pageNum: 1,
// pageStr: '修改',
// }
// 改变了pageObj属性结构使其失去响应式
// console.log(pageObj) // {pageNum: 1, pageStr: '修改'}
// (2)错误写法
// pageObj = reactive({
// pageNum: 1,
// pageStr: '修改',
// })
// pageObj重新赋值改变数据的dom的绑定关系,其失去响应式
// console.log(pageObj) // Proxy {pageNum: 1, pageStr: '修改'}
// (3)正确写法
pageObj.pageNum = 1
pageObj.pageStr = '修改'
console.log(pageObj)
})
</script>
二、使用计算属性
<template>
{{screenWidth}}
</template>
<script lang="ts" setup>
import { computed } from 'vue'
// 当页面宽度改变时触发
const screenWidth = computed(() => {
return document.body.clientWidth
})
</script>
三、vue侦听器
watchEffect
与 watch
的区别
1、 watchEffect
侦听器
<template></template>
<script setup lang="ts">
import { onMounted, reactive, ref, watchEffect, watchPostEffect} from 'vue'
onMounted(() => {
count.value = 1
state.count = 3
console.log(state.count)
})
const count = ref(0)
const state = reactive({ count: 2 })
// reactive 定义的变量,普通的watchEffect无法监听
watchEffect(() => {
// 先打印出0 后打印出3
// -> logs 0
// -> logs 1
console.log(count.value)
// 报错
// console.log(state.count)
})
// ref 和 reactive 中定义的变量都可以监听
watchPostEffect(() => {
// 先打印出0 后打印出3
// -> logs 0
// -> logs 1
console.log(count.value, 'count.value')
// 先打印出2 后打印出3
// -> logs 2
// -> logs 3
console.log(state.count, 'state.count')
})
</script>
2、 watch
侦听器
(1) 侦听单一数据源
<template></template>
<script setup lang="ts">
import { onMounted, reactive, ref, watch } from 'vue'
onMounted(() => {
state.count = 1
count.value = 3
})
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/*
* count 修改后的数据
* prevCount 修改前的数据
*/
console.log(count, prevCount) // -> 1 0
}
)
// 直接侦听一个 ref
const count = ref(2)
watch(count, (count, prevCount) => {
/*
* count 修改后的数据
* prevCount 修改前的数据
*/
console.log(count, prevCount) // -> 3 2
})
</script>
(2)侦听多个数据源
<template></template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
const firstName = ref('')
const lastName = ref('')
const firstIndex = ref(0)
const lastIndex = ref(2)
onMounted(() => {
firstName.value = 'John'
lastName.value = 'Smith'
})
// 如果你在同一个函数里同时改变这些被侦听的来源,侦听器仍只会执行一次, 分别改变时,则会执行多次
watch([firstName, lastName], (newValues, prevValues) => {
// -> ['John', 'Smith'] ['', '']
console.log(newValues, prevValues)
})
watch([firstIndex, lastIndex], (newValues, prevValues) => {
// -> [1, 2] [0, 2]
// -> [1, 3] [1, 2]
console.log(newValues, prevValues)
})
firstIndex.value = 1
lastIndex.value = 3
</script>
(3) 侦听响应式对象
<template></template>
<script setup lang="ts">
import { onMounted, reactive, ref, watch } from 'vue'
const numbers = reactive([1, 2, 3, 4])
watch(
() => [...numbers],
(numbers, prevNumbers) => {
// -> [1,2,3,4,5] [1,2,3,4]
console.log(numbers, prevNumbers)
}
)
numbers.push(5)
</script>
(4) deep 深度监听
<template></template>
<script setup lang="ts">
import { reactive, watch } from 'vue'
const state = reactive({
attributes: {
name: '',
},
})
watch(
() => state,
(state, prevState) => {
// 监听到了name的修改 ,却无法获取正确的prevState值
// -> 'deep' 'Alex' 'Alex'
console.log('deep', state.attributes.name, prevState.attributes.name)
},
{ deep: true }
)
state.attributes.name = 'Alex'
</script>
(5)使用lodash深拷贝监听
<template></template>
<script setup lang="ts">
import { reactive, watch } from 'vue'
import _ from 'lodash'
const state = reactive({
attributes: {
name: 'name',
},
})
watch(
() => _.cloneDeep(state),
(state, prevState) => {
// -> 'lodash' 'Alex' 'name'
console.log('lodash', state.attributes.name, prevState.attributes.name)
}
)
state.attributes.name = 'Alex'
</script>
为了完全侦听深度嵌套的对象和数组,可能需要对值进行深拷贝。这可以通过诸如 lodash.cloneDeep 这样的实用工具来实现。
四、生命周期
选项式 API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
选项式 API | Hook inside setup |
---|---|
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
------------------- | --------------------- |
<script lang="ts" setup>
import { onMounted } from 'vue'
// 使用生命周期
onMounted(() => {})
</script>
五、定义以及使用组件的方法
1、使用vue setup语法糖 -- 推荐使用
// setup 语法糖
<template>
<ElCard>
<ElButton>{{ buttonFont }}</ElButton>
</ElCard>
</template>
<script setup lang="ts">
import { ElButton, ElCard } from 'element-plus'
import { ref } from 'vue'
// 控制当前的状态
let buttonFont = ref('buttonFont')
</script>
2、使用jsx返回HTML模板
1、下载npm i @vitejs/plugin-vue-jsx
插件
2、在vite.config.ts 中配置
import { defineConfig, UserConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ElementPlus from 'unplugin-element-plus/vite'
import vueJsx from '@vitejs/plugin-vue-jsx' // plugin-vue-jsx 的插件
export default defineConfig(({ mode }: UserConfig): UserConfig => {
return {
plugins: [
vue(),
ElementPlus(),
vueJsx({
// options are passed on to @vue/babel-plugin-jsx
}), // 挂载插件
]
}
})
3、创建一个demo.jsx文件
import { defineComponent } from 'vue'
import { ElButton, ElCard } from 'element-plus'
export default defineComponent({
render() {
return (
<ElCard>
<ElButton>demo</ElButton>
</ElCard>
)
},
})
3、使用setup返回HTML模板--不推荐
<template></template>
<script lang="ts">
import { h, ref } from 'vue'
export default {
setup() {
return () => h('div', 'demo') // <div>demo</div>
},
}
</script>
六、组件之间的通信
1、父组件向子组件传值 - 通过 defineProps
传值
父组件
<template>
<myheader :collapsed="collapsed"></myheader>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import myheader from './header.vue'
// 控制当前菜单的状态
const collapsed = ref(false)
</script>
子组件
<template>
<!-- 在HTML中获取父组件的传值 -->
{{collapsed}}
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
const props = defineProps({
collapsed: {
type: Boolean,
default: false,
},
})
// 在js中获取父组件的传值
console.log(props.collapsed) // false
</script>
2、子组件向父组件传值 - 通过 defineEmits
传值
子组件
<script lang="ts" setup>
import { defineEmits} from 'vue'
const emit = defineEmits(['changeCollapsed'])
const changeCollapsed = () => {
emit('changeCollapsed', true)
}
</script>
父组件
<template>
<myheader @change-collapsed="changeCollapsed"></myheader>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import myheader from './header.vue'
// 控制当前的状态
let collapsed = ref(false)
const changeCollapsed = (status: boolean) => {
// 获取子件的传值
console.log(status); // true
collapsed.value = status
}
</script>
3、dom查找:父组件获取子组件上的属性 - 通过 defineExpose
导出 ref 获取
子组件
<template></template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
const childNum = ref(2)
/**
* 不使用defineExpose 将无法通过模板ref或者$parent获取 childNum 的值
*/
defineExpose({
childNum,
})
</script>
父组件
<template>
<child ref="childRef" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import child from '@/components/child.vue'
const childRef = ref()
onMounted(() => {
/**
* 没有使用defineExpose 暴露时值为undefined
* 使用之后获取子组件上的属性和方法正常 为 2
*/
console.log(childRef.value.childNum) // 2
})
</script>
4、dom查找:子组件获取上父组件的属性 - 通过 defineExpose
导出 getCurrentInstance 获取
getCurrentInstance
支持访问内部组件实例, vue 官方强烈反对在应用的代码中使用 getCurrentInstance
,本文仅是做演示使用。
子组件
<template></template>
<script setup lang="ts">
import { onMounted, getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
onMounted(() => {
console.log(instance?.parent?.exposeProxy?.parentNum) // 1
})
</script>
父组件
<template>
<child />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import child from './child.vue'
const parentNum = ref(1)
/**
* 不使用defineExpose 将无法通过模板ref或者$parent获取 childNum 的值
*/
defineExpose({
parentNum,
})
</script>
5、 通过provide和inject组合通信
祖父级组件
<template>
<father />
</template>
<script setup lang="ts">
import father from './father.vue'
import { provide } from 'vue'
provide('name', 'grandfather')
</script>
父级组件
<template>
<child />
</template>
<script setup lang="ts">
import child from './child.vue'
</script>
子级组件
<template>
<div>我是子组件</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
const name = inject('name')
console.log(name) // -> grandfather
</script>
6、使用VueX进行状态管理
展示效果的页面
<template>
<div class="text_box">
<!-- // 从state 中 读取属性 -->
<div>{{ store.state.stateText }}</div>
<!-- // 从getters 中 读取属性 -->
<div>{{ store.getters.doneTodos }}</div>
<!-- // 同步修改 store 中的属性 -->
<input @input="inputMutationsText" v-model="mutationsText" />
<div>{{ store.state.mutationsText }}</div>
<!-- // 异步修改 store 中的属性 -->
<input @input="inputActionsText" v-model="actionsText" />
<div>{{ store.state.actionsText }}</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, provide, ref, reactive } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
//1、 从state 中 读取属性
console.log(store.state.stateText) // -> '001'
//2、 Getter 相当于 store 的计算属性
console.log(JSON.stringify(store.getters.doneTodos))
// -> [{"id":1,"text":"...","done":true}]
/**
* mutations与actions区别
* mutations与actions其本质是没有任何区别的,
* mutations中的函数可以写成异步,
* actions中的函数也可以直接去改变state中的值 -- 不建议这么做
* 在mutations 中写同步函数 在actions 中写异步函数,是VueX在写法上的规定
*/
//3、 同步修改 store 中的属性 -- mutations
let mutationsText = ''
const inputMutationsText = (event: any) => {
// console.log(event.target.value)
store.commit('setMutationsText', event.target.value)
console.log(store.state.mutationsText) // -> 你输入的值
}
//4、 异步修改 store 中的属性 -- actions
let actionsText = ''
const inputActionsText = (event: any) => {
store.dispatch('getActionsText', event.target.value) // -> 你输入的值
}
// 5、 Vuex 允许我们将 store 分割成模块(module) -- Module
console.log(store.state.module.stateText) // -> modulesText
store.commit('module/setMutationsText', 'moduleMutations')
console.log(store.state.module.mutationsText) // -> moduleMutations
store.dispatch('module/getActionsText', 'moduleActions')
console.log(store.state.module.actionsText) // -> moduleActions
</script>
<style lang="scss" scoped>
.text_box {
margin: 30px;
}
</style>
store主文件
import { createStore } from 'vuex'
import module from './modules/module'
export default createStore({
modules: {
module,
},
state: {
stateText: '001',
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false },
],
mutationsText: 'mutationsText',
actionsText: 'actionsText',
},
getters: {
doneTodos: (state) => {
return state.todos.filter((todo) => todo.done)
},
},
mutations: {
setMutationsText(state: any, text) {
state.mutationsText = text
},
setActionsText(state: any, text) {
state.actionsText = text
},
},
actions: {
getActionsText({ commit, state }: any, actionsText) {
commit('setActionsText', actionsText)
},
},
})
module模块
const module = {
namespaced: true,
state: {
stateText: 'modulesText',
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false },
],
mutationsText: 'mutationsText',
actionsText: 'actionsText',
},
getters: {
doneTodos: (state: any) => {
return state.todos.filter((todo: any) => todo.done)
},
},
mutations: {
setMutationsText(state: any, text: string) {
state.mutationsText = text
},
setActionsText(state: any, text: string) {
state.actionsText = text
},
},
actions: {
getActionsText({ commit, state }: any, actionsText: string) {
commit('setActionsText', actionsText)
},
},
}
export default module
7、通过pinia进行状态管理
(1)、下载pinia
yarn add pinia
or npm install pinia
(2)在main
文件中挂载pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
七、在vue3 setup中使用ref的方法
1、基础的ref 获取dom实例方法
<template>
<div ref="page"></div>
</template>
<style lang="scss" scoped></style>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
// 定义一个ref声明名称相同和变量名
const page = ref()
onMounted(() => {
// 获取到ref为page的实例
console.log(page.value, 'page.value')
})
</script>
2、在v-for循环中获取ref dom实例的方法
<template>
<div>
<div
:ref="
(el) => {
if (el) itemArray[i] = el
}
"
v-for="(item, i) in 6"
:key="i"
></div>
</div>
</template>
<style lang="scss" scoped></style>
<script setup lang="ts">
import { onMounted, Ref, ref } from 'vue'
// 申明存储ref实例的数组
let itemArray: Ref<any[]> = ref([])
onMounted(() => {
console.log(itemArray.value, 'itemArray.value')
})
</script>
3、通过ref 获取子组件上的属性或方法 defineExpose -- setup语法糖
使用 <script setup>
的组件是默认关闭的,也即通过模板 ref 或者 $parent
链获取到的组件的公开实例,不会暴露任何在 <script setup>
中声明的绑定。
为了在 <script setup>
组件中明确要暴露出去的属性,使用 defineExpose
编译器宏:
子组件
<template></template>
<script setup lang="ts">
import { ref } from 'vue'
const childNum = ref(2)
/**
* 不使用defineExpose 将无法通过模板ref或者$parent获取 childNum 的值
*/
defineExpose({
childNum,
})
</script>
父组件
<template>
<child ref="childRef" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import child from '@/components/child.vue'
const childRef = ref()
onMounted(() => {
/**
* 没有使用defineExpose 暴露时值为undefined
* 使用之后获取子组件上的属性和方法正常 为 2
*/
console.log(childRef.value.childNum)
})
</script>
八、单文件组件样式特性
1、深度选择器
在<style scoped>
中父组件可以通过使用 :deep()
改变子组件中的样式
<style scoped>
:deep(.red_box) {
color: red;
}
</style>
2、<style module>
<style module>
标签会被编译为 CSS Modules 并且将生成的 CSS 类作为 $style
对象的键暴露给组件:
(1) 未命名样式注入
<template>
<p :class="$style.red">
This should be red
</p>
</template>
<style module>
.red {
color: red;
}
</style>
(2) 自定义注入名称
<template>
<p :class="classes.red">red</p>
</template>
<style module="classes">
.red {
color: red;
}
</style>
(3) 与组合式 API 一同使用
注入的类可以通过 useCssModule
API 在 setup()
和 <script setup>
中使用。对于使用了自定义注入名称的 <style module>
模块,useCssModule
接收一个对应的 module
attribute 值作为第一个参数。
// 默认, 返回 <style module> 中的类
useCssModule()
// 命名, 返回 <style module="classes"> 中的类
useCssModule('classes')
3、状态驱动的动态 CSS
<template>
<p>hello</p>
</template>
<script setup>
const theme = {
color: 'red'
}
</script>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
九、Vue Router
(1)、下载Vue Router
npm install vue-router@4
(2)、创建一个可以被 Vue 应用程序使用的路由实例
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
let routes: RouteRecordRaw[] = [
{
path: '/login',
component: () => import('@/view/login/login.vue'),
},
]
const router = createRouter({
history: createWebHistory(),
routes: routes,
})
export default router
(3)、在main
文件中挂载VueRouter
import { createApp } from 'vue'
import App from './App.vue'
import router from '@/router/index'
const app = createApp(App)
app.use(router)
app.mount('#app')