reactjs - Skapa React-element dynamiskt i Typescript


Jag försöker skapa ett element i React med Typescript baserat på tagName som skickas som props, och jag vill lägga till relaterade elementprops baserat på det tagName. Här är min kod.

type ElementProps<Tag extends keyof JSX.IntrinsicElements> =
  JSX.IntrinsicElements[Tag];

type Props = {
  tagName?: React.ComponentType | keyof JSX.IntrinsicElements;
};

const Editable: React.FC<Props> = ({
  tagName = 'div',
  ...props
}) => {
  const htmlEl = useRef(null);

  const elementProps: ElementProps<typeof tagName> = {
    ...props,
    ref: htmlEl,
  };

  return createElement(tagName, elementProps, children);
};

Jag får detta fel

Type 'keyof IntrinsicElements | ComponentType<{}>' does not satisfy the constraint 'keyof IntrinsicElements'.
  Type 'ComponentClass<{}, any>' is not assignable to type 'keyof IntrinsicElements'.
    Type 'ComponentClass<{}, any>' is not assignable to type '"view"'

Svar #1

Du var nära.

ElementProps förväntar sig en sträng - keyof JSX.IntrinsicElements oavsett om typeof tagName kan vara en ComponentType<{}.

Allt du behöver göra är att flytta typerestriktionen inuti ett typverktyg.

import React, { useRef, createElement } from "react";

type ElementProps<Tag> =
  Tag extends keyof JSX.IntrinsicElements
  ? JSX.IntrinsicElements[Tag]
  : never

type Props = {
  tagName?: React.ComponentType | keyof JSX.IntrinsicElements;
};

const Editable: React.FC<Props> = ({
  tagName = 'div',
  ...props
}) => {
  const htmlEl = useRef(null);

  const elementProps: ElementProps<typeof tagName> = {
    ...props,
    ref: htmlEl,
  };

  return createElement(tagName, elementProps);
};

Lekplats

Jag undrade om jag kunde få de enda detaljerade egenskaperna angående taggen som skickades in i tagnamnet?

Visst, det är möjligt.

import React, { useRef, createElement } from "react";

type ElementProps<Tag> = Tag extends keyof JSX.IntrinsicElements
  ? JSX.IntrinsicElements[Tag]
  : never;

type Values<T> = T[keyof T];

type ObtainHTMLProps<T extends Values<JSX.IntrinsicElements>> =
  T extends React.DetailedHTMLProps<infer Props, HTMLElement> ? Props : never;

type AllowedProps = Values<{
  [Tag in keyof JSX.IntrinsicElements]: {
    tagName: Tag;
  } & ObtainHTMLProps<JSX.IntrinsicElements[Tag]>;
}>;  

const Editable: React.FC<AllowedProps> = ({ tagName = "div", ...props }) => {
  const htmlEl = useRef(null);

  const elementProps: ElementProps<typeof tagName> = {
    ...props,
    ref: htmlEl,
  };

  return createElement(tagName, elementProps);
};

const canvas = <Editable tagName="canvas" width={20} /> // ok
const anchor = <Editable tagName="a" href={'www.example.com'} /> // ok

const invalid = <Editable tagName="canvas" href={'www.example.com'} /> // expected error

Se react-typer node_modules\@types\react\index.d.ts. Du hittar där IntrinsicElements

Genom att ha tagName kan du enkelt härleda det första argumentet för DetailedHTMLProps. Se ObtainHTMLProps.

Nu behöver du skapa en union av alla tillåtna attribut och det är ett ändligt antal egenskaper. Se AllowedProps.