资讯专栏INFORMATION COLUMN

Next.js源码解析【服务端渲染过程,以及_document、_app、pages这三者调用关系】

jeffrey_up / 2407人阅读

摘要:补充上文中出现的部分函数,直接截取自源码,都相对简单,可以作为参考很多参数,太长省略

入口

这是一个基础demo,由开发者自己提供server,用于渲染

const Koa = require("koa")
const Router = require("koa-router")
const next = require("next")
// 创建实例
const app = next({ dev, conf, dir: "./src" })
app.prepare().then(() => {
  const server = new Koa()
  const router = new Router()
  router.get("/", async ctx => {
    // 渲染
    await app.render(ctx.req, ctx.res)
    ctx.respond = false
  })
  server.listen(port)
})

在自定义服务端中通过const app = next()创建实例并使用app.render(req, res)方法进行渲染

所以直接从app.render这个渲染入口开始着手

了解框架逻辑唯一的方式就是看源码,由于源码过于细节,下面我会简化涉及到的代码,仅保留主要逻辑,附带具体地址,有兴趣深入的同学可以看看
app.render

next-server/server/next-server.ts

import { renderToHTML } from "./render.tsx"

// app.render入口函数
this.render(req, res){
    const html = await this.renderToHTML(req, res)
    return this.sendHTML(req, res, html)
}
this.renderToHTML(req, res){
    const html = await this.renderToHTMLWithComponents(req, res)
    return html
}
this.renderToHTMLWithComponents(req, res) {
    // render内的renderToHTML
    return renderToHTML(req, res)
}

可以看到上面都是简单的调用关系,虽然删除了大部分代码,但我们只需要知道,最终它调用了render.tsx内的renderToHTML

这是一个相当长的函数,也就是本篇文章的主要内容,通过renderToHTML能够了解到大部分内容,和上面相同,删除了大部分逻辑,仅保留核心代码

renderToHTML
// next-server/server/render.tsx
function renderToHTML(req, res) {
// 参考下文#补充 loadGetInitialProps,非常简单的函数,就是调用了_app.getInitialProps
// _app.getInitialProps函数内部会先调用pages.Component的getInitialProps
// 也就是在这里,我们编写的组件内的getInitialProps同样会被调用,获取部分初始数据
  let props = await loadGetInitialProps(App, { Component, router, ctx });
  
  // 定义渲染函数,返回html和head
  const renderPage = () => {
    // 参考下文#补充 render
    return render(
      renderToStaticMarkup,
      //渲染_app,以及其内部的pages.Component也就是我们编写的代码,详情参考next/pages/_app.tsx
      
    );
  };
  
// _document.getInitialProps会调用renderPage,渲染_app也就是我们的正常开发时编写的组件代码,详情参考next/pages/_app.tsx
  const docProps = await loadGetInitialProps(Document, { ...ctx, renderPage });
  
// 参考下文#补充 renderDocument
  let html = renderDocument(Document, {
    props,
    docProps,
  });
  return html;
}
小结
req=>
render(req, res)
    renderToHTML(req, res)
        renderToHTMLWithComponents(req, res)
            renderToHTML(req,res)
                _app.initialProps = loadGetInitialProps(App, { Component, router, ctx })
                _document.initialProps = loadGetInitialProps(Document, { ...ctx, renderPage })
                renderDocument(Document, _app.initialProps, _document.initialProps)
<=res

对应

req=>
    _app.getInitialProps()
        Component.getInitialProps()
    _document.getInitialProps()
        _app.render()
            Component.render()
    _document.render()
<=res

这篇文章简要描述next服务端的渲染过程,从中我们也能清楚_document、_app、以及pages内自己编写的组件之间的关系...

要是还没明白,请重新阅读一遍renderToHTML函数内的注释内容

需要注意的一些点,随缘补充

_document只在服务端被执行,浏览器端是不会执行的

react提供的renderToString函数只产出html,也就是纯粹的string,所有数据必须在调用renderToString之前注入

在浏览器端渲染时,存在isInitialRender用于标示是否第一次渲染,如果是第一次渲染,会调用ReactDOM.hydrate(reactEl, domEl)来执行绑定事件,所以部分生命周期(componentWillMount之后)以及事件会在浏览器端执行。

补充

上文中出现的部分函数,直接截取自源码,都相对简单,可以作为参考

