进口
这是一个基本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
<App
Component={EnhancedComponent}
router={router}
{...props}
/>
);
};
// _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<any>) => string,
element: React.ReactElement<any>,
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<C extends BaseContext, IP = {}, P = {}>(Component: NextComponentType<C, IP, P>, ctx: C): Promise<IP | null> {
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 (
'<!DOCTYPE html>' +
renderToStaticMarkup(
<AmpModeContext.Provider value={ampMode}>
<Document
__NEXT_DATA__={{
dataManager: dataManagerData,
props, // The result of getInitialProps
page: pathname, // The rendered page
query, // querystring parsed / passed by the user
buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles
dynamicBuildId, // Specifies if the buildId should by dynamically fetched
assetPrefix: assetPrefix === '' ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML
runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML
nextExport, // If this is a page exported by `next export`
dynamicIds: dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds,
err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML
}}
dangerousAsPath={dangerousAsPath}
ampPath={ampPath}
amphtml={amphtml}
hasAmp={hasAmp}
staticMarkup={staticMarkup}
devFiles={devFiles}
files={files}
dynamicImports={dynamicImports}
assetPrefix={assetPrefix}
{...docProps}
/>
</AmpModeContext.Provider>,
)
)
}