如何在 Vue.js 中的组件之间传递数据
已发表: 2022-03-10跨组件共享数据是 VueJS 的核心功能之一。 它允许您设计一个更加模块化的项目,控制数据范围,并在您的应用程序中创建自然的数据流。
除非您在一个组件中创建整个 Vue 应用程序(这没有任何意义),否则您将遇到需要在组件之间共享数据的情况。
在本教程结束时,您将了解完成此任务的三种方法。
- 使用 props 从父级到子级共享数据,
- 发出自定义事件以从子级共享数据到父级,
- 使用 Vuex 创建应用级共享状态。
好的——让我们开始吧!
使用 Nuxt 构建应用程序
使用 Spotify,您的朋友可以查看您正在播放的内容。 如果互联网的其余部分也能体验到您的算法节奏怎么办? 了解如何使用 Vue.js 和 Nuxt 编写您自己的应用程序来分享您在 Spotify 上收听的内容。 阅读相关文章 →
1. 使用道具从父母到孩子共享数据
VueJS props 是在组件之间共享数据的最简单方法。 道具是我们可以赋予组件的自定义属性。 然后,在我们的模板中,我们可以给这些属性值,并且——BAM——我们将数据从父组件传递到子组件!
例如,假设我们正在处理用户个人资料页面并希望让子组件接受用户名道具。 我们需要两个组件。
- 接受道具的子组件,我们称之为
AccountInfo.vue
。 - 传递 prop 的父组件,我们称之为
ProfilePage.vue
。
在AccountInfo.vue
中,我们可以使用 props 选项声明它接受的道具。 所以,在组件选项中,让我们让它看起来像下面这样。
// AccountInfo.vue <template> <div id='account-info'> {{username}} </div> </template> <script> export default { props: ['username'] } </script>
然后,为了实际传递来自父级( ProfilePage.vue
)的数据,我们像自定义属性一样传递它。
// ProfilePage.vue <account-info username='matt' />
现在如果我们加载我们的页面,我们可以看到我们的AccountInfo
组件正确地呈现了其父级传入的值。
与使用其他 VueJS 指令时一样,我们可以使用 v-bind 动态传递 props。 例如,假设我们要将 username 属性设置为等于一个变量。 我们可以通过使用 v-bind 指令的简写来实现这一点(或者:
为 :)。 代码看起来有点像这样:
<template> <div> <account-info :username="user.username" /> </div> </template> <script> import AccountInfo from "@/components/AccountInfo.vue"; export default { components: { AccountInfo }, data() { return { user: { username: 'matt' } } } } </script>
这意味着我们可以更改我们的数据,并且使用该值的任何子道具也将更新。
提示:始终验证您的道具
如果你想编写更清晰的 Vue 代码,一个重要的技术是验证你的 props。 简而言之,这意味着您需要为您的道具指定要求(即类型、格式等)。 如果不满足这些要求之一(例如,如果传递的道具类型不正确),Vue 将打印出警告。
假设我们希望我们的用户名道具只接受字符串。 我们必须修改我们的 props 对象,使其看起来像这样:
export default { props: { username: String } }
在大型 Vue 应用程序中工作或设计插件时,验证 props 是必不可少的。 它有助于确保每个人都在同一页面上,并按照预期的方式使用道具。
有关我们可以在道具中包含的验证的完整列表,我绝对建议您查看官方文档以进行深入审查。
提示:遵循道具命名约定
根据 VueJS 风格指南,命名 props 的最佳方式是在脚本中声明它们时使用camelCase
,在模板代码中引用它们时使用 kebab-case。
这背后的原因其实很简单。 在 Javascript 中, camelCase
是标准命名约定,而在 HTML 中,它是 kebab-case。
因此,Vue 建议我们遵守每种语言的规范。 值得庆幸的是,Vue 能够自动在两种样式之间进行转换,因此开发人员无需进行额外设置。
// GOOD <account-info :my-username="user.username" /> props: { myUsername: String } // BAD <account-info :myUsername="user.username" /> props: { "my-username": String }
2. 发出事件以从子级到父级共享数据
现在我们已经将数据传递到层次结构中,让我们以另一种方式传递它:从子组件到父组件。 我们不能使用道具,但我们可以使用自定义事件和监听器。
每个 Vue 实例都可以调用一个触发事件的.$emit(eventName)
方法。 然后,我们可以使用 v-on 指令以与其他任何方式相同的方式监听此事件。
创建自定义事件
让我们通过添加一个更改用户名的按钮来构建我们的用户配置文件示例。 在我们的子组件( AccountInfo.vue
)中,让我们创建按钮。
然后,当单击此按钮时,我们将发出一个名为changeUsername
的事件。
<template> <div id='account-info'> <button @click='changeUsername()'>Change Username</button> {{username}} </div> </template> <script> export default { props: { username: String }, methods: { changeUsername() { this.$emit('changeUsername') } } } </script>
在父级内部,我们处理此事件并更改user.username
变量。 就像我们之前讨论的那样,我们可以使用 v-on 指令或简称“@”来监听事件。
<template> <div> <account-info :username="user.username" @changeUsername="user.username = 'new name'"/> </div> </template>
让我们试试看。 您应该看到,当您单击按钮时,用户名更改为“新名称”。
提示:自定义事件可以接受参数
将参数传递给事件的最常见用例是当您希望子组件能够为其属性设置特定值时。 你永远不想直接从组件本身编辑 prop 的值。

