- 起初 Vue3.0 暴露变量必须
return
出来,template
中才能使用;
- Vue3.2 中 只需要在
script
标签上加上 setup
属性,组件在编译的过程中代码运行的上下文是在 setup()
函数中,无需 return
,template
可直接使用。
- 本文章以
Vue2
的角度学习Vue3
的语法,让你快速理解Vue3的Composition Api
- 本文章第十四节为状态库
Pinia
的安装、使用讲解
文件结构
Vue2
中,<template>
标签中只能有一个根元素
,在Vue3
中没有此限制
1 2 3 4 5 6 7 8 9 10 11
| <template> // ... </template>
<script setup> </script>
<style lang="scss" scoped> // 支持CSS变量注入v-bind(color) </style>
|
data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup> import { reactive, ref, toRefs } from 'vue'
const name = ref('Jerry') name.value = 'Tom'
const state = reactive({ name: 'Jerry', sex: '男' }) state.name = 'Tom' const {name, sex} = toRefs(state) </script>
|
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: 'Jery' })
const changeName = () => { state.name = 'Tom' } </script>
|
computed
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup> import { computed, ref } from 'vue'
const count = ref(1)
const doubleCount = computed(() => { return count.value * 2 }) console.log(doubleCount.value) </script>
|
watch
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
| <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(state.count) console.log(`watch监听变化前的数据:${oldVal}`) console.log(`watch监听变化后的数据:${newVal}`) }, { immediate: true, deep: true } ) </script>
|
props父传子
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <span>{{props.name}}</span> // 可省略【props.】 <span>{{name}}</span> </template>
<script setup>
const props = defineProps({ name: { type: String, default: '' } }) </script>
|
父组件
引入子组件,组件会自动注册
1 2 3 4 5 6 7 8
| <template> <child name='Jerry'/> </template>
<script setup> import child from './child.vue' </script>
|
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> 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: 'Jerry' }) const updateName = (name) => { state.name = name } </script>
|
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>
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>
|
nextTick
1 2 3 4 5 6 7
| <script setup> import { nextTick } from 'vue' nextTick(() => { }) </script>
|
ref子组件实例和defineExpose
- 在标准组件写法里,子组件的数据都是默认隐式暴露给父组件的,但在
script-setup
模式下,所有数据只是默认 return 给 template
使用,不会暴露到组件外,所以父组件是无法直接通过挂载 ref 变量获取子组件的数据。
- 如果要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由
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'
const state = reactive({ name: 'Jerry' }) defineExpose({ ...toRefs(state), changeName () { state.name = 'Tom' } }) </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'
const childRef = ref<InstanceType<typeof child>>() nextTick(() => { console.log(childRef.value.name) childRef.value.changeName() }) </script>
|
获取多个子组件实例:在 v-for 中获取子组件实例
这种情况仅适用于 v-for 循环数是固定的情况
,因为如果 v-for 循环数
在初始化之后发生改变,那么就会导致 childRefs 再一次重复添加,childRefs 中会出现重复的子组件实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div v-for="item in 3" :key="item"> <child :ref='addChildRef'/> </div> </template>
<script setup> const childRefs = ref([]) const addChildRef = (el) => { childRefs.value.push(el) } </script>
|
获取多个子组件实例:动态 v-for 获取子组件实例
通过下标来向 childRefs 添加/修改,初始化之后,动态修改 v-for 循环数,会自动根据下标重新修改该下标对应的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <button @click='childNums++'></button> <div v-for="(item, i) in childNums" :key="item"> // 通过下标向 childRefs 动态添加子组件实例 <child :ref='(el) => childRefs[i] = el'/> </div> <button @click='childNums--'></button> </template>
<script setup> const childNums = ref(1) const childRefs = ref([]) </script> 复制代码
|
插槽slot
子组件
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) const titleSlot = reactive(slots.title && slots.title().length) console.log(titleSlot) </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>
|
路由useRoute和useRouter
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script setup> import { useRoute, useRouter } from 'vue-router' const route = useRoute() const router = useRouter() console.log(route.query)
router.push('/newPage') </script>
|
路由导航守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup> import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router' onBeforeRouteLeave((to, from, next) => { next() })
onBeforeRouteUpdate((to, from, next) => { next() }) </script>
|
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) store.state.xxx
store.commit('fnName')
store.dispatch('fnName')
store.getters.xxx </script>
|
Pinia
*全面拥抱 Pinia
吧!
2021年11月24日
,尤大在 Twitter 上宣布:Pinia
正式成为 Vue 官方
的状态库,意味着 Pinia
就是 Vuex 5
,Pinia
的优点:
- 同时支持 Composition Api 和 Options api 的语法;
- 去掉 mutations ,只有 state 、getters 和 actions ;
- 不支持嵌套的模块,通过组合 store 来代替;
- 更完善的 Typescript 支持;
- 清晰、显式的代码拆分;
安装
1 2 3 4 5
| npm install pinia
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 39
| import { defineStore } from 'pinia'
export const useStore = defineStore({ id: 'globalState', state: () => ({ count: 1, data: { name: 'Jerry', sex: '男' } }), getters: { doubleCount: (state) => state.count * 2, tripleCount() { return this.count * 3 } }, actions: { updateData (newData, count) { this.data = { ...newData } this.count = count 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 () { store.updateData({ name: 'Tom', sex: '女' }) store.data = { name: 'Tom', sex: '女' } store.$patch((state) => { state.data = { name: 'Tom', sex: '女' } state.count = 2 }) } </script>
<style lang="scss" scoped> </style>
|
其他方法
替换整个 state
$state
可以让你通过将 store
的属性设置为新对象来替换 store
的整个 state
1 2 3 4 5
| const store = useStore() store.$state = { name: 'Bob', sex: '男' }
|
重置状态
调用 store
上的 $reset()
方法将状态重置为初始值
1 2
| const store = useStore() store.$reset()
|
生命周期
通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
下表包含如何在 Option API 和 setup() 内部调用生命周期钩子
Option API |
setup中 |
beforeCreate |
不需要 |
created |
不需要 |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
activated |
onActivated |
deactivated |
onDeactivated |
原型绑定与组件内使用
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>
|
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' 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>
|
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
| <template> <child/> </template>
<script setup> import { ref, watch, provide } from 'vue' import child from './child.vue'
let name = ref('Jerry') provide('provideState', { name, changeName: () => { name.value = 'Tom' } })
watch(name, () => { console.log(`name变成了${name}`) setTimeout(() => { console.log(name.value) }, 1000) }) </script>
|
子组件
1 2 3 4 5 6 7 8
| <script setup> import { inject } from 'vue' const provideState = inject('provideState', {}) provideState.changeName() </script>
|
自定义指令
Vue3相较于Vue2的自定义声明方法有些不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const app = createApp({})
// 使 v-demo 在所有组件中都可用 app.directive('demo', { // 在绑定元素的 attribute 前或事件监听器应用前调用 created(el, binding, vnode, prevVnode) {}, // 在元素被插入到 DOM 前调用 beforeMount(el, binding, vnode, prevVnode) {}, // 在绑定元素的父组件 // 及他自己的所有子节点都挂载完成后调用 mounted(el, binding, vnode, prevVnode) {}, // 绑定元素的父组件更新前调用 beforeUpdate(el, binding, vnode, prevVnode) {}, // 在绑定元素的父组件 // 及他自己的所有子节点都更新后调用 updated(el, binding, vnode, prevVnode) {}, // 绑定元素的父组件卸载前调用 beforeUnmount(el, binding, vnode, prevVnode) {}, // 绑定元素的父组件卸载后调用 unmounted(el, binding, vnode, prevVnode) {} })
|
比如实现一个默认密文身份证号,点击才展示的指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| app.directive('ciphertext', { created: (el: any) => { console.log(el, 1111) el.style.cursor = 'pointer' const value = el.innerText if (!value || value === 'null' || value === '--') { el.innerText = '--' } else { el.setAttribute('title', '点击查看') el.innerText = hideText(value) el.addEventListener('click', () => { if (el.innerText.indexOf('*') > -1) { el.innerText = value } else { el.innerText = hideText(value) } }) } } })
<span v-ciphertext>{{idNumber}}</span>
|
对 await 的支持
不必再配合 async 就可以直接使用 await 了,这种情况下,组件的 setup 会自动变成 async setup 。
1 2 3
| <script setup> const post = await fetch('/api').then(() => {}) </script>
|
定义组件的name
用单独的<script>
块来定义
1 2 3 4 5
| <script> export default { name: 'ComponentName', } </script>
|