通过构建相同的组件对比 Vue2 与 Vue3
尽管已经有大量的文章介绍过了 Vue3 的巨大变化,但实际上我们并没有真正的 深入 了解我们的代码将如何变化。为展现这些变化,我们将使用 Vue2 和 Vue3 构建一个简单的表单组件。
在文章结尾,你将会理解 Vue2 和 Vue3 之间的主要编程差异,并让你的编程能力逐步变得更好。
如果你想了解如何构建你的第一个 Vue3 程序,请查看我们的初学者教程 Vue3 Alpha app。
征程正式开始,我们的目标是星辰大海!
创建模板
对于大多数组件而言,Vue2 和 Vue3 的代码将非常的 相似。不过 Vue3 支持片段,这意味着一个组件可以拥有多个根节点。
这个特性在列表中渲染组件并移除无用的包装 div 元素时尤其有用。不过在这里,我们让这两个版本的表单组件都保有单个根节点。
<template>
<div class='form-element'>
<h2> {{ title }} </h2>
<input type='text' v-model='username' placeholder='Username' />
<input type='password' v-model='password' placeholder='Password' />
<button @click='login'>
Submit
</button>
<p>
Values: {{ username + ' ' + password }}
</p>
</div>
</template>
唯一的区别是我们访问数据的方式,在 Vue3 中,响应式数据都包装在响应式状态变量中 - 所以我们需要访问该变量去获取相关数据。
<template>
<div class='form-element'>
<h2> {{ state.title }} </h2>
<input type='text' v-model='state.username' placeholder='Username' />
<input type='password' v-model='state.password' placeholder='Password' />
<button @click='login'>
Submit
</button>
<p>
Values: {{ state.username + ' ' + state.password }}
</p>
</div>
</template>
设置响应式数据
这是 Vue2 配置 API 和 Vue3 组件 API 的主要区别。
配置 API 将代码分为不同的属性:数据,计算属性,方法等。与此相对,组件 API 允许我们将代码按照功能分组而不是按照属性类型分组。
假设我们的表单组件只拥有两个属性:username
和 password
。
Vue2 的代码看着像如下的样子 - 我们把两个数据抛到了 data 属性中。
export default {
props: {
title: String
},
data () {
return {
username: '',
password: ''
}
}
}
在 Vue 3.0 中,我们得多做一点努力,使用全新的 setup()
方法,所有组件的初始化都应该在此方法中进行。
此外,为方便开发人员更好的掌控响应式编程,我们可以直接访问 Vue 的响应式 API。
创建响应式的数据包括如下三个步骤:
- 从 vue 中引入
reactive
- 在 reactive 方法中声明对象
- 让
setup
方法返回响应式数据,以便模板访问
代码表现就像这样:
import { reactive } from 'vue'
export default {
props: {
title: String
},
setup () {
const state = reactive({
username: '',
password: ''
})
return { state }
}
}
随后在模板中,即可使用 state.username
和 state.password
访问数据。
在 Vue2 和 Vue3 中创建方法
Vue2 的 Options API 拥有一个独立的方法区域。我们可以在其中定义所有的方法并可按任何方式去组织他们。
export default {
props: {
title: String
},
data () {
return {
username: '',
password: ''
}
},
methods: {
login () {
// 登录方法
}
}
}
Vue3 组件 API 中的 setup 函数也可以处理方法。它的使用方式与数据类似 - 必须先声明方法并 返回,以便组件其他部分访问。
export default {
props: {
title: String
},
setup () {
const state = reactive({
username: '',
password: ''
})
const login = () => {
// 登录方法
}
return {
login,
state
}
}
}
声明周期钩子
在 Vue2 中,可以直接从组件选项中访问 生命周期钩子。在我们的示例中会用到 mounted 事件。
export default {
props: {
title: String
},
data () {
return {
username: '',
password: ''
}
},
mounted () {
console.log('component mounted')
},
methods: {
login () {
// login method
}
}
}
现在有了 Vue3 组件 API ,几乎全部的配置都在 setup() 函数内。其中就包含了 mounted 生命周期钩子。
不过这里默认是不包含生命周期钩子的,所以需要引入 onMounted
作为 Vue3 中调用的方法。这个看起来跟先前引入响应式数据的部分有点像。
随后,在 setup 方法中,可以将 onMounted 方法传递给函数使用。
import { reactive, onMounted } from 'vue'
export default {
props: {
title: String
},
setup () {
// ..
onMounted(() => {
console.log('component mounted')
})
// ...
}
}
计算属性
接下来我们添加计算属性,用于将用户名转换为小写字符。
为了在 Vue2 中完成此操作,我们将添加一个 computed 字段到配置对象中。在此,我们可以像下面这样定义属性:
export default {
// ..
computed: {
lowerCaseUsername () {
return this.username.toLowerCase()
}
}
}
Vue3 的设计 允许开发者引入所需要的内容,并让项目中不存在无必要的包。从本质上讲,他们不希望开发者必须引入无需使用的东西,而这在 Vue2 中是个日益严重的问题。
因此,如果要在 Vue3 中使用计算属性,需要先在组件中引入 computed。
随后,跟之前创建响应式数据差不多,我们可以把响应式数据放到计算属性中使用,代码如下:
import { reactive, onMounted, computed } from 'vue'
export default {
props: {
title: String
},
setup () {
const state = reactive({
username: '',
password: '',
lowerCaseUsername: computed(() => state.username.toLowerCase())
})
// ...
}
}
访问属性
属性的访问在 Vue2 和 Vue3 中有重大区别 - **this**
的含义完全不同。
在 Vue2 中,this
关键字将是整个组件的引用,而不是某个特定属性的引用。虽然这从表面看起来非常简单,不过这让类型支持变得很难。
但是这种设计让我们可以更简单的访问属性 - 做一个简单的样例,就比如在组件挂载时输出 title
属性
mounted () {
console.log('title: ' + this.title)
}
然而在 Vue3 中,我们不再使用 this
来访问属性,提交事件,获取所有属性等操作。
props
- 对组件属性的固定访问方式context
- Vue3 暴露的固定属性(emit, slots, attrs)
使用 props 进行属性访问,上方的代码改写后就像这样:
setup (props) {
// ...
onMounted(() => {
console.log('title: ' + props.title)
})
// ...
}
提交事件
提交事件 与 Vue2 相似都非常简单明了,不过 Vue3 可以让你更好的把控属性或方法的可访问性。
在我们的预想中,想要在按下 “Submit” 按钮后向父组件提交一个登录事件。
Vue2 的代码中只需要调用 this.$emit
并传入数据对象即可。
login () {
this.$emit('login', {
username: this.username,
password: this.password
})
}
不过在 Vue3 中,我们已经知道 this
与 Vue2 不再有着相同的含义,所以这里还需要做一些不一样的事情。
幸运的是,上下文对象暴露了 emit
方法,此方法会提供与 this.$emit
相同的能力。
我们需要做的是,把 context
作为 setup 函数的第二个参数引用进来。然后解构 context 对象以整洁代码。
随后,只需要调用 emit 发送事件即可。一如 Vue2 中的 emit,此函数有两个入参。
- 事件名称
- 随事件传递的负载对象
setup (props, { emit }) {
// ...
const login = () => {
emit('login', {
username: state.username,
password: state.password
})
}
// ...
}
Vue2 与 Vue3 对比的最终代码
很赞,我们的征程已经走到了最后。并且如你所见,Vue2 和 Vue3 所有的概念都是相同的,不过某些访问属性的方式会有所改变。
总的来说,我觉着 Vue3 将帮助开发者编写结构更清晰的diamante - 特别是在大型的代码仓库中。其中最大的原因是组件 API 允许开发者按照特定功能分组,甚至可以提取功能到独立的文件并在需要的时候才引入到组件内。
以下的所有内容都是使用 Vue2 实现的表单组件代码:
<template>
<div class='form-element'>
<h2> {{ title }} </h2>
<input type='text' v-model='username' placeholder='Username' />
<input type='password' v-model='password' placeholder='Password' />
<button @click='login'>
Submit
</button>
<p>
Values: {{ username + ' ' + password }}
</p>
</div>
</template>
<script>
export default {
props: {
title: String
},
data () {
return {
username: '',
password: ''
}
},
mounted () {
console.log('title: ' + this.title)
},
computed: {
lowerCaseUsername () {
return this.username.toLowerCase()
}
},
methods: {
login () {
this.$emit('login', {
username: this.username,
password: this.password
})
}
}
}
</script>
这是 Vue3 的代码:
<template>
<div class='form-element'>
<h2> {{ state.title }} </h2>
<input type='text' v-model='state.username' placeholder='Username' />
<input type='password' v-model='state.password' placeholder='Password' />
<button @click='login'>
Submit
</button>
<p>
Values: {{ state.username + ' ' + state.password }}
</p>
</div>
</template>
<script>
import { reactive, onMounted, computed } from 'vue'
export default {
props: {
title: String
},
setup (props, { emit }) {
const state = reactive({
username: '',
password: '',
lowerCaseUsername: computed(() => state.username.toLowerCase())
})
onMounted(() => {
console.log('title: ' + props.title)
})
const login = () => {
emit('login', {
username: state.username,
password: state.password
})
}
return {
login,
state
}
}
}
</script>
希望本教程有助于你重点理解 Vue2 和 Vue3 代码中看起来不一样的地方。如果还有其他任何疑问,可留言讨论。
祝,编码愉快!
如果你想增强 Vue 开发能力,我创建了一个 所有 Vue 开发者都需要的 3页 PDF 速查表,内含所有基本操作。它为我节省了很多时间,还避免了无数次重复的谷歌搜索。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。