首页 > 解决方案 > 如何让具有多个值的子组件更新父数据

问题描述

代码如下。

我想我在这里错过了一个关键的部分。我已经通过文档并一步一步地观看了整个 vue2。到目前为止一切都说得通,但我被困在似乎是核心部分的东西上。任何帮助,将不胜感激。如果这是完全错误的,请告诉我,我没有嫁给这些东西。

期望的功能:有一个orderVue 实例,它有line items.

order.mounted()我们点击订单数据的 api 端点,包括可能的现有订单项。如果存在现有订单项,我们将设置该订单数据(this.lineitems = request.body.lineitems或类似数据)。这部分工作正常,我可以获得订单总额,因为此时订单的订单项是最新的。

每个行项目都是一个带有数量和产品的可编辑表单。如果我更改任何行项目的数量或产品,我希望子行项目组件通知父组件它已更改,然后父组件将使用新值更新其自己的行项目数据数组,并执行 POST 请求所有当前行项目数据,以便服务器端可以计算新的行项目总数(许多特价、折扣等)。这将为订单的订单项数据返回一个完整的替换数组,然后将其传递给订单项以重新呈现。

问题:

  1. 行项目组件“更新...”方法显然是错误的,但我最大的问题是了解如何让父项使用新数据更新其自己的行项目数据数组。例如

​​​

lineitems = [
  {id: 1000, quantity: 3, product: 555, total: 30.00}, 
  {id: 1001, quantity: 2, product: 777, total: 10.00}
]

如果第二个 line item 更改为数量 1,我如何让 parent 的 lineitems 数据更改为这个?我的主要问题是我不知道父级如何知道需要修改它自己的哪些 lineitems 数据数组,以及如何从更改后的子级获取数据。我假设它是通过一个事件,通过发射进入的,但是我现在是否需要在任何地方传递主键,以便我可以进行循环和比较?如果它是一个新的行项目并且还没有主键怎么办?

  1. 上面提到,我使用现有订单项的数据库主键作为 v-for 键。如果我需要一个“新的 lineitem”,在现有的 lineitem 下方附加一个空白 lineitem,或者如果它是一个没有主键的新订单。这个一般是怎么处理的。

  2. 是否有用于道具而不是我的“初始......”风格的最佳实践?我假设只是在 $emit 上直接使用v-on,但我不确定如何获取相关信息以通过这种方式。

这似乎正是 VueJS 适合的任务,我只是觉得我一直在朝着错误的方向追赶我的尾巴。谢谢您的帮助!

订单项

Vue.component('line-item', {
    props: ["initialQuantity", "initialProduct", "total"],
    data () {
        return {
            // There are more but limiting for example
            quantity: initialQuantity,
            product: initialProduct,
            productOptions = [
                { id: 333, text: "Product A"},
                { id: 555, text: "Product B"},
                { id: 777, text: "Product C"},
            ]
        }
    },
    updateQuantity(event) {
        item = {
            quantity: event.target.value,
            product: this.product
        }
        this.$emit('update-item', item)
    },
    updateProduct(event) {
        item = {
            quantity: this.quantity,
            product: event.target.value
        }
        this.$emit('update-item', item)
    }
    template: `
        <input :value="quantity" type="number" @input="updateQuantity">

        <select :value="product" @input="updateProduct">
            <option v-for="option in productOptions" v-bind:value="option.id"> {{ option.text }} </option>
        </select>

        Line Item Price: {{ total }}
        <hr />
    `
})

订单/应用

var order = new Vue({
    el: '#app',
    data: {
        orderPK: orderPK,
        lineitems: []
    },
    mounted() {
        this.fetchLineItems()
    },
    computed: {
        total() {
            // This should sum the line items, like (li.total for li in this.lineitems)
            return 0.0
    },
    methods: {
        updateOrder(item) {
            // First, somehow update this.lineitems with the passed in item, then
            fetch(`domain.com/orders/${this.orderPK}/calculate`, this.lineitems)
                .then(resp => resp.json())
                .then(data => {
                    this.lineitems = data.lineitems;
                })
        },
        fetchLineItems() {
            fetch(`domain.com/api/orders/${this.orderPK}`)
                .then(resp => resp.json())
                .then(data => {
                    this.lineitems = data.lineitems;
                })
        },
    },
    template: `
        <div>
            <h2 id="total">Order total: {{ total }}</h2>

            <line-item v-for="item in lineitems"
                @update-item="updateOrder"
                :key="item.id"
                :quantity="item.quantity"
                :product="item.product"
                :total="item.total"
                ></line-item>
        </div>
    `
})

标签: javascriptvue.jsvuejs2

解决方案


这是您尝试中的问题列表,这些问题将阻止它显示任何内容,即

  1. quantity: initialQuantity, - 你quantity: this.initialQuantity的意思是,... 等所有其他此类数据
  2. }计算总数缺失
  3. 您的line-item模板无效 - 您有多个“根”元素

