反应段落渲染器的降价类型?

react markdown type for paragraph renderer?

我在包裹 react.markdown 的 github 和 index.d.ts 上都找不到任何东西。它看起来像非常简单的示例,但整个 google 不包含任何示例。我为降价组件编写了一个自定义渲染器,但我无法弄清楚渲染器的类型

import ReactMarkdown, { Renderers, Renderer, NodeType } from "react-markdown";

const PostContent: React.FC<PostContent> = ({ blog }) => {
  const customRenderers: Renderers = {
    paragraph(paragraph) {
      const { node } = paragraph;
      if (node.children[0].type === "image") {
        const image = node.children[0];
        return (
          <div style={{ width: "100%", maxWidth: "60rem" }}>
            <Image
              src={`/images/posts/${blog.slug}/${image.src}`}
              alt={image.alt}
              width={600}
              height={300}
            />
          </div>
        );
      }
    },
  };

  return (
    <article className="">
      <ReactMarkdown renderers={customRenderers}>{blog.content}</ReactMarkdown>
    </article>
  );
};

段落的类型是什么?我检查它是渲染器:

   type Renderer<T> = (props: T) => ElementType<T>

我不知道要传递什么作为 T。我试过了,HtmlParagraphElement 或任何。

渲染器类型

react-markdown 包的类型非常松散。它declares renderers 的类型作为对象 map

{[nodeType: string]: ElementType}

其中键可以是任何 string(不仅仅是有效的节点类型)并且值具有从 React typings 导入的类型 ElementTypeElementType 表示您的渲染器可以是内置元素标签名称,如 "p""div" 或函数组件或采用 any 道具的 class 组件。

您可以将对象键入为

const customRenderers: {[nodeType: string]: ElementType} = { ...

输入道具

ElementType 对于在渲染函数中获得类型安全完全没有用。类型表示 props 可以是任何东西。如果我们能知道 props 我们的渲染函数实际上将被调用什么,那就太好了。

我们的 paragraph 被调用了道具 nodechildrencode 元素使用属性 languagevaluenodechildren 调用。不幸的是,像 languagevalue 这样的自定义属性在 Typescript 中没有任何记录。可以在react-markdown源码的getNodeProps函数中see them being set。每个节点类型都有不同的道具。

键入节点

道具 nodechildren 是我们实际可以获得有用的 Typescript 信息的地方。

react-markdown 类型 show that the type for a node is the Content type imported from the underlying markdown parser package mdast. This Content type 是所有单个 markdown 节点类型的联合。这些单独的类型都有一个不同的 type 属性,其中一个 string 文字与我们要在 renderers 对象上设置的键相匹配!

所以最后我们知道有效键的类型是Content["type"]。我们还知道特定 K 键的 node 属性将是 Extract<Content, { type: K }>,它为我们提供了与此 type 属性 相匹配的联合成员。

props 对象上的 children 属性只是一个典型的 React children 属性,但并非所有节点类型都有子节点。我们可以通过查看 node 的类型并查看它是否具有 children 属性 来知道我们的 props 是否包含 children

type NodeToProps<T> = {
  node: T;
  children: T extends { children: any } ? ReactNode : never;
};

(这与接收到的道具匹配,因为 children 道具始终设置,但如果不支持子项,则为 undefined

现在我们可以为您的 customRenderers 或任何自定义渲染器映射定义严格类型:

type CustomRenderers = {
  [K in Content["type"]]?: (
    props: NodeToProps<Extract<Content, { type: K }>>
  ) => ReactElement;
};

条件覆盖

您的代码将拦截所有 paragraph 个节点,但只有 return 满足条件 node.children[0].type === "image" 时的任何内容。这意味着所有其他段落都将被删除!你需要确保你 总是 return 一些东西。

const PostContent: React.FC<PostContent> = ({ blog }) => {
  const customRenderers: CustomRenderers = {
    // node has type mdast.Paragraph, children is React.ReactNode
    paragraph: ({ node, children }) => {
      if (node.children[0].type === "image") {
        const image = node.children[0]; // type mdast.Image
        return (
          <div
            style={{
              width: "100%",
              maxWidth: "60rem"
            }}
          >
            <img
              src={`/images/posts/${blog.slug}/${image.src}`}
              alt={image.alt}
              width={600}
              height={300}
            />
          </div>
        );
      } 
      // return a standard paragraph in all other cases
      else return <p>{children}</p>;
    },
  };

  return (
    <article className="">
      <ReactMarkdown renderers={customRenderers}>{blog.content}</ReactMarkdown>
    </article>
  );
};

Code Sandbox Link