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


97

因此,我想将道具传递给Vue组件,但是我希望这些道具将来从该组件内部进行更改,例如,当我使用AJAX从内部更新该Vue组件时。因此它们仅用于组件的初始化。

我的cars-listVue组件元素,我将具有初始属性的道具传递给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>

我现在就做它的方式,我的内部single-car组件我指定 this.initialPropertiesthis.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>

但是我的问题是我不知道这是否是正确的方法?会不会给我带来麻烦?还是有更好的方法呢?


13
我认为这是有关Vue的最令人困惑的事情:每个对象data都是two-way绑定的,但是您不能传递data给组件,而是传递给您props,但是您不能更改接收到的内容,props也不能将转换propsdata。那呢 我了解到的一件事是,您应该传递props并触发事件。也就是说,如果组件想要更改props它收到的组件,则应调用一个事件并进行“渲染”。但是随后您将获得与one-wayReact完全一样的绑定,我看不到data那时的用途。相当混乱。
安德烈·佩纳

1
数据主要用于组件的私有用途。组件上下文中放置在其上的所有内容都是反应性的,可以绑定。props的概念是将值传递到组件中,但通过更改传递的值来防止组件能够在父级中静默引入状态更改。最好按照您的指示在事件中使其明确。这是从Vue 1.0到2.0的理念转变。
David K. Hess

今天,我试图从这里开始讨论:forum.vuejs.org/t/…–
bgraves

Answers:


102

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

方法一

将初始属性直接传递给数据。就像更新文档中的示例一样:

props: ['initialCounter'],
data: function () {
    return {
        counter: this.initialCounter
    }
}

但是请记住,如果传递的道具是在父组件状态中使用的对象或数组,则对该道具的任何修改都将导致该父组件状态发生变化。

警告:不建议使用此方法。它将使您的组件无法预测。如果需要从子组件设置父数据,请使用状态管理(如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)
    }
}

方法3

您还可以使用传播运算符来克隆道具。Igor答案中的更多详细信息:https : //stackoverflow.com/a/51911118/3143704

但是请记住,较旧的浏览器不支持传播运算符,并且为了获得更好的兼容性,您需要翻译代码,例如使用babel

脚注

[1]请记住,这是一个内部Vue实用程序,它可能会随新版本而更改。您可能想使用其他方法来复制该道具,请参阅“ 如何正确克隆JavaScript对象? ”。

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


1
因此,将更改传播到父组件是正常的做法吗?
igor

1
@igor我个人会尽量避免使用它。它将使您的组件无法预测。我认为在父组件中进行更改的更好方法是使用“ v-model”方法,在此方法中,您可以将子组件的更改发送到父组件。vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Dominik Serafin

是的,但是深层物体呢?我应该深复制吗?我应该重新设计组件以不使用深层对象吗?例如,具有以下{ ok: 1, notOk: { meh: 2 } }属性的道具:如果使用任何先前的克隆方法设置为内部数据,则道具仍然会更改notOk.meh
Nikso

1
感谢您出色的问答,@ DominikSerafin。这完美地说明了Vue.js的致命弱点。作为一个框架,它可以很好地处理组件化,但是在组件之间传递数据是主要的PITA。只需看看创建的命名梦night。所以现在我需要在所有道具之前加上前缀initial,以便能够在我的伴奏中使用它们。如果我们只要传递一个调用data到任何comp 的prop 并使其自动地在其中传播本地化数据而没有所有这些initialPropName疯狂,那将会更加清洁。
AJB

1
@DominikSerafin我听到你在说什么,你没看错。但是,使用Vue.js编写一个表单生成器应用程序,您将很快明白我的意思。实际上,我正在将共享数据的技术从prop --> comp.data使用Vue-Stash转换为所有数据(或者Vuex也可以使用)转变。但是现在,我只是window像以前一样在“现代” JS框架强加我的意见之前,从对象中弹回数据。这就引出了一个问题:comp.data属性的意义到底是什么?
AJB

24

与@ dominik-serafin的答案一起:

如果要传递对象,则可以使用传播运算符(ES6语法)轻松克隆它:

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

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

computed: { // opt. 2
  recordLocal () {
    return {...this.record}
  }
},

但是最重​​要的是要记住使用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)
  }
})
<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>

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


嘿@ igor-parra,看来您已经提交了重复的答案。最好提及一下,并非所有浏览器都支持传播运算符,并且可能需要转译步骤才能获得更好的兼容性。否则,它看起来很棒-感谢您添加此替代方法!
多米尼克·塞拉芬

@ dominik-serafin是的,是的,我添加了对ES6的引用。在基于Webpack的应用程序中,预先配置babel以充分利用现代javascript很容易。
伊戈尔·帕拉

recordLocal: [...this.record](在我的情况下,记录是一个数组)对我不起作用-加载并检查了vue组件之后,record其中包含项目并且recordLocal为空数组。
克里斯蒂安·

当我通过COMPUTED属性使用它时,它运行得很好,对我来说,当我尝试在数据内部设置时它不起作用=(...但是我使用computed
felipe muner于

小心。散布运算符仅是浅表克隆,因此对于包含对象或数组的对象,您仍将复制指针而不是获取新副本。我使用lodash的cloneDeep。
辛迪·康威

13

我相信您做对了,因为这是文档中所述的内容。

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

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


4
对于按值传递道具,这很好。在这种情况下,它是一个对象,因此更改它会影响父对象。在该页面上:“请注意,JavaScript中的对象和数组是通过引用传递的,因此,如果prop是数组或对象,则在子组件内部对对象或数组本身进行更改将影响父状态。”
杰克

这对我有用,并且有明确记录。据我所知,以上答案中的这一行与vue文档存在冲突:“警告:不建议使用此方法。这会使您的组件不可预测。如果需要从子组件中设置父数据,请使用状态管理(如Vuex)或“ v-model”。“
Nathaniel Rink,

因此,经过4年和数百小时的学习曲线,我又回到了刚开始从window对象弹起var的地方。
AJB
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.