为段落渲染器反应降价类型? 渲染器类型输入道具输入节点条件覆盖

问题描述

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

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 的类型作为对象映射

{[nodeType: string]: ElementType}

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

您可以将对象键入为

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

输入道具

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

我们的 paragraph 使用道具 nodechildren 被调用。 code 元素通过道具 languagevaluenodechildren 被调用。不幸的是,像 languagevalue 这样的自定义道具没有记录在 Typescript 的任何地方。您可以在 react-markdown 源代码的 getNodeProps 函数中see them being set。每种节点类型都有不同的道具。

输入节点

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

react-markdown 类型 shownode 的类型是从底层 Markdown 解析器包 mdast 导入的 Content 类型。 This Content type 是所有单个降价节点类型的联合。这些单独的类型都有一个不同的 type 属性,它的 string 文字与我们要在 renderers 对象上设置的键相匹配!

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

children 对象上的 props 道具只是典型的 React 子道具,但并非所有节点类型都有子节点。我们可以通过查看 props 的类型并查看它是否具有 children 属性来知道我们的 node 是否包含 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 节点,但只会在满足条件 node.children[0].type === "image" 时返回任何内容。这意味着所有其他段落都被删除了!您需要确保总是返回一些东西。

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