Rashomon511/MyBlog

Typescript+Ant-Design + Redux+Next.js搭建服务端渲染框架

Opened this issue · 0 comments

先说说先要搭建这个工具的起因吧!最近这段时间分别了学习Typescript和react服务端渲染,但是苦于没有没有实际使用端场景,我就突然想起了将Typescript与Next结合起来搭建一个服务端渲染端工具,一是这样即可以起到练手的作用,二是如果以后有相应业务需求可以直接拿来用。话不多说,我们开始吧!

Next特点

next.js作为一款轻量级的应用框架,主要用于构建静态网站和后端渲染网站,为什么选Next呢?,只因Next有如下几个优点:

  1. 使用后端渲染
  2. 自动进行代码分割(code splitting),以获得更快的网页加载速度
  3. 简洁的前端路由实现
  4. 使用webpack进行构建,支持模块热更新(Hot Module Replacement)
  5. 可与主流Node服务器进行对接(如express)
  6. 可自定义babel和webpack的配置

创建并初始化项目

这里就不多说了,相信大家都很熟练了

mkdir TAN
cd TAN
npm init -y

安装React

  1. 安装react、react-dom等依赖
npm install react react-dom express next --save
npm install @types/{react,react-dom} --save-dev
  1. 在根目录下新建pages文件夹(一定要命名为pages,这是next的强制约定,不然会导致找不到页面),并新建index.js
export default () => <div>Welcome to next.js!</div>
  1. 将如下脚本添加到package.json,用于启动项目
{
  ...
  "scripts": {
    "dev": "next"
  }
  ...
}

运行npm run dev命令打开 http://localhost:3000即可查看初始页面。

配置Typescript

  1. 安装Typescript
npm install typescript --save

@zeit/next-typescript 不再需要,因为Next.js已内置支持Typescript

  1. 在根目录下新建.babelrc,输入如下代码
{
  "presets": [
    "next/babel"
  ]
}
  1. 在根目录下新建tsconfig.json,输入如下代码
{
  "compileOnSave": false,
  "compilerOptions": {
    "strict": true,
    "target": "esnext",
    "module": "esnext",
    "jsx": "preserve",
    "allowJs": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "removeComments": false,
    "preserveConstEnums": true,
    "sourceMap": true,
    "skipLibCheck": true,
    "baseUrl": ".",
    "typeRoots": [
      "./node_modules/@types/",
    ],
    "lib": [
      "dom",
      "es2015",
      "es2016"
    ]
  },
  "exclude": ["node_modules"]
}

在上面的配置中,请注意:
使用“jsx”:“preserve”而不是react-native,因为Next.js在5.0.0之后支持.jsx文件扩展名,之前它只识别.js文件。

  1. 引入tslint.json,帮助我们对ts的写法进行一些约束规范统一风格。
{
    "extends": ["tslint:latest"],
    "rules": {
      "arrow-parens": false,
      "interface-name": [true, "never-prefix"],
      "no-object-literal-type-assertion": false,
      "no-submodule-imports": false,
      "semicolon": [true, "never"],
      "trailing-comma": [true, {"multiline": "nerver", "singleline": "never"}]
    }
  }

将page/index.js修改为index.tsx,并做如下修改

interface Person {
    name: String;
}
const Rashomon: Person = {
    name: 'rashomon',
}
export default () => <div>{Rashomon.name}!</div>
  1. 根目录下新建server.js,server.js作为入口文件可以启动自定义服务,我们可以在自定义服务中进行路由解析。
const next = require('next');
const { createServer } = require('http');
const { parse } = require('url')
const dev = process.env.NODE_ENV !== 'production';
const port = parseInt(process.env.PORT, 10) || 3000;
const app = next({dev})
const handle = app.getRequestHandler()

