vue2.x向vue3.x过渡,都改变了什么呢?本文从创建vue3项目起步,一起来看看vue3有什么样的改变,以及可以带给我们什么好处吧~
element-plus
、vant
、ant-design-vue
等已经发布支持Vue3.0的版本,这是趋势。☆
选项API----->组合API(composition api),能够更好的组织逻辑,封装逻辑,复用逻辑(编程风格改变)# 全局安装vue脚手架
npm i @vue/cli -g
# 创建Vue项目,选择v3版本
vue create 项目名称
#--------创建完成后--------
# 切换路径
cd 项目名称
# 运行项目
npm run serve
操作步骤:
找到要创建项目的根目录,地址栏输入cmd
打开终端
输入vue create 项目名称
,项目名称自己起哦~
选择手动创建(如图)
vue3的API典型风格:按需导入
链式操作
作用:把App根组件渲染到index.html
页面中
总结:
- Vue的实例化方式发生变化:基于createApp方法进行实例化
- router和store采用use方法进行配置
- 按需导入,为了提升打包的性能
Vue2中的组件模板必须有唯一的根节点
Vue3中组件的模板可以没有根节点
总结:Vue3中组件的模板可以没有根节点(与Vue2进行对比)
Vue3创建实例对象
结论:
- 创建路由实例对象,采用createRouter方式,Vue3典型风格
- 采用hash和history的方式有变化
- Vue2采用mode选项:hash / history
- Vue3采用方法:createWebHashHistory()/createWebHistory()
结论:创建store对象采用createStore方法,而不是new
目标:理解什么是选项API写法,什么是组合API
每个代码写到哪个位置
按照功能划分
总结:
- Vue2项目中使用的选项API
- 代码风格:一个功能逻辑代码分散
- 优点:易于学习 和使用,写代码的位置已经约定好
- 缺点:代码组织性差,相似的逻辑(功能)代码不便于复用,逻辑复杂
- 组合API(hook)
- 以功能为单位阻止代码结构,后续重用功能更加方便
使用细节:
新的组件选项,作为组件中使用组合API的起点
从组件生命周期来看,它的执行顺序在beforeCreate
之前(Vue3中beforeCreate/created已经被废弃了,其实已经被setup替代了)
setup函数中this还没有创建,此时无法访问this,this是undefined
setup中返回的数据用于模板使用:类似于之前的data中提供的数据
Vue3中依然可以使用data中的数据,但是不建议使用(这是选项API的写法)
方法在setup中定义后,也需要return出去
语法格式:
export default {
setup() {
// return ...
}
}
结论:
- setup选项是实现组合API的基础
- 触发的时机:
- this问题
- setup的返回值
vue2.x生命周期钩子函数:
认识vue3.0生命周期钩子函数(相同的生命周期函数可以触发多次)
setup
创建实例前onMounted
挂载DOM后onBeforeUpdate
更新组件前onUpdated
更新组件后onBeforeUnmount
卸载销毁前onUnmounted
:卸载销毁后语法格式:
import { mounted } from 'vue'
export default {
setup () {
mounted(() => {
console.log('xxx')
})
}
}
结论:
- vue3生命周期函数发生了变化
- 去掉两个:
beforeCreate
和created
,添加了setup- 方法名发生变化: 方法名前面多了个on ,中间是驼峰
- 卸载组件的生命周期变化:
onBeforeUnmount
、onUnmounted
- 特点:同一个生命周期可以触发多次
数据的响应式:数据的变化导致视图自动变化。
可以定义一个复杂数据类型
语法格式:
模板中使用obj.msg
import { reactive } from 'vue'
export default{
setup(){
const obj = reactive({
msg: 'Hello'
})
}
}
注
:reactive中的对象属性如果重新赋值会失去响应式能力
需求:模板中不添加obj前缀,直接获取属性
把对象中的单个属性取出来,保证数据的响应式
语法格式:
import { toRef } from 'vue'
export default {
const obj = {
msg: 'hello'
}
const msg = toRef(obj,'msg')
setup() {
return {msg}
}
}
代码演示:
<template>
<div>
<div>{{ msg }}</div>
<div>{{ info }}</div>
<button @click="handleClick">点击</button>
</div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
name: 'App',
setup() {
const obj = reactive({
msg: 'hello',
info: 'Leo'
})
const msg = toRef(obj, 'msg')
const info = toRef(obj, 'info')
const handleClick = () => {
obj.msg = 'hi'
}
return { msg, info, handleClick }
}
}
</script>
<style lang="less"></style>
结论: toRef方法可以把对象中的单个属性取出,并且保证响应式能力
语法格式:
import { toRefs } from 'vue'
export default {
setup() {
const obj = reactive({
msg: 'hello',
info: 'Leo'
})
// 把obj中的属性解构出来
const {msg,info} = toRefs(obj)
return {msg, info}
}
}
结论:toRefs这个方法可以批量转换对象中的属性为独立的响应式数据
主要用于定义普通(基本类型)的数据并保证响应式能力
语法格式:
import {ref} from 'vue'
export default {
setup () {
const count = ref(0)
const handleClick = () => {
// ref定义的数据,在js中操作时需要通过value属性进行
count.value += 1
}
}
}
代码演示:
<template>
<div>
<div>{{ count }}</div>
<button @click="handleClick">点击</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
const count = ref(0)
const handleClick = () => {
// ref定义的数据,在js中操作时需要通过value属性进行
count.value += 1
}
return { count, handleClick }
}
}
</script>
<style lang="less"></style>
结论:
- 如果是基本类型数据,可以使用ref进行定义
- ref其实也可以定义对象,但是访问时需要value属性
注意:vue3中计算属性也是组合API风格
- 回调函数必须return,结果就是计算结果
- 如果计算属性依赖的数据发生变化,那么会重新计算
- 不要在计算属性中进行异步操作
示例代码:
<template>
<div>
今年:{{ age }}岁了<br />
明年:{{ nextAge }}岁了<br />
<button @click="nextAge = 28">点击</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'App',
setup() {
const age = ref(0)
// 只读的写法
// const nextAge = computed(() => {
// return age.value + 1
// })
// 可读可写的写法,需要写成对象格式
const nextAge = computed({
get() {
return age.value + 1
},
set(v) {
age.value = v - 1
}
})
return { age, nextAge }
}
}
</script>
<style lang="less"></style>
总结:
- 计算属性可以直接读取或者修改
- 如果要实现计算属性的修改操作,那么computed的实参应该是对象
- 读取数据触发get方法
- 修改数据触发set方法,set函数的形参就是赋给的值
被侦听的信息变化时,触发侦听器的回调
典型场景:路由参数发生变化后,重新调用接口,获取组件的数据
语法格式:
import {ref, watch} from 'vue'
export default {
setup() {
const count = ref(0)
// 基于侦听器监听数据的变化
// 被侦听的审计局必须是响应式的
watch (count, (newVal,oldVal) => {
// newVal表示修改后的值
// oldVal表示修改前的值
console.log(newVal,oldVal)
})
return {count}
}
}
总结:侦听普通数据可以获取修改前后的值,被侦听的数据必须是响应式的
语法格式:
import {reactive, watch} from 'vue'
export default {
setup () {
const obj = reactive({
msg: 'tom'
})
watch(obj,() => {
// 直接获取到的就是最新值
console.log(obj)
})
}
}
总结:如果侦听对象,那么侦听器的回调函数的两个参数是一样的结果,表示最新的对象数据,此时,也可以直接读取被侦听的对象,那么得到的值也是最新的。
语法格式:
import {ref, watch} from 'vue'
export default {
setup () {
const n1 = ref(1)
const n2 = ref(2)
watch ([n1, n2], (v) => {
// v表示被侦听的所有数据的最新值,类型是数组
console.log(v)
})
const handleClick = () => {
n1.value = 3
n2.value = 4
}
return {n1, n2}
}
}
总结:
- 可以得到更新前后的值
- 侦听到的结果也是数组,数据顺序一致
语法格式:
<template>
<div>
姓名:{{ user.name }}<br />
今年:{{ user.age }}岁了<br />
<button @click="user.age = 13">点击</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
name: 'App',
setup() {
const user = reactive({
name: '小明',
age: 12
})
watch(
() => user.age,
v => {
console.log(v)
}
)
return { user }
}
}
</script>
<style lang="less"></style>
结论:
- 如果侦听对象中某个属性,需要用到函数方式
- 侦听更少的数据有利于提高性能
语法格式:
import {} from 'vue'
export default {
setup () {
// immediate: true 表示,组件渲染后,立即触发一次
watch(() => stuInfo.friend, () => {
console.log('stuInfo')
},{
immediate: true,
// 被侦听的内容需要是函数的写法
deep: true // 深度侦听
})
}
}
总结:
- immediate: true,表示组件渲染时历史调用
- deep: true,表示深度侦听对象里面的子属性(被侦听的内容需要是函数的写法)
获取DOM或者组件实例可以使用ref属性,写法和Vue2.0需要区分开
vue2中规则
结论:
- vue2中可以通过ref直接操作单个DOM和组件
- vue2 中可以批量通过ref操作DOM和组件
vue3 中规则
ref操作单个DOM元素
代码演示:
<template>
<div>
<div ref="info">hello</div>
<button @click="handleClick">点击</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
const info = ref(null)
const handleClick = () => {
// 打印出来是一个对象,value里是DOM元素
// 可以使用info.value.innerHTML打印出来DOM中的内容
console.log(info.value.innerHTML)
}
return { info, handleClick }
}
}
</script>
<style lang="less"></style>
总结:操作单个DOM或者组件的流程
- 定义一个响应式变量
- 把变量返回给模板使用
- 模板中绑定上述返回数据
- 可以通过变量操作DOM 或者组件
批量操作
代码演示:
<template>
<div>
<ul>
<li :ref="setFruits" v-for="item in fruits" :key="item.id">{{ item.name }}</li>
</ul>
<button @click="handleClick">点击</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
const fruits = ref([
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'pear' }
])
const arr = []
const setFruits = el => {
// el 代表单个DOM元素
arr.push(el.innerHTML)
}
const handleClick = () => {
console.log(arr)
}
return { fruits, handleClick, setFruits }
}
}
</script>
<style lang="less"></style>
总结:ref批量操作元素的流程
- 定义一个函数
- 把该函数绑定到ref上(必须动态绑定)
- 在函数中可以通过参数得到单个元素,这个元素一般可以放到数组中
vue2写法
vue3写法
父向子传值
步骤
代码演示
子组件中setup想拿到,setup传入形参即可.
注
:props 值是只读的,不可以直接在子组件中修改
<template>
<div>
父组件
<hr>
<son :money="money"></son>
</div>
</template>
<script>
import Son from './Son.vue'
import { ref } from 'vue'
export default {
name: 'App',
setup () {
const money = ref(100)
return { money }
},
components: {
Son
}
}
</script>
<style lang="less">
</style>
<template>
<div>子组件-----{{ money }}</div>
</template>
<script>
export default {
name: 'Son',
props: {
money: {
type: Number,
default: 0
}
},
setup (props) {
console.log(props.money)
}
}
</script>
<style>
</style>
js
中使用需要写如下代码setup(props) {
const getMoney = () => {
console.log(props.xxx)
}
return { getMoney }
}
总结:
- 子组件模板中直接可以获取props中的属性值
- js代码中需要通过setup中的第一个形参来获取
子向父传值
步骤
setup
传入的第二个形参,向父组件抛出自定义事件代码演示
注
:emits必须写,不写vue会有如下警告。如果还有其他自定义事件,可以继续向数组中添加。
<template>
<div>子组件-----{{ money }}</div>
+ <button @click="handleClick"></button>
</template>
<script>
export default {
name: 'Son',
+ emits: ['sendToPar']
props: {
money: {
type: Number,
default: 0
}
},
+ setup (props, context) {
console.log(props.money)
+ const handleClick = () => {
+ context.emit('sendToPar', 50)
+ }
+ return { handleClick }
}
}
</script>
<style>
</style>
总结:
- 通过setup函数的参数二 context.emit方法 触发自定义事件
context.emit('xxx',xx)
- 子组件触发的自定义事件需要在emits选项中进行声明,直观的看到本组件中触发了哪些自定义事件
<template>
<div>
父组件
<hr>
+ <son :money="money" @sendToPar="getMoney"></son>
</div>
</template>
<script>
import Son from './Son.vue'
import { ref } from 'vue'
export default {
name: 'App',
setup () {
const money = ref(100)
+ const getMoney = (val) => {
console.log(val)
+ money.value = money.value - val
+ }
+ return { money, getMoney }
},
components: {
Son
}
}
</script>
<style lang="less">
</style>
使用场景:有一个父组件,里头有子组件,孙组件,有很多后代组件,共享父组件数据
父组件向子孙组件传递数据
步骤
provide
向外传递数据inject
接收来自祖先组件的数据代码演示
<template>
<div>
父组件
<hr>
<son :money="money" @sendToPar="getMoney"></son>
</div>
</template>
<script>
import Son from './Son.vue'
+import { ref, provide } from 'vue'
export default {
name: 'App',
setup () {
const money = ref(100)
// 向子孙组件传递数据
+ provide('moneyInfo', 1000)
// 子组件自定义事件函数
const getMoney = (val) => {
console.log(val)
money.value = money.value - val
}
return { money, getMoney }
},
components: {
Son
}
}
</script>
<style lang="less">
</style>
子组件
中注册子孙组件<template>
<div>子组件-----{{ money }}</div>
<button @click="handleClick">点击</button>
<hr>
+ <grand-son></grand-son>
</template>
<script>
+import GrandSon from './GrandSon.vue'
export default {
name: 'Son',
emits: ['sendToPar'],
props: {
money: {
type: Number,
default: 0
}
},
setup (props, context) {
console.log(props.money)
const handleClick = () => {
context.emit('sendToPar', 50)
}
return { handleClick }
},
+ components: {
+ GrandSon
+ }
}
</script>
<style>
</style>
<template>
<div>孙子组件-----{{ money }}</div>
</template>
<script>
import { inject } from 'vue'
export default {
name: 'GrandSon',
setup () {
const money = inject('moneyInfo')
return { money }
}
}
</script>
<style>
</style>
总结:
- 父传子孙数据:使用provide
- 子孙得到数据:使用inject
子孙组件传递给父组件数据
步骤
代码演示
<template>
<div>
父组件
<hr>
<son :money="money" @sendToPar="getMoney"></son>
</div>
</template>
<script>
import Son from './Son.vue'
import { ref, provide } from 'vue'
export default {
name: 'App',
setup () {
const money = ref(100)
// 向子孙组件传递一个函数
+ const handleMoney = (val) => {
+ console.log('子孙组件传递回来的数据', val)
+ }
+ provide('handleMoney', handleMoney)
// 子组件自定义事件函数
const getMoney = (val) => {
console.log(val)
money.value = money.value - val
}
return { money, getMoney }
},
components: {
Son
}
}
</script>
<style lang="less">
</style>
<template>
<div>
孙子组件
+ <button @click="handleSend">点击</button>
</div>
</template>
<script>
import { inject } from 'vue'
export default {
name: 'GrandSon',
setup () {
+ const handleMoney = inject('handleMoney')
+ const handleSend = () => {
+ handleMoney(200)
+ }
+ return { handleSend }
}
}
</script>
<style>
</style>
总结: 子组件传递数据给爷爷组件,需要通过provide 一个函数的方式实现
- 爷爷组件传递一个函数,后续通过函数的形参获取数据
- 孙子组件获取并调用该函数传递数据
☆
v-model语法糖vue2使用回顾
v-model是value和@input的语法糖
本质是属性绑定和事件绑定的结合
<my-com v-model="info"></my-com>
<my-com :value="info" @input="info=$event"></my-com>
- 如果是原始DOM的事件,那么$event表示js的原生事件对象
- 如果是组件的自定义事件,那么$event是#emit传递的数据
总结:vue2中v-model的应用场景
- 用到表单元素上:$event表示事件对象
- 用到组件上:$event表示子组件传递的数据
vue3中v-model新特性
v-model的本质是:modelValue和 @update:modelValue两者的绑定
代码演示
<template>
<div>
父组件
{{info}}
{{msg}}
<hr>
<text-event v-model:modelValue="info" v-model:msg="msg"></text-event>
<!-- 等效写法 -->
<!-- <text-event :modelValue="info" @update:modelValue="info=$event" /> -->
</div>
</template>
<script>
import { ref } from 'vue'
// 引入子组件
import TextEvent from './TextEvent.vue'
export default {
name: 'App',
components: {
// 注册组件
TextEvent
},
setup () {
const info = ref('hi~')
const msg = ref('Leo')
return { info, msg }
}
}
</script>
<template>
<div>
子组件
{{modelValue}}
{{msg}}
<button @click="handleClick">点击</button>
</div>
</template>
<script>
export default {
name: 'TextEvent',
props: {
modelValue: {
type: String,
default: ''
},
msg: {
type: String,
default: ''
}
}
}
</script>
修改父组件传递的值
子组件
{{modelValue}}
{{msg}}
总结:
- v-model可以通过绑定多个属性的方式,向子组件传递多个值并且保证双向绑定
- 可以替代vue2中
.sync
修饰符(sync修饰符在vue3中已经被废弃)
本系列会持续更新,有帮到小伙伴儿的话,记得点赞+收藏哦~