Typescript+Ant-Design + Redux+Next.js搭建服务端渲染框架
先说说先要搭建这个工具的起因吧!最近这段时间分别了学习Typescript和react服务端渲染,但是苦于没有没有实际使用端场景,我就突然想起了将Typescript与Next结合起来搭建一个服务端渲染端工具,一是这样即可以起到练手的作用,二是如果以后有相应业务需求可以直接拿来用。话不多说,我们开始吧!
Next特点
next.js作为一款轻量级的应用框架,主要用于构建静态网站和后端渲染网站,为什么选Next呢?,只因Next有如下几个优点:
- 使用后端渲染
- 自动进行代码分割(code splitting),以获得更快的网页加载速度
- 简洁的前端路由实现
- 使用webpack进行构建,支持模块热更新(Hot Module Replacement)
- 可与主流Node服务器进行对接(如express)
- 可自定义babel和webpack的配置
创建并初始化项目
这里就不多说了,相信大家都很熟练了
mkdir TAN
cd TAN
npm init -y
2
3
安装React
- 安装react、react-dom等依赖
npm install react react-dom express next --save
npm install @types/{react,react-dom} --save-dev
2
- 在根目录下新建pages文件夹(一定要命名为pages,这是next的强制约定,不然会导致找不到页面),并新建index.js
export default () => <div>Welcome to next.js!</div>
- 将如下脚本添加到package.json,用于启动项目
{
...
"scripts": {
"dev": "next"
}
...
}
2
3
4
5
6
7
运行npm run dev命令打开 http://localhost:3000即可查看初始页面。
配置Typescript
- 安装Typescript
npm install typescript --save
@zeit/next-typescript 不再需要,因为Next.js已内置支持Typescript
- 在根目录下新建.babelrc,输入如下代码
{
"presets": [
"next/babel"
]
}
2
3
4
5
- 在根目录下新建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"]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
在上面的配置中,请注意: 使用“jsx”:“preserve”而不是react-native,因为Next.js在5.0.0之后支持.jsx文件扩展名,之前它只识别.js文件。
- 引入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"}]
}
}
2
3
4
5
6
7
8
9
10
11
将page/index.js修改为index.tsx,并做如下修改
interface Person {
name: String;
}
const Rashomon: Person = {
name: 'rashomon',
}
export default () => <div>{Rashomon.name}!</div>
2
3
4
5
6
7
- 根目录下新建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}`)
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
更改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
- 安装antd以及babel-plugin-import实现按需加载
npm install antd --save
npm install babel-plugin-import --save-dev
2
- 修改.babelrc文件
{
"presets": [
"next/babel"
],
"plugins": [
["import", { "libraryName": "antd", "style": false }]
]
}
2
3
4
5
6
7
8
引入Less
- 安装less、@zeit/next-less 依赖
npm install less @zeit/next-less null-loader --save
- 根目录下新建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
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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>
2
3
4
5
6
7
8
9
- 如要使用自定义的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,
},
···
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行启动命令,页面如下图:
引入redux
- 安装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
2
由于篇幅关系创建store、reducers、saga、action的过程我们就不再这里缀述,感兴趣的同学可以查看github上的代码。我们在这里着重讲一下如何引入store,为了在页面初始化时引入store,需要自定义
_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))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
最终实现一个计数器与列表,如下图:
自定义错误处理
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>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
总结
到此为止,我们已经基本实现了一个基于Typescript+Ant-Design + Redux+Next.js的服务端渲染框架,也熟悉了一部分next的用法,想要了解更多可以前往官网地址为next.js。希望大家可以有所收获,想要了解更多不同技术实现的服务端渲染框架的同学可以看这里https://github.com/zeit/next.js/tree/7.0.0-canary.8/examples