用React和Sandpack建立一个互动博客

2,174 阅读14分钟

如果不使用特定的库来实现互动性,在博客上实现互动性可能会很复杂。这些库为与博客的各个部分进行交互提供了一个简单的界面。

人们出于各种原因建立了博客,从为一个包、库或框架建立文档,到写个人博客。许多这样的博客还使用静态的、非交互式的代码片断来展示使用其工具的各种方法。

在这篇文章中,我将介绍一种在博客上使用Sandpack(CodeSandbox新近开源的用于交互式代码样本的组件工具包)使代码实例更具交互性的方法。我们将用React建立一个博客,并使用Sandpack来使其具有交互性。

前提条件

本教程假定你具备以下条件。

  • Node≥ v12.20
  • npm≥ v6.14.15
  • 具有HTML、CSS、JavaScript和React的基本知识

什么是Sandpack?

Sandpack是一个组件工具箱,可用于创建和显示实时编码样本。它是一个开源的工具,由CodeSandbox背后的团队建立和维护--它实际上是为CodeSandbox提供动力的同一个浏览器内捆绑器。

沙盒包括以下功能。

  • 一个代码编辑器的体验
  • 在编辑器中对代码进行实时预览
  • 支持所有主要的JavaScript框架
  • 支持npm的依赖性
  • 直接整合CodeSandbox,使你能在CodeSandbox中直接打开代码样本

自2021年12月发布以来,Sandpack在技术生态系统中迅速得到了认可,它已经在一些平台上得到了应用你可以在以下地方看到它的运行情况。

在我们开始建立博客之前,让我们回顾一下一些重要的Sandpack概念。
这些概念包括对sandpack-react 包和它的一些组件的简要概述。你可以在其文档中查看Sandpack组件和属性的完整列表。

在我们开始之前,让我们先引导一个React应用程序,在其中我们将演示Sandpack,随后用它来构建我们的博客。

用Vite创建一个React应用程序

我们将使用Vite来引导我们的React应用程序。

# npm 6.x
npm init vite@latest sandpack-blog --template react

# npm 7+, extra double-dash is needed:
npm init vite@latest sandpack-blog -- --template react

该命令创建一个名为sandpack-blog 的React应用程序。一旦应用程序被成功创建,使用切换到应用程序目录。

# switch into app directory
cd sandpack-blog

使用npm install 命令安装所有的依赖项。

安装sandpack-react

sandpack-react 是为Sandpack创建的随时可用的React包。我们现在要把它作为一个依赖项添加到我们的React应用程序中。

npm i @codesandbox/sandpack-react

一旦我们完成了安装依赖项,我们就可以运行我们的应用程序。

npm run dev

在你的浏览器上,导航到localhost:3000 。如果React应用程序已经正确创建,你应该在你的浏览器中看到这个。

A successfully bootstrapped React/Vite application

现在,让我们回顾一下我们将在项目中使用的主要Sandpack组件。

对Sandpack组件的回顾

Sandpack 组件是一个预设组件。它包装了其他单独的sandpack 组件,并为我们提供了一个预先配置好的组件,可以随时使用。这意味着Sandpack组件几乎不需要任何配置。

让我们看看它是如何在我们的React应用程序中工作的。在你最喜欢的代码编辑器中打开已启动的应用程序,进入/src/App.jsx ,然后用以下内容替换我们的内容。

import React from 'react';
import { Sandpack } from "@codesandbox/sandpack-react";
import "@codesandbox/sandpack-react/dist/index.css";

function App() {
  return (
    <>
      <h1>My Sandpack blog</h1>
      <Sandpack />
    </>
  )
}
export default App;

这应该为我们呈现一个Sandpack编辑器和一个预览面板。代码编辑器中的代码是一个vanilla JavaScript代码模板,这是Sandpack 组件使用的默认模板。

The default vanilla JS Sandpack template in the editor

让我们来看看我们的/src/App.jsx 中的代码。

import React from 'react';
import { Sandpack } from "@codesandbox/sandpack-react";
import "@codesandbox/sandpack-react/dist/index.css";

