简体   繁体   English

如何为 Glimmer 组件指定模板?

[英]How to specify template for Glimmer Component?

I have a typical Glimmer "base" component:我有一个典型的 Glimmer “基础”组件:

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

It has a template like normally, but the actual implementations of that component are child componenents, that override some of the template getters and parameters so that it works with various different data types.它有一个像往常一样的模板,但该组件的实际实现是子组件,它覆盖了一些模板 getter 和参数,以便它与各种不同的数据类型一起工作。

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

etc.等等

My question is: How do I specify that all the child components should use the parent class template, so I don't have to duplicate the same fairly complex HTML for all child components?我的问题是:如何指定所有子组件都应使用父 class 模板,这样我就不必为所有子组件复制相同的相当复杂的 HTML? Visually the components are supposed to look identical so any changes would have to be replicated across all child component types.从视觉上看,组件应该看起来相同,因此任何更改都必须在所有子组件类型中复制。 Therefore multiple duplicated templates isn't ideal.因此,多个重复的模板并不理想。

In Ember Classic components there was layout and layoutName properties so I could just do:在 Ember Classic 组件中,有layoutlayoutName属性,所以我可以这样做:

layoutName: 'components/component-name'

in the base component and all child components did automatically use the defined template.在基础组件中,所有子组件都自动使用定义的模板。

Now that I'm migrating to Glimmer components I can't seem to figure out how to do this.现在我正在迁移到 Glimmer 组件,我似乎无法弄清楚如何做到这一点。 I have tried:我努力了:

  • layout property layout属性
  • layoutName property layoutName属性
  • template property template属性
  • Using the child components without a template in hope that they would automatically fall back to the parent class template.使用没有模板的子组件,希望它们会自动回退到父 class 模板。

Only thing that seems to work is creating Application Initializer like this:唯一可行的方法是像这样创建 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'));

But that feels so hacky that I decided to ask here first if there is a proper way to do this that I have missed?但这感觉太老套了,以至于我决定先在这里问一下,是否有一种我错过的正确方法来做到这一点?

How to specify template for Glimmer Component?如何为 Glimmer 组件指定模板?

tl;dr: you should avoid this. tl;博士:你应该避免这种情况。

There are two answers to two, more specific, questions:对于两个更具体的问题,有两个答案:

What is the recommended way to manage complex components with shared behaviors?管理具有共享行为的复杂组件的推荐方法是什么?

Typically, you'll want to re-work your code to use either composition or a service.通常,您需要重新编写代码以使用组合或服务。

Composition作品

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

Where BaseBehaviors' template is: BaseBehaviors 的模板在哪里:

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

Service服务

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

and the service can be created with并且可以使用创建服务

ember g service base

then, instead of accessing everything on this , you'd access everything on this.base然后,您无需访问this上的所有内容,而是访问this.base上的所有内容

Ignoring all advice, how do I technically do the thing ?忽略所有建议,我该如何在技术上做这件事

Co-located components (js + hbs as separate files), are combined into one file at build time, which works like this:位于同一位置的组件(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>

The above js and hbs file becomes the following single file:上面的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);

So this means you can use setComponentTemplate anywhere at the module level, to assign a template to a backing class.所以这意味着您可以在模块级别的任何地方使用setComponentTemplate ,将模板分配给支持 class。

Why is this not recommended over the other approaches?为什么不建议使用其他方法?

All of this is a main reason the layout and related properties did not make it in to Octane.所有这些都是layout和相关属性没有进入 Octane 的主要原因。

Formally supported Component inheritance results in people getting "clever"正式支持的组件 inheritance 导致人们变得“聪明”

this in of itself, isn't so much of a problem, as it is what people can do with the tool.这本身并不是什么大问题,因为这是人们可以使用该工具做的事情。 Bad inheritance is the main reason folks don't like classes at all -- and why functional programming has been on the rise -- which is warranted, Definitely a bit of an over-correction, as the best code uses both FP and OP, when appropriate.糟糕的 inheritance 是人们根本不喜欢类的主要原因——以及为什么函数式编程一直在上升——这是有道理的,绝对有点过度纠正,因为最好的代码同时使用 FP 和 OP,在适当的时候。 and doesn't get dogmatic about this stuff.并且对这些东西没有教条。