但是,幸运的是,我们可以在自定义事件中使用传递参数来使父组件更改值。
假设我们要修改changeUsername
事件,以便我们可以向它传递一个值。
$emit
方法采用可选的第二个参数作为参数。 所以我们所做的就是在我们的事件名称之后添加我们的新用户名值。
this.$emit('changeUsername', 'mattmaribojoc')
然后,在我们的父组件中,我们可以使用特殊的$event
变量内联访问这些值,或者我们可以编写一个带有参数的处理程序方法。
<account-info :username="user.username" @changeUsername="user.username = $event"/> OR <account-info :username="user.username" @changeUsername="changeUsername($event)"/> export default { ... methods: { changeUsername (username) { this.user.username = username; } } }
3.使用Vuex创建应用级共享状态
好的——我们知道如何在父母/孩子之间共享数据,但是其他组件呢? 如果我们想传递数据,我们是否必须创建一个极其复杂的层次系统?
谢天谢地没有。 多年来,出色的 Vuex 状态管理库一直在简化开发人员的生活。 简而言之,它创建了一个所有组件都可以访问的集中式数据存储。
在我们之前使用的方法(道具/发射事件)中,每个组件都有自己的数据状态,然后我们在组件之间共享这些状态。 但是,Vuex 让我们将所有共享数据提取到每个组件都可以轻松访问的单个状态中。 这种共享状态称为存储。
让我们试试看。
因为 Vuex 与 Vue 的核心代码是分开的,所以我们首先必须将其安装并导入到我们的项目中。 首先,我们必须在项目 CLI 中运行npm install vuex --save
。
然后,使用包含以下代码的 index.js 文件创建一个 src/store 文件夹。
// store/index.js import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: {}, getters: {}, mutations: {}, actions: {} });
要将它包含在我们的根 Vue 实例中,我们必须导入我们的 store/index.js 文件并将其传递给我们的 Vue 构造函数。
// main.js import store from "./store"; new Vue({ store, ...
访问组件内部的 Vue Store
由于我们将 Vuex 存储添加到根 Vue 实例中,它会被注入到根的所有子节点中。 如果我们想从一个组件访问 store,我们可以通过this.$store
。
现在,让我们深入了解 Vuec 商店的四个部分中的每一个的细节。
1. 状态
Vuex 状态是一个包含应用程序级数据的对象。 所有 Vue 实例都可以访问这些数据。
对于我们的商店,让我们创建一个存储更多用户配置文件数据的用户对象。
export default new Vuex.Store({ state: { user: { username: 'matt', fullName: 'Matt Maribojoc' } }, getters: {}, mutations: {}, actions: {} });
我们可以像这样在任何实例组件中访问这些数据。
mounted () { console.log(this.$store.state.user.username); },
2.吸气剂
我们使用 Vuex getter 返回状态数据的修改值。 考虑 getter 的一个好方法是将它们视为计算属性。 例如,getter 和计算属性一样,缓存它们的结果,并且仅在修改依赖项时重新评估。
在我们之前的商店的基础上,假设我们想要创建一个基于全名属性返回用户名字的方法。
getters: { firstName: state => { return state.user.fullName.split(' ')[0] } }
Vuex getter 属性可用于store.getters
对象上的组件。
mounted () { console.log(this.$store.getters.firstName); }
提示:了解默认的 Getter 参数
默认情况下,Vuex getter 接受两个参数。
- state — 我们应用程序的状态对象;
- getters — store.getters 对象,这意味着我们可以调用我们商店中的其他 getter。
您声明的每个 getter 都需要第一个 state 参数。 根据您设计代码的方式,您的 getter 可以使用第二个“getters”参数相互引用。
让我们创建一个姓氏获取器,它只是从我们的全名状态属性中删除我们的名字值。 这个例子需要 state 和 getters 对象。
lastName (state, getters) { return state.user.fullName.replace(getters.firstName, ''); }
提示:将自定义参数传递给 Vuex Getter
getter 的另一个很酷的特性是我们可以通过让我们的 getter 返回一个方法来传递自定义参数。
prefixedName: (state, getters) => (prefix) => { return prefix + getters.lastName; } // in our component console.log(this.$store.getters.prefixedName("Mr."));
3. 突变
突变是正确更改状态对象值的唯一方法。 需要注意的一个重要细节是突变必须是同步的。
像 getter 一样,mutations 总是接受 Vuex 状态属性作为它们的第一个参数。 它们还接受一个自定义参数(称为有效负载)作为第二个参数。
例如,让我们进行突变以将用户名更改为特定值。
mutations: { changeName (state, payload) { state.user.fullName = payload } },
然后,我们可以使用store.commit
方法从我们的组件中调用此方法,并将我们的有效负载作为第二个参数。
this.$store.commit("changeName", "New Name");
通常情况下,您会希望您的有效负载成为一个对象。 这不仅意味着您可以将多个参数传递给突变,而且由于对象中的属性名称,它使您的代码更具可读性。
changeName (state, payload) { state.user.fullName = payload.newName }
有两种不同的方法可以使用有效负载调用突变。
- 您可以将突变类型作为第一个参数,将有效负载作为第二个参数。
- 您可以声明传递单个对象,其中一个属性用于类型,另一个属性用于有效负载。
this.$store.commit("changeName", { newName: "New Name 1", }); // or this.$store.commit({ type: "changeName", newName: "New Name 2" });
两者的工作方式没有真正的区别,因此完全取决于个人喜好。 请记住,在整个项目中始终保持一致总是最好的,所以无论您选择哪个,坚持下去!
4. 行动
在 Vuex 中,动作与突变非常相似,因为我们使用它们来改变状态。 但是,操作不会改变值本身。 相反,动作会提交突变。
此外,虽然 Vuex 突变必须是同步的,但动作却不是。 例如,使用操作,我们可以在 API 调用之后调用突变。
尽管我们看到的大多数 Vuex 处理程序都接受状态作为它们的主要参数,但动作接受一个上下文对象。 这个上下文对象允许我们访问 Vuex 存储中的属性(例如 state、commit、getter)。
这是一个等待两秒钟然后提交changeName
突变的 Vuex 操作示例。
actions: { changeName (context, payload) { setTimeout(() => { context.commit("changeName", payload); }, 2000); } }
在我们的组件中,我们使用 store.dispatch 方法来运行我们的函数。 我们传递参数就像我们传递突变一样。 我们声明类型并在第二个参数中传递任何自定义参数。
this.$store.dispatch("changeName", { newName: "New Name from Action" });
包起来
现在,您应该知道在 VueJS 中跨组件共享数据的三种不同方式:props、自定义事件和 Vuex 存储。
我希望本教程能帮助您更深入地了解一些不同的 Vue 方法和最佳实践。 让我知道您是如何将它们实施到您的项目中的!
延伸阅读
如果您有兴趣更深入地了解每种技术的技术方面/功能,这里有一些很好的起点。
- Vuex 官方指南网站
- 用于道具和自定义事件的 VueJS 文档
- “WTF 是 Vuex 吗? Vue 应用程序数据存储初学者指南”,Anthony Gore,Vue.js 开发人员