赞
踩
侃侃而谈
我们将从以下几大方向来说明他们的区别及使用差异:
创建项目的环境
对TS的支持程度
API风格
数据状态的创建方式
监听机制
页面组件根节点个数
生命周期钩子
内置组件
VueRouter路由管理器
【特别说明】:下文中的Vue2指的是小于Vue2.7的版本,因为Vue官方为了依赖的兼容、浏览器的兼容性等,将Vue3的一些特性移植回到了Vue2.7这个版本
一、创建项目的环境
Vue2环境的创建主要通过官方提供的VueCL脚手架进行创建,VueCL脚手架是基于webpack的静态打包工具(webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。)
//安装脚手架
npm install -g @vue/cli
# OR
yarn global add @vue/cli
//创建一个项目
vue create my-project
# OR
vue ui
Vue2创建的项目结构
【tips】:目前脚手架仍处于维护中对于新项目请用以下方式创建。
2. Vue3不再使用webpack而是使用自家研发的Vite作为静态打包构建工具。(Vite是下一个现代的前端开发与构建工具,其构建速度比webpack更加快速,无论应用程序大小如何,都始终极快的模块热替换(HMR),对 TypeScript、JSX、CSS 等支持开箱即用,对Vue提供了一等公民支持。)
//使用命令行创建一个项目
npm init vue@latest
//项目配置
✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add an End-to-End Testing Solution? … No / Cypress / Playwright
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
Scaffolding project in ./<your-project-name>...
Done.
Vue3创建的项目结构
【tips】:Vue2/3两者在创建的时候可以选择使用JS开发或者是TS开发。
二、对TS的支持程度
Vue2/3都支持TS,因为Vue本身就是使用TS写的,但Vue3对于TS的支持程度及性能会更加友好,因为Vite本身对TS也是开箱即用的,不需要过多配置。
而Vue2需要一些配置且对于较大的项目来说可能会遇到性能瓶颈,例如可能半天才会TS类型提示,对于新项目建议使用Vue3配合Vite开发,对于Vue2老项目官方也建议迁移到Vite上进行开发。
三、API风格
众所周知Vue2和Vue3API风格上是不同的,Vue2只有选项式API(options API),Vue3在此基础上增加了组合式API(composition API),也就是说Vue3中既可以用选项式API也可以用组合式API进行开发。
选项式API
选项式 API 以“组件实例”的概念为中心 (即上述例子中的 this),对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致。同时,它将响应性相关的细节抽象出来,并强制按照选项来组织代码,从而对初学者而言更为友好。
https://cn.vuejs.org/guide/introduction.html#api-styles
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件处理器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
【tips】:选项式开发其实就是在固定的实例中做固定的事情,可以通过this访问相应实例,非常直观又好理解。另外选项式API中也可以使用setup()的方式使用组合式函数API。
组合式API
组合式 API 的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对 Vue 的响应式系统有更深的理解才能高效使用。相应的,它的灵活性也使得组织和重用逻辑的模式变得更加强大。
https://cn.vuejs.org/guide/introduction.html#api-styles
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import type { Ref} from 'vue'
// 响应式状态
const count:Ref<number> = ref(0)
// 用来修改状态、触发更新的函数
function increment():void{
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
【tips】:组合式开发摆脱了以往的固定实例束缚,可以在任何地方使用组合式API,比如说在.js文件中,甚至可以从.js文件中导出响应性的状态,相当于一个.js文件就是一个组件,而这个组件中既可以用组合式的API也可以享受到强大的原生js的编程能力。
那么问题来了,什么时候应该使用选项式?什么时候应该用组合式?
官方是这么说的,选项API其实是基于组合式API实现的,对于初学者来说建议使用选项式API,因为这样更容易理解和上手。两者的使用场景如下:
选项式API:
未使用构建工具时(未使用构建工具时一般都会使用选项式,因为组合式是针对单文件组件的。如:在传统的HTML中经常使用选项式API,开发特别方便。)
项目复杂度较低时
渐进增强的应用
组合式API:
使用构建工具
在单文件组件中(*.vue)
复杂的大型应用
完整的单页面应用开发
四、数据状态的创建方式
Vue2
Vue2创建数据主要通过data(){ return {}}中创建数据,以函数形式返回状态这样每个组件会创建一个独立的数据,组件与组件间状态互不影响。
//选项式方式创建
<template>
<div class="">
{{ name }}
<button @click="changeName()">改变</button>
</div>
</template>
<script>
export default {
data() { //创建数据
return {
name: '三叶雨'
}
},
mounted() {//生命周期函数
this.name = 'hello 三叶雨'
},
methods: {//方法定义
changeName() {
this.name = '改变 三叶雨'
}
}
}
</script>
<style lang="scss"></style>
【tips】:Vue2.7版本也可以像Vue3那样采用组合式函数创建数据状态。
Vue3
Vue3的数据状态主要通过ref和reactive来创建,他们都会返回一个响应性的对象,其中在函数中访问ref时需要通过.value的形式访问定义的状态,在template中则不需要它会自动解包。
另外ref和reactive两者的使用场景也是有一定区别的
ref:
解决了reactive的缺点,组合式函数中推荐使用ref定义状态。
reactive:
有限值类型:它只适用于对象类型(对象、数组和集合类型诸如Map
和Set
)
无法替换整个对象:因为vue的响应性时通过对象的属性进行追踪的,一旦被替换就会丢失其响应性
不支持解构:一旦进行对象解构,解构的状态响应性会丢失
//组合式函数方式创建
<template>
<div class="">
{{ name }}
<button @click="changeName()">改变</button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import type { Ref } from 'vue'
// 创建数据
const name: Ref<string> = ref('三叶雨')
// 生命周期函数
onMounted(() => {
name.value = 'hello 三叶雨'
})
// 方法定义
function changeName(): void {
name.value = '改变 三叶雨'
}
</script>
<style lang="scss"></style>
//选项式中使用组合式API
<template>
<div class="">
{{ name }}
<button @click="changeName()">改变</button>
</div>
</template>
<script lang="ts">
import { ref, reactive, onMounted } from 'vue'
import type { Ref } from 'vue'
export default {
setup() {
// 创建数据
const name: Ref<string> = ref('三叶雨')
// 返回值会暴露给模板和其他的选项式 API 钩子(状态必须导出)
return {
name
}
},
mounted() {
//生命周期函数
this.name = 'hello 三叶雨'
},
methods: {
//方法定义
changeName(): void {
this.name = '改变 三叶雨'
}
}
}
</script>
<style lang="scss"></style>
五、监听机制
Vue2
Vue2响应性是基于Object.defineProperty()实现的,它本身有一些限制,比如说不可以监测到对象属性的删除与添加,不可以监测到数组的变化。但我们发现其实使用Vue2时是可以监测的以上这些改变的,那是因为Vue官方抹平了这些限制。
原理:
Vue2的响应式原理其实结合了Object.defineProperty()的数据劫持,以及发布订阅者模式,数据劫持就是通过递归遍历data里的数据,用Object.defineProperty()给每一个属性添加getter和setter,并且把data里的属性挂载到vue实例中,修改vue实例上的属性时,就会触发对应的setter函数,向订阅器发布更新消息,对应的Watcher订阅者会收到通知,调用自身的回调函数,让编译器去更新视图,从而达到数据驱动视图的目的。
Object.defineProperty()
会接收三个参数:
obj 需要观察的对象;
prop 是 obj 上的属性名;
descriptor 对 prop 属性的描述。
//响应系统的简易核心代码
// 相当于在 data(){ return { }} 中创建的数据
const data = {
name: '三叶雨',
age: 25,
obj: {
a: 1,
b: 2
}
}
observer(data)
function observer(data) {
//观察数据
if (typeof data !== 'object' || data == null) {
return
}
const keys = Object.keys(data)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
let value = obj[key]
defineReactive(obj, key, value)
}
}
function defineReactive(obj, key, value) {
observer(value)
// 数据劫持
Object.defineProperty(obj, key, {
get() {
console.log(`有人读取props里的${key}属性`)
return value
},
set(newValue) {
console.log(`有人修改了props里的${key}属性,值为${newValue},需要去更新视图`)
if (newValue === value) return
observer(newValue)
value = newValue
}
})
}
【tips】:从以上代码中可以发现,当数据嵌套较深时,是较为耗费性能的,而Vue3则很好的解决了这个问题。
2. Vue3
Vue3响应性是基于ES6语法中的ProxyAPI对数据进行代理,他监测的是整个对象而不再是对象的某个属性,消除了基于Object.defineProperty()实现的不能监测对象的属性的删除与增加以及数组的变化等诸多问题。
原理:
Vue3的数据劫持通过Proxy函数对代理对象的属性进行劫持,通过Reflect对象里的方法对代理对象的属性进行修改,代理对象不需要遍历,配置项里的回调函数可以通过参数拿到修改属性的键和值
这里用到了Reflect对象里的三个方法,get,set和deleteProperty,方法需要的参数与配置项中回调函数的参数相同。
Reflect里的方法与Proxy里的方法是一一对应的,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。
new Proxy() 会接收两个个参数:
target:需要使用 Proxy 进行观察的目标对象;
handler:对目标对象属性进行处理的对象,包含了处理属性的回调函数等。
//响应系统的简易核心代码
const target = {
name: '三叶雨',
age: 25,
obj: {
a: 1,
b: 2
}
}
var p = reactive(target)
console.log(p.name) // 获取值: 三叶雨
p.obj.a = 10 // 获取值: {a : 1}
console.log(p.obj.a) // 获取值: {a : 10}
function isObject(data) {
if (typeof data !== 'object' || data == null) {
return false
}
return true
}
function reactive(target) {
return createReactiveObject(target)
}
function createReactiveObject(target) {
let observed
// 判断如果不是一个对象的话返回
if (!isObject(target)) return target
// target观察前的原对象;proxy观察后的对象:observed
observed = new Proxy(target, {
get(target, key, receiver) {
const res = target[key]
console.log('获取值:', res)
// todo: 收集依赖...
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
console.log('设置值:', value)
target[key] = value
}
})
return observed
}
六、页面组件根节点个数
Vue2
在Vue2中组件页面的根节点只能有一个标签节点,所有内容都应在此标签下创建。
<template>
<div>
{{msg}}
</div>
</template>
<script>
export default{
data(){
return{
msg:"我是根节点"
}
}
}
</script>
<style lang="scss" scoped>
</style>
Vue2的应用实例只能有一个
import Vue from "vue";
import App from "./App.vue";
new Vue({
render: (h) => h(App),
}).$mount("#app");
Vue3
在Vue3中页面的根节点可以有多个,而不需要全部内容包裹在一个根节点下面。
<template>
<div>
{{msg1}}
</div>
<div>
{{msg2}}
</div>
<div>
{{msg3}}
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import type { Ref } from 'vue'
const msg1:Ref<string>=ref('我是根节点1')
const msg2:Ref<string>=ref('我是根节点2')
const msg3:Ref<string>=ref('我是根节点3')
</script>
Vue3的应用实例可以有多个
const app1 = createApp({
/* ... */
})
app1.mount('#container-1')
const app2 = createApp({
/* ... */
})
app2.mount('#container-2')
七、生命周期钩子
Vue2生命周期函数
beforeCreate:组件创建前
created:组件创建完成后
beforeMount:组件挂载节点前
mounted:组件挂载完成后
beforeUpdate:组件更新前
updated:组件更新完成后
beforeDestroy:组件销毁前
destroyed:组件销毁完成后
keep-alive 生命周期(组件被keep-alive包含时)
activated:调用时机为首次挂载以及每次从缓存中被重新插入时
deactivated:在从 DOM 上移除、进入缓存以及组件卸载时调用
Vue3生命周期函数
Vue3生命周期函数(以下生命周期针对组合式API,选项式生命周期同Vue2)
setup:开始创建组件之前,在beforeCreate和created之前执行。
onBeforeMount() : 组件挂载到节点前
onMounted() : 组件挂载完成后
onBeforeUpdate(): 组件更新前
onUpdated(): 组件更新完成后
onBeforeUnmount(): 组件卸载前
onUnmounted(): 组件卸载完成后
keep-alive 生命周期(组件被keep-alive包含时)
onActivated:调用时机为首次挂载以及每次从缓存中被重新插入时
onDeactivated:在从 DOM 上移除、进入缓存以及组件卸载时调用
我们对比上方两版本的生命周期函数不难发现,vue3使用setup代替了beforeCreate和created生命周期函数,将beforeDestroy及destroyed更名为beforeUnmount及unmounted,同时在生命周期函数前增加了“on”关键字,总体来说生命周期都一致。
八、父子组件通信
1.Vue2组件通信
vue2中数据由prop属性定义来接收,可通过数组或对象方式定义接收的属性,通过$emit传递事件及数据。
父组件向子组件传递数据
//父组件 HomeView
<template>
<div class="">
<ChildComponent :name="parentName"></ChildComponent>
</div>
</template>
<script lang="ts">
import ChildComponent from '@/components/ChildComponent.vue'
export default {
name: 'HomeView',
components: {
ChildComponent
},
data() {
return {
parentName: '三叶雨'
}
}
}
</script>
<style lang="scss"></style>
//子组件 ChildComponent
<template>
<div class="">
{{ name }}
</div>
</template>
<script lang="ts">
export default {
name: 'ChildComponent',
props: {
name: {
type: String,
default: ''
}
},
data() {
return {}
}
}
</script>
<style lang="scss"></style>
子组件向父组件传递数据或事件
// 子组件 ChildComponent
<template>
<div class="">
<span>{{ name }}</span>
<!--使用$emit传递数据或事件给父组件 -->
<button @click="changeName">改变名字</button>
<!-- 与上方写法等价 -->
<!-- <button @click="$emit('changeName', { newName: '我是三叶雨的雨' })">改变名字</button> -->
</div>
</template>
<script lang="ts">
export default {
name: 'ChildComponent',
props: {
name: {
type: String,
default: ''
}
},
data() {
return {}
},
methods: {
changeName(): void {
this.$emit('changeName', { newName: '我是三叶雨的雨' })
}
}
}
</script>
<style lang="scss"></style>
<template>
<div class="">
<ChildComponent :name="parentName" @change-name="onChangeName"></ChildComponent>
</div>
</template>
<script lang="ts">
import ChildComponent from '@/components/ChildComponent.vue'
export default {
name: 'HomeView',
components: {
ChildComponent
},
data() {
return {
parentName: '三叶雨'
}
},
methods: {
onChangeName(data: { newName: string; }) {
this.parentName = data.newName
}
}
}
</script>
<style lang="scss"></style>
2.Vue3组件通信
vue3的组合式API中没有this的概念,接收数据由宏defineProps进行接收,可通过数组或对象形式定义接收的属性,通过
父组件向子组件传递数据
//父组件 HomeView
<template>
<div class="">
<ChildComponent :name="parentName"></ChildComponent>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import type { Ref } from 'vue'
import ChildComponent from '@/components/ChildComponent.vue'
const parentName: Ref<string> = ref('三叶雨')
</script>
<style lang="scss"></style>
//子组件 ChildComponent
<template>
<div class="">
<span>{{ name }}</span>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { withDefaults } from 'vue'
defineProps<{
name: string
}>()
</script>
<style lang="scss"></style>
子组件向父组件传递数据或事件
//子组件 ChildComponent
<template>
<div class="">
<span>{{ name }}</span>
<button @click="changeName">改变名字</button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { withDefaults } from 'vue'
defineProps<{
name: string
}>()
// const emits = defineEmits['changeName']
const emits = defineEmits<{
(e: 'changeName', data: { newName: string }): void
}>()
function changeName() {
emits('changeName', {
newName: '我是三叶雨的雨'
})
}
</script>
<style lang="scss"></style>
//父组件 HomeView
<template>
<div class="">
<ChildComponent :name="parentName" @change-name="onChangeName"></ChildComponent>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import type { Ref } from 'vue'
import ChildComponent from '@/components/ChildComponent.vue'
const parentName: Ref<string> = ref('三叶雨')
function onChangeName(data: { newName: string }) {
parentName.value = data.newName
}
</script>
<style lang="scss"></style>
九、内置组件
vue3中新增了两个内置组件
Teleport
一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。可以通过“to”指定要传递到哪个标签下。
使用场景:一般用于消息弹窗、盒子弹窗popup等,解决了嵌套深,css样式难写的问题。
Suspense(实验性)
是一个内置组件,用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。
十、VueRouter路由管理器
Vue3中因为引入了组合式API,而这个API中没有this的概念,所以引入了一些新的函数,用来替代this.$router和this.$route,他们是useRouter函数和useRoute函数。
this.$router 对应 const router=useRouter()
this.$route 对应 const route=useRoute()
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import type { Ref } from 'vue'
import type { ArticleInfo } from "@/types/views/ArticleDetailView.vue"
import { useRouter, useRoute } from 'vue-router'
import { api } from '@/assets/config/api'
import { request } from '@/assets/common/request'
const router = useRouter()
const route = useRoute()
const articleInfoData: Ref<Array<ArticleInfo>> = ref([])
onMounted(async () => {
await getArticleInfo()
.then((res) => {
articleInfoData.value = res
})
.catch((err) => {
console.log(err)
})
})
function getArticleInfo(): Promise<Array<ArticleInfo>> {
return new Promise((resolve, reject) => {
request
.get(api.GetArticleInfo, {
id: route.params.id
})
.then((res) => {
resolve(res?.data?.data)
})
.catch((err) => {
reject(err)
})
})
}
function navToDetail(id: number): void {
router.push({
name: 'article_detail',
params: {
id: id
}
})
}
</script>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。