在这里,我们导入了ReactSandpackSandpack css 样式表,其中包括标准Sandpack 组件的样式。

function App() {
  return (
    <>
      <h1>My Sandpack blog</h1>
      <Sandpack />
    </>
  )
}
export default App;

在这里,我们有我们的功能性App 组件,它包含一个片段,包裹着<h1>Sandpack 组件。片段使我们能够返回多个元素,而无需向DOM添加额外的节点。

通过props定制Sandpack编辑器

Sandpack接受各种用于定制编辑器的props。让我们看一下其中的几个。

    • template:template 道具使我们能够指定我们的应用程序要使用的预定义模板。Sandpack ,目前支持以下模板。

      • "vanilla" - 这是默认的
      • "angular"
      • "react"
      • "react-ts"
      • "vanilla-ts"
      • "vue"
      • "vue3"
      • "svelte"
    • themetheme 道具使我们能够改变我们正在使用的代码编辑器的主题。Sandpack目前有以下预定义的模板。

      • "light" - 这是默认的
      • "dark"
      • "sandpack-dark"
      • "night-owl"
      • "aqua-blue"
      • "github-light"
      • "monokai-pro"
    • options选项道具使我们能够配置Sandpack的一些内部组件。它接受很多不同的选项;让我们看看其中的几个。

      • showLineNumbers: 切换编辑器的行号;你可以通过truefalse ,但默认是false
      • showInlineErrors: 突出显示内联错误;你可以通过true 或 。false
      • showTabs :切换编辑器上的文件标签的可见性;你可以通过truefalse 。只有在打开多个文件的情况下,文件标签才会默认显示,可以使用showTabs 道具来强制显示文件标签。
      • externalResources :一个外部资源的数组,例如外部CSS或JS资源的静态链接,它们被注入到你的HTML的head ,然后是全局可用。
    • customSetup :使我们能够有一个自定义的Sandpack 配置;如果我们不想使用Sandpack's 预定义的模板,我们可以制作自己的模板。

    • files :这是一个object ,包含所有要在编辑器中使用的自定义文件。文件路径被设置为键,代码被设置为值。

    • dependencies: 这是一个依赖对象,包含一个正在使用的npm 包的列表。key 应该是包的名称,而值是version ,格式和它在package.json

      ...
      <Sandpack
      ...
      customSetup={{
        files: {
          '/App.js' : `
            export default function App() {
              return <h1>Custom setup</h1>
            } 
          `
        },
        dependencies: {
          react: "17.0.2",
          "@mdx-js/mdx": "^1.6.22",
        }
      }}
      />
      ...
      

      里面的完全相同

现在我们继续建立我们的博客,因为我们已经对Sandpack 的工作方式有了基本了解。你可以看看这个CodeSandbox演示,了解各种配置。

建立我们的博客

我们将使用MDX 来建立我们的博客。MDX 是一种标记语言,使我们能够使用JSX ,这使我们能够在标记中使用可重用的组件。

我们将涵盖以下步骤。

  1. 安装我们的软件包和依赖项
  2. 配置MDX 插件
  3. 构建我们的项目文件夹
  4. 为我们的React博客添加内容
  5. 编写我们的文章MDX

第1步:安装软件包和依赖项

在我们引导的React 应用程序中安装以下依赖项。

  • @mdx-js/rollup: 这是一个MDX插件,作为Vite的捆绑器

    npm install @mdx-js/rollup@next
    
  • @mdx-js/react: 这是一个基于上下文的组件提供者,用于将React与MDX ,使我们能够在一个点上传递所有的组件,所以我们不必将它们导入所有的MDX 文件中。它不是强制性的,因为组件可以直接导入到每个文件中

    npm install @mdx-js/react@next
    
  • react-router-dom :这是用来在React中创建一个路由器

    npm install react-router-dom@6
    
  • unist-util-visit: 这是一个unist 工具,我们将用来为MDX创建一个插件,这样我们就可以从我们的代码中使用元数据

    npm install unist-util-visit
    
  • @vitejs/plugin-react-refresh: 这是另一个Vite插件,比起自带的默认Vite插件,mdx ,更容易使用

    npm install -D @vitejs/plugin-react-refresh
    

