javascript - 对于许多相同组件之间的兄弟通信,我应该如何将数据存储在最低公共祖先中?
问题描述
背景:
- 我是一名 Python/Vue 开发人员;自 2016 年以来,我一直在使用 Vue。
- 我有一个经营减肥/膳食计划业务的客户:客户付钱让她准备每周的单页 PDF 菜单,告诉他们(客户)一周中每天的早餐、午餐和晚餐吃什么。(示例菜单的图像)
- 每顿饭都显示为成分列表。
- 现在她正在 Excel 中准备这些菜单,她聘请我来重现和扩展她在 Excel 中的功能,但在 Python/Vue 应用程序中。
- 我为她构建的应用程序有许多“页面”(“顶级”组件),允许她添加/修改/删除对象,如客户、配料和食谱(图像),但 UI 中最复杂的部分是她可以定义一周中每一天的每一餐的餐食的组件(图片)。该组件名为
WeeklyMenu.vue
. WeeklyMenu.vue
它本身包含七个DailyMenu.vue
孩子,一周中的每一天(周一、周二等)一个。(图片)- 每个
DailyMenu.vue
组件本身包含四个Meal.vue
组件,一种用于四种膳食类型中的每一种:早餐、午餐、晚餐和小吃。(图片) - 重要提示:目前,
DailyMenu.vue
和Meal.vue
组件本身包含它们的数据,而不是从 Vuex 存储中访问它。- 例如,每餐的成分列表
Meal.vue
作为组件属性中的mealIngredients
变量包含在组件中。data
(图片) - 旁注:这意味着当页面加载时,有很多 HTTP 请求被发送到后端,因为所有餐点都在请求自己的数据,而不是通过 Vuex 操作发送单个请求(例如) . 这似乎不是最佳实践。
- 例如,每餐的成分列表
问题:
- 问题是她现在要求我添加功能,其中对一个子组件中的数据的更改应该更新另一个子组件中的数据。
- 例如,她希望应用程序能够正常工作,以便当她在一周的几顿不同餐点中使用相同的食谱时,其中一餐中的成分更改将传播到具有相同食谱的其他餐点。(图片说明)
我的问题:
处理这种情况的最佳做法是什么?我应该将成分数据移动到 Vuex 存储中还是(以同样的方式)最常见的祖先WeeklyMenu.vue
组件?如果是这样,它应该如何工作?每顿饭应该有一个单独的变量吗?或者我应该有一个包含所有不同餐点数据的对象?如果我使用单个对象,我是否需要担心Meal.vue
组件中该对象的观察者会触发,即使对不同餐点的数据进行了更改?
如果我将所有膳食成分存储在单独的变量中,我需要将所有这些传递给每餐(因此每餐都需要接收其他每餐的成分作为单独的道具)。所以这似乎不是正确的方法。
如果用户对特定餐点进行特定更改,我将如何只让具有相同名称的其他餐点做出反应?
相关链接:
- VueJs 2.0 中兄弟组件之间的通信
- 我正在研究将成分数据移动到“最低共同祖先”方法(此处和此处)
WeeklyMenu.vue
中描述的组件级别是否有意义。
- 我正在研究将成分数据移动到“最低共同祖先”方法(此处和此处)
我试图处理的情况的简化示例:
- 没有 Vuex:https ://codepen.io/NathanWailes/pen/zYBGjME
- 使用 Vuex:https ://codepen.io/NathanWailes/pen/WNxWxWe
- 除了传播之外,一切正常(包括保存在 Vuex 中的状态):https ://codepen.io/NathanWailes/pen/KKMYNVZ
解决方案
我最终在 CodePen 中的一个简单示例中让它工作,当我试图让它在实际站点上工作时,我将使用它作为指南。
我对这个解决方案的发现总结是,“当 Vuex 状态对象的嵌套条目更新时,Vue实际上会更新;您不必担心它不会检测到这些更改。所以只保留所有数据就可以了当您有许多需要相互响应的重复同级组件时,在单个大型 Vuex 存储对象中。”
这是 CodePen:https ://codepen.io/NathanWailes/pen/NWRNgNz
截屏
CodePen 示例的功能总结
- 用于填充菜单的数据都存在于 Vuex 存储中的单个
weeklyMenu
对象中,该对象具有子对象以将数据分解为不同的日期/餐点。 - 各个餐点具有计算属性
get
和set
函数,因此它既可以从商店获取更改,也可以更新商店。 - DailyMenu 和 WeeklyMenu 组件通过简单地具有迭代 Vuex
weeklyMenu
对象的计算属性来获取它们的聚合数据,并且它“正常工作”。 - 我通过迭代 Vuex 突变中的膳食并寻找具有相同“成分名称”的膳食来更新同名膳食以相互匹配。
编码
HTML
<html>
<body>
<div id='weekly-menu'></div>
<h3>Requirements:</h3>
<ul>
<li>Each row should have all the numbers in it summed and displayed ('total daily calories').</li>
<li>The week as a whole should have all the numbers summed and displayed ('total weekly calories').</li>
<li>If two or more input boxes have the same text, a change in one numerical input should propagate to the other same-named numerical inputs.</li>
<li>Ideally the data (ingredient names and calories) should be stored in one place (the top-level component or a Vuex store) to make it more straightforward to populate it from the database with a single HTTP call (which is not simulated in this example).</li>
</ul>
</body>
</html>
JavaScript
const store = new Vuex.Store(
{
state: {
weeklyMenu: {
Sunday: {
Breakfast: {
name: 'aaa',
calories: 1
},
Lunch: {
name: 'bbb',
calories: 2
},
},
Monday: {
Breakfast: {
name: 'ccc',
calories: 3
},
Lunch: {
name: 'ddd',
calories: 4
},
}
}
},
mutations: {
updateIngredientCalories (state, {dayOfTheWeekName, mealName, newCalorieValue}) {
state.weeklyMenu[dayOfTheWeekName][mealName]['calories'] = newCalorieValue
const ingredientNameBeingUpdated = state.weeklyMenu[dayOfTheWeekName][mealName]['name']
for (const dayOfTheWeekName of Object.keys(state.weeklyMenu)) {
for (const mealName of Object.keys(state.weeklyMenu[dayOfTheWeekName])) {
const mealToCheck = state.weeklyMenu[dayOfTheWeekName][mealName]
const ingredientNameToCheck = mealToCheck['name']
if (ingredientNameToCheck === ingredientNameBeingUpdated) {
mealToCheck['calories'] = newCalorieValue
}
}
}
},
updateIngredientName (state, {dayOfTheWeekName, mealName, newValue}) {
state.weeklyMenu[dayOfTheWeekName][mealName]['name'] = newValue
}
}
}
)
var Meal = {
template: `
<td>
<h4>{{ mealName }}</h4>
Ingredient Name: <input v-model="ingredientName" /><br/>
Calories: <input v-model.number="ingredientCalories" />
</td>
`,
props: [
'dayOfTheWeekName',
'mealName'
],
computed: {
ingredientCalories: {
get () {
return this.$store.state.weeklyMenu[this.dayOfTheWeekName][this.mealName]['calories']
},
set (value) {
if (value === '' || value === undefined || value === null) {
value = 0
}
this.$store.commit('updateIngredientCalories', {
dayOfTheWeekName: this.dayOfTheWeekName,
mealName: this.mealName,
newCalorieValue: value
})
}
},
ingredientName: {
get () {
return this.$store.state.weeklyMenu[this.dayOfTheWeekName][this.mealName]['name']
},
set (value) {
this.$store.commit('updateIngredientName', {
dayOfTheWeekName: this.dayOfTheWeekName,
mealName: this.mealName,
newValue: value
})
}
}
}
};
var DailyMenu = {
template: `
<tr>
<td>
<h4>{{ dayOfTheWeekName }}</h4>
Total Daily Calories: {{ totalDailyCalories }}
</td>
<meal :day-of-the-week-name="dayOfTheWeekName" meal-name="Breakfast" />
<meal :day-of-the-week-name="dayOfTheWeekName" meal-name="Lunch" />
</tr>
`,
props: [
'dayOfTheWeekName'
],
data: function () {
return {
}
},
components: {
meal: Meal
},
computed: {
totalDailyCalories () {
let totalDailyCalories = 0
for (const mealName of Object.keys(this.$store.state.weeklyMenu[this.dayOfTheWeekName])) {
totalDailyCalories += this.$store.state.weeklyMenu[this.dayOfTheWeekName][mealName]['calories']
}
return totalDailyCalories
}
}
};
var app = new Vue({
el: '#weekly-menu',
template: `<div id="weekly-menu" class="container">
<div class="jumbotron">
<h2>Weekly Menu</h2>
Total Weekly Calories: {{ totalWeeklyCalories }}
<table class="table">
<tbody>
<daily_menu day-of-the-week-name="Sunday" />
<daily_menu day-of-the-week-name="Monday" />
</tbody>
</table>
</div>
</div>
`,
data: function () {
return {
}
},
computed: {
totalWeeklyCalories () {
let totalWeeklyCalories = 0
for (const dayOfTheWeekName of Object.keys(this.$store.state.weeklyMenu)) {
let totalDailyCalories = 0
for (const mealName of Object.keys(this.$store.state.weeklyMenu[dayOfTheWeekName])) {
totalDailyCalories += this.$store.state.weeklyMenu[dayOfTheWeekName][mealName]['calories']
}
totalWeeklyCalories += totalDailyCalories
}
return totalWeeklyCalories
}
},
components: {
daily_menu: DailyMenu
},
store: store
});
推荐阅读
- qt - 如何从数据库中的坐标向地图添加项目?
- elasticsearch - 在 Elasticsearch 中为每个子查询指定大小
- ansible - Ansible yum 模块看不到带有选项 installroot 的存储库
- c# - 跨负载均衡应用程序的并发和锁定
- angular - 如何对对象执行动态变化检测
- javascript - 如何使用多个 .chunk 文件创建文件?
- android - Android - Firestore 离线数据同步
- ios - Facebook 广告管理器未显示 iOS 应用程序中的标准事件
- bash - 如何从数据中提取列并创建新的命名文件
- android - 将 LiveData 转换为 RxJava 可观察对象