【问题标题】:Are the props in a Vue component mutable?Vue 组件中的 props 是否可变?
【发布时间】:2021-11-12 11:17:20
【问题描述】:

伙计们!最近在做一个 Vue.js 项目,想尽量拆分成小组件。有一个组件从父组件呈现列表,我想从道具更改某些操作的一些属性。在这一点上,我只是好奇 props 变量是否在 Vue.js 组件中是可变的。

下面是一个示例代码以便更好地理解:

<template>
    <div>
        <div
            class="d-flex align-items-center mb-2 justify-between"
            v-for="(option, index) in options"
            :key="index"
        >
            <b-input-group>
                <b-input-group-prepend>
                    <b-button variant="secondary">
                        <b-icon icon="grip-horizontal"></b-icon>
                    </b-button>
                </b-input-group-prepend>

                <b-form-input
                    placeholder="Option"
                    @input="updateOption(index, $event)"
                    :value="option.answer"
                ></b-form-input>

                <b-input-group-append>
                    <b-button variant="secondary" @click="removeOption(index)">
                        Remove
                    </b-button>
                </b-input-group-append>
            </b-input-group>
            <b-form-checkbox
                :id="`option-${index}`"
                name="options"
                class="f-14 text-muted ml-1"
                v-model="option.correct"
            >
                Correct?
            </b-form-checkbox>
        </div>
    </div>
</template>

<script>
export default {
    name: 'multi-options',
    props: ['options'],
    methods: {
        updateOption(index, value) {
            this.options[index].answer = value // Should this work or not?
        },
        removeOption(index) {
            this.options.splice(index, 1)
        },
        addOption() {
            this.options.push({
                answer: '',
                correct: false,
            })
        },
    },
}
</script>

感谢任何评论。