第二步:配置MDX 插件

我们现在将配置MDX 插件。这使Vite能够理解mdx 语法。
vite.config.js 文件中添加以下几行代码。

import { defineConfig } from "vite";
import reactRefresh from '@vitejs/plugin-react-refresh';
import mdx from "@mdx-js/rollup";
import { visit } from "unist-util-visit";

function rehypeMetaAsAttributes() {
  return (tree) => {
    visit(tree, "element", (node) => {
      if (node.tagName === "code" && node.data && node.data.meta) {
        node.properties.meta = node.data.meta;
      }
    });
  };
};

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    reactRefresh(),
    mdx({
      rehypePlugins: [rehypeMetaAsAttributes],
      providerImportSource: "@mdx-js/react",
    }),
  ],
});

这里发生了三件事。

  1. 我们导入了所有必要的软件包
  2. 我们创建了一个名为rehypeMetaAsAttributesrehype插件,使我们能够将可以传递给代码块的元数据设置为编译后代码的属性。我们将在以后创建Sandpack组件时看到这一点的重要性。
  3. 然后,我们将这些插件传入Vite配置中

在这一点上,我们的应用程序可能会因为一个错误而中断:Error [ERR_REQUIRE_ESM] 。这是因为mdx 包只作为一个ES模块发布。

所以,为了让这个问题对我们有用,我们可以用几种不同的方法来解决。

我们可以在我们的package.json 文件中添加一个模块类型。

{
  "name": "sandpack-blog",
  "version": "0.0.0",
  "type" : "module",
  "scripts": {
    ...
  },
  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  }
}

或者,我们可以将vite.config.js 文件的名称改为vite.config.mjs

以上两种方法都可以,但我将使用第一种方法。

第3步:构建项目文件夹

我们将有两个主要的文件夹来存放我们的项目。第一个是components 文件夹,我们将在这里构建我们的可重用组件,第二个是pages 文件夹,它包含我们所有的页面。

将以下文件夹添加到src文件夹中。

src
├── components
|  ├── Blog
|  |  |  └── index.jsx
|  ├── CodeEditor
|  |  |  └── index.jsx
|  ├── ErrorBoundary
|  |  |  └── index.jsx
|  └── Navbar
|  |  |  └── index.jsx
├── pages
|  ├── blog
|  |  ├──Blogs.jsx
|  |  ├── intro-to-react.mdx
|  |  ├── javascript-classes.mdx
|  |  ├── index.js
|  |  └── intro-to-tailwind.mdx
   └── index.mdx

第四步:为我们的React博客添加内容

现在,我们在文件中填充内容,这些内容将构成我们的博客。在/App.jsx 文件中,添加以下内容。

import React from 'react';
import { 
  BrowserRouter,
  Routes,
  Route,
} from "react-router-dom";
import "@codesandbox/sandpack-react/dist/index.css";
import Home from './pages/index.mdx'
import Blogs from './pages/blog/Blogs.jsx'
import { MDXProvider } from '@mdx-js/react';
import CodeEditor from './components/CodeEditor';
import Blog from './components/Blog';
import Navbar from './components/Navbar'
import './App.css'

function App() {
  const components = {
  CodeEditor
}

  return (
  <main>
    <MDXProvider components={components}>
      <BrowserRouter>
        <Navbar />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="blog">
            <Route path="" element={<Blogs />} />
            <Route path=":title" element={<Blog />}/>
          </Route>
          <Route path="*" element={<p>404</p>}/>
        </Routes>
      </BrowserRouter>
    </MDXProvider>
  </main>
  );
}

export default App;

首先,我们导入我们的包和文件,然后我们创建一个路由器。这个路由器将使我们能够导航到不同的路线。我们也有MDXProvider ,它是MDX 的提供者。我们将我们的博客组件传递给它,这样我们就不必在每个MDX 文件中导入它们。

/components/Navbar/index.jsx 文件中,添加以下内容。

import React, { Component } from 'react';
import { NavLink } from "react-router-dom";


