typescript - Hur tillämpar man de återstående props som passerar in i komponenten och inte är deklarerade med @Prop-dekoratorn?
Jag konverterar en React-komponent jag byggt till en Stencil-webbkomponent, och jag är osäker på hur jag hämtar alla props som skickats till komponenten som inte definierats med @Prop-dekoratorn. Här är min React-kod:
import { ButtonHTMLAttributes } from "react";
export default function CoreButton({
name,
children,
...props
}: ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button
name={`example ${name}`}
{...props}
>
{children}
</button>
);
}
Och här är konceptuellt hur jag vill att min Stencil-kod ska fungera:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'core-button',
})
export class CoreButton {
@Prop() name: string;
render() {
return (
<button name={`example ${this.name}`} {...this.restProps}>
<slot />
</button>
);
}
}
Jag vill ha möjligheten att utöka vilken prop som helst som normalt skulle kunna skickas in i , avlyssna de jag vill lägga till anpassad logik för genom att deklarera dem med @Prop och sedan sprida de återstående propsen på det faktiska elementet utan att hårdkoda hundratals attribut per anpassad komponent. Tack.
Svar #1
Nej. Det är inte möjligt. Webkomponenter är lite mer komplicerade än traditionella React-komponenter.
Webbkomponent är i grunden ett förbättrat HTMLElement. Så om du försöker sprida DOM-elementet får du ingenting:
const a = document.createElement('div');
a.tabIndex = 1;
const b = { ...a } ;
// Output: {}
Så, i ditt fall är det meningslöst att göra {...this} och också {...this.restProps} betyder ingenting eftersom egenskapen restProps inte finns på ditt anpassade element.
Att nu deklarera en egendom med @Prop-dekoreraren gör två saker.
- Ställ in en övervakare så att när värdet på egenskapen ändras av anroparen, triggas
renderautomatiskt. - Hjälp till att etablera ett förhållande mellan
propertyochattribute(Detta är två olika saker. I React-världen existerar inte attribut. Allt är prop).
Ta exemplet med din core-button-komponent; du kan skapa en komponent som visas nedan och försöka lägga till count-egenskapen till komponenten. Men eftersom count inte är deklarerad som en prop, kommer Stencil inte att kunna avgöra om den ska utlösa render-metoden.
// Custom element
const a = document.createElement('core-button');
a.count= 10;
Som en lösning kan du samla alla egenskaper i ett vanligt objekt med en funktion och sedan använda det i din renderingsmetod som:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'core-button',
})
export class CoreButton {
@Prop() name: string;
@Prop() count: number;
@Prop() disabled: boolean;
render() {
const restProps = this.collect();
return (
<button name={`example ${this.name}`} {...restProps}>
<slot />
</button>
);
}
collect() {
return {
count: this.count,
disabled: this.disabled
};
}
}
Du kan ta det ett steg längre genom att skapa en automatisk hjälpare för att uppnå detta. Använd paketet som reflect-metadata och läs all reflektionsinformation för Prop-dekorationen som är satt på din CoreButton-klass och läs sedan dessa egenskaper med undantag för den du inte behöver. Du kan sedan använda denna funktion i varje komponent där du behöver detta.
Svar #2
Min lösning är en hjälpmetod som hämtar alla föräldrats attribut, kontrollerar om de har definierats på föräldern och returnerar de återstående i en Record<string, string>.
/**
* Forwards all undeclared attributes from an element to a child.
*
* This is useful if you don't want to redefine all HTML attributes on a wrapping component.
*
* @example
* The component `Input` forwards all undeclared attributes to the underlying `input` element.
*
* ```tsx
* <div class="component-wrapper">
* <input {...forwardProps(this.el)} />
* </div>
* ```
*
* @param element The element to forward props from. Probably `this.el`
* @returns A record of all attributes on the element that are not already defined on the component
*/
export const forwardProps = (element: HTMLElement): Record<string, string> => {
if (!element.hasAttributes()) return {};
const attributes = {};
for (const attribute of element.attributes) {
if (!(attribute.name in element)) {
attributes[attribute.name] = attribute.value;
}
}
return attributes;
};
