yaofly2012 / note

Personal blog
https://github.com/yaofly2012/note/issues
44 stars 5 forks source link

Nextjs #42

Open yaofly2012 opened 5 years ago

yaofly2012 commented 5 years ago

Nextjs是什么?

Docs

  1. Nextjs围绕Page这个概念构建。

React脚手架

create-react-app umi next

yaofly2012 commented 5 years ago

Learn

  1. 默认构建SSR,单页应用

路由系统

  1. 约定pages目录下的JS文件;
  2. 会自动把QueryString进行encodeURIComponent(哪里做的);

组件

  1. 布局组件用起来不是很方便啊,每个页面都要引用,怎么优化?

后端渲染

  1. 取QS参数 利用withRouter方法注入router属性(可以对任意组件注入该属性),然后通过该属性访问路由QueryString。

  2. Route masking(实现干净简洁的URL) 怎么翻译?有点类似给URL取个别名。 原理是什么?reload发生404貌似是前端实现的别名。

  3. 自定义服务-结合express

    • next不会自动更新server.js

getInitialProps

统一了SSR调用接口的规范。

  1. Server触发还是Client触发的行为 第一次访问时Server触发,前端页面切换时Client触发!怎么做到的?

CSS

CSS开发方式也变了

  1. 传统方式(基于CSS文件)
  2. 用JS写CSS(CSS in js)【推荐】

style-jsx

CSS组件化隔离

部署?

还不会

静态导出

  1. 发现全局变量__NEXT_DATA__干嘛用的?发现是getInitialProps方法获取到的相关值

懒加载模块

  1. Next默认的模块行为: automatic code splitting

懒加载组件

兼容性

yaofly2012 commented 5 years ago

自定义Babel配置

  1. 如果根目录有.babelrc文件,则nextjs就把他当做对babel的配置,并且nextjs本身也会采用这里的自定义的配置,所以如果自定义babel配置,必须把next依赖的配置也配上。
yaofly2012 commented 5 years ago

一、页面路由

next路由系统整体两方面:

  1. 服务端:基于文件系统路由生成规则;

  2. 客户端: 支持单页应用的客户端路由系统。

  3. Simple client-side routing (page based)

  4. nextjs的路由是基于文件系统的。 那个pages目录下的所有文件都会被当初路由吗? 甚至_error, _app, _document, [动态路由] ?

Issues

  1. 在什么节点把文件系统转成路由配置?怎么做的? router manifest

二、next/link

Link组件管理前端页面切换?是API方式的语法糖?

  1. will prefetch the page and navigation will happen without a page refresh 会预加载?点击的时候再加载把?
  2. 子组件必须支持onClick事件
  3. href & as属性的区分
    • href: 表示页面在pages目录下的结构,可以认为是页面对应的文件目录结构。
    • as: 表示页面在浏览器URL输入栏里展示的URL,在客户端跳转时有效。

好像没这么简单。动态路由时,href的用处呢?路由参数取值是从as指定的URL里取的

  1. scroll 标记是打开新页面时否滚动页面顶部,跟公司老框架记录窗口位置异曲同工。

Issues:

  1. Warning: You're using a string directly inside . This usage has been deprecated. Please add an tag as child of

为啥?

三、next/router模块

管理页面切换

API

events对象 & Router事件

  1. on
  2. off

注意: 回调函数的参数url是表示展示在浏览器里的URL(即用户看到的URL)。

事件

绑定事件的节点

Router events should be registered when a component mounts (useEffect or componentDidMount/componentWillUnmount) or imperatively when an event happens.

整个都是客户端路由体系,理应只在客户端才进行Router的操作。

浏览器popState监听

next为了正常处理单页应用的页面切换,监听posState事件。

If the function you pass into beforePopState returns false, Router will not handle popstate;

测试发现只是没有发生页面切换,但是浏览器URL还是改成目标的url了。

Shadow Routing

改变URL但不用执行页面的getInitialProps。 Shadow Routing存在的意义:

Shallow routing works only for same page URL changes.

即页面A里跳转页面A,并且只有当跳转当前页面时shadow routing才有效。

场景呢?

  1. 带有filter的页面

除了不执行getInitialProps还有什么不同呢?

  1. 以为非shadow的同页面跳转会导致页面组件重新创建,其实是不是的:跳转相同页面是否shadow都不会重新创建组件的。

useRouter hook

The above router object comes with an API similar to next/router.

两个对象不是一样的

