react服務端渲染框架Next.js踩坑(二)

這節課咱們一塊兒來完成項目的架構 源碼githubjavascript

react服務端渲染框架Next.js踩坑(一)
react服務端渲染框架Next.js踩坑(二)
react服務端渲染框架Next.js踩坑(三)
react服務端渲染框架Next.js踩坑(四)css

1、引入less

爲了更方便的處理咱們的樣式,咱們選擇使用less來處理樣式,並使用css modules。css modules很容易學,由於它的規則少,同時又很是有用,能夠保證某個組件的樣式,不會影響到其餘組件。
安裝less npm install less @zeit/next-less --save,而後更改一下next.config.js,配置css modules很簡單,cssModules設置爲true就OK了。java

// next.config.js
const path = require('path')
const withLess = require('@zeit/next-less')

if (typeof require !== 'undefined') {
  require.extensions['.less'] = () => {}
}
module.exports = withLess({
  cssModules: true, // 開啓css modules
  cssLoaderOptions: {
    importLoaders: 1,
    localIdentName: '[local]___[hash:base64:5]',
  },
  webpack(config) {
    const eslintRule = {
      enforce: 'pre',
      test: /.(js|jsx)$/,
      loader: 'eslint-loader',
      exclude: [
        path.resolve(__dirname, '/node_modules'),
      ],
    }
    config.module.rules.push(eslintRule)
    return config
  },
})
複製代碼

在pages目錄下新建一個style.less文件node

.container{
  background-color: red;
}
複製代碼

在pages/index.js中引入style.less,cssModules調用樣式的時候不須要直接填寫class名,直接經過引入的style調用樣式 className={style.container}react

// pages/index.js
import React from 'react'
import style from './style.less'

const Home = () => {
  return (
    <div className={style.container}>hello world</div>
  )
}

export default Home
複製代碼

2、引入redux

寫過react的同窗應該對它都不陌生,按照官方教程一頓複製粘貼基本都能用。那如何在next中使用redux呢?next自己集成了項目入口,可是咱們須要自定義入口,這樣咱們才能在這裏配置redux和引入公用的header和footer組件。
在pages目錄新建_app.js文件,安裝reduxnpm install redux react-redux redux-thunk next-redux-wrapper --savewebpack

// pages/_app.js
import App, { Container } from 'next/app'
import React from 'react'
import { Provider } from 'react-redux'
import withRedux from 'next-redux-wrapper'
import makeStore from '../store'


class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {}
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }
    return { pageProps }
  }

  componentDidMount() {}

  render() {
    const { Component, pageProps, store } = this.props
    return (
      <Container> <Provider store={store}> <Component {...pageProps} /> </Provider> </Container> ) } } export default withRedux(makeStore)(MyApp); 複製代碼

而後在根目錄下建立store文件夾,store裏建立index.js 和 reducer.js。git

一個項目裏咱們會建立不少個頁面,若是咱們把全部頁面的 reducer 所有放在一塊兒的話,很明顯不利於咱們代碼的簡潔性,也不利於咱們後期維護,因此咱們須要分拆各個頁面的reducer,爲每一個頁面獨立建立本身的 reducer,而後在/store/reducer.js裏整合。拆分以後的 reducer 都是相同的結構(state, action),而且各自獨立管理該頁面 state 的更新。github

Redux 提供了 combineReducers 去實現這個模式。web

// ~/store/reducer.js
import { combineReducers } from 'redux'

const reducer = combineReducers({
});

export default reducer;
複製代碼

之後咱們每建立一個reducer的時候,只須要在這裏引入,而且添加到 combineReducers 裏就可使用他了。express

// ~/store/index.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import reducer from './reducer'

const exampleInitialState = { }
const makeStore = (initialState = exampleInitialState) => {
  return createStore(reducer, initialState, applyMiddleware(thunk));
};

export default makeStore;
複製代碼

基本redux結構配置好了,後面咱們在寫頁面的時候再慢慢完善。

3、自定義服務端路由server.js

在next.js中是根據文件夾名來肯定路由,可是出現動態路由也就是後面帶參數好比/a/:id的時候就會出現問題了,雖然咱們能夠在 getInitialProps 方法中獲取到 id 參數,可是這樣會和 /a 路由衝突,而且很不優雅。因此咱們須要自定義服務端路由。
在根目錄建立server.js文件,安裝express npm install express --save