Component Inheritance is harder to debug组件 Inheritance 更难调试

Things that are a "Foo" but are a subclass of "Foo" may not actually work like "Foo", because in JS, there aren't strict rules around inheritance, so you can override getters, methods, etc, and have them provide entirely different behavior.属于“Foo”但属于“Foo”子类的东西实际上可能不像“Foo”那样工作,因为在 JS 中,围绕 inheritance 没有严格的规则,因此您可以覆盖 getter、方法等并拥有它们提供完全不同的行为。

This confuses someone who is looking to debug your code.这会让想要调试您的代码的人感到困惑。

Additionally, as someone is trying to do that debugging, they'll need to have more files open to try to under stand the bigger picture, which increases cognitive load.此外,当有人尝试进行调试时,他们需要打开更多文件以尝试了解更大的图景,这会增加认知负担。

Component inheritance allows folks to ignore boundaries组件 inheritance 允许人们忽略边界

This makes unit testing harder -- components are only tested as "black boxes" / something you can't see in to -- you test the inputs and outputs, and nothing in between.这使得单元测试变得更加困难——组件只作为“黑盒”/你看不到的东西进行测试——你测试输入和输出,中间什么都没有。

If you do want to test the in-between, you need to extract either regular functions or a service (or more rendering tests on the specific things).如果您确实想测试中间情况,则需要提取常规函数或服务(或对特定事物进行更多渲染测试)。

I would say this is the classic case for a composition, where TypeAComponent and TypeBComponent use the BaseComponent .我想说这是组合的经典案例,其中TypeAComponentTypeBComponent使用BaseComponent

So you have your BaseComponent with all the HTML, that basically is your template .所以你有你的BaseComponent和所有 HTML,基本上就是你的模板 I think its important here to think a bit more of Components also as possible Templates, not only full Components.我认为在这里将组件更多地考虑为可能的模板很重要,而不仅仅是完整的组件。 So lets call this the TemplateComponent .所以让我们称之为TemplateComponent

So you have your TemplateComponent which could also be a template-only component.所以你有你的TemplateComponent也可以是一个模板组件。 Then you have as template for TypeAComponent and TypeBComponent :然后你有作为TypeAComponentTypeBComponent的模板:

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

this allows you to have a getter propertyThatIsChanged to overwrite pieces.这允许您拥有一个 getter propertyThatIsChanged来覆盖片段。 Common behaviour can also be placed on the TemplateComponent , or, if its common code , maybe on a BaseCodeComponent , that only contains shared code, while I would rather not do this.公共行为也可以放在TemplateComponent上,或者,如果它的公共代码,也许放在只包含共享代码的BaseCodeComponent上,而我宁愿不这样做。

For areas you want to replace this also opens the possibility to use Blocks .对于要替换的区域,这也打开了使用Blocks的可能性。 The TemplateComponent , for example, could use has-block to check if a :title exists, use this block then ( {{yield to="default"}} ), and if not just use {{@title}} .例如, TemplateComponent可以使用has-block来检查:title是否存在,然后使用此块( {{yield to="default"}} ),如果不使用{{@title}}

So, to the only obvious downside of this: you have to proxy all params.因此,唯一明显的缺点是:您必须代理所有参数。 This seems ugly at first, but generally I think its better for components not to have too many arguments.起初这看起来很难看,但通常我认为组件不要有太多 arguments 会更好。 At some point an options or data argument could be better, also because it can be built with js when necessary.在某些时候, optionsdata参数可能会更好,因为它可以在必要时使用 js 构建。 It should also be mentioned that there is an open RFC that would address this issue .还应该提到的是,有一个开放的 RFC可以解决这个问题 With the upcoming SFCs, I think this is the much more future-proof solution overall.对于即将推出的 SFC,我认为这是整体上更具前瞻性的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM