最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Is it possible to create web components without using class? - Stack Overflow

programmeradmin2浏览0评论

If everything in JavaScript es from the prototype, I would imagine there is some way to create web ponents without using class. I never found any article about it, I don't know if it's because it's a bad practice or because it's not possible.

If everything in JavaScript es from the prototype, I would imagine there is some way to create web ponents without using class. I never found any article about it, I don't know if it's because it's a bad practice or because it's not possible.

Share Improve this question edited Jul 17, 2022 at 16:33 Boruto asked Jul 17, 2022 at 16:29 BorutoBoruto 871 silver badge5 bronze badges 17
  • 1 To create a web ponent, you usually use class, but I want to know how it can be created using just functions – Boruto Commented Jul 17, 2022 at 16:36
  • 1 look on the right side, no class used, that's what your be dealing with.. no reason not to not use a class – Lawrence Cherone Commented Jul 17, 2022 at 16:56
  • 2 ... this is a mon phenomena in javascript development. Most javascript programmers (and I'm deliberately not including Typescript programmers) are much more fortable with functional style but the people who actually develop javascript implement javascript in C/C++. Therefore, every time they try to improve the language they create features from the point of view of an OO programmer - which generally tend to not match the way most javascript programmers think... – slebetman Commented Jul 17, 2022 at 18:20
  • 1 ... However, there are excellent frameworks out there written from the point of view of javascript programmers. Check out vue.js and svelte for examples of really good ponent systems that work the way javascript programmers think: using simple functions instead of doing stuff the OO-way. You may even want to look at React.js and it's functional ponent API. – slebetman Commented Jul 17, 2022 at 18:22
  • 2 @slebetman ... The usage of classes is not a criteria which defines an OO language. The usage of functions, especially when mentioning contexts like react/vue/svelte does not make one use a "… functional style …", but a programming style based on functions. Functional starts with the usage of higher order functions. Why am'I nitpicking? Because precise wording matters, especially when it es to ments like above, where the argumentation is close to "we/ours versus theirs". And whatever your preferred JS programming style is, it most probably fulfills more criteria of OOP than of FP. – Peter Seliger Commented Jan 29, 2024 at 20:52
 |  Show 12 more ments

2 Answers 2

Reset to default 11

Yes, it can be done without using the class syntax. The trick is to use Reflect.construct to substitute the call to super. Here's a small example snippet, containing a factory (createCustomElementFactory) to create the 'class' you need for registering a custom element using customElements.define.

To play with a (way more evolved) module for this, see/fork this Stackblitz project.

Worked out the idea into a web ponent factory on Github.

const myCustomElementCreator = createCustomElementFactory();
const connectedCallback = function() {
  const isCustom = this.getAttribute(`is`);

  if (!isCustom) {
    this.style.display = `block`;
    this.style.padding = `2px 5px`;
    this.style.border = `1px solid #999`;
  }

  this.insertAdjacentHTML(`afterbegin`, `<div>I am <i>&lt;${
    this.tagName.toLowerCase()}${
      isCustom ? ` is="${isCustom}"` : ``}></i></div>`);

  console.log(`We just connected the ${
    isCustom ? `custom` : `autonomous`} element '${
      this.tagName.toLowerCase()}${isCustom ? `[is="${isCustom}"]` : ``}'`);
};
const myBrandNewElemClass = myCustomElementCreator({ connectedCallback } );
const myBrandNewParagraphClass = myCustomElementCreator({
  connectedCallback,
  forElem: HTMLParagraphElement }); 

// register 2 elements
customElements.define( "my-brand-new-element", myBrandNewElemClass );

customElements.define( 
  "my-brand-new-paragraph", 
  myBrandNewParagraphClass,
  { extends: `p` } );

document.body.insertAdjacentHTML(
  `beforeend`, 
  `<my-brand-new-element>Hello world!</my-brand-new-element>`
);
document.body.insertAdjacentHTML(
  `beforeend`, 
  `<p is="my-brand-new-paragraph">Hello paragraph!</p>`
);

function createCustomElementFactory() {
  const paramsPlaceholder = ({
    get all() {
      return {
        connectedCallback, 
        disconnectedCallback, 
        adoptedCallback, 
        attributeChangedCallback,
        observedAttributes, 
        forElem } = {}
    }
  });

  function CustomElementConstructorFactory(params = paramsPlaceholder.all) {
    const elemProto = params.forElem?.prototype instanceof HTMLElement 
      ? params.forElem : HTMLElement;
      
    function CustomElementConstructor() {
      if (elemProto !== HTMLElement) {
        self = Reflect.construct( params.forElem, [], CustomElementConstructor );
        return self;
      }

      return Reflect.construct( HTMLElement, [], CustomElementConstructor );
    }

    CustomElementConstructor.prototype = elemProto.prototype;
    CustomElementConstructor.observedAttributes = params.observedAttributes;

    Object.entries( params ).forEach( ([name, cb]) => { 
      if (cb && cb instanceof Function) { 
        CustomElementConstructor.prototype[name] = cb; } 
      } );

    return CustomElementConstructor;
  }

  return (params = paramsPlaceholder.all) => CustomElementConstructorFactory(params);
  }

Based on @Koolinc answer, I was able to extend this to make it work with Dart:

import 'dart:js_interop';
import 'dart:js_interop_unsafe';

import 'package:web/web.dart';

class WebComponent<T extends HTMLElement> {
  late T element;
  String get extendsType => 'HTMLElement';

  void connectedCallback() {}

  void disconnectedCallback() {}

  void adoptedCallback() {}

  void attributeChangedCallback(
    String name,
    String? oldValue,
    String? newValue,
  ) {}

  Iterable<String> get observedAttributes => [];

  R getRoot<R extends JSObject>() {
    final hasShadow = element.shadowRoot != null;
    return (hasShadow ? element.shadowRoot! : element) as R;
  }

  bool get isCustom {
    return element.getAttribute('is') != null;
  }

  static void define(
    String tag,
    WebComponent Function() create, {
    String? extendsTag,
  }) {
    final obj = _factory(create);
    if (extendsTag != null) {
      window.customElements.define(
        tag,
        obj,
        ElementDefinitionOptions(extends_: extendsTag),
      );
    } else {
      window.customElements.define(tag, obj);
    }
  }
}

@JS('Reflect.construct')
external JSAny _reflectConstruct(
  JSObject target,
  JSAny args,
  JSFunction constructor,
);

final _instances = <HTMLElement, WebComponent>{};

JSFunction _factory(WebComponent Function() create) {
  final base = create();
  final elemProto = globalContext[base.extendsType] as JSObject;
  late JSAny obj;

  JSAny constructor() {
    final args = <String>[].jsify()!;
    final self = _reflectConstruct(elemProto, args, obj as JSFunction);
    final el = self as HTMLElement;
    _instances.putIfAbsent(el, () => create()..element = el);
    return self;
  }

  obj = constructor.toJS;
  obj = obj as JSObject;

  final observedAttributes = base.observedAttributes;

  obj['prototype'] = elemProto['prototype'];
  obj['observedAttributes'] = observedAttributes.toList().jsify()!;

  final prototype = obj['prototype'] as JSObject;
  prototype['connectedCallback'] = (HTMLElement instance) {
    _instances[instance]?.connectedCallback();
  }.toJSCaptureThis;
  prototype['disconnectedCallback'] = (HTMLElement instance) {
    _instances[instance]?.disconnectedCallback();
    _instances.remove(instance);
  }.toJSCaptureThis;
  prototype['adoptedCallback'] = (HTMLElement instance) {
    _instances[instance]?.adoptedCallback();
  }.toJSCaptureThis;
  prototype['attributeChangedCallback'] = (
    HTMLElement instance,
    String name,
    String? oldName,
    String? newName,
  ) {
    _instances[instance]?.attributeChangedCallback(name, oldName, newName);
  }.toJSCaptureThis;

  return obj as JSFunction;
}

发布评论

评论列表(0)

  1. 暂无评论