You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在深层数组/对象上创建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 12:17:09