keys in router: pathname,route,query,asPath,events,push,replace,reload,back,prefetch,beforePopState
keys in Router: router,readyCallbacks,ready,push,replace,reload,back,prefetch,beforePopState

区别在哪里?为啥存在这些差别?

withRouter

通过withRouter注入的router对象和通过useRouter获取的router对象不相等,但是属性一样。

If useRouter is not the best fit for you, withRouter can also add the same router object to any component

什么情况下不适合使用withRouter?

  1. withRouter是hook, hook的使用本身是受限的
  2. 其他的呢?

'next/router'模块默认导出和Router对象

一般直接把'next/router'模块默认导出命名为Router,但这并不是Router对象(属于具体一个应用的路由实例对象),通过useRouter或者withRouter可以获取应用的Router对象。

Above Router object comes with the following API:

  • route - String of the current route
  • pathname - String of the current path excluding the query string
  • query - Object with the parsed query string. Defaults to {}.
  • asPath - String of the actual path (including the query) shows in the browser
  • push(url, as=url) - performs a pushState call with the given url
  • replace(url, as=url) - performs a replaceState call with the given url
  • beforePopState(cb=function) - intercept popstate before router processes the event
const CustomLink = withRouter(({children, href, router}) => {
    var routerGotByHook = useRouter();
    console.log(`keys in Router: ${Object.keys(Router)}`)
    console.log(`keys in router got by useRouter: ${Object.keys(routerGotByHook)}`)
    console.log(`keys in router got by withRouter: ${Object.keys(router)}`)
    console.log(`routerGotByHook === router -> ${routerGotByHook === router}`)
    console.log(`routerGotByHook === Router -> ${routerGotByHook === Router}`)
    console.log(`router === Router -> ${router === Router}`)
    return (
        <a href={href} onClick={onClickHandler(href)}>
        {children}
        <style jsx>{`
            a {
                margin-right: 10px;
            }
        `}</style>
        </a>
    )
})

Router对象API:

  1. 属性
    • pathname,
    • route,
    • query,
    • asPath
    • events
  2. 方法
    • push
    • replace
    • reload
    • back
    • prefetch
    • beforePopState

有些方法和'next/router'的导出模块一样的。

Issues

  1. Error: Route name should start with a "/", got "post/or-here"

为何?

  1. next前端页面切换都做些什么?

    Sometimes when switching between pages, Next.js needs to download pages(chunks) from the server before rendering the page. And it may also need to wait for the data. So while doing these tasks, browser might be non responsive.

  2. 在Page组件模块和在APP组件模块里给Router添加事件有什么不同吗? 看现象没有区别呢?Router应该是单例的吧?可以添加多个事件处理函数吗?

    • 首页组件所在的模块只会加载一次,在模块顶层操作Router对象添加事件处理函数也只会执行一次。
    • App组件里是公共的逻辑 各个页面共享的逻辑(如论是前端切换,还是手动刷新都可以执行)
    • 页面A,B。如果只在页面A里添加Router事件处理函数,在前端切换时好像在B 页面也能触发,但是手动刷新B页面就不行了。所以公共的逻辑还是在App组件里执行。
  3. You should only use "next/router" inside the client side of your app.

为何Router.events.on可以,而Router.beforePopState不可以

  1. Using router events in getInitialProps is discouraged as it may result in unexpected behavior.

怎么个'unexpected behavior.' ?

yaofly2012 commented 5 years ago

Dynamic Routing 动态路由匹配

版本:Next.js 9 or newer.

? a.k.a. url slugs, pretty urls, et al

预定义的路由(即静态路由)

  1. nextjs的路由是基于文件系统的,即pages的目录结构就是URL路径结构;
  2. 目录默认的是index.js页面

动态路由即路径参数(named URL parameters)

规则见引入背景

  1. A filename or directory name that is wrapped with [] would be considered a named parameter
  2. Explicit route segments would take priority over dynamic segments, matched from left-to-right
  3. Route parameters would be required, never optional
  4. Route parameters will be merged into the query object (accessible from getInitialProps or router via withRouter) — these parameters can not be overridden by a query parameter
  1. 参数值保存在route.query对象里,和QS一样;
  2. 路径参数都是String处理。

Issues ?

  1. 如何从服务端获取路径参数的值?

  2. Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)

  3. utils.js:12 Warning: You're using a string directly inside . This usage has been deprecated. Please add an tag as child of

缺点:

  1. 都是字符串,不能类型校验?
  2. 都是必需的,没法缺省。 参考文档里有提到:

    Optional route parameters are not expressible through the filesystem.

  3. 能力这么有限,根本原因还是基于文件的路由系统 url path必须是合法的文件名字,导致很难有很强的表达能力。

