在Vue.js 2中将道具作为初始数据传递的正确方法是什么?

Dom*_*fin 74 javascript vue.js

所以我想将道具传递给Vue组件,但我希望这些道具将来会从该组件内部改变,例如当我使用AJAX从内部更新该Vue组件时.所以它们只用于组件的初始化.

我的cars-listVue组件元素,我将具有初始属性的props传递给single-car:

// cars-list.vue

<script>
    export default {
        data: function() {
            return {
                cars: [
                    {
                        color: 'red',
                        maxSpeed: 200,
                    },
                    {
                        color: 'blue',
                        maxSpeed: 195,
                    },
                ]
            }
        },
    }
</script>

<template>
    <div>
        <template v-for="car in cars">
            <single-car :initial-properties="car"></single-car>
        </template>
    </div>
</template>
Run Code Online (Sandbox Code Playgroud)

我现在这样做的方式是在我的single-car组件里面我分配 this.initialProperties给我this.data.propertiescreated()初始化钩子.它起作用并且具有反应性.

// single-car.vue

<script>
    export default {
        data: function() {
            return {
                properties: {},
            }
        },
        created: function(){
            this.data.properties = this.initialProperties;
        },
    }
</script>

<template>
    <div>Car is in {{properties.color}} and has a max speed of {{properties.maxSpeed}}</div>
</template>
Run Code Online (Sandbox Code Playgroud)

但我的问题是我不知道这是否是一种正确的方法呢?这不会给我带来一些麻烦吗?或者有更好的方法吗?

Dom*_*fin 71

感谢这个https://github.com/vuejs/vuejs.org/pull/567我现在知道了答案.

方法1

将初始道具直接传递给数据.与更新文档中的示例一样:

props: ['initialCounter'],
data: function () {
    return {
        counter: this.initialCounter
    }
}
Run Code Online (Sandbox Code Playgroud)

但请记住,如果传递的prop是在父组件状态中使用的对象或数组,则对该prop的任何修改都将导致该父组件状态的更改.

警告:不建议使用此方法.它会使您的组件变得不可预测.如果需要从子组件设置父数据,请使用Vuex之类的状态管理或使用"v-model".https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components

方法2

如果你的初始道具是一个对象或数组,如果你不想让子状态的变化传播到父状态,那么只需使用例如Vue.util.extend[1]制作道具的副本,而不是直接将它指向子数据,如下所示:

props: ['initialCounter'],
data: function () {
    return {
        counter: Vue.util.extend({}, this.initialCounter)
    }
}
Run Code Online (Sandbox Code Playgroud)

方法3

您还可以使用spread运算符来克隆道具.Igor回答的更多细节:https://stackoverflow.com/a/51911118/3143704

但请记住,旧浏览器不支持扩展运算符,为了更好的兼容性,您需要转换代码,例如使用babel.

脚注

[1]请记住这是一个内部Vue实用程序,它可能会随着新版本而改变.您可能希望使用其他方法来复制该prop,请参阅" 如何正确克隆JavaScript对象? ".

我在测试它的小提琴:https: //jsfiddle.net/sm4kx7p9/3/

  • 感谢@DominikSerafin 的精彩问答。这完美地说明了 Vue.js 的致命弱点。作为一个框架,它可以很好地处理组件化,但在组件之间传递数据是一个主要的 PITA。只需看看所创建的噩梦般的命名法即可。所以现在我需要在所有道具前加上“initial”前缀,以便能够在我的组件中使用它们。如果我们可以将一个名为“data”的 prop 传递给任何 comp,并让它自动传播本地化数据,而不需要所有这些“initialPropName”疯狂,那就干净多了。 (2认同)

jon*_*eda 13

我相信你做得对,因为它是在文档中说明的.

定义使用prop的初始值作为其初始值的本地数据属性

https://vuejs.org/guide/components.html#One-Way-Data-Flow

  • 对于按值传递的道具,这很好。在这种情况下,它是一个对象,因此更改它会影响父对象。从该页面:“请注意,JavaScript 中的对象和数组是通过引用传递的,因此如果 prop 是数组或对象,则在子组件内改变对象或数组本身将影响父状态。” (4认同)
  • 因此,经过 4 年和数百小时的学习曲线后,我又回到了刚开始时从“window”对象中弹起变量的地方。 (2认同)

Igo*_*rra 12

与@ dominik-serafin的答案相配:

如果您传递的是opbject,可以使用spread operator(ES6)轻松克隆它:

 props: {
   record: {
     type: Object,
     required: true
   }
 },

data () { // opt. 1
  return {
    recordLocal: {...this.record}
  }
},

computed: { // opt. 2
  recordLocal () {
    return {...this.record}
  }
},
Run Code Online (Sandbox Code Playgroud)

但最重要的是要记住使用opt.2,如果您传递计算值,或者超过异步值.否则,本地值将不会更新.

演示:

Vue.component('card', { 
  template: '#app2',
  props: {
    test1: null,
    test2: null
  },
  data () { // opt. 1
    return {
      test1AsData: {...this.test1}
    }
  },
  computed: { // opt. 2
    test2AsComputed () {
      return {...this.test2}
    }
  }
})

new Vue({
  el: "#app1",
  data () {
    return {
      test1: {1: 'will not update'},
      test2: {2: 'will update after 1 second'}
    }
  },
  mounted () {
    setTimeout(() => {
      this.test1 = {1: 'updated!'}
      this.test2 = {2: 'updated!'}
    }, 1000)
  }
})
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

<div id="app1">
  <card :test1="test1" :test2="test2"></card>
</div>

<template id="app2">
  <div>
    test1 as data: {{test1AsData}}
    <hr />
    test2 as computed: {{test2AsComputed}}
  </div>
</template>
Run Code Online (Sandbox Code Playgroud)

https://jsfiddle.net/nomikos3/eywraw8t/281070/

  • 小心。扩展运算符仅进行浅克隆,因此对于包含对象或数组的对象,您仍然会复制指针而不是获取新副本。我使用 lodash 的 cloneDeep。 (2认同)

小智 7

我第二次或第三次在回到旧的 vue 项目时遇到这个问题。

不知道为什么它在 vue 中如此复杂,但我们可以通过watch完成:

export default {

  props: ["username"],

  data () {
    return {
      usernameForLabel: "",
    }
  },

  watch: {
    username: {
      immediate: true,
      handler (newVal, oldVal) {
        this.usernameForLabel = newVal;
      }
    },
  },
Run Code Online (Sandbox Code Playgroud)