Next.js 踩坑入门系列(三)— 目录重构&&再谈路由

12,005 阅读5分钟

Next.js踩坑入门系列

目录重构

来说一说为什么要目录重构吧,在踩坑系列(一)的最后我提到了,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了~越来越有一个系统样子了

代码地址