setup语法糖以及pinia的使用

vue3 compositionAPI 相关 (vue3.2 setup语法糖)

1、文件结构

vue2中限制template只能有一个根元素,vue3没有限制

2、data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script setup>
import { reactive, ref, toRefs } from 'vue'
// ref声明响应式数据,用于声明基本数据类型
const name = ref('YoLin')
// 修改
name.value = 'Deng'
// reactive声明响应式数据,用于声明引用数据类型
const state = reactive({
name: 'YoLin',
sex: '男'
})
// 修改
state.name = 'Deng'

// 使用toRefs解构
const { name, sex } = toRefs(state)
// 在template中可直接使用{{ name }}、{{ sex }}
</script>

3、method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
// 调用方法
<button @click='changeName'>按钮</button>
</template>

<script setup>
import { reactive } from 'vue'

const state = reactive({
name: 'YoLin'
})

// 声明method方法
const changeName = () => {
state.name = 'Deng'
}
</script>

4、computed

1
2
3
4
5
6
7
8
<script setup>
import { computed, ref } from 'vue'

let count = ref(0)
const doubuleCount = computed(() => {
return count.value * 2
})
</script>

5、watch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import { watch, reactive } from 'vue'

const state = reactive({
count: 1
})
const changeCount = () => {
state.count = state.count * 2
}
watch(() => state.count, (newVal, oldVal) => {
console.log(newVal)
}, {
immediate: true, //立刻执行
deep: true // 深度监听
})
</script>

6、props 父传子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
子组件
<template>
<span>{{props.name}}</span>
// 可省略【props.】
<span>{{name}}</span>
</template>

<script setup>
// import { defineProps } from 'vue'
// defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineProps: true】

// 声明props
const props = defineProps({
name: {
type: String,
default: ''
}
})
</script>

父组件
引入子组件,组件会自动注册
<template>
<child name='Jerry'/>
</template>

<script setup>
// 引入子组件
import child from './child.vue'
</script>

7、emit 子传父

子组件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<span>{{props.name}}</span>
// 可省略【props.】
<span>{{name}}</span>
<button @click='changeName'>更名</button>
</template>

<script setup>
// import { defineEmits, defineProps } from 'vue'
// defineEmits和defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】

// 声明props
const props = defineProps({
name: {
type: String,
default: ''
}
})
// 声明事件
const emit = defineEmits(['updateName'])

const changeName = () => {
// 执行
emit('updateName', 'Tom')
}
</script>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<child :name='state.name' @updateName='updateName'/>
</template>

<script setup>
import { reactive } from 'vue'
// 引入子组件
import child from './child.vue'

const state = reactive({
name: 'YoLin'
})

// 接收子组件触发的方法
const updateName = (name) => {
state.name = name
}
</script>

8、v-model

支持绑定多个v-model,v-model 是v-model:modelValue的简写

绑定其他字段,如v-model:name

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<span @click="changeInfo">我叫{{ modelValue }},今年{{ age }}岁</span>
</template>

<script setup>
// import { defineEmits, defineProps } from 'vue'
// defineEmits和defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】

defineProps({
modelValue: String,
age: Number
})

const emit = defineEmits(['update:modelValue', 'update:age'])
const changeInfo = () => {
// 触发父组件值更新
emit('update:modelValue', 'Tom')
emit('update:age', 30)
}
</script>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
// v-model:modelValue简写为v-model
// 可绑定多个v-model
<child
v-model="state.name"
v-model:age="state.age"
/>
</template>

<script setup>
import { reactive } from 'vue'
// 引入子组件
import child from './child.vue'

const state = reactive({
name: 'Jerry',
age: 20
})
</script>

9、nextTick

1
2
3
4
5
6
7
<script setup>
import { nextTick } from 'vue'

nextTick(() => {
// ...
})
</script>

10、子组件ref变量和defineExpose

1、在标准的组件写法中,子组件的数据是默认隐式暴露给父组件的,但在script-setup模式下,所以的数据只是默认return给template使用,不会暴露到外部组件,所以父组件无法通过挂载ref变量来获取子组件的数据

