目录
一:vue3全家桶
- Vue3:https://vuejs.org/
- VueRouter(V4):https://router.vuejs.org/
- Pinia(V2):https://pinia.vuejs.org/
- Vite构建工具:https://vitejs.dev/
- ElementPlus:https://element-plus.gitee.io/zh-CN/
- Vant(v3):https://vant-contrib.gitee.io/vant/#/zh-CN
- 技术栈:Vue3+VueRouter4+Pinia2+Vant3/ElementPlus
二:环境搭建
- @vue/cli
- vite(推荐)
- vue3在vscode中的插件
- volar插件
三:vue3中的SFC变化
- 支持多个style标签
- 支持多个script标签
- template支持多个根节点
四:两种语法规范
- 选项式
- 向下兼容,完全支持vue2的写法
- 可以使用setup()+选项式的写法,也可以完全使用setup()选项式写法
-
<script lang="ts"> import { ref } from "vue"; export default { setup(props, context){ console.log('---props',props) console.log('---context',context) const num = ref(1000) const add = ()=>{ num.value++ } return { num, add } }, methods: { sub () { this.num-- } } } </script> <template> <h1 v-text="num"></h1> <button @click="add">自增</button> <button @click="sub">自减</button> </template>
- 组合式(推荐写法)在<script setup>,只支持组合式写法,规避选项式写法。
-
<script setup lang="ts"> import {ref} from 'vue' const num = ref(1000) const add = ()=>{ num.value++ } const sub = ()=>{ num.value-- } </script> <template> <h1 v-text="num"></h1> <button @click="add">自增</button> <button @click="sub">自减</button> </template>
-
五: vue3最重要的特性
-
开发思想的变化:Vue3通过使用组合API,可以方便地封装Hooks,分离组件中的“逻辑关注点”。
-
具体怎么实践这种思想?第一步,用组合API替换掉传统的选项写法;第二步,梳理逻辑关注点封装自定义Hooks。
-
自定义Hooks有几个需要注意的问题:自定义Hooks一定要以use*开头,自定义Hooks可以被复用,自定义Hooks不要过度与泛滥。
-
思考:组件封装 与 Hooks封装,有什么本质的区别?前者是视图结构的封装,后者是逻辑功能的封装。
-
<template> <span v-text="num"></span> <button @click="add">自增</button> <button @click="sub">自减</button> </template> <script lang="ts" setup> import useNum from './hooks/useNum' const {num,add,sub} = useNum() </script>
import {ref} from 'vue' function useNum(){ const num = ref(1) const add = ()=>{ num.value++ } const sub = ()=>{ num.value-- } return { num, add, sub, } } export default useNum
六:组合API详解
- ref:用于定义声明式变量(等同于data中的变量)(用于定义基本数据类型)
-
<template> <span v-text="num"></span> </template> <script lang="ts" setup> import {ref} from 'vue' const num = ref(100) //num 是一个对象。 // 1:在逻辑中使用时取值需要用num.value // 2:在视图中直接使用num即可 console.log(num) // RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 100, _value: 100} // dep: Set(1) {ReactiveEffect} // __v_isRef: true // __v_isShallow: false // _rawValue: 100 // _value: 100 // value: 100 // [[Prototype]]: Object </script>
-
- reactive:定义响应式变量,一般用于引用数据类型
-
<template> <span v-for="i in list" :key="i" v-text="i"></span> </template> <script lang="ts" setup> import {reactive} from 'vue' const list = reactive([1,2,3,4,5]) </script>
-
- isRef:判断一个变量是否为ref对象
-
<template> </template> <script lang="ts" setup> import {isRef,ref,reactive} from 'vue' const num = ref(100) const arr = reactive([1,2,3]) console.log(isRef(num))//true console.log(isRef(arr))//false </script>
-
- unref:返回一个值,如果访问的是ref变量,则返回ref变量的value值,如果不是ref变量则直接返回这个变量
-
<template> </template> <script lang="ts" setup> import {ref,unref,reactive} from 'vue' const num = ref(1000) const arr = reactive([1,2,3,4,5]) const obj = {name:'chy',age:80} const a = unref(num) const b = unref(arr) const c = unref(obj) console.log(a,b,c) //1000 // Proxy {0: 1, 1: 2, 2: 3, 3: 4, 4: 5} // {name: 'chy', age: 80} </script>
-
- toRef:把reactive对象中的某个变量变成ref变量
-
<template> </template> <script lang="ts" setup> import {isRef,reactive,toRef} from 'vue' const obj = reactive({name:'chy',age:19}) const name = toRef(obj.name) console.log(isRef(name))//true </script>
-
- toRefs:把reactive对象变成一个ref对象
- 应用:在子组件中接收父组件传递过来的 props时,我们解构其中的某项就失去了响应式,这时候我们需要使用 toRefs把它变成响应式的。
-
<template> </template> <script lang="ts" setup> import { ToRefs,reactive,isRef, toRefs } from 'vue'; const arr = reactive([1,2,3,4]) const refvalue = toRefs(arr) console.log(isRef(refvalue))//false </script>
- shallowRef:对复杂层级的对象,只将其第一层变成 ref 响应。 (性能优化)
-
<template> <span v-text="obj"></span> </template> <script lang="ts" setup> import {ref,shallowRef,isRef} from 'vue' const cc = {a:{c:{d:8}},z:3} const obj = shallowRef(cc) //cc就是第一层 console.log(isRef(obj)) //true console.log(isRef(obj.value.a.c.d)) //false </script>
-
- triggerRef:强制更新一个 shallowRef对象的渲染。
-
<template> <span v-text="obj.z" @click="change"></span> </template> <script lang="ts" setup> import {ref,shallowRef,isRef,triggerRef} from 'vue' const cc = {a:{c:{d:8}},z:3} const obj = shallowRef(cc) //cc就是第一层 console.log(isRef(obj)) //true console.log(isRef(obj.value.a.c.d)) //false const change = ()=>{ console.log(2222) obj.value.z++ triggerRef(obj) } </script>
-
- readonly:把一个对象变成只读
- 语法:const rs = readonly(ref对象 | reactive对象 | 普通对象)
- isReadonly:判断一个变量是不是只读的
- const bol = isReadonly(变量)
- isReactive:判断一个变量是否为reactived的
- 被 readonly代理过的 reactive变量,调用 isReactive 也是返回 true的。
- isProxy:判断一个变量是不是 readonly 或 reactive的。
- toRaw:得到返回 reactive变量或 readonly变量的"原始对象"。
-
<template> </template> <script lang="ts" setup> import {reactive, toRaw, readonly } from 'vue'; const arr = reactive([1,2,3,4,5]) const obj = readonly({name:'chy',age:19}) console.log(toRaw(arr)) // Array(5) // 0: 1 // 1: 2 // 2: 3 // 3: 4 // 4: 5 // length: 5 console.log(toRaw(obj)) // Object // age: 19 // name: "chy" </script>
-
- markRaw:把一个普通对象标记成"永久原始",从此将无法再变成proxy了。
- 语法:const raw = markRaw({a,b})
- shallowReactive:定义一个reactive变量,只对它的第一层进行Proxy,,所以只有第一层变化时视图才更新
-
<template> </template> <script lang="ts" setup> import { reactive, shallowReactive,isReactive } from 'vue'; const obj = shallowReactive(reactive({name:'chy',age:{chy:30}})) console.log(obj) console.log(isReactive(obj))//true console.log(isReactive(obj.name))//false </script>
-
- shallowReadonly:定义一个reactive变量,只有第一层是只读的
- computed:对响应式变量进行缓存计算。
- 语法:const c = computed(fn / {get,set})
- watch:用于监听响应式变量的变化,组件初始化时,它不执行。
- 语法:const stop = watch(x, (new,old)=>{}),调用stop() 可以停止监听。
- 语法:const stop = watch([x,y], ([newX,newY],[oldX,oldY])=>{}),调用stop()可以停止监听。
-
<template> <div v-text="num1" @click="add1"></div> <div v-text="num2" @click="add2"></div> <div @click="click">点我停止监听</div> </template> <script lang="ts" setup> import { watch, ref } from 'vue'; const num1 = ref(1) const num2 = ref(2) const add1 = ()=>{ num1.value ++ } const add2 = ()=>{ num2.value++ } const stop = watch([num1,num2],([a,b],[c,d])=>{ console.log(a,b) console.log('------------------') console.log(c,d) }) const click = ()=>{ stop() } </script>
- watchEffect:相当于是 react中的 useEffect(),用于执行各种副作用。
- const stop = watchEffect(fn),默认其 flush:'pre',前置执行的副作用。
- watchPostEffect,等价于 watchEffect(fn, {flush:'post'}),后置执行的副作用。
- watchSyncEffect,等价于 watchEffect(fn, {flush:'sync'}),同步执行的副作用。
- 特点:watchEffect 会自动收集其内部响应式依赖,当响应式依赖发变化时,这个watchEffect将再次执行,直到你手动 stop() 掉它。
- 生命周期钩子
- 选项式的 beforeCreate、created,被setup替代了。setup表示组件被创建之前、props被解析之后执行,它是组合式 API 的入口。
- 选项式的 beforeDestroy、destroyed 被更名为 beforeUnmount、unmounted。
- 新增了两个选项式的生命周期 renderTracked、renderTriggered,它们只在开发环境有用,常用于调试。
- 在使用 setup组合时,不建议使用选项式的生命周期,建议使用 on* 系列 hooks生命周期。
-
<template> </template> <script lang="ts" setup> import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onErrorCaptured, onRenderTracked, onRenderTriggered } from 'vue' // 挂载阶段 onBeforeMount(()=>console.log('---开始挂载')) onRenderTracked(()=>console.log('---跟踪')) onMounted(()=>console.log('---挂载完成')) // 更新阶段 onRenderTriggered(()=>console.log('---触发')) onBeforeUpdate(()=>console.log('---开始更新')) onUpdated(()=>console.log('---更新完成')) // 销毁阶段 onBeforeUnmount(()=>console.log('---开始销毁')) onUnmounted(()=>console.log('---销毁完成')) // 与动态组件有关 onActivated(()=>console.log('---激活')) onDeactivated(()=>console.log('---休眠')) // 异常捕获 onErrorCaptured(()=>console.log('---错误捕获')) </script>
- provide / inject:在组件树中自上而下地传递数据.
- provide('key', value)
- const value = inject('key', '默认值')
- 关于setup代码范式(最佳实践)
- 只使用 setup 及组合API,不要再使用vue选项了。
- 有必要封装 hooks时,建议把功能封装成hooks,以便于代码的可维护性
- 能用 vite就尽量使用vite,能用ts 就尽量使用ts。
七:vue3新语法细节
- 在Vue2中,v-for 和 ref 同时使用,这会自动收集 $refs。当存在嵌套的v-for时,这种行为会变得不明确且效率低下。在Vue3中,v-for 和 ref 同时使用,这不再自动收集$refs。我们可以手动封装收集 ref 对象的方法,将其绑定在 ref 属性上。
-
<template> <div v-for="i in 6" :key="i" :ref="setref"> <span v-for="i in 6" :key="i" :ref="setref" v-text="i"></span> </div> </template> <script lang="ts" setup> let arr = [] const setref = (ev)=>{ arr.push(ev) } console.log(arr) </script>
-
-
在Vue3中,使用 defineAsyncComponent 可以异步地加载组件。需要注意的是,这种异步组件是不能用在Vue-Router的路由懒加载中。
-
<template> </template> <script lang="ts" setup> import {defineAsyncComponent} from 'vue' const asyncComponent = defineAsyncComponent({ loader:()=>import('./pages/pageA.vue') , delay:200,//延迟200毫秒 timeout:3000//超出3000毫秒报错 }) </script>
-
-
Vue3.0中的 $attrs,包含了父组件传递过来的所有属性,包括 class 和 style 。在Vue2中,$attrs 是接到不到 class 和 style 的。在 setup 组件中,使用 useAttrs() 访问;在非 setup组件中,使用 this.$attrs /setupCtx.attrs 来访问。
-
<template> </template> <script lang="ts" setup> import { useAttrs } from 'vue'; const option = useAttrs() console.log(option) </script>
-
-
Vue3中,移除了 $children 属性,要想访问子组件只能使用 ref 来实现了。在Vue2中,我们使用 $children 可以方便地访问到子组件,在组件树中“肆意”穿梭。
-
Vue3中,使用 app.directive() 来定义全局指令,并且定义指令时的钩子函数们也发生了若干变化。
-
app.directive('highlight', { // v3中新增的 created() {}, // 相当于v2中的 bind() beforeMount(el, binding, vnode, prevVnode) { el.style.background = binding.value }, // 相当于v2中的 inserted() mounted() {}, // v3中新增的 beforeUpdate() {}, // 相当于v2中的 update()+componentUpdated() updated() {}, // v3中新增的 beforeUnmount() {}, // 相当于v2中的 unbind() unmounted() {} })
-
-
Vue3中新增了 emits 选项。在非<script setup>写法中,使用 emits选项 接收父组件传递过来的自定义,使用 ctx.emit() / this.$emit() 来触发事件。在<script setup>中,使用 defineEmits 来接收自定义事件,使用 defineProps 来接收自定义事件。
-
import { defineEmits, defineProps} from 'vue'; //接受父组件传过来的数据 const props = defineProps({ value:{type:String,default:''} }) // const emit = defineEmits(['自定义事件1','自定义事件2',...]) const emit = defineEmits(['自定义事件1','自定义事件2'])
<template> <child :num="num" @change="num = $event"></child> </template> <script lang="ts" setup> import {ref} from 'vue' import child from './components/child.vue' const num = ref(1000) </script>
<template> <span v-text="num" @click="emit('change',dev)"></span> </template> <script lang="ts" setup> import { defineEmits, defineProps,ref} from 'vue'; //父传子 const props = defineProps({ num:{type:Number,default:0} }) //子传父 const emit = defineEmits(['change']) console.log(emit) //$emit('change',dev)或者emit('change',dev) const dev = ref(10) </script>
-
-
Vue3中 移除了 $on / $off / $once 这三个事件 API,只保留了 $emit 。
-
Vue3中,移除了全局过滤器(Vue.filter)、移除了局部过滤器 filters选项。取而代之,你可以封装自定义函数或使用 computed 计算属性来处理数据。
-
Vue3 现在正式支持了多根节点的组件,也就是片段,类似 React 中的 Fragment。使用片段的好处是,当我们要在 template 中添加多个节点时,没必要在外层套一个 div 了,套一层 div 这会导致多了一层 DOM结构。可见,片段 可以减少没有必要的 DOM 嵌套。
-
我们已经知道,使用 provide 和 inject 这两个组合 API 可以组件树中传递数据。除此之外,我们还可以应用级别的 app.provide() 来注入全局数据。在编写插件时使用 app.provide() 尤其有用,可以替代app.config.globalProperties。
-
在Vue2中,Vue.nextTick() / this.$nextTick 不能支持 Webpack 的 Tree-Shaking 功能的。在 Vue3 中的 nextTick ,考虑到了对 Tree-Shaking 的支持。
-
Vue3中,对于
v-if/v-else/v-else-if
的各分支项,无须再手动绑定 key了, Vue3会自动生成唯一的key。因此,在使用过渡动画 <transition>对多个节点进行显示隐藏时,也无须手动加 key了。 -
Vue3中,$listeners 被移除了。因此我们无法再使用 $listeners 来访问、调用父组件给的自定义事件了。
-
Vue2中,根组件挂载 DOM时,可以使用 el 选项、也可以使用 $mount()。但,在 Vue3中只能使用 $mount() 来挂载了。并且,在 Vue 3中,被渲染的应用会作为子元素插入到 <div id='app'> 中,进而替换掉它的innerHTML。
-
在Vue2中,组件有一个 render 选项(它本质上是一个渲染函数,这个渲染函数的形参是 h 函数),h 函数相当于 React 中的 createElement()。在Vue3中,render 函数选项发生了变化:它的形参不再是 h 函数了。h 函数变成了一个全局 API,须导入后才能使用。
-
Vue3中新增了实验性的内置组件 <suspense>,它类似 React.Suspense 一样,用于给异步组件加载时,指定 Loading指示器。需要注意的是,这个新特征尚未正式发布,其 API 可能随时会发生变动。
-
Vue3中,过渡动画<transition>发生了一系列变化。之前的 v-enter 变成了现在的 v-enter-from , 之前的 v-leave 变成了现在的 v-leave-from 。另一个变化是:当使用<transition>作为根结点的组件,从外部被切换时将不再触发过渡效果。
-
同一节点上使用 v-for 和 v-if ,在Vue2中不推荐这么用,且v-for优先级更高。在Vue3中,这种写法是允许的,但 v-if 的优秀级更高。
-
在Vue2中,静态属性和动态属性同时使用时,不确定最终哪个起作用。在Vue3中,这是可以确定的,当动态属性使用 :title 方式绑定时,谁在前面谁起作用;当动态属性使用 v-bind='object'方式绑定时,谁在后面谁起作用。
-
当使用watch选项侦听数组时,只有在数组被替换时才会触发回调。换句话说,在数组被改变时侦听回调将不再被触发。要想在数组被改变时触发侦听回调,必须指定deep选项。
-
<template> <div v-for='t in list' v-text='t.task'></div> <button @click.once='addTask'>添加任务</button> </template> <script setup> import { reactive, watch } from 'vue' const list = reactive([ { id:1, task:'读书', value:'book' }, { id:2, task:'跑步', value:'running'} ]) const addTask = () => { list.push({ id:3, task:'学习', value:'study' }) } // 当无法监听一个引用类型的变量时 // 添加第三个选项参数 { deep:true } watch(list, ()=>{ console.log('list changed', list) }, { deep:true }) </script>
-
-
Vue3中,新增了 <teleport>组件,这相当于 ReactDOM.createPortal(),它的作用是把指定的元素或组件渲染到任意父级作用域的其它DOM节点上。上面第 16个知识点中,用到了 <teleport> 加载 animate.css 样式表,这算是一种应用场景。
-
<template> <!-- 当Modal弹框显示时,将其插入到<body>标签中去 --> <teleport to='body'> <div class='layer' v-if='visibled' @click.self='cancel' > <div class='modal'> <header></header> <main><slot></slot></main> <footer></footer> </div> </div> </teleport> </template> <script setup> import { defineProps, defineEmits, toRefs } from 'vue' const props = defineProps({ visibled: { type: Boolean, default: false } }) const emit = defineEmits(['cancel']) const cancel = () => emit('cancel') </script> <style lang="scss"> .layer { position:fixed; bottom: 0; top: 0; right: 0; left: 0; background-color: rgba(0,0,0,0.7); .modal { width: 520px; position: absolute; top: 100px; left: 50%; margin-left: -260px; box-sizing: border-box; padding: 20px; border-radius: 3px; background-color: white; } } </style>
<template> <Modal :visibled='show' @cancel='show=!show'> <div>弹框主体内容</div> </Modal> <button @click='show=!show'>打开弹框</button> </template> <script setup> import { ref, watch } from 'vue' import Modal from './components/Modal.vue' const show = ref(false) </script>
-
-
在Vue3中,移除了 model 选项,移除了 v-bind 指令的 .sync 修饰符。在Vue2中,v-model 等价于 :value + @input ;在Vue3中,v-model 等价于 :modelValue + @update:modelValue 。在Vue3中,同一个组件上可以同时使用多个 v-model。在Vue3中,还可以自定义 v-model 的修饰符。