相比React新官网,我做了哪些变化?

本站点重度参考自React的新官网,但我也不是伸手党,非常多的核心实现变化也话费了自己很多的时间,本文记录了我在React官网基础上做了什么变化,以及为什么。

Note

本站点包含很多可实时运行的Demo,在PC端阅读将获得更好的体验!

如果你是一个能力还不错的 React 开发者,那么你在看到这个网站的瞬间,应会有一股非常浓重的即视感,为什么呢?因为这个网站你可以认为就是 React 新的官网的副本,在知道 React 官网新的版本进入 beta 的时候,我第一时间就去观摩了,并且被他的设计和丰富的组件惊艳了。正好那时候我正在准备自己的网站,同时也是使用Nextjs作为基础框架,一冲动就 copy 了一份过来进行修改。

本来我自己是打算做一个带编辑器的版本的,但是吧,这个工程量着实又些大,要想真的稳定可靠可能每个半年做不起来,所以短时间内想要完成网站上线,这应该是一个更好的选择。

不过虽然是抄过来的,但是我还是做了一些修改的,所以这也是这篇文章的主题,我在 React 官网的基础上做了哪些修改。

MDX 版本

我本身在做的编辑器也是基于 MDX 语法的,所以呢我对 MDX 还是非常熟悉,并且我一直在关注 MDX2.0,从将近一年前 MDX 仓库对于 2.0 的 ISSUE 开始就一直在我的 Watch List 里面。直到 2021-10 月份,MDX2 终于发布了第一个 2.0 的 aplha 版本,初步稳定了 API,我就开始使用了起来。

所以在 copy React 新官网的第一时间,我就查看了他的 MDX 版本,很遗憾,仍然是 v1 的版本。这也可以理解,其实 React 官网的 beta 版本早在 2021 年初期就开始准备了,那时 MDX2 仍然只是一个提案,我们不指望 React 官方会使用一个不稳定的版本。

而对于我个人来说,自然是需要面向未来编程的,所以上 2.0 是必须的。这个过程还是有些曲折的,因为 MDX2 相对于 MDX1 有较大的变化,尤其在输出方面。

对于以下这段 MDX 代码:

# Hello MDX

const name = 'Jokcy'

<MdxButton id="myBtn">Click Me</MdxButton>

MDX2 rc-1 的输出如下:

/*@jsxRuntime automatic @jsxImportSource react*/
import {
  Fragment as _Fragment,
  jsx as _jsx,
  jsxs as _jsxs,
} from 'react/jsx-runtime';
export const name = 'Jokcy';
function MDXContent(props = {}) {
  const _components = Object.assign(
      {
        h1: 'h1',
      },
      props.components
    ),
    {MdxButton, wrapper: MDXLayout} = _components;
  const _content = _jsxs(_Fragment, {
    children: [
      _jsx(_components.h1, {
        children: 'Hello MDX',
      }),
      '\\n',
      '\\n',
      _jsx(MdxButton, {
        id: 'myBtn',
        children: 'Click Me',
      }),
    ],
  });
  return MDXLayout
    ? _jsx(
        MDXLayout,
        Object.assign({}, props, {
          children: _content,
        })
      )
    : _content;
}
export default MDXContent;

MDX2 rc-2 的输出如下:

要更新到一个不稳定的版本果然困难重重啊。。。rc 版本居然还有这么大的 breaking changes

/*@jsxRuntime automatic @jsxImportSource react*/
import {
  Fragment as _Fragment,
  jsx as _jsx,
  jsxs as _jsxs,
} from 'react/jsx-runtime';
export const name = Jokcy;
function MDXContent(props = {}) {
  const {wrapper: MDXLayout} = props.components || {};
  return MDXLayout
    ? _jsx(
        MDXLayout,
        Object.assign({}, props, {
          children: _jsx(_createMdxContent, {}),
        })
      )
    : _createMdxContent();
  function _createMdxContent() {
    const _components = Object.assign(
        {
          h1: 'h1',
        },
        props.components
      ),
      {MdxButton} = _components;
    if (!MdxButton) _missingMdxReference('MdxButton', true);
    return _jsxs(_Fragment, {
      children: [
        _jsx(_components.h1, {
          children: 'Hello MDX',
        }),
        _jsx(MdxButton, {
          id: 'myBtn',
          children: 'Click Me',
        }),
      ],
    });
  }
}
export default MDXContent;
function _missingMdxReference(id, component) {
  throw new Error(
    'Expected ' +
      (component ? 'component' : 'object') +
      ' `' +
      id +
      '` to be defined: you likely forgot to import, pass, or provide it.'
  );
}