2、如果想要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由defineExpose来完成

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<span>{{state.name}}</span>
</template>

<script setup>
import { reactive, toRefs } from 'vue'
// defineExpose无需引入
// import { defineExpose, reactive, toRefs } from 'vue'

// 声明state
const state = reactive({
name: 'YoLin'
})

// 将方法、变量暴露给父组件使用,父组件才可通过ref API拿到子组件暴露的数据
defineExpose({
// 解构state
...toRefs(state),
// 声明方法
changeName () {
state.name = 'Deng'
}
})
</script>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<child ref='childRef'/>
</template>

<script setup>
import { ref, nextTick } from 'vue'
// 引入子组件
import child from './child.vue'

// 子组件ref(TypeScript语法)
const childRef = ref<InstanceType<typeof child>>()

// nextTick
nextTick(() => {
// 获取子组件name
console.log(childRef.value.name)
// 执行子组件方法
childRef.value.changeName()
})
</script>

11、插槽

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
// 匿名插槽
<slot/>
// 具名插槽
<slot name='title'/>
// 作用域插槽
<slot name="footer" :scope="state" />
</template>

<script setup>
import { useSlots, reactive } from 'vue'
const state = reactive({
name: '张三',
age: '25岁'
})

const slots = useSlots()
// 匿名插槽使用情况
const defaultSlot = reactive(slots.default && slots.default().length)
console.log(defaultSlot) // 1
// 具名插槽使用情况
const titleSlot = reactive(slots.title && slots.title().length)
console.log(titleSlot) // 3
</script>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<child>
// 匿名插槽
<span>我是默认插槽</span>
// 具名插槽
<template #title>
<h1>我是具名插槽</h1>
<h1>我是具名插槽</h1>
<h1>我是具名插槽</h1>
</template>
// 作用域插槽
<template #footer="{ scope }">
<footer>作用域插槽——姓名:{{ scope.name }},年龄{{ scope.age }}</footer>
</template>
</child>
</template>

<script setup>
// 引入子组件
import child from './child.vue'
</script>

12、路由useRoute和useRouter

1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { useRoute, useRouter } from 'vue-router'
// 必须先声明
const route = useRoute()
const router = useRouter()

// 路由信息
console.log(route.query)
// 路由跳转
router.push('/index')
</script>

13、路由导航守卫

1
2
3
4
5
6
7
8
9
10
11
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
//添加一个导航守卫,当组件将要离开的时候触发
onBeforeRouteLeave((to, from, next) => {
next()
})
// 添加一个导航守卫,当组件将要更新时触发
onBeforeRouteUpdate((to, from, next) =>{
next()
})
</script>

14、store

vuex

*Vue3 中的Vuex不再提供辅助函数写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script setup>
import { useStore } from 'vuex'
import { key } from '../store/index'

// 必须先声明调用
const store = useStore(key)

// 获取Vuex的state
store.state.xxx

// 触发mutations的方法
store.commit('fnName')

// 触发actions的方法
store.dispatch('fnName')

// 获取Getters
store.getters.xxx
</script>

15、pinia

Pinia 就是 Vuex 5Pinia 的优点:

  • 1、同时支持 Composition Api 和 Options api 的语法;

  • 2、去掉 mutations ,只有 state 、getters 和 actions ;

  • 3、不支持嵌套的模块,通过组合 store 来代替;

*4、更完善的typescript支持

  • 5、清晰、显式的代码拆分

安装:

1
2
3
4
5
# 使用 npm
npm install pinia

# 使用 yarn
yarn add pinia

main.js引入

1
2
3
4
5
6
7
import App from './App.vue'
import { createApp } from 'vue'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

配置store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { defineStore } from 'pinia'

