简体   繁体   中英

How can I make StencilJS component to render without component tag itself with TSX?

While I understand this is probably a terrible practice, I need to build StencilJS component such that inside render(), I don't want to render component tag itself due to already existing style guide and it expect DOM to be constructed in certain way. Here is what I'm trying to achieve - component code (from HTML or within another component):

<tab-header-list>
  <tab-header label="tab 1"></tab-header>
  <tab-header label="tab 2"></tab-header>
</tab-header-list>

when rendered, I want generated DOM to be something like:

<tab-header-list>
  <ul>
    <li>tab 1</li>
    <li>tab 2</li>
  </ul>
</tab-header-list>

so inside tab-header-list render() function, I'm doing

return (
  <ul>
    <slot/>
  </ul>
);

and I can do this inside tab-header render() function

@Element() el: HTMLElement;
@Prop() label: string;

render() {
  this.el.outerHTML = `<li>${this.label}</li>`;
}

to get what I want but how can I do this with TSX ? (for simplicity sake, above code is really simple but what I really need to build is lot more complicated li tag with events etc so I would like to use TSX)

Tried to store DOM to variable but I'm not sure how I can assign it as this.el (outerHTML seem to be only way I can come up with, but I feel there must be better way)

@Element() el: HTMLElement;
@Prop() label: string;

render() {
  var tabheaderDOM = (<li>{this.label}</li>);

  // how can I assign above DOM to this.el somehow?
  //this.el.outerHTML = ?
}

I appreciate any help I can get - thanks in advance for your time!

Unfortunately, you can't use custom elements without tags, but there is a workaround for it:

You can use Host element as reference to the result tag.

render () {
  return (
    <Host>....</Host>
  )
}

Then in your stylesheet you can set the display property for it:

:host {
  display: contents;
}

display: contents causes an element's children to appear as if they were direct children of the element's parent, ignoring the element itself

Beware: it doesn't work in IE, opera mini... https://caniuse.com/#feat=css-display-contents

UPD:

If you are not using the shadowDOM then you need to replace :host by the tag name like:

tab-header {
  display: contents;
}

Functional components might be able to help you achieve this. They are merely syntactic sugar for a function that returns a TSX element, so they are completely different to normal Stencil components. The main difference is that they don't compile to web components, and therefore only work within TSX. But they also don't result in an extra DOM node because they simply return the template that the function returns.

Let's take your example:

@Element() el: HTMLElement;
@Prop() label: string;

render() {
  this.el.outerHTML = `<li>${this.label}</li>`;
}

you could write it as a functional component:

import { FunctionalComponent } from '@stencil/core';

interface ListItemProps {
  label: string;
}

export const ListItem: FunctionalComponent<ListItemProps> = ({ label }) => (
  <li>{label}</li>
);

and then you can use it like

import { ListItem } from './ListItem';

@Component({ tag: 'my-comp' })
export class MyComp {
  render() {
    return (
      <ul>
        <ListItem label="tab 1" />
        <ListItem label="tab 2" />
      </ul>
    );
  }
}

Which will render as

<ul>
  <li>tab 1</li>
  <li>tab 2</li>
</ul>

Instead of a label prop you could also write your functional component to accept the label as a child instead:

export const ListItem: FunctionalComponent = (_, children) => (
  <li>{children}</li>
);

and use it like

<ListItem>tab 1</ListItem>

BTW Host is actually a functional component. To find out more about functional components (and there limitations), see https://stenciljs.com/docs/functional-components .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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