const Nav = () => {
  let activeStyle = {
    fontWeight: 'bold',
    color: 'black',
    fontSize: '18px'
  };

    return (
      <nav className="navbar">
        <h1>My Blog</h1>
        <div>
          <NavLink 
            to='/' 
            style={({ isActive }) =>
              isActive ? activeStyle : undefined
            }
          >
            Home 
          </NavLink>

          <NavLink 
            to='/blog'
            style={({ isActive }) =>
              isActive ? activeStyle : undefined
            }
          >
            Blog
          </NavLink>
        </div>
      </nav>
    )
}

export default Nav;

这就创建了我们的Navbar 。我们首先在这里导入所有的包,然后我们创建了Nav 。我们从react-router-dom 中导入的NavLink 组件被用来创建我们的导航链接。

/components/Blog/index.jsx 文件中,添加以下内容。

import React, { lazy, Suspense } from 'react';
import { useParams } from "react-router-dom";
import ErrorBoundary from '../ErrorBoundary'

export default function Blog(){
  let { title } = useParams();

  const Blog = lazy(()=> import(`../../pages/blog/${title}.mdx`))

  return(
    <ErrorBoundary fallback={<p>404</p>}>
      <Suspense fallback={<p>Loading.....</p>}>
        <Blog />
      </Suspense>
    </ErrorBoundary>
  )
}

这个组件将为我们动态地获取我们的帖子并渲染它们。在这里,我们导入我们的包,然后我们创建blog 组件。使用useParams Hooks,我们从每个帖子的URL中获取标题参数。

标题作为每个文件的名称,我们能够使用React的Lazy组件动态加载它。我们也有我们的ErrorBoundary ,如果发生错误或标题不存在,它可以作为回退。Suspense 也可以作为懒惰加载期间的回退。

/components/ErrorBoundary/index.jsx 文件中,添加以下内容。

import React from "react";
import ReactDOM from "react-dom";

class ErrorBoundary extends React.Component {
    constructor(props) {
    super(props);
    this.state = { error: false };
  }
  static getDerivedStateFromError(error) {
    return { error };
  }

  componentDidCatch(error) {
    // Log or store the error
    console.error(error);
  }

