[vuejs/vue]动态props传递数组默认是双向绑定?

2023-12-09 232 views
8

父组件传给子组件数组的时候,发现在没有使用.sync的情况下,子组件修改此数组会同步到父组件。 不论是普通数组还是对象数组,子组件内对传递下来的数组的增删改操作都会同步到父组件的数组。 issues基本都是英文的,搜索后也不好确定有没有相关问题,因此这里反馈下。

以下代码可复制到 https://jsfiddle.net/yyx990803/okv0rgrk/ 测试

html

<div id="app">
    {{ message }}//{{strs | json}}
    <child :strs="strs"></child>
    <child2 :strs="strs"></child2>
</div>

javascript

var c = Vue.extend({
    template: '<div><h1>{{msg}}</h1></div>' + 
              '<div v-for="str of strs">' + 
              '<input v-model="str"/>' + 
              '<button @click="del(str)">del</button>' +
              '</div>',
    props: ['strs'],
  data: function () {
    return {
        msg: 'child'
    }
  },
  methods: {
    del: function (item) {
        this.strs.$remove(item);
    }
  }
});

var c2 = Vue.extend({
    template: '<div><h1>{{msg}}</h1></div>' + 
              '<div v-for="str of strs"><input v-model="str"/></div>',
    props: ['strs'],
  data: function () {
    return {
        msg: 'child2'
    }
  }
});

new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue.js!',
        str: 'string',
        strs: ['a','s','d','f'],
        objs: [
            {name: 'a'},{name: 'b'},{name: 'c'},{name: 'd'}
        ]
    },
    components: {
        child: c, child2: c2
    }
})

回答

7

是正常的行为,文档说了按引用传递的 props 和是否双向无关。

0

Note that if the prop being passed down is an Object or an Array, it is passed by reference. Mutating the Object or Array itself inside the child will affect parent state, regardless of the binding type you are using.

官网的解释

8

尤大威武,刚准备回复尤大的说法,就发现尤大已经回复并关闭了!

0

正想装个逼。帮答下。然后过了2分钟。尤大回答了,然后closed了。点个赞。

6

请问下,如果传的是对象?又想用单向绑定的话?应该怎么写呢? 我现在的解决办法是在父组件中造一个新对象传入子组件,比如:var a = JSON.parse(JSON.stringify(a)),再把a传下去。 请问这样优雅吗?

1

有个问题,如果引用的props是通过vuex store绑定的,这时候vuex会有mutation inside component的warning,这种情况应该如何处理比较好

8

@shuaibird 对store的改动,不直接在具体组件里改动store里的东西,而是分两步走:1. 写到vuex的mutations里面;2. 在具体组件里commit。这样看起来略烦,但不是多此一举,略增加编写复杂度,减少调试复杂度。

9

@Plasmatium 你说的这个我知道。只是最近在用一些组件库,发现不少组件props传入array时候都会直接对array进行操作。所以貌似目前稳妥的做法只能自己修改组件了

0

@shuaibird 这样的话,可否把传入目标组件props的array先deep copy一份,给该组件自用

考虑到可能确实需要有双向绑定的功能,那么在父组件中,监听这个array的副本,如果被子组件在内部改了,那么就commit给store

0

@Plasmatium 这个我想过,但是遇到一个还没想懂的问题,就是例如这个props是需要通过ajax请求获取的数据,我尝试过通过global event bus去解决这个事,但是总觉得这样做的话会让代码变得一点都不优雅 而且这种实现方法还遇到另外一个问题,不知道组件内的event listener在beforeCreate函数中绑定是不是合理的做法

9

Hi,@shuaibird 根据vuex文档,异步任务建议放在actions里面,不知到这么做是否合理:在actions里面异步获取该props,然后commit一个mutation,这个mutation更新了state.asyncProps,然后getters里面返回该props的深拷贝,供给需要的组件使用。这么做看起来比较啰嗦,但似乎比较符合状态驱动的意思

具体如下,在store的actions里面:

actions: {
  async getProps ({commit}) {
    //...
    await asyncProps = request(url)
    commit('updateProps', { asyncProps })
    //...
  }
}

在store的mutations里面:

mutations: {
  updateProps (state, { asyncProps }) {
    state.props = asyncProps
  }
}

在store的getters里面:

getters: {
  copiedProps (state) {
    return deepCopy(state.props)
  }
}

在代码合适的地方dispatch('getProps', {url})

7

@Plasmatium 今天我想到了一个简单一点的实现方法,就是对store传进component的state进行copy操作到data里边,component的value绑定data相对应的key值,然后watch store传进来的state,变化就对组件内部的data进行copy 另外,之所以会遇到这个问题,是因为我用了一些开源组件。所以个人觉得比较理想的做法是,不对组件的props直接进行修改,例如传进组件的props是array,现在要对这个props进行item删除的操作,我看到的开源组件不少是直接使用splice函数。我自己写的组件则会emit一个新的array,对原array不进行直接的操作 我写的immutableRemove函数

/**
 * remove an element of an array without mutate the original array & return a new one
 * @param  {Array} items
 * @param  {Number} index
 * @return {Array}       Return a new array without the removed element
 */
const immutableRemove = (items, index) => [...items.slice(0, index), ...items.slice(index + 1, items.length)]
0

@shuaibird 是的,第三方直接操作数组的component就只能先给他copy好再传入了

1

考虑到可能确实需要有双向绑定的功能,那么在父组件中,监听这个array的副本,如果被子组件在内部改了,那么就commit给store

@Plasmatium 请问接收props过来的对象后,已经使用computed将其复制一遍,当改变了该computed变量的值后,发现原来的props对象的值也变了,这可能是什么原因?还麻烦指点一下

8

@BoleLee 你是深度复制的么?最好化简为一个小例子贴下代码。

0

@yyx990803 @zhangquxi 我试过这样可以更优雅一点clone prop 请教下尤大大这样合适吗?

props: {
  modules: Object
},
data() {
    return {
      selectedModules: Vue.util.extend({}, this.modules)
    };
  },
6

这种做法是违背了Vue到单向数据流, 但是在多数场景下不会有什么不良影响,所以我的想法是这样的: 如果需要保证自组件的修改不影响父级组件, 那么就在自组件里面深复制一份代码, 否则的话我直接改这个prop的值就行(虽然违背了单向数据流, 但是实现起来简单)