网站搜索

typescript - LitElement:当模板被定义为 const 时,如何调用函数


当模板在单独的文件中定义时,无法在组件中调用函数。但如果模板直接定义,例如返回渲染 HTML 调用 this.funct,它是有效的。当模板在单独的文件中定义时,如何调用函数?

组件.ts

import { LitElement, html} from 'lit-element';
import { cTemplate } from './template/ctemplate';

@customElement('card-form')
export class cardFormComponent extends LitElement {

  constructor() {
    super();
  }

  render() {
    return cTemplate;
  }

  createRenderRoot() {
    return this;
  }

  validateForm() {
    alert('ok');
  }
}


**tempalate.ts**


import {  html } from 'lit-element';

export const cTemplate = html`
 <div>
        <button class="button" @click="${this.validateForm}">Validate</button>
      </div>
';

答案#1

我不确定你为什么要把模板放进另一个文件,但我想你的实际用例可能更复杂,或者你正试图在多个组件中重复使用相同的模板。

无论哪种情况,在尝试回答如何让你的具体示例工作之前,我建议先了解一下智能组件与哑组件的区别。简而言之,这个模板知道的太多了,它知道在按钮被点击时应该调用像 validateForm 这样的函数(这使模板与组件紧密耦合,因此某种程度上违背了将它们放在不同文件中的初衷)。一个可能更好的方法(如果你想解耦的话)是提供一个在按钮点击时运行的回调函数。该回调函数可以是点击按钮时要执行的任何操作。

现在,要修正你的例子(也许还可以让它更灵活),你必须理解在 JavaScript 中 this 的含义。this 是你函数的当前上下文,根据你的函数如何被创建和调用,它会被设置为不同的值。

在你的 template.ts 文件中,你没有一个函数(虽然模块被转译的方式会使其变成一个函数),但你仍然有一个 this,在这个上下文中,this 是模块本身的上下文。所以在你的模板片段中,你试图调用 module.validateForm,但它不存在。这是因为你在模块加载时创建片段,而不是在 render 需要它的时候创建。

解决方案的第一步是将代码片段创建到一个函数中并导出它,以便可以在 render 内部调用该函数来准备代码片段。通过这种方式,你可以开始控制函数内部的 this。我不确定下一步是否必要,但如果 this 仍然不是你的组件类的上下文,你将需要使用 bind 来强制 this 指向组件类。

请参阅下面的完整示例:
Component.ts

import { LitElement, html} from 'lit-element';
import { cTemplate } from './template/ctemplate'; // cTemplate is now a function, not a const

@customElement('card-form')
export class cardFormComponent extends LitElement {

  constructor() {
    super();
    this.cTemplate = cTemplate.bind(this); // This step forces this inside of the cTemplate function to be the component class. This might not be needed.
  }

  render() {
    return this.cTemplate(); // .bind() returns a new function, which we save on the component class so we use this in order to call the new version which has this = component class
  }

  validateForm() {
    alert('ok');
  }
}


**tempalate.ts**


import {  html } from 'lit-element';

export function cTemplate() {
    return html`
        <div>
            <button class="button" @click="${this.validateForm}">Validate</button>
        </div>
    ';
}

为了改进:
BetterComponent.ts

import { LitElement, html} from 'lit-element';
import { cTemplate } from './template/ctemplate';

@customElement('card-form')
export class cardFormComponent extends LitElement {

  constructor() {
    super();
    this.cTemplate = cTemplate.bind(this);
  }

  render() {
    return this.cTemplate(this.validateForm, 'Validate');
  }

  validateForm() {
    alert('ok');
  }
}


**tempalate.ts**


import {  html } from 'lit-element';

export function cTemplate(onClick, label) {
    // cTemplate is now a function that creates a snippet for a button that calls a function when the button is clicked (or, a button "component")
    return html`
        <div>
            <button class="button" @click="${onClick}">${label}</button>
        </div>
    ';
}

我仍然要强调,这感觉像是一种有点奇怪的模式。如果你想创建一个可以重复使用的按钮组件,那么我会建议将其做成一个完整的独立 Web 组件。让 render 返回目前 cTemplate 所做的内容,而在 cardForm 内部,我会让 render 仅仅创建类似于:

html`<my-button label="${'Validate'}" .onClick="${this.validateForm}"></my-button>`

你也可以让 my-button 像真正的按钮一样触发 click 事件,并使用 @click。

答案#2

为澄清您代码片段中的问题:

  1. 你并没有在 template.ts 中定义一个模板函数,你只是在创建一个不变的模板结果。模板通常应该是函数。
  2. 你在 render() 中没有调用模板。
  3. 独立的模板函数不应该使用 this。

这就是我会改写它的方式:

import { LitElement, html} from 'lit-element';
import { cTemplate } from './template/ctemplate';

@customElement('card-form')
export class cardFormComponent extends LitElement {

  render() {
    return cTemplate(this.validateForm, 'Validate');
  }

  validateForm() {
    alert('ok');
  }
}

/** template.ts **/
import {  html } from 'lit-element';

export const cTemplate = (onClick, label) => html`
  <div>
    <button class="button" @click="${onClick}">${label}</button>
  </div>
`;

请注意,我没有绑定模板函数或点击处理程序。相反,将数据作为参数传递给模板,LitElement 会负责使用宿主元素作为 this 值来调用事件处理程序。