- 起初 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 反对;
- 清晰、显式的代码拆分;
装置
# 应用 npmnpm install pinia# 应用 yarnyarn 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>