然后还有一些小问题:

  1. 你想要@change选择的处理程序,而不是@input,如果你的代码运行,你会看到区别,
  2. 同样,您需要@change输入,否则您将发出获取请求以更改每次击键时的项目,可能不是您想要的

所以,尽管如此,我还是生成了一些可以满足你所有需要的工作代码——不过,公平地说,主要是为了我自己的“学习”:p

// ******** some dummy data and functions to emulate fetches
const products = [
    { id: 333, text: "Product A", unitPrice: 10},
    { id: 555, text: "Product B", unitPrice: 11},
    { id: 777, text: "Product C", unitPrice: 12},
];

let dummy = [
    {id: 1, quantity:2, product: 333, total: 20},
    {id: 2, quantity:3, product: 777, total: 36},
];

const getLineItems = () => new Promise(resolve => setTimeout(resolve, 1000, JSON.stringify({lineitems: dummy})));
const update = items => {
    return new Promise(resolve => setTimeout(() => {
        dummy = JSON.parse(items);
        dummy.forEach(item => 
            item.total = parseFloat(
                (
                    item.quantity * 
                    (products.find(p => p.id === item.product) || {unitPrice: 0}).unitPrice *
                    (item.quantity > 4 ? 0.9 : 1.0)
                ).toFixed(2)
            )
        );
        let res = JSON.stringify({lineitems: dummy});
        resolve(res);
    }, 50));
}

//********* lineItem component
Vue.component('line-item', {
    props: ["value"],
    data () {
        return {
            productOptions: [
                { id: 333, text: "Product A"},
                { id: 555, text: "Product B"},
                { id: 777, text: "Product C"},
            ]
        }
    },
    methods: {
        doupdate() {
            this.$emit('update-item', this.value.product);
        }
    },
    template: `
        <p>
            <input v-model="value.quantity" type="number" @change="doupdate()"/>

            <select v-model="value.product" @change="doupdate()">
                <option v-for="option in productOptions" v-bind:value="option.id"> {{ option.text }} </option>
            </select>

            Line Item Price: {{ '$' + value.total.toFixed(2) }}
        </p>
    `
})

//********* Order/App
const orderPK = '';
var order = new Vue({
    el: '#app',
    data: {
        orderPK: orderPK,
        lineitems: []
    },
    mounted() {
        // initial load
        this.fetchLineItems();
    },
    computed: {
        carttotal() {
            return this.lineitems.reduce((a, {total}) => a + total, 0)
        }
    },
    methods: {
        updateOrder(productCode) {
            // only call update if the updated item has a product code
            if (productCode) {
                // real code would be
                // fetch(`domain.com/orders/${this.orderPK}/calculate`, this.lineitems).then(resp => resp.json())
                // dummy code is
                update(JSON.stringify(this.lineitems)).then(data => JSON.parse(data))
                
                .then(data => this.lineitems = data.lineitems);
            }
        },
        fetchLineItems() {

            // real code would be
            //fetch(`domain.com/api/orders/${this.orderPK}`).then(resp => resp.json())
            // dummy code is
            getLineItems().then(data => JSON.parse(data))
            
            .then(data => this.lineitems = data.lineitems);
            
        },
        addLine() {
            this.lineitems.push({
                id: Math.max([this.lineitems.map(({id}) => id)]) + 1, 
                quantity:0, 
                product: 0, 
                total: 0
            });
        }
    },
    template: `
        <div>
            <h2 id="total">Order: {{lineitems.length}} items, total: {{'$'+carttotal.toFixed(2)}}</h2>

            <line-item v-for="(item, index) in lineitems"
                :key="item.id"
                v-model="lineitems[index]"
                @update-item="updateOrder"
            />
            <button @click="addLine()">
                Add item
            </button>
        </div>
    `
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
        <div id="app">
        </div>

注意:里面可能有一些效率低下的代码,请不要判断太苛刻,我才用vuejs一周


推荐阅读