Vue 嵌套组件实现动态表格:子组件emit事件时如何获取父级v-for行索引
两级结构Vue动态表格行索引获取问题解法
问题核心根因
子组件CaRow向上触发update-cellValue自定义事件时,仅传递了列索引、单元格新旧值,没有绑定触发事件对应的行上下文,父组件在统一的全局事件回调中自然无法定位到目标行,也就没办法完成二维数组的单元格值更新。
两种不破坏两级组件架构的实现方案
- 方案1:父组件绑定事件时通过闭包注入当前行索引
不需要修改子组件的现有事件传递逻辑,只需在父组件v-for遍历渲染CaRow时,把当前遍历得到的行索引作为参数注入事件回调即可。
示例代码:
这种方式改动量最小,不需要调整子组件的props定义,适合快速上线的场景。<!-- 父组件模板部分 --> <CaRow v-for="(line, rowIdx) in table" :key="rowIdx" :cellValArr="table[rowIdx]" @update-cellValue="(colIdx, newVal, oldVal) => updateCellVal(rowIdx, colIdx, newVal, oldVal)" /> - 方案2:子组件接收行索引作为prop,触发事件时一并上抛
这种方式更符合Vue单向数据流的设计原则,组件可复用性更强。父组件把行索引通过prop传给CaRow,子组件在emit事件时把这个行索引作为第一个参数抛出,不管父组件怎么绑定事件,都可以直接拿到完整的单元格定位信息。
两种方案都可以保持行组件的独立复用能力,不需要把逻辑拆回单层嵌套结构。
原代码中的隐含问题
- 子组件额外维护了本地
cells数组拷贝prop传入的cellValArr,没有做响应式同步,当父组件的table数据更新时,子组件的本地视图不会同步变化,容易出现数据不一致的问题。可以直接删除本地cells数组,直接用cellValArr做v-for遍历渲染。 - 子组件最外层用
<div>包裹<tr>元素,不符合HTML表格的DOM嵌套规范,会被浏览器自动修正结构,导致样式错乱。可以直接删除外层div,让子组件的根元素为<tr>。 - 子组件通过
event.target.parentElement.cellIndex获取列索引的方式十分脆弱,一旦单元格DOM结构变动(比如新增包裹层)就会失效,直接用v-for遍历生成的列索引即可,不需要操作DOM获取属性。 - 所有单元格共用同一个
id='cell'属性,违反HTML唯一性规范,改成class="cell"即可。
修正后的完整可运行代码
父组件(业务入口)
<template> <table class="center"> <CaRow v-for="(line, rowIdx) in table" :key="rowIdx" :rowIndex="rowIdx" :cellValArr="table[rowIdx]" @update-cellValue="updateCellVal" /> </table> </template> <script> import CaRow from './components/CaRow.vue' export default { name: 'App', components: { CaRow }, data(){ return{ table: [[1,2,3], [4,5,6], [7,8,9]], } }, methods: { // 回调可直接获取行、列索引完成数据更新 updateCellVal(rowIdx, colIdx, newValue, oldValue) { this.table[rowIdx][colIdx] = newValue } }, } </script>
子组件(CaRow行组件)
<template> <tr class="center"> <td class="cell" v-for="(cell, colIdx) in cellValArr" :key="colIdx"> <input type="number" @input="updateCellValueEv(colIdx, $event)" v-model.lazy.trim="cellValArr[colIdx]"/> </td> </tr> </template> <script> export default { name: 'CaRow', props: { cellValArr: { type: Array, default: () => [], }, rowIndex: { type: Number, required: true }, title: { type: String, default: `noNameRow` } }, methods: { updateCellValueEv(colIdx, event){ let val = event.target.value; if (val === '') val = 0; const oldNumber = parseFloat(this.cellValArr[colIdx]); const newNumber = parseFloat(val); if(!isNaN(newNumber) && !isNaN(oldNumber)){ // 触发事件时把行索引一并上抛 this.$emit('update-cellValue', this.rowIndex, colIdx, newNumber, oldNumber); } }, }, } </script>
内容的提问来源于stack exchange,提问作者Rod Bell
相关产品推荐
相关产品推荐