MDX1 的输出如下:

/* @jsxRuntime classic */
/* @jsx mdx */

export const name = 'Jokcy';
const makeShortcode = (name) =>
  function MDXDefaultShortcode(props) {
    console.warn(
      'Component ' +
        name +
        ' was not imported, exported, or provided by MDXProvider as global scope'
    );
    return <div {...props} />;
  };
const MdxButton = makeShortcode('MdxButton');
const layoutProps = {
  name,
};
const MDXLayout = 'wrapper';
export default function MDXContent({components, ...props}) {
  return (
    <MDXLayout
      {...layoutProps}
      {...props}
      components={components}
      mdxType="MDXLayout">
      <h1>{`Hello MDX`}</h1>

      <MdxButton id="myBtn" mdxType="MdxButton">
        Click Me
      </MdxButton>
    </MDXLayout>
  );
}

MDXContent.isMDXComponent = true;

这两者的渲染方式差距事非常大的,所以使用 MDX1 的 React 官网要想迁移到 MDX2,effort 还是相对挺大的,比如:

在 MDX1 中,对于没有直接 import 的组件,会有一个makeShortcode来进行提示,并且能正常渲染,而在 MDX2 中,并没有这样的 runtime 判断,MdxButton组件是直接从props.components进行读取的(当然正常会使用MDXProvider进行组件传递),如果读取不到则就是undefined,并且会直接进行渲染,那么 runtime 自然就报错了。

再比如:

直接export const name = 'Jokcy'的时候,在 MDX1 中会作为layoutProps并且传递给Layout组件。而在 MDX2 中,则直接编译成了export const name = 'Jokcy'

而且因为需要在编译期间注入 Layout 相关信息,我还自己写了一个 post loader,未来也有机会能够开源。

这种完全的 breaking change 都是我需要进行修复的,所以这部分也是花费我时间最多的地方,大致概括需要更新的内容如下:

  • 渲染MDXProvider的地方需要更新
  • 读取节点的方式要略过Fragment
  • 对于codeinlineCode两个版本的处理完全不同,MDX 没有inlineCode节点类型
  • MDX 没有mdxType属性,之前通过mdxType进行判断的方式都需要进行修改

将来有机会我会分享这部分开发内容给大家,或者未来也许我也会把这个站点的代码开源。

Layout

React 官网作为一个框架介绍、学习、API 展示型的网站,工具属性是拉满的,一个非常清晰的导航结构是需要的,所以 React 官方采用了三览结构,左侧导航搜索,中间阅读,右侧本页概览。

React官方截图

而这作为一个偏展示性的网站来说,有些过于工具化,并且三栏的设计也让人不能集中注意力在阅读上。我非常欣赏 Medium 的设计,简洁美观,毫不花哨却处处透露出设计感,所以我希望自己的网站也能够更简洁并便于阅读。所以在这个基础上,我对整体的布局做了修改。

我改版之后的

我将左侧栏去除,无论多大屏都是用顶部导航的设计,只在右侧贴边展示了一个页面标题快捷导航。总体来说会更利于集中于文章内容的阅读。当然这个设计未来可能也会继续修改,谁知道呢,毕竟是个人网站,想咋改咋改 😄。

当然以上是针对大屏的,对于小屏我并没有太多的修改。React 使用的是 tailwindCss 作为样式基础,而非使用组件库,可能也有对于页面大小的要求有关吧。我个人在 React 生态中是更喜欢 Css in Js 方案的,但是在使用了 tailwind 之后觉得设计确实也非常不错,难怪会成为流行的样式框架,未来也会考虑使用。

更新中…

Follow Me

Jokcy的二维码