【问题讨论】:

    标签: javascript vue.js mutable vue-props


    【解决方案1】:

    不,从来没有。父/子之间的Vue js通信是一种方式。从父母那里收到的道具永远不能被改变,否则,如果您启用严格模式,您将收到警告。

    父母给孩子的数据必须以props 发送。来自父母的孩子的数据必须在events内发送

    这里最好的方法是在子组件中本地操作数据,通过在组件安装时设置本地数据,然后在发生变化时向父组件发出事件(仅当您需要让父组件知道时发生了什么事)

    props: ['options'],
    
    data: () => ({
        local_options: []
    }),
    
    mounted () {
        this.local_options = [ ...this.options]
    },
    
    watch: {
       options: function () {
           deep: true,
           handler () {
              this.local_options = [ ...this.options]
           }
       }
    },
    
    methods: {
       updateOption(index, value) {
           this.local_options[index].answer = value
           this.$emit('updated', this.local_options)
       },
    
       removeOption(index) {
           this.local_options.splice(index, 1)
           this.$emit('updated', this.local_options)
       },
    
       addOption() {
           this.local_options.push({
               answer: '',
               correct: false,
           })
           this.$emit('updated', this.local_options)
       },
    },
    

    然后在你的父组件中,你有你的子组件,你可以根据子事件做一些事情:

    <multi-options-component :options="options" @updated="doSomething" />
    
    ....
    methods: {
        doSomething (options) {
             // Some logic here
        }
    }
    

    【讨论】:

    • 我建议您从每个方法中的 prop 派生 local_options 而不是将其派生在 mounted 挂钩上,原因有两个:1. 您必须手动确保两个选项在任何时候,这意味着你基本上失去了 Vue 提供的反应性优势。例如doSomething 可能由于某种原因失败,现在您的 local_options 不同步。 2.如果你的子组件由于任何原因从父组件更新(例如options被更新),mounted将不会被再次调用,所以你有问题。
    • 很好,我已经为选项道具添加了一个深度观察者,所以如果父母更新选项孩子也会这样做。
    • 我已经添加了另一个答案,说明为什么这是一个坏主意,原因有很多,而且是一种更好的方法。请检查。
    【解决方案2】:

    我相信@Luciano 喜欢为子节点设置一个单独的本地状态并手动维护其反应性的方式是一种灾难(更不用说考虑到 Vue 已经内置了反应性,它的方式过度杀伤了)。这个我在cmets里有提过,这里再补充一下:

    1. 如果doSomething任何 原因失败,则父级的options 数组将不会更新,而local_options 数组已在组件内更新。现在父状态和本地状态不同步,您遇到了问题。
    2. 无需在您构建的每个可重用组件中使用深层watcher。如果您有数百个这样的子组件,您会遇到性能问题,并且会不必要地消耗大量内存,为每个子组件创建和维护单独的本地状态。
    3. 您正在失去 Vue 在尝试手动确保(尽管以容易出错的方式)本地状态始终与父状态同步时提供的反应性优势。这不是你应该使用 Vue.js 的方式。

    所以我添加了另一个答案,它在可重用性和 DRY 原则方面比我之前的答案更好,同时也简单且无混乱:

    您需要将一个数组与单个自定义 update 事件一起传回父级,但不是在 mounted 生命周期挂钩中创建此数组,而是在每个方法中随时创建它:

    props: ['options'],
    
    methods: {
       updateOption(index, value) {
           let local_options = [ ...this.options]
           local_options[index].answer = value
           this.$emit('update', local_options)
       },
    
       removeOption(index) {
           let local_options = [ ...this.options]
           local_options.splice(index, 1)
           this.$emit('update', local_options)
       },
    
       addOption() {
           let local_options = [ ...this.options]
           local_options.push({
               answer: '',
               correct: false,
           })
           this.$emit('update', local_options)
       },
    },
    

    然后在子组件使用v-on: 指令或其简写@ 发出update 事件时更新您父组件的option 数据

    <multi-options :options="options" @update="handleUpdate" />
    
    ....
    methods: {
        handleUpdate(options) {
             this.options = options; // this reassignment re-renders the child component
        }
    }
    

    编辑:我错过了您在带有v-model="option.correct"&lt;b-form-checkbox&gt; 元素中的道具上使用v-model。这将创建与道具的双向绑定,因为它转换为:checked="option.correct" @change="option.correct = $event"。这意味着您正在改变子组件内的道具。改为这样做:

                <b-form-checkbox
                    :id="`option-${index}`"
                    name="options"
                    class="f-14 text-muted ml-1"
                    :checked="option.correct"
                    @change="setOptionsCorrect(index, $event)"
                >
                    Correct?
                </b-form-checkbox>
    

    methods下添加方法:

    setOptionsCorrect(index, value) {
        let local_options = [ ...this.options]
        local_options[index].correct = value
        this.$emit('update', local_options)
    }
    

    编辑 2:如果您坚持使用 v-model 作为样式偏好,您可以从选项派生一个计算属性。但是你需要一个单独的 setter 来将事件发送给父级,否则你会遇到同样的“mutating props”问题

    computed : {
        computedOptions: {
            get: function() {
                return this.options
            },
            set: function(newOptions) {
                this.$emit("update", newOptions)
            }
        }
    },
    methods: {
       updateOption(index, value) {
           let local_options = [ ...this.options]
           local_options[index].answer = value
           this.options = local_options
       },
    
       removeOption(index) {
           let local_options = [ ...this.options]
           local_options.splice(index, 1)
           this.options = local_options
       },
    
       addOption() {
           let local_options = [ ...this.options]
           local_options.push({
               answer: '',
               correct: false,
           })
           this.options = local_options
       },
    }
    

    【讨论】:

    • 这是没有意义的,一旦你在你的子组件中改变了一个绑定属性,比如 v-model=option.correct ,你将在发出事件之前直接改变道具。
    • @Luciano 这就是为什么你永远不应该在道具上使用 v-model ,它会创建两种方式的绑定。检查对我的答案的编辑
    • 所以您建议不要使用 v-model 指令并在单向数据绑定中处理所有内容?我认为您想利用反应性......如果您愿意,可以将本地数据设置为初始数据状态,但是您在这里所做的只会以大型组件的混乱意大利面条代码结束。请阅读文档vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
    • 确定如果 OP 想简单地将选项作为初始值传递,然后不更新父组件中的道具,正如文档所说,那么在数据函数中设置它是正确的方式(不是在挂载的钩子中,而不是深度观察者)。但是 OP 希望组件的状态与父组件的选项数组同步,在这种情况下,您不应该这样做。我通过将组件绑定到 prop 而不是 local_options 来更新组件,从而利用 Vue 反应性。您能否详细说明这将如何结束意大利面条代码?我渴望学习:)
    • 另外,来自 docs 的 computed 属性是一个好主意,除了我们必须向它添加 getter 和 setter 以使其在 OP 情况下工作。我也会将这种方式添加到我的答案中。
    【解决方案3】:

    你不应该改变组件本身的 props。来自docs,这是为了防止子组件意外改变父组件的状态,这会使您的应用程序的数据流更难理解。

    您可以做的是从子组件发出自定义事件:

    updateOption(index, value) {
        this.$emit("myUpdateEvent", { index, value });
    }
    removeOption(index) {
        this.$emit("myRemoveEvent", { index });
    }
    addOption() {
        this.$emit("myAddEvent");
    }
    

    然后在父组件中监听它们并更改您作为道具传递给子组件的原始数组。

    <multi-options @myAddEvent="handleAddEvent()" @myUpdateEvent="handleUpdateEvent($event.index, $event.value)" @myRemoveEvent="handleRemoveEvent($event.index)" :options="myOptions"/>
    
    data() {
        return {
            myOptions: [],
        }
    },
    methods: {
        handleUpdateEvent(index, value) {
            this.myOptions[index].answer = value
        },
        handleRemoveEvent(index) {
            this.myOptions.splice(index, 1);
        },
        handleAddEvent() {
            this.myOptions.push({
                    answer: '',
                    correct: false,
                });
        }
    }
    

    【讨论】:

    • 这会起作用,但是您通过向父级添加逻辑来耦合您的组件。在可重用性方面,孩子应该操作数组并将变异的数据发送回父母。
    • 对,我写答案的时候没有想到这一点。谢谢:)
    • 我添加了一个新的答案,展示了在解决我们两个答案中的问题时更好的方法
    猜你喜欢
    • 2021-01-15
    • 2018-09-06
    • 2020-09-16
    • 2020-08-26
    • 2023-03-18
    • 1970-01-01
    • 2021-08-26
    • 1970-01-01
    • 2017-07-08
    相关资源
    最近更新 更多