参考

引入背景-[RFC] Dynamic Routes #7607

yaofly2012 commented 5 years ago

PK 公司老框架

对比下公司老框架SAP来看next 组成:

  1. 管理页面渲染
    • 单例的APP对象
    • 页面对象
  2. 路由
    • 单例的
  3. SSR和CSR的管理?
yaofly2012 commented 5 years ago

API 路由

pages目录下除了可以放置页面外还可以定义接口,2个约定:

  1. pages/api目录结构作为接口的URL;
  2. 模块作为接口响应处理函数,即默认导出必须是个函数,否则报错

    TypeError: resolver is not a function

路由匹配

  1. 跟页面路由匹配规则一致,也支持动态路由。
  2. 如果API接口匹配失败(即404),触发的跟页面路由404同样的404页面。

    处理函数

    export default (req, res) => {}
  3. 默认已经使用中间件处理了req参数。

接口配置?

export const config = {
  api: {
    bodyParser: false
  }
};

Issues

  1. 如何添加公共中间件?
  2. 如何定制接口404的默认处理?
yaofly2012 commented 5 years ago

自定义Web服务 & 路由匹配

一、next模块

1. next函数

const next = require('next');
const app = next(opts: object);

2. app对象

1. render方法

渲染指定的页面,中间件函数。

app.render(req, res, '/a', req.query)

app.render方法的四个实参构成了getInitialProps函数的实参对象的属性。

2. renderToHTML

get: async ({ req, res, pagePath, queryParams}) => ({
        // 获取新鲜数据的方式
        data: await app.renderToHTML(req, res, pagePath, queryParams)
    })

渲染指定页面,返回页面HTML字符串。

3. getRequestHandler

获取nextjs默认的路由匹配函数

二、Issues

  1. next函数的参数对象的dev, dir,quiet配置为啥不能再next.config.js里配置?
  2. 自定义服务没有自动更新功能,怎么可以做的?

三、自定义路由匹配

自定义路由是建立在自定义web服务之上的。

四、SSR缓存

4.1 场景

cacheable-response

Server Side Rendering (SSR) is a luxurious

4.2 缓存媒介

  1. 库cacheable-response
  2. 库 Keyv
  3. 公司Redis ?

五、web服务框架

引用fastify,得研究研究

Framework Version Router? Requests/sec
hapi 18.1.0 29,998
Express 4.16.4 38,510
Restify 8.0.0 39,331
Koa 2.7.0 50,933
Fastify 2.0.0 76,835
     
http.Server 10.15.2 71,768
yaofly2012 commented 5 years ago

一、自定义配置

1. nextjs也是高度可配置的,并且nextjs本身是基于webpack, babel构建的,所以nextjs的配置主要分为三类:

1.2 next.config.js配置文件

配置文件约定项目根目录的next.config.js文件

  1. 正常的nodejs模块,可以执行任意nodejs代码。但是代码不会进过webpack, babel转化,所以语法规范都要看node版本是否支持。

1.3 pageExtensions

js文件里也可以写jsx,为啥还需要jsx后缀的文件?

1.4 generateBuildId

为啥要自定义buildID(默认实现方案【怎么实现的?】无法支持多服务build时统一的buildId ?)

二、webpack配置

  1. webpack配置函数会被执行2次,分别用于构建server, client。

三、babel配置

四、使用配置

4.1. nextjs build过程中使用

4.2. 应用程序使用(Server,Client)

1. 在构建阶段读取配置

利用process.env访问配置变量。

  1. 只能通过process.env方式引用吗?怎么实现的?
  2. 了解process.env

2. 运行时读取配置

注意事项:

  1. 运行时配置会增加渲染开销(具体还得研究下)
  2. 和自动预渲染冲突?

Issues/Concern:

  1. 在render函数把serverRuntimeConfig的值喷到页面时,Client端有个waring: ??

    Warning: Text content did not match. Server: "serverRuntimeConfig.mySecret = secret" Client: "serverRuntimeConfig.mySecret = undefined"

存在SSR时才报,客户端路由不报。 SSR和CSR渲染的内容不一致,应该是FOFT问题

貌似是ReactDOM报的。FOUC: Warning: Text content did not match. Server: "...css..."

关于FOUC的文章也很多啊

  1. webkit The FOUC Problem
  2. How to prevent Flash of Unstyled Content on your websites