通过构建相同的组件对比 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 允许我们将代码按照功能分组而不是按照属性类型分组。

假设我们的表单组件只拥有两个属性:usernamepassword

Vue2 的代码看着像如下的样子 - 我们把两个数据抛到了 data 属性中。

export default {
  props: {
    title: String
  },
  data () {
    return {
      username: '',
      password: ''
    }
  }
}

Vue 3.0 中,我们得多做一点努力,使用全新的 setup() 方法,所有组件的初始化都应该在此方法中进行。

此外,为方便开发人员更好的掌控响应式编程,我们可以直接访问 Vue 的响应式 API。

创建响应式的数据包括如下三个步骤:

  1. 从 vue 中引入 reactive
  2. 在 reactive 方法中声明对象
  3. setup 方法返回响应式数据,以便模板访问

代码表现就像这样:

import { reactive } from 'vue'

export default {
  props: {
    title: String
  },
  setup () {
    const state = reactive({
      username: '',
      password: ''
    })

    return { state }
  }
}

随后在模板中,即可使用 state.usernamestate.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 来访问属性,提交事件,获取所有属性等操作。

  1. props - 对组件属性的固定访问方式
  2. 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,此函数有两个入参。

  1. 事件名称
  2. 随事件传递的负载对象
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 速查表,内含所有基本操作。它为我节省了很多时间,还避免了无数次重复的谷歌搜索。

Vue.js

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://medium.com/javascript-in-plain-e...

译文地址:https://learnku.com/vuejs/t/52287

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!