  render() {
    if(this.state.error){
      return this.props.fallback
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

这是ErrorBoundary ,让我们在组件树的任何地方捕捉错误,并显示一个回退UI。这对我们的实现很重要,因为用户可能会在URL中输入一个错误或不存在的标题。如果出现错误,我们的组件会处理它并显示一个回退。你可以在React文档中阅读更多关于ErrorBoundary

/components/CodeEditor/index.jsx 文件中,添加以下内容。

import React from "react";
import { nightOwlTheme, Sandpack } from "@codesandbox/sandpack-react";

export default function CodeEditor(props) {
  let { children, template = "react", externalResources = [] } = props;

  // convert the children to an array
  let codeSnippets = React.Children.toArray(children);

  // using the array.reduce method to reduce the children to an object containing
  // filename as key then other properties like the code, if the file is hidden as 
  // properties
  const files = codeSnippets.reduce((result, codeSnippet) => {
    if (codeSnippet.type !== "pre") {
      return result;
    }

    const { props } = codeSnippet.props.children;
    let filePath; // path in the folder structure
    let fileHidden = false; // if the file is available as a tab
    let fileActive = false; // if the file tab is shown by default

    if (props.meta) {
      // get our metadata from the prop
      const [name, ...params] = props.meta.split(" ");
      filePath = (template === "react" ? "/" : "/src/") + name;
      if (params.includes("hidden")) {
        fileHidden = true;
      }
      if (params.includes("active")) {
        fileActive = true;
      }
    } else {
      // if no name is given to the file, we give them defaults based on 
      // the language
      if (props.className === "language-js") {
        filePath = "/App.js";
      } else if (props.className === "language-css") {
        filePath = "/styles.css";
      } else if (props.className === "language-vue") {
        filePath = "/src/App.vue";
      } else {
        throw new Error(`Code block is missing a filename: ${props.children}`);
      }
    }

    if (result[filePath]) {
      throw new Error(
        `File ${filePath} was defined multiple times. Each file snippet should have a unique path name`
      );
    }

    result[filePath] = {
      code: props.children,
      hidden: fileHidden,
      active: fileActive,
    };
    return result;
  }, {});

  return (
    <Sandpack
      template={template}
      theme={nightOwlTheme}
      customSetup={{
        files,
        dependencies: {},
      }}
      options={{
        showLineNumbers: true,
        showInlineErrors: false,
        showTabs: true,
        externalResources,
      }}
    />
  );
}

这是CodeEditor ,将在我们的博客中使用。该文件以导入所有使用的包开始。然后在组件中,我们对我们的props进行解构。

孩子们的props,在React组件内部保存内容,将成为我们的代码编辑器的内容,这取决于我们传递给它的内容。这意味着我们可以通过直接在它的标签之间传递代码来使用我们的CodeEditor 组件;标签之间的所有内容都是子项。

// line 3 to 15 is the children in this example
<CodeEditor>
```js App.js
import React from 'react';
import ReactDOM from 'react-dom';

export default function App() {
  return (
    <div>
      <h1>Hello World</h1>
    </div>
  );
}

```

模板道具是我们的代码编辑器的模板,与上面的沙盒模板一样。默认情况下,它被设置为react ,所以如果没有传递任何东西,就会使用reactexternalResources 道具接收一个所有使用的外部资源的数组。

第5步:写我们的帖子mdx

现在,我们可以开始用Markdown编写我们的帖子,并看到每个页面的结果。在/pages/index.mdx 文件中,添加以下内容。

# Home

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse nec arcu a felis hendrerit blandit. Integer orci libero, gravida sit amet enim et, tempor mattis diam. Donec augue ipsum, semper ac elit mollis, posuere maximus turpis. Pellentesque faucibus varius vulputate. Sed placerat mi tincidunt diam ullamcorper, aliquam tincidunt nisl sagittis. Nunc velit est, vulputate non purus vitae, vehicula finibus lacus. Curabitur blandit efficitur dignissim. In hac habitasse platea dictumst. Etiam volutpat eleifend tortor, nec cursus turpis aliquet ac. Fusce eget suscipit nisi.

Duis commodo pretium metus, ac pulvinar justo lobortis quis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ullamcorper luctus eros in aliquam. Integer congue urna vitae quam rhoncus tristique. Vestibulum semper, arcu nec pellentesque gravida, sapien tortor feugiat purus, at auctor elit mauris ut purus. Quisque odio nibh, elementum vitae tellus hendrerit, vestibulum placerat ex. Duis non sapien ante.

## Section 2

Nunc ultrices tellus consequat, eleifend neque nec, pellentesque dui. Quisque hendrerit scelerisque dui, nec sagittis quam varius ac. Nunc eget vulputate neque. Morbi a vulputate sapien. Sed non tristique elit, eu rutrum nulla. Sed fringilla diam eu tempus tincidunt. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

这个页面显示/blog 路线上的所有帖子的列表。

/pages/blog/intro-to-react.mdx 文件中,添加以下内容。

export const meta = {
  title: 'Introduction to React.js',
  description: 'Getting started with React framework',
  date: 'Jan 02, 2022',
  readTime: 2,
  file: 'intro-to-react'
}

# {meta.title}

## Hello world in React

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

<CodeEditor>
```js
import React from 'react';
import ReactDOM from 'react-dom';

export default function App() {
  return (
    <div>
      <h1>Hello World</h1>
    </div>
  );
}

Components

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

import Greet from './Greet.js';

export default function MyApp() {
  return (
    <div>
      <h1>My App</h1>
      <Greet />
    </div>
  );
}
function Greeting({ name }) {
  return <h3>Hello, {name}!</h3>;
}

export default function Greet() {
  return (
    <div>
      <Greeting name="Divyesh" />
      <Greeting name="Sarah" />
      <Greeting name="Taylor" />
    </div>
  );
}
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

export default function Expo() {
  return (
    <div>
      <Greeting name="Divyesh" />
      <Greeting name="Sarah" />
      <Greeting name="Taylor" />
    </div>
  );
}

Props

Conclusion

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Iaculis at erat pellentesque adipiscing commodo. Sodales ut etiam sit amet nisl purus in mollis nunc. Aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis. Tellus orci ac auctor augue mauris augue neque gravida. Risus viverra adipiscing at in tellus. Ultrices vitae auctor eu augue. Tempus urna et pharetra pharetra massa massa ultricies. Elementum facilisis leo vel fringilla. Quis blandit turpis cursus in hac habitasse. Elit duis tristique sollicitudin nibh sit amet commodo nulla facilisi. Massa id neque aliquam vestibulum morbi blandit cursus risus at. Vitae sapien pellentesque habitant morbi tristique senectus. Elementum nisi quis eleifend quam adipiscing vitae proin. Aliquam nulla facilisi cras fermentum.


在`/pages/blog/javascript-classes.mdx` 文件中,添加以下内容。

export const meta = { title: "Javascript Classes", description: "Classes in Javascript", date: "Jan 01, 2022", readTime: 2, file: "javascript-classes", };

{meta.title}

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

```js class.js active Class Person { constructor(name) { this.name = name; } sayName() { console.log(this.name); } } ```

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.


在`/pages/blog/intro-to-tailwind.mdx` 文件中,添加以下内容。

export const meta = { title: "Intro to Tailwind", description: "Tailwind CSS", date: "Jan 01, 2022", readTime: 2, file: "intro-to-tailwind", };

{meta.title}

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

<CodeEditor template="vanilla" externalResources={['cdn.tailwindcss.com']} >

import "./styles.css";

document.getElementById("app").innerHTML = `
<h1 class="text-center font-bold">Hello Tailwind!</h1>
<div class='mt-5'>
  We use the same configuration as Parcel to bundle this sandbox, you can find more
  info about Parcel 
  <a href="https://parceljs.org" target="_blank" rel="noopener noreferrer">here</a>.
</div>
`;


在`/pages/blog/index.js` 文件中,添加以下内容。

import * as post1 from './intro-to-react.mdx' import * as post2 from './javascript-classes.mdx' import * as post3 from './intro-to-tailwind.mdx'

export default [ post1, post2, post3 ]


这个文件将是我们导出所有帖子的地方。我们是手动做的,因为我们不能在客户端读取文件系统,但这将更容易,而且可以很容易地用[Next.js](https://nextjs.org/docs/advanced-features/using-mdx)做动态,[Gatsby](https://www.gatsbyjs.com/docs/how-to/routing/mdx/)也可以很容易地获取帖子。

现在,我们应该有三个帖子,每个都有各自的内容。

![Our blog's homepage](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec5ec0d54f8e4ec7939d171c602e1092~tplv-k3u1fbpfcp-zoom-1.image)

![Introduction to React.js blog post](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0fd44f9bfa75457ebd1b137f5b7c5619~tplv-k3u1fbpfcp-zoom-1.image)

![Intro to Tailwind blog post](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4d83f643212c4fbba7b15460f68d08be~tplv-k3u1fbpfcp-zoom-1.image)

看看[CodeSandbox上的演示](https://codesandbox.io/s/sandpack-blog-d02n0),再[看看我们的博客](https://d02n0.sse.codesandbox.io/blog)。我在演示中加入了一个CodeViewer组件,这适用于我们只想查看代码而不进行编辑的情况。

总结
--

我们用React、MDX和Sandpack建立了一个博客,使我们的代码实例更具互动性。我希望你能从这篇文章中学习到一些新的东西!

访问[Sandpack的官方网站](https://sandpack.codesandbox.io/docs/),了解更多关于如何进一步定制代码样本的信息。

如果你有任何问题或对该主题有更多见解,请在评论中给我留言。我也很想看看你的构建。

### 资源和进一步阅读

*   [Sandpack文档](https://sandpack.codesandbox.io/docs/)
*   [React文档](https://beta.reactjs.org/)
*   [MDX文档](https://mdxjs.com/)

The post[Build an interactive blog with React and Sandpack](https://blog.logrocket.com/build-interactive-blog-with-react-sandpack/)appeared first on[LogRocket Blog](https://blog.logrocket.com).