app.prepare().then(() => {
    createServer((req, res) => {
      const parsedUrl = parse(req.url, true)
      handle(req, res, parsedUrl)
    }).listen(port, err => {
        console.log(err, port)
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
  })

更改package.json启动命令    "dev": "node server.js", 运行npm run dev,你会发现提示一行错误:It looks like you're trying to use TypeScript but do not have the required package(s) installed.意思是试图使用TypeScript,但没有安装所需的包。我们只需运行下列命令即可

npm install --save-dev @types/node

再次运行命令打开http://localhost:3000即可看到我们的页面。

引入antd

  1. 安装antd以及babel-plugin-import实现按需加载
npm install antd --save
npm install babel-plugin-import --save-dev
  1. 修改.babelrc文件
{
    "presets": [
      "next/babel"
    ],
    "plugins": [
      ["import", { "libraryName": "antd", "style": false }]
    ]
}

引入Less

  1. 安装less、@zeit/next-less 依赖
npm install less @zeit/next-less null-loader --save 
  1. 根目录下新建next.config.js, 输入如下代码
const withLess = require('@zeit/next-less');

module.exports = withLess({
  lessLoaderOptions: {
    javascriptEnabled: true,
  },
  webpack: (config, { isServer }) => {
    if (isServer) {
      const antStyles = /antd\/.*?\/style.*?/
      const origExternals = [...config.externals]
      config.externals = [
        (context, request, callback) => {
          if (request.match(antStyles)) return callback()
          if (typeof origExternals[0] === 'function') {
            origExternals[0](context, request, callback)
          } else {
            callback()
          }
        },
        ...(typeof origExternals[0] === 'function' ? [] : origExternals),
      ]

      config.module.rules.unshift({
        test: antStyles,
        use: 'null-loader',
      })
    }
    return config
  },
})

3.引入antd样式
在page文件夹下新建index.less文件,引入antd样式,并在index.tsx中引入,

@import '~antd/dist/antd.less';

并在index.tsx中做如下修改

import {Button} from 'antd';
import './index.less'
interface Person {
    name: String;
}
const Rashomon: Person = {
    name: 'rashomon',
}
export default () => <Button type='primary'>{Rashomon.name}!</Button>
  1. 如要使用自定义的antd主题,举个例子,我们改变一下antd的主题色,安装less-vars-to-js
npm install less-vars-to-js --save

根目录下新建assets文件并新建antd-custom.less

@primary-color: #08979c;

在next.config.js中读取该样式文件,经过less-vars-to-js将less文件的内容作为字符串接收,并返回一个包含所有变量的对象。
next.config.js添加如下内容:

... // 添加内容
const lessToJS = require('less-vars-to-js');
const fs = require('fs');
const path = require('path');

// Where your antd-custom.less file lives
const themeVariables = lessToJS(
  fs.readFileSync(path.resolve(__dirname, './assets/antd-custom.less'), 'utf8')
)
...
  lessLoaderOptions: {
    javascriptEnabled: true,
    modifyVars: themeVariables, 
  },
 ···

运行启动命令,页面如下图:
image.png

引入redux

  1. 安装redux与redux-saga,引入redux-saga是为了解决异步请求操作
npm install redux react-redux next-redux-wrapper @types/react-redux --save
npm install redux-saga next-redux-saga @types/next-redux-saga --save

由于篇幅关系创建store、reducers、saga、action的过程我们就不再这里缀述,感兴趣的同学可以查看github上的代码。我们在这里着重讲一下如何引入store,为了在页面初始化时引入store,需要自定义,在pages文件夹下新建_app.tsx.
_app.tsx帮我们做如下几件事:

  • 控制页面初始化
  • 当页面变化时保持页面布局
  • 当路由变化时保持页面状态
  • 使用componentDidCatch自定义处理错误
  • 注入额外数据到页面里 (如 GraphQL 查询)

因此,在pages文件下新建_app.tsx,引入store,代码如下

import React from 'react'
import App from 'next/app';
import { Provider } from 'react-redux';
import Head from 'next/head'
import withRedux from 'next-redux-wrapper'
import withReduxSaga from 'next-redux-saga'
import Layout from '../components/Layout' // 页面基础布局
import initStore from '../redux/store'  // store
import '../static/style/index.less' // antd样式

class MyApp extends App {
    // next.js提供了一个标准的获取远程数据的接口:getInitialProps,通过getInitialProps我们可以获取到远程数据并赋值给页面的props。
    // getInitialProps即可以用在服务端也可以用在前端
    static async getInitialProps({ Component, ctx }: any) {
        let pageProps = {};
        if (Component.getInitialProps) {
            pageProps = await Component.getInitialProps({ ctx });
        }
        return { pageProps };
    }
    render() {
        const { Component, pageProps, store }: any = this.props
        return (
            <Provider store={store}>
                <Head>
                    <title>Tarn</title>
                </Head>
                <Layout>
                    <Component {...pageProps} />
                </Layout>
            </Provider>
        )
    }
}
export default withRedux(initStore)(withReduxSaga(MyApp))

最终实现一个计数器与列表,如下图:
image.png
image.png

自定义错误处理

404和500错误客户端和服务端都会通过error.js组件处理。如果你想改写它,则在pages文件下新建_error.js,当用户访问错误路由时会访问_error页面

import React from 'react'
export default class Error extends React.Component {
  static getInitialProps({ res, err }) {
    const statusCode = res ? res.statusCode : err ? err.statusCode : null;
    return { statusCode }
  }
  render() {
    return (
      <p>
        {this.props.statusCode
          ? `An error ${this.props.statusCode} occurred on server`
          : 'An error occurred on client'}
      </p>
    )
  }
}

总结

到此为止,我们已经基本实现了一个基于Typescript+Ant-Design + Redux+Next.js的服务端渲染框架,也熟悉了一部分next的用法,想要了解更多可以前往官网地址为next.js。希望大家可以有所收获,想要了解更多不同技术实现的服务端渲染框架的同学可以看这里https://github.com/zeit/next.js/tree/7.0.0-canary.8/examples