Javascript 如何";避免直接对道具进行变异;在递归组件中
我理解这个概念:子对象处理其自己的道具数据副本,当它被更改时,它可以Javascript 如何";避免直接对道具进行变异;在递归组件中,javascript,vue.js,vuejs2,vue-component,Javascript,Vue.js,Vuejs2,Vue Component,我理解这个概念:子对象处理其自己的道具数据副本,当它被更改时,它可以$emit进行更改,以便父对象可以更新自己 然而,我处理的是递归树结构,例如文件系统,这是一个很好的类比 [ { type: 'dir', name: 'music', children: [ { type: 'dir', name: 'genres', children: [ { type: 'dir', name: 'triphop', children: [ { type: 'f
$emit
进行更改,以便父对象可以更新自己
然而,我处理的是递归树结构,例如文件系统,这是一个很好的类比
[
{ type: 'dir', name: 'music', children: [
{ type: 'dir', name: 'genres', children: [
{ type: 'dir', name: 'triphop', children: [
{ type: 'file', name: 'Portishead' },
...
]},
]}
]}
]}
我有一个名为Thing
的递归组件,它采用childtree
属性,看起来有点像这样:
修改名称显然会直接修改道具,这是要避免的,Vue会发出警告
然而,我能看到的避免这种情况的唯一方法是每个组件都做一个childtree
的深度拷贝;然后$emit
复制我们的副本,并让@input
(或类似)将其复制回原始版本;一路爬到树上,这会改变树下的道具,造成另一个所有东西的深度复制
这让人感觉在任何大小的树上都是非常低效的
有更好的办法吗?我知道您可以欺骗Vue,使其不发出错误/警告。解决您的问题的方法是仅从子组件传递事件。
尽管如此,我强烈建议您使用其他解决方案,例如:
通过这种方式,您可以提供一个函数,将基本组件的状态更改为所有子组件(必须注入函数)。单向数据流 每次更新父组件时,子组件中的所有道具都将使用最新值刷新。这意味着您不应尝试在子组件内变异道具。如果您这样做,Vue将在控制台中警告您 请注意,JavaScript中的对象和数组是通过引用传递的,因此 如果道具是数组或对象,则对对象或数组本身进行变异 子组件内部将影响父状态 下面是一个使用 当我们刚开始的时候 当我们使用
因此,如果您想更新子组件中的任何道具值,您需要使用lodash。以下是我大致的处理方法:
<template>
<div>
<input v-model="myName" @input="updateParent()" />
<thing v-for="(child, i) in myChildren" :key="child.uniqueId"
@input="setChild(i, $event)"
>
</thing>
</div>
</template>
<script>
export default {
props: ['node'],
data() {
return {
myName: this.node.name,
myChildren: this.node.children.slice(0),
};
},
methods: {
updateParent() {
this.$emit('input', {name: this.myName, children: this.myChildren});
},
setchild(i, newChild) {
this.$set(this.myChildren, i, newChild);
}
}
}
</script>
导出默认值{
道具:['node'],
数据(){
返回{
myName:this.node.name,
myChildren:this.node.children.slice(0),
};
},
方法:{
updateParent(){
this.$emit('input',{name:this.myName,children:this.myChildren});
},
setchild(i,newChild){
this.$set(this.myChildren,i,newChild);
}
}
}
这意味着树的每个节点:
- 在自己的“名称”副本上工作
- 保存自己的子对象数组(即使子对象是“真实的”-它们是属于对象的引用,不属于要更改的“我们的”,但数组是我们的)
- 通知父级在更新时替换它
- 侦听已更新的子节点
这似乎有效,似乎避免了道具的变异。这里没有显示一件事,但我发现很关键,那就是为每个孩子设置一个唯一的ID。我通过从全局计数器(在
$root
上)顺序创建ID来实现这一点。在您的示例中,“技巧”可以,而“正常”则不行,这正是因为此警告背后的原因;道具在组件更新时被覆盖。双向数据流(技巧中的v模型)可能在大规模上对您不利。单向数据流($emit或Vuex)通常更可取。谢谢,但cloneDeep是我想要避免的。这意味着每个节点都必须深度克隆它下面的整个树。实际上,在您的最后一个组件中[input el]将实现深度克隆,您希望在不向父组件提供任何线索的情况下更改某些内容。我需要在每个级别(如本例中的目录名)更改某些内容,谢谢<代码>$emit:问题是子级的“值”是子级自己的值及其下的整个子树。由于javascript是通过引用传递的,所以在每个阶段都需要进行深度克隆/复制(代价昂贵,速度慢)。Vuex、事件总线和提供/注入都是组件之间共享对象的方式,但在递归情况下,每个组件实例都需要处理嵌套属性,而不是顶级对象。因此,为了使用这些解决方案,它需要维护一个密钥列表来找到自己——这就是v。如果其他节点发生更改,则很难维护。它可以使用一个对象,您需要的是将它们在该对象上的位置传递给嵌套实例,以便它们知道要更改对象的哪一部分。是的,但前提是它们的祖先索引不会更改。e、 g.在根a3.b2.c5处找到的对象-如果在a3之前插入元素,则a3变为a4。或者链中的任何其他位置。这不是必需的,您可以使用一些独特的方法,例如路径:
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
<template>
<div>
<input v-model="myName" @input="updateParent()" />
<thing v-for="(child, i) in myChildren" :key="child.uniqueId"
@input="setChild(i, $event)"
>
</thing>
</div>
</template>
<script>
export default {
props: ['node'],
data() {
return {
myName: this.node.name,
myChildren: this.node.children.slice(0),
};
},
methods: {
updateParent() {
this.$emit('input', {name: this.myName, children: this.myChildren});
},
setchild(i, newChild) {
this.$set(this.myChildren, i, newChild);
}
}
}
</script>