這節課咱們一塊兒來完成項目的架構 源碼githubjavascript
react服務端渲染框架Next.js踩坑(一)
react服務端渲染框架Next.js踩坑(二)
react服務端渲染框架Next.js踩坑(三)
react服務端渲染框架Next.js踩坑(四)css
爲了更方便的處理咱們的樣式,咱們選擇使用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
複製代碼
寫過react的同窗應該對它都不陌生,按照官方教程一頓複製粘貼基本都能用。那如何在next中使用redux呢?next自己集成了項目入口,可是咱們須要自定義入口,這樣咱們才能在這裏配置redux和引入公用的header和footer組件。
在pages目錄新建_app.js文件,安裝reduxnpm install redux react-redux redux-thunk next-redux-wrapper --save
webpack
// 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結構配置好了,後面咱們在寫頁面的時候再慢慢完善。
在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
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參數,使其進行相關操做。 重啓服務後,頁面就會出現
點擊按鈕後變成上面咱們自定義了服務端路由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
就沒法訪問到頁面了。
至此咱們完成了項目的基本架構,在接下去的章節咱們開始學習寫具體的頁面。當前的目錄結構
|-- 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
複製代碼