// defineStore 调用后返回一个函数,调用该函数获得 Store 实体
export const useStore = defineStore({
// id: 必须,在所有 Store 中唯一
id: 'globalState',
// state: 返回对象的函数
state: () => ({
count: 1,
data: {
name: 'Jerry',
sex: '男'
}
}),
// getter 第一个参数是 state,是当前的状态,也可以使用 this 获取状态
// getter 中也可以访问其他的 getter,或者是其他的 Store
getters: {
// 通过 state 获取状态
doubleCount: (state) => state.count * 2,
// 通过 this 获取状态(注意this指向)
tripleCount() {
return this.count * 3
}
},
actions: {
updateData (newData, count) {
// 使用 this 直接修改
this.data = { ...newData }
this.count = count

// 使用 $patch 修改多个值
this.$patch({
data: { ...newData },
count
})
}
}
})

使用store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
// 获取 store 的 state
<p>姓名:{{store.data.name}}</p>
<p>性别:{{store.data.sex}}</p>

// 调用 mutations 方法 / 修改 store
<button @click='update'>修改用户信息</button>

// 获取 getter
<p>获取getter:{{store.doubleCount}}</p>
</template>

<script setup>
import { useStore } from '@store/store.js'
const store = useStore()

function update () {
// 通过 actions 定义的方法修改 state
store.updateData({ name: 'Tom', sex: '女' })

// 通过 store 直接修改
store.data = { name: 'Tom', sex: '女' }

// 同时改变多个状态
store.$patch((state) => {
state.data = { name: 'Tom', sex: '女' }
state.count = 2
})
}
</script>

<style lang="scss" scoped>
</style>

其他方法

1、$state可以替换store的整个state

2、重制状态, $reset() 方法将状态重置为初始值

1
2
3
const store = useStore()
store.$state = {}
store.$reset()

16、声明周期

通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。

Option API: beforeCreate、created、beforeMount、mounted、beforeUnmount(原vue2的beforeDestroy)、unmounted(原vue2的destroyed)、errorCaptured
activated、deactivated(在keep-alive标签下才有)

renderTracked(新增,状态跟踪, 每次渲染后重新收集响应式依赖)、renderTriggered(新增、状态触发, 每次触发页面重新渲染时自动执行), 新增的这两个用于调试使用

Composition API: setup中不需要beforeCreate 和 created, 其余只需要在Option API名称前面加上“on”

17、原型绑定与组件内使用

main.js

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)

// 获取原型
const prototype = app.config.globalProperties

// 绑定参数
prototype.name = 'Jerry'

组件内使用

1
2
3
4
5
6
7
8
9
<script setup>
import { getCurrentInstance } from 'vue'

// 获取原型
const { proxy } = getCurrentInstance()

// 输出
console.log(proxy.name)
</script>

18、V-bind() css 变量注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<span>Jerry</span>
</template>

<script setup>
import { ref, reactive } from 'vue'
// prop接收样式
const props = defineProps({
border: {
type: String,
default: '1px solid yellow'
}
})

// 常量声明样式
const background = 'red'

// 响应式数据声明样式
const color = ref('blue')
const style = reactive({
opacity: '0.8'
})
</script>

<style lang="scss" scoped>
span {
// 使用常量声明的样式
background: v-bind(background);

// 使用响应式数据声明的样式
color: v-bind(color);
opacity: v-bind('style.opacity');

// 使用prop接收的样式
border: v-bind('props.border');
}
</style>

19、Provide 和inject

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<child/>
</template>

<script setup>
import { provide } from 'vue'
import { ref, watch } from 'vue'
// 引入子组件
import child from './child.vue'

let name = ref('Jerry')
// 声明provide
provide('provideState', {
name,
changeName: () => {
name.value = 'Tom'
}
})

// 监听name改变
watch(name, () => {
console.log(`name变成了${name}`)
setTimeout(() => {
console.log(name.value) // Tom
}, 1000)
})
</script>

子组件

1
2
3
4
5
6
7
8
<script setup>
import { inject } from 'vue'
// 注入
const provideState = inject('provideState')

// 子组件触发name改变
provideState.changeName()
</script>

20、对await 的支持

不必再配合 async 就可以直接使用 await 了,这种情况下,组件的 setup 会自动变成 async setup

1
2
3
<script setup>
const post = await fetch('/api').then(() => {})
</script>

21、定义组件的name

用单独的<script>块来定义

1
2
3
4
5
<script>
export default {
name: 'ComponentName',
}
</script>