【问题标题】:How to specify template for Glimmer Component?如何为 Glimmer 组件指定模板?
【发布时间】:2022-08-14 11:36:29
【问题描述】:

我有一个典型的 Glimmer \"base\" 组件:

import Component from \'@glimmer/component\';
export default class BaseComponent extends Component { ... }

它有一个像往常一样的模板,但该组件的实际实现是子组件,它覆盖了一些模板 getter 和参数,以便它与各种不同的数据类型一起工作。

export default class TypeAComponent extends BaseComponent { ... }
export default class TypeBComponent extends BaseComponent { ... }

等等

我的问题是:如何指定所有子组件都应该使用父类模板,这样我就不必为所有子组件复制相同的相当复杂的 HTML?从视觉上看,组件应该看起来相同,因此任何更改都必须在所有子组件类型中复制。因此,多个重复的模板并不理想。

在 Ember Classic 组件中有 layoutlayoutName 属性,所以我可以这样做:

layoutName: \'components/component-name\'

在基础组件中,所有子组件都自动使用定义的模板。

现在我正在迁移到 Glimmer 组件,我似乎无法弄清楚如何做到这一点。我努力了:

  • layout 财产
  • layoutName财产
  • template 财产
  • 在没有模板的情况下使用子组件,希望它们会自动回退到父类模板。

唯一可行的方法是像这样创建 Application Initializer:

