【问题标题】:What is the best way to handle irregular nested object/arrays with Meteor使用 Meteor 处理不规则嵌套对象/数组的最佳方法是什么
【发布时间】:2019-06-06 14:48:13
【问题描述】:

我很难找到一种优雅而有效的方法来处理 nodeJS 中的不规则嵌套数组/对象。不规则是指可以具有 1、2、3 或更多维度的数组/对象。

例如,两种不同的情况:

第一种情况:

"head": {
    "firstElement": {
        "Choice1": [
            "Some text",
            "some text"
        ],
        "Choice2": [
            "Some text",
            "Some text"
        ]
    },
    "SecondElement": [
        "Some text",
        "Some text"
    ]
}

第二种情况:

"head": {
    "firstElement": [
        "Some text",
        "Some text"
    ],
    "secondElement": [
        "Some text",
    ]
}

我想要的是能够提取所有这些,保留除我不想要的“选择”之外的所有内容,最后在 div 中清楚地打印出来。

预期:

<div>
   <h1>FirstElement</h1>
       <h2>Choice#</h2>
           <p>Some text</p>
           <p>Some text</p>
   <h1>SecondElement</h1>
      <p>Some text</p>
      <p>Some text</p>
</div>

HTML 中的嵌套方面不是问题,我只是在算法上苦苦挣扎。

【问题讨论】:

  • 欢迎来到 Stack Overflow。我不清楚您要在这里实现什么...您是否要呈现反映数据结构的 html?
  • 完全正确,但对最终结果进行了后期处理 choices# 并几乎按原样显示(只需将 json 形式转换为 &lt;h1&gt;&lt;h2&gt;&lt;p&gt; 等等。我试过了使用Object.keys()Object.values 来获取和存储所有内容并使用{{#with}}{{#each}} 进行打印。但是由于数据不规则,它会产生错误。也许我可以通过创建一个包含对象的数组来做到这一点一个键和关联的值以及最终嵌套的键/值,并在其上应用{{#each}}
  • 如果你使用 React,我认为这很容易做到。从未对 Blaze 感兴趣,因此无法对此发表评论。你总是可以放入一个 React 组件来做到这一点:)
  • 感谢您的提示!我现在从来没有使用过 React,但我会研究一下,听到关于它的好消息。我想是时候学习了 ;) 我会尝试用 Blaze 的方式来做,但如果它很乱,我会尝试使用 React
  • 一开始我并不热衷于 React,因为它将 js 和标记混搭在同一个文件中,但实际上它是一个奖励,因为您在两者之间没有界限。一旦你理解了语法,你就可以设计任何你喜欢的东西——我认为这是你遇到复杂问题并且模板系统不够灵活的情况下所需要的。

标签: javascript arrays node.js meteor nested


【解决方案1】:

处理任意级别的深度始终是一项具有挑战性的任务,尤其是当结构(如您的示例中)可以在不同类型之间另外变化时。

要处理这样的结构,您可以使用递归模板,它会迭代结构并生成内容或继续向下扫描到最低级别的子级。

