如何为Glimmer Component指定模板?

2izufjch  于 2022-09-28  发布在  其他
关注(0)|答案(2)|浏览(166)

我有一个典型的Glimer“基础”组件:

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

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

export default class TypeBComponent extends BaseComponent { ... }


我的问题是:如何指定所有子组件都应该使用父类模板,这样就不必为所有子组件复制相同的相当复杂的HTML?从外观上看,组件应该看起来是相同的,因此任何更改都必须在所有子组件类型中复制。因此,多个重复模板并不理想。
在Ember Classic组件中,有layoutlayoutName属性,因此我只需执行以下操作:

layoutName: 'components/component-name'

在基本组件和所有子组件中,都会自动使用定义的模板。
现在我正在迁移到Glimmer组件,我似乎不知道如何做到这一点。我尝试过:

  • layout属性
  • layoutName属性
  • template属性
  • 使用没有模板的子组件,希望它们能够自动回退到父类模板。

似乎唯一有效的方法是按如下方式创建应用程序初始化器:

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

但这让我感到很恼火,所以我决定先问一下这里是否有我错过的合适的方法来做这件事?

egmofgnx

egmofgnx1#

如何为Glimmer Component指定模板?

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上的所有内容,而是访问e1c1d1e中的所有内容

忽略所有建议,我在技术上怎么做

位于同一位置的组件(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及其相关特性没有加入辛烷的主要原因。

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

这本身并不是什么问题,而是人们可以用这个工具做什么。糟糕的继承是人们根本不喜欢类的主要原因,也是函数式编程不断增加的原因,这是有道理的!当然,这有点过于纠偏,因为最好的代码在适当的时候同时使用FP和OP,并且不会对这些东西武断。

组件继承更难调试

属于“Foo”但属于“Foo”子类的东西实际上可能不会像“Foo“那样工作,因为在JS中,继承没有严格的规则,所以您可以覆盖getter、方法等,并让它们提供完全不同的行为。
这会让想要调试代码的人感到困惑。
此外,当有人试图进行调试时,他们需要打开更多的文件来理解更大的情况,这会增加认知负荷。

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

这使得单元测试变得更加困难——组件只作为“黑盒”/你看不到的东西进行测试——你测试输入和输出,两者之间没有任何东西。
如果您确实想测试中间层,则需要提取常规函数或服务(或对特定内容进行更多呈现测试)。

1l5u6lss

1l5u6lss2#

我想说这是合成的经典案例,其中TypeAComponent和e1d1e使用BaseComponent
所以你有了BaseComponent和所有的HTML,这基本上就是你的模板。我认为在这里,重要的是要更多地考虑组件以及可能的模板,而不仅仅是完整的组件。因此,让我们称之为TemplateComponent
所以你有了TemplateComponent,它也可以是一个只包含模板的组件。然后,作为TypeAComponentTypeBComponent的模板:

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

这允许您使用getterpropertyThatIsChanged来覆盖片段。常见行为也可以放在TemplateComponent上,或者,如果它的公共代码,可能放在只包含共享代码的BaseCodeComponent中,而我宁愿不这样做。
对于要替换的区域,也可以使用Blocks。例如,TemplateComponent可以使用has-block检查:title是否存在,然后使用该块({{yield to="default"}}),如果不存在,则只使用e1d 15d1e。
因此,唯一明显的缺点是:必须代理所有参数。起初这看起来很难看,但一般来说,我认为组件最好不要有太多的参数。在某种程度上,optionsdata参数可能更好,也因为在必要时可以用js构建。还应该提到的是,有一个开放的RFC that would address this issue。随着即将到来的证监会,我认为这是一个更加经得起未来考验的整体解决方案。

相关问题