app.register(\'template:components/child1-component\', app.lookup(\'template:components/base-component\'));
app.register(\'template:components/child2-component\', app.lookup(\'template:components/base-component\'));

但这感觉太老套了,以至于我决定先在这里问是否有一种我错过的正确方法来做到这一点?

    标签: javascript ember.js glimmer.js


    【解决方案1】:

    如何为 Glimmer 组件指定模板?

    tl;博士:你应该避免这种情况。

    对于两个更具体的问题,有两个答案:

    管理具有共享行为的复杂组件的推荐方法是什么?

    通常,您需要重新编写代码以使用组合或服务。

    作品

    <BaseBehaviors as |myAPI|>
      <TypeAComponent @foo={{myAPI.foo}} @bar={{myAPI.bar}} />
    <BaseBehaviors>
    

    BaseBehaviors 的模板在哪里:

    {{yield (hash
      foo=whateverThisDoes
      bar=whateverThisBarDoes
    )}}
    

    服务

    export default class TypeAComponent extends Component { 
      @service base;
    }
    

    并且可以使用创建服务

    ember g service base
    

    然后,您无需访问this 上的所有内容,而是访问this.base 上的所有内容

    忽略所有建议,我如何在技术上做这件事?

    位于同一位置的组件(js + hbs 作为单独的文件)在构建时合并到一个文件中,其工作方式如下:

    // app/components/my-component.js
    import Component from '@glimmer/component';
    
    export default class MyComponent extends Cmoponent {
     // ..
    }
    
    {{! app/components/my-component.hbs }}
    <div>{{yield}}</div>
    

    上面的js和hbs文件变成下面的单个文件:

    // app/components/my-component.js
    import Component from '@glimmer/component';
    import { hbs } from 'ember-cli-htmlbars';
    import { setComponentTemplate } from '@ember/component';
    
    export default class MyComponent extends Cmoponent {
     // ..
    }
    
    setComponentTemplate(hbs`{{! app/components/my-component.hbs }}
    <div>{{yield}}</div>
    `, MyComponent);
    

    所以这意味着您可以在模块级别的任何地方使用setComponentTemplate,将模板分配给支持类。

    为什么不建议使用其他方法?

    所有这些都是layout 和相关属性没有进入 Octane 的主要原因。

    正式支持的组件继承导致人们变得“聪明”

    这本身并不是什么大问题,因为这是人们可以使用该工具做的事情。糟糕的继承是人们根本不喜欢类的主要原因——以及为什么函数式编程一直在上升——这是有道理的!绝对有点过度纠正,因为最好的代码在适当的时候同时使用 FP 和 OP,并且不会对这些东西变得教条。

    组件继承更难调试

    属于“Foo”但属于“Foo”子类的东西实际上可能不像“Foo”那样工作,因为在 JS 中,没有严格的继承规则,因此您可以覆盖 getter、方法等,并拥有它们提供完全不同的行为。

    这会让想要调试您的代码的人感到困惑。

    此外,当有人尝试进行调试时,他们需要打开更多文件以尝试了解更大的图景,这会增加认知负担。

    组件继承允许人们忽略边界

    这使得单元测试变得更加困难——组件只作为“黑盒”/你看不到的东西进行测试——你测试输入和输出,中间什么都没有。

    如果您确实想测试中间部分,则需要提取常规函数或服务(或对特定事物进行更多渲染测试)。

    【讨论】:

    • 好的。好像没有什么好的解决办法。就我而言,该组件更像是“图像集合”。您提供输入集合(数组)、过滤器(例如“类型”的图像)和各种其他选项,组件显示图像和按钮来管理它们。因此,当单个页面中可以有多个 TypeA 组件时,服务自然不会起作用。我之前也在研究组合,但我认为当前组件具有的属性选项数量会使实现更加复杂。至少与目前添加不同类型的容易程度相比。
    • 您能否就您的要求提供更多详细信息?我可以演示一些不同的组件模式来解决您的问题的根源
    • 让我们看看我是否可以以某种方式简化它以适应这里的 cmets。假设该组件是&lt;ImageASelector @type="foo" @title="bar" @onchange={{callback}} **dozen of other possible options**&gt; 该组件默认情况下是最小化的,但在单击时会展开,因此它不会占用太多空间。展开时还会显示用于管理图像的按钮,例如添加新图像、选择图像等。一个页面可以有十几个这样的组件,选项略有不同。
    • 现在,有几个不同的图像可以有这样的选择器。让我们说“截图”、“视频缩略图”、“照片”。它们保存在不同的数据库模型中,具有不同的属性和要求。这些选择器将使用 90% 的相同代码 100% 的相同模板,但子组件会覆盖一些内部属性,例如最大尺寸、允许的新图像文件格式、关于如何处理新添加图像的内部组件逻辑那种类型的,或者当图像被“选择”时要做什么。
    • 我可以将所有这些子类属性作为附加参数传递。但是,当有半打这些并且它们(并且应该)对于不同的选择器类别总是相同时,这不仅会使主模板混乱,而且在更改或添加某些内容时更难确保它们保持同步。而在内部设置所有属性的子类是完全可靠的,它的所有用途都保持不变。
    【解决方案2】:

    我想说这是作文的经典案例,TypeAComponentTypeBComponent利用BaseComponent

    所以你有你的BaseComponent 和所有的 HTML,基本上就是你的模板.我认为在这里将组件更多地考虑为可能的模板很重要,而不仅仅是完整的组件。所以让我们称之为TemplateComponent

    所以你有你的TemplateComponent,它也可以是一个模板组件。然后你有 TypeAComponentTypeBComponent 的模板:

    <TemplateComponent
      @type={{@title}}
      @title={{@title}}
      @onchange={{@onchange}}
      @propertyThatIsChanged={{this.propertyThatIsChanged}}
      ...
    />
    

    这允许你有一个 getter propertyThatIsChanged 来覆盖碎片。常见的行为也可以放在TemplateComponent,或者,如果它的常见代码,也许在一个只包含共享代码的BaseCodeComponent 上,而我宁愿不这样做。

    对于要替换的区域,这也打开了使用Blocks 的可能性。 例如,TemplateComponent 可以使用has-block 来检查:title 是否存在,然后使用此块({{yield to="default"}}),如果不使用{{@title}}

    因此,唯一明显的缺点是:您必须代理所有参数。起初这看起来很难看,但通常我认为组件不要有太多参数会更好。在某些时候,optionsdata 参数可能会更好,因为它可以在必要时使用 js 构建。还应该提到的是,有一个开放的 RFC that would address this issue。对于即将推出的 SFC,我认为这是整体上更具前瞻性的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-13
      • 2019-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多