<template name="recursive">
    {{#with data}}
        {{#if isString this}}
            <!--
            Print string values directly as paragraph.
            No recursion included.
            -->
            <p>{{this}}</p>
        {{else if isArray this}}
            <!--
            If we have an array f values then iterate
            and call a new template instance but don't 
            increase the level, since the values in the 
            array could also be just strings
            -->
            {{#each this}}
                {{> recursive data=this level=level}}
            {{/each}}
        {{else if isObject this}}
            <!--
            Now with an object we need to transform it into
            an iterable version, first (using the mapped helper).
            We also need to increase the level of nesting using incLevel.
            -->
            {{#each map in (mapped this)}}
                <!-- this is to keep the code more tidy and readable -->
                {{> headline level=level title=map.key}}
                {{> recursive data=map.value level=incLevel}}
            {{/each}}
        {{/if}}
    {{/with}}
</template>

出于可读性的原因,我们可以提取逻辑来确定要渲染到自己的模板中的标题:

<template name="headline">
    {{#if level1 level}}
        <h1>{{title}}</h1>
    {{else if level2 level}}
        <h2>{{title}}</h2>
    {{else}}
        {{level}}
    {{/if}}
</template>

阅读模板代码中的 cmets 以了解其中发生的情况。

现在,由于预期渲染逻辑的复杂性,我们需要一些助手来

  • 检测数据类型
  • 检测深度级别,增加深度级别
  • 将对象映射到可迭代对象
Template.recursive.helpers({
  mapped (obj) {
    return Object.keys(obj).map(key => ({ key, value: obj[ key ] }))
  },
  level() {
    return Template.instance().data.level || 1
  },
  incLevel() {
    const level = Template.instance().data.level
    return level ? level + 1 : 2
  },
  isArray (element) {
    return Array.isArray(element)
  },
  isString (element) {
    return typeof element === 'string'
  },
  isObject (element) {
    return typeof element === 'object'
  }
})

请注意,级别以 1 开头,因为您的标题也以 1 开头。 说到标题,我们还需要标题模板的助手:

Template.headline.helpers({
  level1(lvl) {
    return lvl === 1
  },
  level2(lvl) {
    return lvl === 2
  },
  // ... up to 6
  // then fallback to <strong> if lvl > 6
})

你可以这样调用模板(注意,我把它作为一个例子放在&lt;body&gt;中):

<body>
{{> recursive data=example1}}
{{> recursive data=example2}}
</body>

example1example2 是助手,将数据传递给您的模板:

const example1 = {
  'head': {
    'firstElement': {
      'Choice1': [
        'Some text',
        'some text'
      ],
      'Choice2': [
        'Some text',
        'Some text'
      ]
    },
    'SecondElement': [
      'Some text',
      'Some text'
    ]
  }
}

const example2 = {
  'head': {
    'firstElement': [
      'Some text',
      'Some text'
    ],
    'secondElement': [
      'Some text',
    ]
  }
}

Template.body.helpers({
  example1() {
    return Object.values(example1)
  },
  example2() {
    return Object.values(example2)
  }
})

现在您可能想知道为什么我没有包含以下要求:

保留所有我不想要的“选择”

这是因为有了这样的模板,您已经完成了一项复杂的任务:

  • 检测结构
  • 决定如何呈现内容
  • 渲染内容

将另一个职责挤入这样的模板中会毁掉你的代码(在某些时候,我假设你的结构比这个例子中的更复杂)。

那么你能做些什么来解决这个问题呢?

  • 确保只将数据传递给将要呈现的模板
  • 让“父”模板处理处理(删除不需要的选择),这基本上是一项无需 Blaze 即可完成的任务,因为它主要由查找和过滤组成。
  • 保留此模板之外的任何内容,这超出了呈现某些给定数据的范围。
  • 如果在使用这种封装方法时仍然遇到问题或遇到问题,请对原始数据的嵌套性质进行修订。也许您可以首先对事物进行规范化以接收更线性的结构化数据。

【讨论】:

  • 感谢 Jankapunkt !我考虑过递归模板,但不想这样做,因为我认为存在一种更简单的方法。过滤字段很重要,我必须考虑一个可以使用它的解决方案。我会看看我今天能做什么!
  • 您好,所以我通过更改存储数据的方式解决了我的问题。我没有一个潜在的深层嵌套数组/对象,而是一个对象数组,其中包含一个字符串和我需要对它们进行分组和显示的其他信息。现在看起来像这样(不理想,但工作正常):"Array": [ { "value": "text1", "level": x, "variable": false/true (true if it's a #choice, depending on user selection), "text-type": "h5", "h6", "p" or other "parent": parent value (optional), "type": "type of data (optional), } ]
猜你喜欢
  • 2013-08-18
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 2021-10-28
  • 2021-10-23
  • 2010-12-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多