Next.js踩坑入门系列
- (一) Hello Next.js
- (二) 添加Antd && CSS
- (三) 目录重构&&再谈路由
- (四) Next.js中期填坑
- (五) 引入状态管理Redux
- (六) 再次重构目录
- (七) 其他相关知识
目录重构
来说一说为什么要目录重构吧,在踩坑系列(一)的最后我提到了,Next.js很强大,为我们封装了路由,只需要在pages下面新建js文件,里面写上我们熟悉的页面也就是react组件,就会渲染出来!
其实对于开发来说没区别,但是项目庞大以后,一个路由对应一个js文件,但是如果页面很复杂其实不是这个React组件也会很复杂,不是很符合组件化理念,后期也不好维护啊。而且,肯定要加redux的,这样的话就更加混乱了。所以现在趁着还清醒,赶快重新构建一下~
抽离Layout
我其实是想边学Next.js边用它搭建一个系统,具体是什么慢慢开发最后大家就清楚了。系统的样式就打算仿照掘金来写,感觉很简洁掘金的样式很简单,就是一个Header,然后就是下方的内容区域了,最后这篇文章完事的样子会变成下面这样
首先,我们在跟目录下新建一个components文件夹,专门用来放我们的组件,新写一个Header.js:
// /components/Header.js
import React, { Component } from 'react';
import Link from 'next/link';
import { color_youdao, color_youdao_border } from '../constants/CustomTheme';
class Header extends Component {
constructor(props) {
super(props);
const { title } = props;
this.state = { title };
}
render() {
const { title } = this.state;
return (
<div className='header-container'>
<Link href='/'>
<div className='logo-container'>
<img className='logo' alt='logo' src='/static/logo.png' />
<span className='sys-name'>XXX系统</span>
</div>
</Link>
<h2>{title}</h2>
<style jsx>{`
.header-container {
height: 60px;
background-color: ${color_youdao};
border: 1px solid ${color_youdao_border};
margin-bottom: 10px;
}
h2 {
text-align: center;
line-height: 60px;
font-size: 1.6rem;
font-weight: 500;
color: #fff;
}
.logo-container {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 15px;
left: 20px;
cursor: pointer;
}
.sys-name {
display: inline-block;
margin-left: 10px;
font-size: 20px;
color: #fff;
font-weight: 600;
}
.logo {
width: 30px;
height: 30px;
}
`}</style>
</div>
)
}
}
export default Header;
然后,把Layout.js里面加上Header,然后我们每个组件都使用Layout嵌套,就完成了整个骨架的搭建~真的很简单!
// /components/Layout.js
import { Fragment } from 'react';
import Head from 'next/head';
import Header from './Header';
export default ({title, children }) => (
<Fragment>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<meta charSet='utf-8' />
<title>Next-Antd-Scafflod</title>
<link rel='stylesheet' href='/_next/static/style.css' />
</Head>
<style jsx global>{`
* {
margin: 0;
padding: 0;
}
body {
font-family: Helvetica, 'Hiragino Sans GB', 'Microsoft Yahei', '微软雅黑', Arial, sans-serif;
}
.content-container {
display: flex;
flex-direction: column;
align-items: center;
}
`}</style>
<Header title={title} />
<div className='content-container'>
{children}
</div>
</Fragment>
);
OK,现在Layout组件就暂时算完成了。
抽离页面组件
上面提到过,pages作为next的路由索引目录,那么我就想让它专心的做路由,就不要做组件的复杂逻辑了,因此,我想把组件的内部实现全部放在components文件夹下。然后路由页面只需要直接引用组件就可以啦~
// /components/Home/Home.js 页面组件
import React, { Fragment } from 'react';
import { Button } from 'antd';
import Link from 'next/link';
import Layout from '../Layout';
const Home = () => (
<Layout title='首页'>
<Fragment>
<h1>Hello Next.js</h1>
<Link href='/userList'>
<Button type='primary'>用户列表页</Button>
</Link>
</Fragment>
</Layout>
);
export default Home;
// /pages/index.js 路由组件
import Home from '../components/Home/Home';
export default Home;
其实很简单,但是这么看起来就很清晰嘛,O(∩_∩)O哈哈~
静态资源引用
静态资源的引用比如图片,你可以使用CDN然后src直接填写url,也可以通过工程内部的静态文件引用。Next同样为我们提供了非常简便的方式,与引用css一样,css是通过Head组件来引入页面的,静态文件next官网推荐我们在根目录新建一个static文件夹,然后静态文件放在static文件夹下,引用的时候使用绝对路径的形式,next就会找到它们~就像下面这样:
<img className='logo' alt='logo' src='/static/logo.png' />
抽离静态常量
然后就是抽离静态常量,这个就很简单了,新建一个constants文件夹,把我们经常用到的常量在里面定义好,然后就可以使用了,比如CSS的配色(从我选择的系统配色不知道小伙伴是不是能猜出来我是哪个公司的),^_^比如将来引入Redux的Action type。
// /constants/ConstTypes.js
export const RoleType = {
1: '管理员',
10: '普通用户'
}
// 使用
import { RoleType } from '../../constants/ConstTypes';
完成目录重构
现在基本暂时完成了目录重构(将来引入redux肯定还得重构一次)。目录结构如下:
-- root
| -- components // 组件目录
| -- constants // 常量目录
| -- pages // 路由目录
| -- static // 静态资源目录
| -- .babelrc
| -- .eslintrc
| -- .gitignore
| -- package.json
| -- ...其他配置文件
再谈路由
Next.js的路由刚开始给我的感觉就是,我靠,很NB啊。但是慢慢的用起来发现,坑还真不少。前面几篇也提到了,它是以pages下面的js文件作为路由路径惊醒匹配的,所以也就是说你想用到的页面全要以js文件的形式放进pages,那么层级嵌套关系怎么办?ok,尝试了一下,很容易解决了。
路由层级
[需求]: 与用户相关的包括用户列表,用户详细信息等等...这两个功能应该是同属于用户子模块,所以应该与首页不是同级关系。
[解决办法]:pages下面新建子目录user里面包括userList.js和userDetail.js。
-- pages
| -- user
| -- userList.js
| -- userDetail.js
| -- index.js
可以看到,这样算是解决了一个问题。
路由参数
紧接着,问题又出现了,我们需要查看用户详情,以往来说,很容易想到 /user/userDetail/:username
,这种嘛,参数通过url的params获取,但是,悲剧了。查了一下Next.js路由API,人家没给你提供params,只提供了query。
query形式路由
也就是说,暂时我们需要/user/userDetail?username=XXX
的形式来实现工程,虽然说没什么问题,但是可能每个人习惯不一样吧。当然,对于我这种好说话的人,我可以接受O(∩_∩)O哈哈~
// 其实Next的Link组件的href属性可以传入一个对象
<Link href={{ pathname: '/user/userDetail', query: { username: text } }}>
<a>{text}</a>
</Link>
ok,实现效果就是这样,反正符合预期,只是使用query代替params了。
P.S.真实是我不想费事搞这个东西,应该是可以解决的,稍后说我的想法
params形式路由
下面我来说说我的理解吧:
首先,是为什么它不支持params形式的路由,前面提到过了,他是根据pages下的js文件来匹配路由的,那么你用params的路由势必/user/userDetail/:username
,那么解析器会以为我应该寻找的是pages目录下面user目录下面UserDetail目录下面的${username}文件,不用想肯定找不到啊,这时候就是404页面了。所以这是我的理解,他为什么只使用query。
其次,我认为两者只是形式上的区别,并没有本质上的区别,也就是实现效果是一样的,都能跳转到指定页面嘛,何必纠结呢?^_^
最后,就是我看完路有部分的文档,我认为是可以做到params形式的跳转的,官方文档里可以自定义server:
// 官方文档自定义server
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
从上面可以看出来,我们可以将a路由匹配到b页面。也就是我们可以把/user/userDetail/:username
的路由匹配到/user/userDetail?username=${username}
上面嘛。不就解决问题了O(∩_∩)O哈哈机智如我,不过我没试验过,只是猜测,目前优先想开发一个系统,这里留坑,以后有机会再填~
总结&&预告
这篇文章讲的还是有点多了,哈哈,不过觉得越写越有灵感,而且说一句,Next.js的官方文档是我读过最喜欢的英文文档了Vue的是最好的中文文档^_^。
接下来准备往项目里加入redux了~越来越有一个系统样子了