render
function render(
    renderElementToString: (element: React.ReactElement) => string,
    element: React.ReactElement,
    ampMode: any,
  ): { html: string; head: React.ReactElement[] } {
    let html
    let head
  
    try {
      html = renderElementToString(element)
    } finally {
      head = Head.rewind() || defaultHead(undefined, isAmp(ampMode))
    }
  
    return { html, head }
  }
loadGetInitialProps
export async function loadGetInitialProps(Component: NextComponentType, ctx: C): Promise {
    if (process.env.NODE_ENV !== "production") {
      if (Component.prototype && Component.prototype.getInitialProps) {
        const message = `"${getDisplayName(Component)}.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.`
        throw new Error(message)
      }
    }
    // when called from _app `ctx` is nested in `ctx`
    const res = ctx.res || (ctx.ctx && ctx.ctx.res)
  
    if (!Component.getInitialProps) {
      return null
    }
  
    const props = await Component.getInitialProps(ctx)
  
    if (res && isResSent(res)) {
      return props
    }
renderDocument
function renderDocument(
    Document: DocumentType,
    {...很多参数,太长省略}
  ): string {
    return (
      "" +
      renderToStaticMarkup(
        
          
        ,
      )
    )
  }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109451.html

相关文章

  • Nextjs中文文档

    摘要:中文站点中文站当前翻译版本为。注意将不能使用在子组件中。只能使用在页面中。替换路由组件默认将新推入路由栈中。以防服务端渲染发生错误,建议事件写在生命周期里。禁止文件路由默认情况,将会把下的所有文件匹配路由如渲染为如果你的项目使用 Next.js 是一个轻量级的 React 服务端渲染应用框架。 Next.js中文站点 http://nextjs.frontendx.cn Next.j...

    luckyw 评论0 收藏0
  • 基于 Webpack 4 多入口生成模板用于服务渲染的方案及实战

    摘要:原作者原链接基于多入口生成模板用于服务端渲染的方案及实战法律声明警告本作品遵循署名非商业性使用禁止演绎未本地化版本协议发布。这是什么背景现代化的前端项目中很多都使用了客户端渲染的单页面应用。 原作者:@LinuxerPHL原链接:基于 Webpack 4 多入口生成模板用于服务端渲染的方案及实战 法律声明 警告:本作品遵循 署名-非商业性使用-禁止演绎3.0 未本地化版本(CC BY-...

    big_cat 评论0 收藏0
  • 基于 Webpack 4 多入口生成模板用于服务渲染的方案及实战

    摘要:原作者原博文地址基于多入口生成模板用于服务端渲染的方案及实战法律声明警告本作品遵循署名非商业性使用禁止演绎未本地化版本协议发布。这是什么背景现代化的前端项目中很多都使用了客户端渲染的单页面应用。 原作者:@LinuxerPHL原博文地址: 基于 Webpack 4 多入口生成模板用于服务端渲染的方案及实战 法律声明 警告:本作品遵循 署名-非商业性使用-禁止演绎3.0 未本地化版本(...

    Lavender 评论0 收藏0
  • 从零开始,揭秘React服务渲染核心技术

    摘要:不过这时的控制台会抛出这样一则警告提醒我们在服务端渲染时用来取代,并警告我们在时将不能用去混合服务端渲染出来的标签。综上所述,服务端和客户端都是需要路由体现的。我们画一下重点,意思很明确,就是为了服务端渲染而打造的。 抛砖引玉 在早几年前,jquery算是一个前端工程师必备的技能。当时很多公司采用的是java结合像velocity或者freemarker这种模板引擎的开发模式,页面渲染...

    googollee 评论0 收藏0
  • webpack实战

    摘要:和类似的预处理器还有等。的用处非常多,包括给自动加前缀使用下一代语法等,目前越来越多的人开始用它,它很可能会成为预处理器的最终赢家。 webpack实战 查看所有文档页面:全栈开发,获取更多信息。快马加鞭,加班加点,终于把这个文档整理出来了,顺便深入地学习一番,巩固知识,就是太累人,影响睡眠时间和质量。极客就是想要把事情做到极致,开始了就必须到达终点。 原文链接:webpack实战,原...

    cyrils 评论0 收藏0

发表评论

0条评论

jeffrey_up

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<