vuejs 组件中 props 的深度反应性

Posted

技术标签:

【中文标题】vuejs 组件中 props 的深度反应性【英文标题】:Deep reactivity of props in vuejs components 【发布时间】:2020-11-15 00:51:59 【问题描述】:

我正在尝试构建一个简单的页面构建器,它有一个行元素、它的子列元素和我需要调用的最后一个组件。为此,我设计了一个架构,我将数据集定义到根组件并通过道具将数据推送到其子元素。所以假设我有一个根组件:

<template>
    <div>
        <button @click.prevent="addRowField"></button>
        <row-element v-if="elements.length" v-for="(row, index) in elements" :key="'row_index_'+index" :attrs="row.attrs" :child_components="row.child_components" :row_index="index"></row-element>
    </div>
</template>

<script>
    import RowElement from "../../Components/Builder/RowElement";

    export default 
        name: "edit",
        data()
            return
                elements: [],
            
        ,
        created() 
            this.listenToEvents();
        ,
        components: 
            RowElement,
        ,
        methods:
            addRowField() 
                const row_element = 
                    component: 'row',
                    attrs: ,
                    child_components: [

                    ]
                
                this.elements.push(row_element)
            
        ,
    
</script>

在这里你可以看到我有一个按钮,我正在尝试推送元素,并且它的元素正在通过道具传递给它的子元素,所以这个RowElement 组件有以下代码:

<template>
    <div>
        <column-element v-if="child_components.length" v-for="(column,index) in child_components" :key="'column_index_'+index"  :attrs="column.attrs" :child_components="column.child_components" :row_index="row_index" :column_index="index"></column-element>
    </div>
    <button @click="addColumn"></button>
</template>

<script>
    import ColumnElement from "./ColumnElement";
    export default 
        name: "RowElement",
        components: ColumnElement,
        props: 
            attrs: Object,
            child_components: Array,
            row_index: Number
        ,
        methods:
            addColumn(type, index) 
                this.selectColumn= false
                let column_element = 
                    component: 'column',
                    child_components: []
                ;
                let component = 

                //Some logic here then we are emitting event so that it goes to parent element and there it can push the columns

                eventBus.$emit('add-columns', column: column_element, index: index);
            
        
    
</script>

所以现在我必须在根页面上监听事件,所以我有:

eventBus.$on('add-columns', (data) => 
    if(typeof this.elements[data.index] !== 'undefined')
        this.elements[data.index].child_components.push(data.column)
);

现在我需要再次访问这些数据 ColumnComponent 所以在 columnComponent 文件中我有:

<template>
    //some extra div to have extended features
    <builder-element
        v-if="!loading"
        v-for="(item, index) in child_components"
        :key="'element_index_'+index" :column_index="column_index"
        :element_index="index" class="border bg-white"
        :element="item" :row_index="row_index"
    >
    </builder-element>
</template>

<script>
    export default 
        name: "ColumnElement",
        props: 
            attrs: Object,
            child_components: Array,
            row_index: Number,
            column_index: Number
        ,
    
</script>

还有我最后的BuilderElement

<template>
    <div v-if="typeof element.component !== 'undefined'" class="h-10 w-10 mt-1 mb-2 mr-3 cursor-pointer  font-bold text-white rounded-lg">
        <div>element.component</div>
        <img class="h-10 w-10 mr-3" :src="getDetails(item.component, 'icon')">
    </div>
    <div v-if="typeof element.component !== 'undefined'" class="flex-col text-left">
        <h5 class="text-blue-500 font-bold">getDetails(item.component, 'title')</h5>
        <p class="text-xs text-gray-600 mt-1">getDetails(item.component, 'desc')</p>
    </div>
</template>

<script>
    export default 
        name: "BuilderElement",
        data()
            return
                components:[
                    id: 1, title:'Row', icon:'/project-assets/images/row.png', desc:'Place content elements inside the row', component_name: 'row',
                    //list of rest all the components available
                ]
            
        ,
        props: 
            element: Object,
            row_index: Number,
            column_index: Number,
            element_index: Number,
        ,
        methods:
            addElement(item,index)
                //Some logic to find out details
                let component_element = 
                    component: item.component_name,
                    attrs: ,
                    child_components: [
                    ]
                

                eventBus.$emit('add-component', component: component_element, row_index: this.row_index, column_index: this.column_index, element_index: this.element_index);

            ,
            getDetails(component, data) 
                let index = _.findIndex(this.components, (a) => 
                    return a.component_name === component;
                )
                console.log('Component'+ component);
                console.log('Index '+index);
                if(index > -1) 
                    let component_details = this.components[index];
                    return component_details[data];
                
                else
                    return null;
            ,
        ,
    
</script>

如您所见,我再次发出名为 add-component 的事件,该事件再次在根组件中进行侦听,因此在以下侦听器中进行了侦听:

eventBus.$on('add-component', (data) => 
    this.elements[data.row_index].child_components[data.column_index].child_components[data.element_index] = data.component
);

它显示了我的vue-devtools 中的数据集,但它没有出现在构建器元素中:

图片 FYR:

这是我的根组件:

这是我的行组件:

这是我的列组件:

这是我的构建器元素:

我不知道为什么这些数据没有传递给它的子组件,我的意思是最后一个组件对道具没有反应,任何更好的想法都非常感谢。

谢谢

【问题讨论】:

复制问题需要一段时间,但您是否尝试过使用 this.$set 设置动态属性? @pai.not.pi 你有什么例子吗?在小提琴/jsbin 中? 【参考方案1】:

问题在于您在 addComponent 方法中设置数据的方式。

当您通过直接修改数组的索引来更改数组时,Vue 无法获取更改,例如,

arr[0] = 10

正如他们在change detection guide 中定义的数组突变,

Vue 包装了观察到的数组的变异方法,因此它们也将 触发视图更新。包装的方法是:

    推() pop() shift() unshift() 拼接() 排序() 反向()

所以你可以改变。

this.elements[data.row_index].child_components[data.column_index].child_components[data.element_index] = data.component

到,

this.elements[data.row_index].child_components[data.column_index].child_components.splice(data.element_index, 1, data.component);

【讨论】:

以上是关于vuejs 组件中 props 的深度反应性的主要内容,如果未能解决你的问题,请参考以下文章

没有模板的 vuejs 反应性

Vue子组件(deep)深度监听props对象属性无效的解决办法

从刀片 Laravel 传递时,Prop 反应性不起作用

VueJs 组件的 Prop 相关计算属性对对象数组 Prop 更改没有反应

错误:超过最大更新深度。反应原生

如何在Vuejs中将反应性道具传递给孩子