如何在深层数组/对象上创建v-model并简化冗余代码?
优化深层数组属性的v-model绑定与索引查找
核心思路
通过通用递归查找函数定位目标节点,再用带get/set的计算属性实现简洁的v-model绑定,既适配节点位置动态变化的场景,又彻底消除冗余代码。
1. 实现通用节点查找函数
编写递归函数,根据传入的tagName路径链(如['mj-head', 'mj-attributes', 'mj-all'])从template中逐层定位目标节点:
// 递归查找指定tagName链的节点 const findNodeByTagChain = (nodes, tagChain) => { if (!nodes || tagChain.length === 0) return null const currentTag = tagChain[0] const node = nodes.find(item => item.tagName === currentTag) if (!node) return null // 递归遍历子节点,直到匹配完整个链 return tagChain.length === 1 ? node : findNodeByTagChain(node.children, tagChain.slice(1)) }
2. 用计算属性封装双向绑定
创建计算属性,通过get方法读取目标属性值,set方法更新属性,满足v-model的双向绑定需求:
const globalFontFamily = computed({ get() { const targetNode = findNodeByTagChain(template.value, ['mj-head', 'mj-attributes', 'mj-all']) return targetNode?.attributes?.['font-family'] || '' }, set(newValue) { const targetNode = findNodeByTagChain(template.value, ['mj-head', 'mj-attributes', 'mj-all']) if (targetNode) { targetNode.attributes['font-family'] = newValue } } })
3. 简化模板中的v-model绑定
直接将输入框的v-model绑定到上述计算属性:
<input v-model="globalFontFamily" placeholder="Fonte geral" type="text">
4. 移除冗余索引变量
原有的headTagIndex、headTagAttributesIndex、headTagAttributesAllIndex变量可直接删除,由通用查找函数替代。
优化后的完整代码
<template> <div class="builder"> <div class="sidebar"> <div class="block"> <input v-model="globalFontFamily" placeholder="Fonte geral" type="text"> </div> </div> <div class="editor"> <div v-if="compiledTemplate" v-html="compiledTemplate.html"/> <div v-else>Loading</div> </div> <div class="ads"/> </div> </template> <script setup> const {$mjml} = useNuxtApp() const template = ref([ { "tagName": "mj-head", "children": [ { "tagName": "mj-attributes", "children": [ { "tagName": "mj-all", "attributes": { "font-family": "Comic sans-serif" } } ] } ] }, { "tagName": "mj-body", "children": [ { "tagName": "mj-section", "attributes": { "background-color": "red" }, "children": [ { "tagName": "mj-column", "attributes": { "padding-top": "100px", "padding-bottom": "100px" }, "children": [ { "tagName": "mj-text", "attributes": { "align": "center", "color": "#F45E43" }, "content": "Testing component" } ] } ] }, { "tagName": "mj-section", "attributes": { "full-width": "full-width", "background-color": "blue" }, "children": [ { "tagName": "mj-column", "attributes": { "padding-top": "100px", "padding-bottom": "100px" }, "children": [ { "tagName": "mj-text", "attributes": { "align": "center", "color": "#F45E43" }, "content": "Testing component" } ] } ] }, { "tagName": "mj-section", "attributes": { "background-color": "pink" }, "children": [ { "tagName": "mj-column", "attributes": { "padding-top": "100px", "padding-bottom": "100px" }, "children": [ { "tagName": "mj-text", "attributes": { "align": "center", "color": "#F45E43" }, "content": "Testing component" } ] } ] }, { "tagName": "mj-section", "attributes": { "full-width": "full-width", "background-color": "teal" }, "children": [ { "tagName": "mj-column", "attributes": { "padding-top": "100px", "padding-bottom": "100px" }, "children": [ { "tagName": "mj-text", "attributes": { "align": "center", "color": "#F45E43" }, "content": "Testing component" } ] } ] } ] } ]) // 递归查找指定tagName链的节点 const findNodeByTagChain = (nodes, tagChain) => { if (!nodes || tagChain.length === 0) return null const currentTag = tagChain[0] const node = nodes.find(item => item.tagName === currentTag) if (!node) return null return tagChain.length === 1 ? node : findNodeByTagChain(node.children, tagChain.slice(1)) } // 封装全局字体的双向绑定计算属性 const globalFontFamily = computed({ get() { const targetNode = findNodeByTagChain(template.value, ['mj-head', 'mj-attributes', 'mj-all']) return targetNode?.attributes?.['font-family'] || '' }, set(newValue) { const targetNode = findNodeByTagChain(template.value, ['mj-head', 'mj-attributes', 'mj-all']) if (targetNode) { targetNode.attributes['font-family'] = newValue } } }) const compiledTemplate = computed(() => $mjml({ "tagName": "mjml", "children": template.value })) </script>
扩展说明
- 若需绑定更多深层属性,只需复制
globalFontFamily的模式,修改tag链和属性名即可。例如绑定第一个mj-section的背景色:const firstSectionBgColor = computed({ get() { const targetNode = findNodeByTagChain(template.value, ['mj-body', 'mj-section']) return targetNode?.attributes?.['background-color'] || '' }, set(newValue) { const targetNode = findNodeByTagChain(template.value, ['mj-body', 'mj-section']) if (targetNode) { targetNode.attributes['background-color'] = newValue } } }) - 递归函数自动适配节点位置变化,无需手动更新索引,代码维护性大幅提升。
内容的提问来源于stack exchange,提问作者Caio Kawasaki
相关产品推荐
相关产品推荐