// ~server.js
const express = require('express')
const next = require('next')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare()
  .then(() => {
    const server = express()
    // 頁面
    server.get('/', (req, res) => {
      return app.render(req, res, '/home', req.query)
    })

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen(port, (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
  })
複製代碼

固然咱們的啓動文件也要改,package.json 裏的 scripts 改爲

"scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
}
複製代碼

這樣路由 / 就會指向到 pages文件夾下的home目錄。
咱們在pages目錄下建立home文件夾,而且把pages下的 index.js 和 style.less移動到home下,這個之後就是咱們的首頁了。移完後咱們pages的目錄結構以下。

|-- pages
    |-- home
        |-- index.js
        |-- style.less
    |-- _app.js
複製代碼

在命令行運行 npm run dev 程序,瀏覽器打開http://localhost:3000

4、配置頁面的redux

home文件夾建立好了,咱們來爲首頁添加redux。在home目錄下建立store文件夾,store建立 index.js、reducer.js、action.js和constants.js。index.js是用來暴露出reducer和action,方便咱們的調用。constants.js是用來存放 action 的type常量,之後咱們就能夠直接在這裏維護 action 的type常量,避免出現寫錯type的低級錯誤。
首先是reducer

// ~pages/home/store/reducer.js
import * as constants from './constants'

const defaultState = {
  homeData: '我是首頁',
}

export default (state = defaultState, action) => {
  switch (action.type) {
    case constants.CHANGE_HOME_DATA:
      return Object.assign({}, state, {
        homeData: action.data,
      })
    default:
      return state;
  }
}
複製代碼

咱們定義一個改變 homeData 的reducer,若是action.type 等於 constants.CHANGE_HOME_DATA 常量咱們就改變 state 裏 homeData 的值。
用過redux的同窗都知道,在 reducer 裏原先的 state 是不容許被改變的,因此咱們這裏使用 Object.assign 進行深拷貝。

// ~pages/home/store/action.js
import * as constants from './constants'

/* eslint-disable import/prefer-default-export */
export const changeHomeData = (data) => {
  return {
    type: constants.CHANGE_HOME_DATA,
    data,
  }
}
複製代碼

定義一個 action 來管理reducer,傳入 type 和 data 給reducer。

固然別忘記建立type常量 constants.CHANGE_HOME_DATA

// ~pages/home/store/constants.js

/* eslint-disable import/prefer-default-export */
export const CHANGE_HOME_DATA = 'home/CHANGE_HOME_DATA'
複製代碼
// ~pages/home/store/index.js
import reducer from './reducer'
import * as actionCreators from './action'
import * as constants from './constants'

export { reducer, actionCreators, constants }
複製代碼

把reducer,actionCreators和constants暴露出來,方便其餘頁面引用。

接下來須要在根目錄的reducer進行耦合

// ~store/reducer
import { combineReducers } from 'redux'
import { reducer as homeReducer } from '../pages/home/store'

const reducer = combineReducers({
  home: homeReducer,
});

export default reducer;
複製代碼

這樣咱們就建立好了 home 頁面的 store,咱們只須要 state.home.homeData 就能夠引用 homeData 數據。
既然建立好了,那咱們怎麼在home是用呢?編輯~pages/home/index.js

// ~pages/home/index.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import style from './style.less'
import { actionCreators } from './store'

class Home extends Component {
  static async getInitialProps() {
    return { }
  }

  render() {
    const { homeData, changeHome } = this.props
    return (
      <div> <div className={style.container}>{homeData}</div> <button type="button" onClick={() => { changeHome() }}>改變homeData</button> </div>
    )
  }
}

const mapState = (state) => {
  return {
    homeData: state.home.homeData,
  }
}

const mapDispatch = (dispatch, props) => {
  return {
    changeHome() {
      const data = '我改變了'
      dispatch(actionCreators.changeHomeData(data));
    },
  }
}

export default connect(mapState, mapDispatch)(Home)
複製代碼

next.js的redux使用和 react的redux使用如出一轍,經過 mapState 得到state, mapDispatch來定義方法,在mapDispatch能夠經過dispatch觸發action,而後action傳給reducer參數,使其進行相關操做。 重啓服務後,頁面就會出現

點擊按鈕後變成

4、優化

上面咱們自定義了服務端路由server.js,這裏有一個問題,咱們訪問 http://localhost:3000/ 時候會出現首頁,可是若是咱們訪問 http://localhost:3000/home 也是會訪問到同一個頁面,由於next的文件路由咱們並無禁止掉,因此咱們須要修改一下 next.config.js,新增一個字段 useFileSystemPublicRoutes: false

// next.config.js
const path = require('path')
const withLess = require('@zeit/next-less')

if (typeof require !== 'undefined') {
  require.extensions['.less'] = () => {}
}
module.exports = withLess({
  useFileSystemPublicRoutes: false,
  cssModules: true,
  cssLoaderOptions: {
    importLoaders: 1,
    localIdentName: '[local]___[hash:base64:5]',
  },
  webpack(config) {
    const eslintRule = {
      enforce: 'pre',
      test: /.(js|jsx)$/,
      loader: 'eslint-loader',
      exclude: [
        path.resolve(__dirname, '/node_modules'),
      ],
    }
    config.module.rules.push(eslintRule)
    return config
  },
})
複製代碼

重啓後再訪問 http://localhost:3000/home 就沒法訪問到頁面了。

5、總結

至此咱們完成了項目的基本架構,在接下去的章節咱們開始學習寫具體的頁面。當前的目錄結構

|-- next-cnode
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintrc
    |-- next.config.js
    |-- package.json
    |-- server.js
    |-- components
    |-- store
    |   |-- index.js
    |   |-- reducer.js
    |-- pages
    |   |-- home
    |       |-- store
    |           |-- index.js
    |           |-- reducer.js
    |           |-- action.js
    |           |-- constants.js
    |       |-- index.js
    |       |-- style.less
    |   |-- _app.js
    |-- static
複製代碼
相關文章
相關標籤/搜索