共计 10787 个字符,预计需要花费 27 分钟才能阅读完成。
- 起初 Vue3.0 裸露变量必须
return
进去,template
中能力应用; - Vue3.2 中 只须要在
script
标签上加上setup
属性,组件在编译的过程中代码运行的上下文是在setup()
函数中,无需return
,template
可间接应用。 - 本文章以 Vue2 的角度学习 Vue3 的语法,让你疾速了解 Vue3 的 Composition Api
- 本文章第十四节为状态库
Pinia
的装置、应用解说
一、文件构造
Vue2 中,<template>
标签中只能有一个根元素,在 Vue3 中没有此限度
<template>
// ...
</template>
<script setup>
// ...
</script>
<style lang="scss" scoped>
// 反对 CSS 变量注入 v -bind(color)
</style>
二、data
<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>
三、method
<template>
// 调用办法
<button @click='changeName'> 按钮 </button>
</template>
<script setup>
import {reactive} from 'vue'
const state = reactive({name: 'Jery'}) // 申明 method 办法
const changeName = () => { state.name = 'Tom'}
</script>
四、computed
<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>
参考 vue 实战视频解说:进入学习
七、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
*Vue3 中的 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
* 全面拥抱 Pinia
吧!
2021 年 11 月 24 日,尤大在 Twitter 上发表:Pinia
正式成为 Vue 官网的状态库,意味着 Pinia
就是 Vuex 5
,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() 外部调用生命周期钩子
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
import {createApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
// 获取原型
const prototype = app.config.globalProperties
// 绑定参数
prototype.name = 'Jerry'
组件内应用
<script setup>
import {getCurrentInstance} from 'vue'
// 获取原型
const {proxy} = getCurrentInstance()
// 输入
console.log(proxy.name)
</script>
十七、v-bind() CSS 变量注入
<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
父组件
<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>
子组件
<script setup>
import {inject} from 'vue'
// 注入,第二个参数为默认值
const provideState = inject('provideState', {})
// 子组件触发 name 扭转
provideState.changeName()
</script>
十九、自定义指令
Vue3 相较于 Vue2 的自定义申明办法有些不同
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) {}})
比方实现一个默认密文身份证号,点击才展现的指令
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。
<script setup>
const post = await fetch('/api').then(() => {})
</script>
二十一、定义组件的 name
用独自的 <script>
块来定义
<script>
export default {name: 'ComponentName',}
</script>