Next.js踩坑入門系列(三)— 目錄重構&&再談路由

luffyZh發表於2018-09-02

Next.js踩坑入門系列

目錄重構

來說一說為什麼要目錄重構吧,在踩坑系列(一)的最後我提到了,Next.js很強大,為我們封裝了路由,只需要在pages下面新建js檔案,裡面寫上我們熟悉的頁面也就是react元件,就會渲染出來!
其實對於開發來說沒區別,但是專案龐大以後,一個路由對應一個js檔案,但是如果頁面很複雜其實不是這個React元件也會很複雜,不是很符合元件化理念,後期也不好維護啊。而且,肯定要加redux的,這樣的話就更加混亂了。所以現在趁著還清醒,趕快重新構建一下~

抽離Layout

我其實是想邊學Next.js邊用它搭建一個系統,具體是什麼慢慢開發最後大家就清楚了。系統的樣式就打算仿照掘金來寫,感覺很簡潔~掘金的樣式很簡單,就是一個Header,然後就是下方的內容區域了,最後這篇文章完事的樣子會變成下面這樣~

Next.js踩坑入門系列(三)— 目錄重構&&再談路由
首先,我們在跟目錄下新建一個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
複製程式碼

Next.js踩坑入門系列(三)— 目錄重構&&再談路由
可以看到,這樣算是解決了一個問題。

路由引數

緊接著,問題又出現了,我們需要檢視使用者詳情,以往來說,很容易想到 /user/userDetail/:username,這種嘛,引數通過url的params獲取,但是,悲劇了。查了一下Next.js路由API,人家沒給你提供params,只提供了query。

Next.js踩坑入門系列(三)— 目錄重構&&再談路由

query形式路由

也就是說,暫時我們需要/user/userDetail?username=XXX的形式來實現工程,雖然說沒什麼問題,但是可能每個人習慣不一樣吧。當然,對於我這種好說話的人,我可以接受O(∩_∩)O哈哈~

// 其實Next的Link元件的href屬性可以傳入一個物件
 <Link href={{ pathname: '/user/userDetail', query: { username: text } }}>
   <a>{text}</a>
 </Link>
複製程式碼

Next.js踩坑入門系列(三)— 目錄重構&&再談路由
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了~越來越有一個系統樣子了

程式碼地址

相關文章