# vue3 · api
## 文件結構
```
<template>
// ...
</template>
<script setup>
// ...
</script>
<style lang="scss" scoped>
// 支持CSS變量注入v-bind(color)
</style>
```
## 數據
```
<script setup>
import { reactive, ref, toRefs } from 'vue'
// ref聲明響應式數據,用于聲明基本數據類型
const name = ref('Jerry')
// 修改
name.value = 'Tom'
// reactive聲明響應式數據,用于聲明引用數據類型
const state = reactive({
name: 'Jerry',
sex: '男'
})
// 修改
state.name = 'Tom'
// 使用toRefs解構
const {name, sex} = toRefs(state)
// template可直接使用{{name}}、{{sex}}
</script>
```
## 方法
```
<template>
// 調用方法
<button @click='changeName'>按鈕</button>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({
name: 'Jery'
})
// 聲明method方法
const changeName = () => {
state.name = 'Tom'
}
</script>
```
## 計算屬性
```
<script setup>
import { computed, ref } from 'vue'
const count = ref(1)
// 通過computed獲得doubleCount
const doubleCount = computed(() => {
return count.value * 2
})
// 獲取
console.log(doubleCount.value)
</script>
```
## watch 監聽
```
<script setup>
import { watch, reactive } from 'vue'
const state = reactive({
count: 1
})
// 聲明方法
const changeCount = () => {
state.count = state.count * 2
}
// 監聽count
watch(
() => state.count,
(newVal, oldVal) => {
console.log(state.count)
console.log(`watch監聽變化前的數據:${oldVal}`)
console.log(`watch監聽變化后的數據:${newVal}`)
},
{
immediate: true, // 立即執行
deep: true // 深度監聽
}
)
</script>
```
## props父傳子
### 子組件
```
<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>
```
## emit子傳父
### 子組件
```
<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>
```
### 父組件
```
<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`
### 子組件
```
<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>
```
### 父組件
```
<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
```
<script setup>
import { nextTick } from 'vue'
nextTick(() => {
// ...
})
</script>
```
## ref子組件實例和defineExpose
* 在標準組件寫法里,子組件的數據都是默認隱式暴露給父組件的,但在 script-setup 模式下,所有數據只是默認 return 給 template 使用,不會暴露到組件外,所以父組件是無法直接通過掛載 ref 變量獲取子組件的數據。
* 如果要調用子組件的數據,需要先在子組件顯示的暴露出來,才能夠正確的拿到,這個操作,就是由 defineExpose 來完成。
### 子組件
```
<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: 'Jerry'
})
// 將方法、變量暴露給父組件使用,父組件才可通過ref API拿到子組件暴露的數據
defineExpose({
// 解構state
...toRefs(state),
// 聲明方法
changeName () {
state.name = 'Tom'
}
})
</script>
```
### 父組件
#### 1. 獲取一個子組件實例
```
<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>
```
#### 2. 獲取多個子組件實例:在 v-for 中獲取子組件實例
這種情況僅適用于 v-for`循環數是固定的情況`,因為如果 v-for`循環數`在初始化之后發生改變,那么就會導致 childRefs 再一次重復添加,childRefs 中會出現重復的子組件實例
```
<template>
<div v-for="item in 3" :key="item">
<child :ref='addChildRef'/>
</div>
</template>
<script setup>
// 省略...
// 子組件實例數組
const childRefs = ref([])
// 通過 addChildRef 方法向 childRefs 添加子組件實例
const addChildRef = (el) => {
childRefs.value.push(el)
}
</script>
```
#### 3. 獲取多個子組件實例:動態 v-for 獲取子組件實例
通過下標來向 childRefs 添加/修改,初始化之后,動態修改 v-for 循環數,會自動根據下標重新修改該下標對應的數據
```
<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
### 子組件
```
<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>
```
### 父組件
```
<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
```
<script setup>
import { useRoute, useRouter } from 'vue-router'
// 必須先聲明調用
const route = useRoute()
const router = useRouter()
// 路由信息
console.log(route.query)
// 路由跳轉
router.push('/newPage')
</script>
```
## 路由導航守衛
```
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 添加一個導航守衛,在當前組件將要離開時觸發。
onBeforeRouteLeave((to, from, next) => {
next()
})
// 添加一個導航守衛,在當前組件更新時觸發。
// 在當前路由改變,但是該組件被復用時調用。
onBeforeRouteUpdate((to, from, next) => {
next()
})
</script>
```
## store
### Vuex
```
<script setup>
import { useStore } from 'vuex'
import { key } from '../store/index'
// 必須先聲明調用
const store = useStore(key)
// 獲取Vuex的state
store.state.xxx
// 觸發actions的方法
store.commit('fnName')
// 觸發actions的方法
store.dispatch('fnName')
// 獲取Getters
store.getters.xxx
</script>
```
### Pinia
* 同時支持 Composition Api 和 Options api 的語法;
* 去掉 mutations ,只有 state 、getters 和 actions ;
* 不支持嵌套的模塊,通過組合 store 來代替;
* 更完善的 Typescript 支持;
* 清晰、顯式的代碼拆分;
#### 安裝
```
# 使用 npm
npm install pinia
# 使用 yarn
yarn add pinia
```
#### main.js 引入
```
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
```
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
```
<template>
// 獲取 store 的 state
<p>姓名:{{store.data.name}}</p>
<p>性別:{{store.data.sex}}</p>
// 調用 actions 方法 / 修改 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>
```
#### 其他方法
**替換整個 state**
`$state`可以讓你通過將`store`的屬性設置為新對象來替換`store`的整個`state`
```
const store = useStore()
store.$state = {
name: 'Bob',
sex: '男'
}
```
**重置狀態**
調用`store`上的`$reset()`方法將狀態重置為初始值
```
const store = useStore()
store.$reset()
```
## 生命周期
通過在生命周期鉤子前面加上 “on” 來訪問組件的生命周期鉤子。
下表包含如何在 Option API 和 setup() 內部調用生命周期鉤子
<table><thead><tr><th><strong>Option API</strong></th><th><strong>setup中</strong></th></tr></thead><tbody><tr><td>beforeCreate</td><td>不需要</td></tr><tr><td>created</td><td>不需要</td></tr><tr><td>beforeMount</td><td>onBeforeMount</td></tr><tr><td>mounted</td><td>onMounted</td></tr><tr><td>beforeUpdate</td><td>onBeforeUpdate</td></tr><tr><td>updated</td><td>onUpdated</td></tr><tr><td>beforeUnmount</td><td>onBeforeUnmount</td></tr><tr><td>unmounted</td><td>onUnmounted</td></tr><tr><td>errorCaptured</td><td>onErrorCaptured</td></tr><tr><td>renderTracked</td><td>onRenderTracked</td></tr><tr><td>renderTriggered</td><td>onRenderTriggered</td></tr><tr><td>activated</td><td>onActivated</td></tr><tr><td>deactivated</td><td>onDeactivated</td></tr></tbody></table>
## 原型綁定與組件內使用
### main.js
```
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 獲取原型
const prototype = app.config.globalProperties
// 綁定參數
prototype.name = 'Jerry'
```
### 組件內使用
~~~javascript
<script setup>
import { getCurrentInstance } from 'vue'
// 獲取原型
const { proxy } = getCurrentInstance()
// 輸出
console.log(proxy.name)
</script>
~~~
## v-bind() CSS變量注入
~~~javascript
<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>
~~~
## provide和inject
### 父組件
~~~javascript
<template>
<child/>
</template>
<script setup>
import { ref, watch, provide } 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>
~~~
### 子組件
~~~javascript
<script setup>
import { inject } from 'vue'
// 注入,第二個參數為默認值
const provideState = inject('provideState', {})
// 子組件觸發name改變
provideState.changeName()
</script>
~~~
## 自定義指令
Vue3相較于Vue2的自定義聲明方法有些不同
~~~javascript
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) {}
})
~~~
比如實現一個默認密文身份證號,點擊才展示的指令
~~~javascript
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 。
~~~javascript
<script setup>
const post = await fetch('/api').then(() => {})
</script>
~~~
## 定義組件的name
用單獨的`<script>`塊來定義
~~~javascript
<script>
export default {
name: 'ComponentName',
}
</script>
~~~
更優雅的方式,安裝插件:`vite-plugin-vue-setup-extend`,就可以按以下方式定義name了
#### 配置?`vite.config.ts`
~~~javascript
import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [VueSetupExtend()]
})
~~~
#### 使用
~~~javascript
<script setup name="ComponentName">
// todo
</script>
~~~