使用 MDX 自定义元素和短代码

Avatar of Agney Menon
Agney Menon

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

MDX 对于博客、幻灯片和组件文档等内容来说是一个杀手级功能。它允许您编写 Markdown 而无需担心 HTML 元素、其格式和位置,并在必要时加入自定义 React 组件的魔力。

让我们利用这种魔力,看看如何通过用我们自己的 MDX 组件替换 Markdown 元素来自定义 MDX。在此过程中,我们将介绍在使用这些组件时的“短代码”概念。

提醒一下,这里的代码片段基于 GatsbyJSReact,但 MDX 也可以与 不同的框架 一起编写。如果您需要了解 MDX 的入门知识,请 先从这里开始。本文在此基础上扩展了更多高级概念。

设置布局

我们几乎总是希望在通用布局中渲染基于 MDX 的页面。这样,就可以将它们与我们网站上的其他组件一起排列。我们可以使用正在使用的 MDX 插件指定默认的 Layout 组件。例如,我们可以使用 gatsby-plugin-mdx 插件定义一个布局,如下所示

{
  resolve: `gatsby-plugin-mdx`,
  options: {
    defaultLayouts: {
      default: path.resolve('./src/templates/blog-post.js'),
    },
    // ...other options
  }
}

这将需要 src/templates/blog-post.js 文件包含一个组件,该组件将渲染它接收到的 children 属性。

import { MDXRenderer } from 'gatsby-plugin-mdx';


function BlogPost({ children }) {
  return (
    <div>{children}</div>
  );
}


export default BlogPost;

如果我们正在以编程方式创建页面,则必须使用名为 MDXRenderer 的组件来实现相同的功能,如 Gatsby 文档 中所述。

自定义 Markdown 元素

虽然 MDX 是一种允许我们编写自定义 HTML 和 React 组件的格式,但它的强大之处在于使用自定义内容呈现 Markdown。但是,如果我们想自定义这些 Markdown 元素在屏幕上的呈现方式呢?

我们当然可以 为其编写一个 remark 插件,但 MDX 为我们提供了一个更好、更简单的解决方案。默认情况下,这些是 Markdown 呈现的一些元素

名称HTML 元素MDX 语法
段落<p>
标题 1<h1>#
标题 2<h2>##
标题 3<h3>###
标题 4<h4>####
标题 5<h5>#####
标题 6<h6>######
无序列表<ul>-
有序列表<ol />1.
图片<img />![alt](https://image-url)
完整的组件列表可在 MDX 文档 中找到。

为了用我们的自定义 React 组件替换这些默认值,MDX 附带了一个名为 MDXProviderProvider 组件。它依赖于 React Context API 来注入新的自定义组件并将它们合并到 MDX 提供的默认组件中。

import React from 'react';
import { MDXProvider } from "@mdx-js/react";
import Image from './image-component';


function Layout({ children }) {
  return (
    <MDXProvider
      components={{
        h1: (props) => <h1 {...props} className="text-xl font-light" />
        img: Image,
      }} 
    >
      {children}
    </MDXProvider>
  );
}


export default Layout;

在此示例中,MDX 文件中的任何 H1 标题(#)都将被 Provider 组件的 prop 中指定的自定义实现替换,而所有其他元素将继续使用默认值。换句话说,MDXProvider 能够获取我们用于 H1 元素的自定义标记,将其与 MDX 默认值合并,然后在我们在 MDX 文件中编写标题 1(#)时应用自定义标记。

MDX 和自定义组件

自定义 MDX 元素很棒,但如果我们想将我们自己的组件引入其中呢?

---
title: Importing Components
---
import Playground from './Playground';


Here is a look at the `Playground` component that I have been building:


<Playground />

我们可以将组件导入 MDX 文件,并像使用任何 React 组件一样使用它。当然,虽然这对于博客文章中的组件演示等内容很有效,但如果我们想在所有博客文章中都使用 Playground 呢?将它们导入所有页面会很麻烦。相反,MDX 为我们提供了使用短代码的选项。以下是 MDX 文档对短代码的描述

[短代码]允许您将组件公开到应用程序或网站中的所有文档。对于常见的组件(如 YouTube 嵌入、Twitter 卡片或文档中经常使用的任何其他内容)来说,这是一个有用的功能。

要在 MDX 应用程序中包含短代码,我们必须再次依赖 MDXProvider 组件。

import React from 'react';
import { MDXProvider } from "@mdx-js/react";
import Playground from './playground-wrapper';


function Layout({ children }) {
  return (
    <MDXProvider
      components={{
        h1: (props) => <h1 {...props} className="text-xl font-light" />
        Playground,
      }} 
    >
      {children}
    </MDXProvider>
  );
}


export default Layout;

将自定义组件包含到 components 对象中后,我们就可以在 MDX 文件中使用它们,而无需导入。

---
title: Demoing concepts
---


Here's the demo for the new concept:


<Playground />


> Look ma! No imports

直接操作子组件

在 React 中,我们获得了用于使用 React.Children 操作子级的顶级 API。我们可以使用它们将新属性传递给子组件,从而更改其顺序或确定其可见性。MDX 为我们提供了一个特殊的包装器组件来访问 MDX 传递的子组件。

要添加包装器,我们可以像以前一样使用 MDXProvider

import React from "react";
import { MDXProvider } from "@mdx-js/react";
const components = {
  wrapper: ({ children, ...props }) => {
    const reversedChildren = React.Children.toArray(children).reverse();
    return <>{reversedChildren}</>;
  },
};
export default (props) => (
  <MDXProvider components={components}>
    <main {...props} />
  </MDXProvider>
);

此示例反转子级,以便它们按我们编写的相反顺序出现。

我们甚至可以放开手脚,在所有 MDX 子级进入时为它们添加动画

import React from "react";
import { MDXProvider } from "@mdx-js/react";
import { useTrail, animated, config } from "react-spring";


const components = {
  wrapper: ({ children, ...props }) => {
    const childrenArray = React.Children.toArray(children);
    const trail = useTrail(childrenArray.length, {
      xy: [0, 0],
      opacity: 1,
      from: { xy: [30, 50], opacity: 0 },
      config: config.gentle,
      delay: 200,
    });
    return (
      <section>
        {trail.map(({ y, opacity }, index) => (
          <animated.div
            key={index}
            style={{
              opacity,
              transform: xy.interpolate((x, y) => `translate3d(${x}px,${y}px,0)`),
            }}
          >
            {childrenArray[index]}
          </animated.div>
        ))}
      </section>
    );
  },
};


export default (props) => (
  <MDXProvider components={components}>
    <main {...props} />
  </MDXProvider>
);

总结

MDX 在开箱即用时就具有灵活性,但通过扩展插件可以使其功能更加强大。感谢 gatsby-plugin-mdx,我们在短时间内实现了以下功能:

  1. 创建帮助格式化 MDX 输出的默认 Layout 组件。
  2. 用自定义组件替换从 Markdown 渲染的默认 HTML 元素。
  3. 使用短代码来摆脱在每个文件中导入组件的麻烦。
  4. 直接操作子级以更改 MDX 输出。

同样,这只是 MDX 为简化静态网站内容创作所做贡献的一小部分。

更多关于 MDX 的内容