本來打算至少一週一篇的,但是最近事兒趕事兒全趕到一塊兒了,項目多了起來還順便搬了一次家,讓我想起了一個段子,一個程序員爲了避免長房租答應房東教他孩子學習編程^_^北漂不易,且行且珍惜~但願每個北漂程序員都能早日財富自由,若是實在太累了就換個城市吧~react
上一講有關路由的坑仍是沒填明白,本來params路由自認爲已經沒問題了,不過最近在測試的時候,發現進入系統的時候是沒問題的,可是若是在params路由頁面進行刷新,會404頁面。因此,繼續fix~git
// server.js
server.get('/user/userDetail', (req, res) => {
return app.render(req, res, `/user/userDetail/${req.query.username}`);
});
server.get('*', (req, res) => {
const parsedUrl = parse(req.url, true);
const { pathname } = parsedUrl;
if (typeof pathname !== 'undefined' && pathname.indexOf('/user/userDetail/') > -1) {
const query = { username: pathname.split('/')[3] };
return app.render(req, res, '/user/userDetail', query);
}
return handle(req, res);
});
複製代碼
上面這樣就真的能夠了,刷新頁面也沒有任何問題~程序員
寫過react SPA的你們應該基本都用過redux,按照官方教程一頓複製粘貼基本都能用,須要注意的就是redux會建立一個全局惟一的store包在整個應用的最外層。喏,這個是redux官方的示例:github
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
複製代碼
那麼問題來了,我得有個東西讓他包起來對不對,在Next.js上來就跟我說了,默認是index,而後在組件裏再使用link來進行跳轉,這跟傳統的router有點區別啊。怎麼辦呢?官方給咱們的解決辦法就是APP,用它來實現將應用包成一個總體(原諒我這麼理解了)。編程
注意了:下面也是約定俗成的
咱們須要在pages文件夾下新建一個_app.js文件,很差意思其餘名字不能夠,而後寫上以下代碼,就能夠啦~json
// /pages/_app.js
export default class MyApp extends App {
render () {
const {Component, pageProps} = this.props
return (
<Container>
<Component {...pageProps} />
</Container>
)
}
}
複製代碼
ok,這樣就能夠了。由於咱們什麼也沒幹,只是在pages文件夾下增長了一個_app.js,怎麼來看是否起做用了呢,我打印了一下props的router(由於稍後重構頁面的時候會用到),能夠看出來,雖然仍是渲染的首頁,可是控制檯能夠打印出router信息,因此仍是那句話,既然選擇了Next.js就須要按照它制定的規則來~redux
前幾篇文章說了,整個系統的架構大概就是上下佈局,頂部導航欄是固定的,因此抽離出來了一個Layout組件,這樣的話每一次每個新組建外部都須要包一層Layout而且須要手動傳title,才能正確展現,有了APP這個組件咱們就能夠來重構一下Layout,這樣就不須要每一個頁面都包一層Layout了~bash
// constants.js
// 路由對應頁面標題
export const RouterTitle = {
'/': '首頁',
'/user/userList': '用戶列表',
'/user/userDetail': '用戶詳情'
};
複製代碼
// components/Home/Home.js
import { Fragment } from 'react';
import { Button } from 'antd';
import Link from 'next/link';
const Home = () => (
<Fragment>
<h1>Hello Next.js</h1>
<Link href='/user/userList'>
<Button type='primary'>用戶列表頁</Button>
</Link>
</Fragment>
);
export default Home;
複製代碼
// /pages/_app.js
import App, {Container} from 'next/app';
import Layout from '../components/Layout';
import { RouterTitle } from '../constants/ConstTypes';
export default class MyApp extends App {
constructor(props) {
super(props);
const { Component, pageProps, router } = props;
this.state = { Component, pageProps, router };
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.Component !== prevState.Component
|| nextProps.pageProps !== prevState.pageProps
|| nextProps.router !== prevState.router) {
return {
Component: nextProps.Component,
pageProps: nextProps.pageProps,
router: nextProps.router
};
}
return null;
}
render () {
const { Component, pageProps, router } = this.props;
return (
<Container>
<Layout title={RouterTitle[router.pathname]}>
<Component {...pageProps} />
</Layout>
</Container>
);
}
}
複製代碼
好啦,如今這樣就能夠了,內部可能也須要小改一下。總之Layout部分就抽離出來了。愈來愈有規範化的系統樣子了~antd
這裏說一點個人感想,由於Next幫咱們作了不少配置的東西,因此在寫起來的時候就是須要按照它的約定俗成的規則,好比路由,APP,靜態資源這種。我以爲這樣寫有好處也有壞處吧,仁者見仁智者見智,至少我是挺喜歡的,由於出問題了看文檔很快就會解決,其餘的自行配置的SSR框架就會因人而異的出現各類莫名bug,還不知道要怎麼去解決~架構
react這個框架只專一於View層,其餘不少東西都須要額外引入,狀態管理redux就是一個React應用必備的東西,因此慢慢的也就變成是React全家桶一員~關於狀態管理機制不是這裏所要講的,太深奧了,還不太會的應該好好看看react相關知識了,這裏只是講在Next.js裏如何引入redux以及redux-saga(若是喜歡用redux-thunk能夠用redux-thunk,不過我以爲thunk不須要配置啥,因此就用saga寫例子了)。仍是老樣子,引入了新東西,就須要提早安裝啊~
// 安裝redux相關依賴
yarn add redux redux-saga react-redux
// 安裝next.js對於redux的封裝依賴包
yarn add next-redux-wrapper next-redux-saga
複製代碼
若是你使用的是單純的客戶端SPA應用(相似於create-react-app建立的那種),那麼只安裝
redux和redux-saga
就能夠了,由於咱們是基於next.js來搭建的腳手架,因此仍是按照人家的標準來的~
瞭解redux的都知道,store,reducer,action這些合起來共同完成redux的狀態管理機制, 由於咱們選擇使用redux-saga來處理異步函數,因此還須要一個saga文件。所以咱們一個一個來:
// /redux/store.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer, { exampleInitialState } from './reducer';
import rootSaga from './saga';
const sagaMiddleware = createSagaMiddleware();
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
// 開發模式打印redux信息
const { logger } = require('redux-logger');
middleware.push(logger);
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
function configureStore (initialState = exampleInitialState) {
const store = createStore(
rootReducer,
initialState,
bindMiddleware([sagaMiddleware])
);
// saga是系統的常駐進程
store.runSagaTask = () => {
store.sagaTask = sagaMiddleware.run(rootSaga);
};
store.runSagaTask();
return store;
}
export default configureStore;
複製代碼
爲了方便調試,開發時我又引入了redux-logger,用於打印redux相關信息。
老生常談,此次我也簡單的來用redux官方最簡單的示例計數器Counter來簡單地實現了,最後的視線效果以下圖:
// /redux/actions.js
export const actionTypes = {
FAILURE: 'FAILURE',
INCREMENT: 'INCREMENT',
DECREMENT: 'DECREMENT',
RESET: 'RESET',
};
export function failure (error) {
return {
type: actionTypes.FAILURE,
error
};
}
export function increment () {
return {type: actionTypes.INCREMENT};
}
export function decrement () {
return {type: actionTypes.DECREMENT};
}
export function reset () {
return {type: actionTypes.RESET};
}
export function loadData () {
return {type: actionTypes.LOAD_DATA};
}
複製代碼
import { actionTypes } from './actions';
export const exampleInitialState = {
count: 0,
};
function reducer (state = exampleInitialState, action) {
switch (action.type) {
case actionTypes.FAILURE:
return {
...state,
...{error: action.error}
};
case actionTypes.INCREMENT:
return {
...state,
...{count: state.count + 1}
};
case actionTypes.DECREMENT:
return {
...state,
...{count: state.count - 1}
};
case actionTypes.RESET:
return {
...state,
...{count: exampleInitialState.count}
};
default:
return state;
}
}
export default reducer;
複製代碼
上面兩個內容尚未涉及到saga部分,由於簡單的reudx計數器並無涉及到異步函數,因此使用saga這麼高級的功能咱們還須要請求一下數據~😄。正好有個用戶列表頁,咱們這裏使用下面這個API獲取一個線上可用的用戶列表數據用戶數據接口
/* global fetch */
import { all, call, put, take, takeLatest } from 'redux-saga/effects';
import { actionTypes, failure, loadDataSuccess } from './actions';
function * loadDataSaga () {
try {
const res = yield fetch('https://jsonplaceholder.typicode.com/users');
const data = yield res.json();
yield put(loadDataSuccess(data));
} catch (err) {
yield put(failure(err));
}
}
function * rootSaga () {
yield all([
takeLatest(actionTypes.LOAD_DATA, loadDataSaga)
]);
}
export default rootSaga;
複製代碼
而後在咱們用用戶列表頁初始化獲取數據,代碼以下:
import { connect } from 'react-redux';
import UserList from '../../components/User/UserList';
import { loadData } from '../../redux/actions';
UserList.getInitialProps = async (props) => {
const { store, isServer } = props.ctx;
if (!store.getState().userData) {
store.dispatch(loadData());
}
return { isServer };
};
const mapStateToProps = ({ userData }) => ({ userData });
export default connect(mapStateToProps)(UserList);
複製代碼
說實話這個地方稀裏糊塗弄出來的,next.js與本來的react寫法仍是有些區別,狀態容器和展現容器劃分的也不是很分明,我暫時使用路由部分來作狀態容器,反正也成功了,下一節來從新劃分一下redux目錄結構,爭取讓項目更加合理一些~
此次時間拖的比較久,真的抱歉,最近思路也有點斷,不在科研狀態,哈哈。但願你們不要見怪,開始靜下心了!這篇文章仍是偏使用,遠離仍是建議你們去看redux相關文檔,講得更清楚,這裏只是next.js怎麼使用redux-saga。接下來想了一下,讓工程目錄更加合理,而後就是把Next.js還沒涉及到的統一寫幾個Demo給你們示範一下~