从头学服务器组件#2:发明组件

这是“从头学服务器组件”系列的第 2 篇文章。这个系列的文章来自于 Dan Abramov 所写的《RSC From Scratch. Part 1: Server Components》[1]这篇长文,为了方便理解和学习,我将这篇长文拆分成了一个系列进行讲解。

回顾

在上一篇文章《从头学服务器组件#1:发明 JSX》[2]中,我们为实现的博客站点引入了 JSX 这一个 JS 的语法扩展,并编写了将 JSX 树结构转换成 HTML 字符串的渲染函数 renderJSXToHTML()

在这之后,我们就要考虑 UI 界面的拆分了。我们可以把一张页面看成是由多个模块组成,每个模块各自独立,又能自由组合。在实际开发中,我们使会用「组件(Components)」 这一结构来实现这些模块。

组件

不管你的代码是运行在客户端还是服务端,对页面 UI 进行拆分都是必要的。我们采用函数组件的方式来表示这一个个页面模块——给它们起名,并通过函数参数方式,将props 信息传递给组件。

先看下之前的实现。

createServer(async (req, res) => {
  const author = "Jae Doe";
  const postContent = await readFile("./posts/hello-world.txt""utf8");
  sendHTML(
    res,
    <html>
      <head>
        <title>My blog</title>
      </head>

      <body>
        <nav>
          <a href="/">Home</a>
          <hr />
        </nav>
        <article>
          {postContent}
        </article>
        <footer>
          <hr />
          <p><i>(c) {author}, {new Date().getFullYear()}</i></p>
        </footer>
      </body>

    </html>
  );
}).listen(8080);

拆分组件

那么按照我们最新的构想,我们将博客详情页的内容拆分成 2 个组件:BlogPostPageFooter(按照约定,组件首字母大写)。

function BlogPostPage({ postContent, author }{
  return (
    <html>
      <head>
        <title>My blog</title>
      </head>
      <body>
        <nav>
          <a href="/">Home</a>
          <hr />
        </nav>
        <article>
          {postContent}
        </article>
        <Footer author={author} />
      </body>
    </html>

  );
}

function Footer({ author }{
  return (
    <footer>
      <hr />
      <p>
        <i>
          (c) {author} {new Date().getFullYear()}
        </i>
      </p>
    </footer>

  );
}

再用 <BlogPostPage postContent={postContent} author={author} /> 替换原来位置上的代码。

createServer(async (req, res) => {
  const author = "Jae Doe";
  const postContent = await readFile("./posts/hello-world.txt""utf8");
  sendHTML(
    res,
    <BlogPostPage
      postContent={postContent}
      author={author}
    />
  );
}).listen(8080);

现在,如果不对 renderJSXToHTML() 函数不做任何修改,运行程序,发现最终生成的 HTML 字符串是有问题的。

<!-- 并非是有效的 HTML... -->
<function BlogPostPage({postContent,author}) {...}>
</function BlogPostPage({postContent,author}) {...}>

问题出在之前实现的 renderJSXToHTML() 函数——只考虑了 jsx.type 是字符串的情况(例如 "html""footer""p")。

if (jsx.$$typeof === Symbol.for("react.element")) {
  // Existing code that handles HTML tags (like <p>).
  let html = "<" + jsx.type;
  // ...
  html += "</" + jsx.type + ">";
  return html;

但是在这里,我们的组件标记在编译后,jsx.type 的值是组件函数本身,也就是 BlogPostPage,所以执行 "<" + jsx.type + ">" 会直接把函数源代码打印出来了。因此,我们还需要扩展 renderJSXToHTML()功能,增加当 jsx.type 的值为函数时的处理逻辑。

增加组件渲染支持

这块实现相对来说不是很复杂:判断 jsx.type 是函数后,表示正在处理函数组件,调用函数组件,并将 jsx.props 作为参数传入,得到返回的 JSX 结构,再带入 renderJSXToHTML() 函数,转换成最终的 HTML 字符串就行了。

下面是代码实现。

if (jsx.$$typeof === Symbol.for("react.element")) {
  if (typeof jsx.type === "string") { // 是像 <div> 这样的 HTML 标记吗?
    // 处理 HTML 标记 (比如:<p>).
    let html = "<" + jsx.type;
    // ...
    html += "</" + jsx.type + ">";
    return html;
  } else if (typeof jsx.type === "function") { // 是像 <BlogPostPage> 这样的组件吗?
    // 使用 props 作为参数调用组件函数,得到返回的 JSX 结构,并转换成 HTML 字符串
    const Component = jsx.type;
    const props = jsx.props;
    const returnedJsx = Component(props);
    return renderJSXToHTML(returnedJsx); 
  } else throw new Error("Not implemented.");
}

添加完这块判断后,renderJSXToHTML() 函数在碰到 <BlogPostPage author="Jae Doe" /> 这类 JSX 元素后,调用 BlogPostPage() 函数,并将 { author: "Jae Doe" } 参数传递给它,会得到由所有 HTML 标记构成的 JSX 结构,然后将这个结构再一次传递回 renderJSXToHTML(),就得到最终的 HTML 字符串了。

仅仅需要这些操作,我们就完成了对组件渲染的支持。

打开这里的线上 demo[3]查看,发现渲染结果跟之前的一样,说明成功了。

从头学服务器组件#2:发明组件

总结

本节我们实现了 UI 页面的拆分,将博客详情页拆分成 BlogPostPageFooter 两个组件,并通过修改 renderJSXToHTML() 函数实现,增加了对组件渲染的支持,并最终得到了跟之前一样的渲染效果。

现在,我们的博客应用只有详情页,比较单调,接下来我们再引入博客主页,或叫索引页(Index Page),来展示所有的博文列表。为此,我们需要增加路由功能。

到此为止,下一篇再说,再见!

参考资料

[1]

《RSC From Scratch. Part 1: Server Components》: https://github.com/reactwg/server-components/discussions/5

[2]

《从头学服务器组件#1:发明 JSX》: https://www.yuque.com/zhangbao/blog/piohrmf1ghe5y7g4

[3]

线上 demo: https://codesandbox.io/p/sandbox/thirsty-frost-8oug3o?file=%2Fserver.js


原文始发于微信公众号(写代码的宝哥):从头学服务器组件#2:发明组件

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243757.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!