最新消息:看到那些跳动的图片、文字了吗?点击点击 O(∩_∩)O~~

Next.js 初体验

若思若想 onlyling 3735浏览

在做 React SSR 的时候,最先考虑的是 UMI 的方案,因为管理后台也在用这一套,使用上比较容易上手,但是尝试后发现有一点麻烦。

当前项目有些配置数据是全局配置,需要提前、同步加载好,CSR 的 UMI 可以通过 app.tx 中的 getInitialState 加载,结合 @umijs/plugin-model 很简单的共享数据。在 SSR 这边,一旦使用了 getInitialState 就会在渲染的时候降级为 CSR,同时 @umijs/plugin-model 现在并不适合 SSR,model 需要在 DOM 节点挂载好后才初始化好。

当然,如果只是提前加载全局数据,也可以在 app.ts 文件里暴露一个 ssr 对象,里面有 modifyGetInitialPropsCtx 方法,请求数据放到 ctx 上,每个组件都可以通过 getInitialProps 方式拿到数据,但是我觉得这样有点奇怪/丑。如果以后一旦涉及到数据更新,貌似没有合适的方式。

当然,UMI 的 SSR 已经适配了 dva 数据管理工具,以前没怎么用过这个,不是很想去踩坑,另外没发现怎么在应用初始化的时候同步更新/拉取全局数据。

UMI SSR 这方面的文档真的有点不全面。

回到今天的主角,虽然以前简单用了一下 Next.js,但是都是很多年前的事情了,在使用方式上似乎没有多大的变动。

首先是基础代码,Next.js 的 GitHub 仓库里有很多例子,按照需要的工具,把 TypeScript+Less+Antd 的基础代码抄好了,借鉴的例子有 with-typestylewith-typescript-eslint-jestwith-ant-design-less,运行 yarn dev 就能看到 HTML 代码直出的页面。

基础代码的问题解决了,接下来解决接口请求,以前自己也从零开始写过 React SSR 的像个代码,比较清楚在数据请求的时候分为两个过程,页面刷新的时候在服务器端,页面切换、操作的时候在浏览器端。

在 request 工具上,最开始选择是 isomorphic-fetch,从它实现上也能看出它比较简单,在不同的端使用不同的依赖,保障请求正常。在使用的过程中却发现会有报错,有点可惜,还好找到了另一个相同功能的库,cross-fetch。只需要 import Fetch from 'cross-fetch'; 就能在业务使用,同时只需要保障接口是一个完整的地址,浏览器端可以通过 / 开头的绝对路由请求当前站点/应用的接口,但是服务器端可不认 /,需要一个完整的地址。

当前项目并没有涉及到登录、用户状态的需求,这里也省了很多麻烦事,比如一个用户登录后,在服务器端的请求都需要在 request header 带上 cookie/token,如果是浏览器端,都会默认带上或同缓存中拿到通过配置带上。服务器端比较麻烦,不可能把这些信息放到一个全局变量中,这样所有请求都共享同一个状态了。

在以往的 demo 中,我的操作是在每次访问的时候,新生成一个 request 对象,带上当前 HTTP 上的一些 header 信息。

接下来要解决全局数据共享的问题,当然是选择 rematch,个人认为它是 redux 相关方案中最好用的,虽然它现在和 TypeScript 配合的时候有点小遗憾,但是已经很不错了。

按照 with-rematch 的例子把代码抄好,发现 _app.ts 里有一个 initialReduxState 数据,最开始以为框架已经自动处理好了,万万没想到,它只是一个示例,需要自己把这个数据注入到 App 组件内。

Next.js 在每个页面文件中,可以向外暴露一个 getServerSideProps 函数用于服务器端获取数据,如果在 App 组件内这样操作,应该就没多大问题了?好吧,文档中有指出 App 组件不支持 getServerSideProps 函数。

在翻阅其他相关的 Next.js redux 代码库的时候发现,大伙都在 App 组件用 getInitialProps 函数获取初始化数据,虽然文档上说用 getServerSideProps 代替它,但现在也只好试试看。

import App from 'next/app';
import type { AppProps, AppContext } from 'next/app';
import { Provider } from 'react-redux';

import { useStore, initializeStore } from '@/store';
import BaseLayout from '@/layouts/base-layout/base-layout';
import Head from '@/b-components/head/head';

import '../global.less';

const defaultPageProps = {};

export default function CustomApp({
  Component,
  pageProps = defaultPageProps,
  initialReduxState,
}: AppProps & { initialReduxState: any }) {
  const store = useStore(initialReduxState);

  return (
    <Provider store={store}>
      <BaseLayout>
        <Head />
        <Component {...pageProps} />
      </BaseLayout>
    </Provider>
  );
}

// 初始化 redux
// 初始化全局数据
// 参考:https://github.com/naponmeka/nextjs-typescript-with-rematch/blob/master/lib/withRematch.tsx
CustomApp.getInitialProps = async (appContext: AppContext) => {
  const store = initializeStore();

  await store.dispatch.BaseConfig.PutConfig();

  const appProps = await App.getInitialProps(appContext);

  // 不放到 pageProps 里面,避免每个组件都接受到 props

  return {
    ...appProps,
    initialReduxState: store.getState(),
  };
};

大概就像上面那样,每次渲染前就能拿到数据。

最基础的几个功能、要点已经完成,可以开始撸业务代码了,除了获取数据的时候需要注意在 getServerSideProps 中获取,组件通过 props 接受,其他和 CSR 方案差距不大。

antd 的 message 需要先判断环境再使用。

Next.js 并非什么都十分完美的框架,比如 @zeit/next-less.module.less 文件自动 CSS Modules 并不生效,并没有 UMI 的 auto-module 方案方便;在 jsx/tsx 文件中导入静态文件需要通过第三方插件实现,next-compose-pluginsnext-optimized-images;Next.js 默认 node_modules 内文件不做转换,需要使用 next-transpile-modules 转换部分 es module 导出的库。

在自己搭建的项目中,我们可以直接在代码中拿到当前运行环境的一些变量,比如 process.env.NODE_HOST,在打包的时候区分开发、线上环境,但是 Next.js 并不会,需要通过配置 publicRuntimeConfig 传递,再通过 next/config 在业务代码中获取。

最后打包上线的时候,案例说只需要把打包后的 .next 文件上传至服务器,并且通过 next 开启一个自带的 Node.js 应用,但是打开页面后会有一些 js 文件报错。如果在服务器端打包就不会出问题,暂时不太明白为啥,可能是部分依赖引用路径有点小问题吧。在生产环境的服务器上打包有风险点,可能需要引入 Docker 来部署上线。

转载请注明:OnlyLing - Web 前端开发者 » Next.js 初体验