【问题标题】:Convert prototype to ES6 Class将原型转换为 ES6 类
【发布时间】:2021-12-19 19:01:03
【问题描述】:

尝试将使用原型编写的 js“类”转换为 ES6 类,新版本可以正常运行,但不会在 DOM 中显示任何内容,而旧版本可以。

类如何工作的细节应该不重要,但是,我正在尝试在 preact 中实现一个自定义 Web 组件。

// common imports for completeness
import { h, render, FunctionComponent, VNode } from "preact";
// old version: working ✅

function connectedCallback() {
  this._vdom = toVdom(this, this._vdomComponent);
  render(this._vdom, this._root);
}

function disconnectedCallback() {
  render((this._vdom = null), this._root);
}

function toVdom(element: HTMLElement, nodeName?: FunctionComponent<any>) {
  if (element.nodeType === 3) return element.data;
  if (element.nodeType !== 1) return null;
  const children = Array.from(element.childNodes).reverse().map(cn => toVdom(cn));
  return h(nodeName || element.nodeName.toLowerCase(), {}, children);
}

function createCustomElement(Component: FunctionComponent<any>) {
  function PreactElement() {
    const inst = Reflect.construct(HTMLElement, [], PreactElement);
    inst._vdomComponent = Component;
    inst._root = document.body;
    return inst;
  }
  PreactElement.prototype = Object.create(HTMLElement.prototype);
  PreactElement.prototype.constructor = PreactElement;
  PreactElement.prototype.connectedCallback = connectedCallback;
  PreactElement.prototype.detachedCallback = disconnectedCallback;

  return PreactElement;
}
// new version: not working ❌

const createCustomElement = (Component: FunctionComponent<any>) => {
  return class PreactElement extends HTMLElement {
    private readonly _vdomComponent = Component;
    private readonly _root = document.body;
    private _vdom: VNode<any> | null = null;

    private static toVdom = (element: HTMLElement, nodeName?: FunctionComponent<any>) => {
      if (element.nodeType === 3) return element.data;
      if (element.nodeType !== 1) return null;
      const children = Array.from(element.childNodes).reverse().map(cn => PreactElement.toVdom(cn));
      return h(nodeName || element.nodeName.toLowerCase(), {}, children);
    }

    connectedCallback = () => {
      this._vdom = PreactElement.toVdom(this, this._vdomComponent);
      render(this._vdom, this._root);
    }

    disconnectedCallback = () => {
      render((this._vdom = null), this._root);
    }
  };
};

【问题讨论】:

  • 调试时间!向方法添加断点(如果您的环境不支持,则调用 console.log())。你期望被调用的所有方法实际上都被调用了吗?是否所有变量都具有预期值。给老版本的代码加上同样的调试,有什么不同?
  • "新版本运行时出现错误..." 请出示。
  • 如果有,打字稿应该捕捉它。但是你可以通过在每个方法中添加console.log('myMethodName:this', this) 来验证它,看看它看起来是否正确。
  • 不知道这是否是您的问题的根源,但在工作/原型版本中有一个“detachedCallback”方法,而您的类版本有“disconnectedCallback”。
  • 感谢@AlexWayne,我找到了问题的根源。事实证明,类上的箭头函数没有实现基类的方法。我不得不将 connectedCallback 和 disconnectedCallback 更改为常规类函数。

标签: javascript reactjs typescript web-component preact


【解决方案1】:
const createCustomElement = (Component: FunctionComponent<any>) => {
  return class PreactElement extends HTMLElement {
    private readonly _vdomComponent = Component;
    private readonly _root = document.body;
    private _vdom: VNode<any> | null = null;

    private static toVdom = (element: HTMLElement, nodeName?: FunctionComponent<any>) => {
      if (element.nodeType === 3) return element.data;
      if (element.nodeType !== 1) return null;
      const children = Array.from(element.childNodes).reverse().map(cn => PreactElement.toVdom(cn));
      return h(nodeName || element.nodeName.toLowerCase(), {}, children);
    }

    connectedCallback() {
      this._vdom = PreactElement.toVdom(this, this._vdomComponent);
      render(this._vdom, this._root);
    }

    disconnectedCallback() {
      render((this._vdom = null), this._root);
    }
  };
};

【讨论】: