本来打算至少一周一篇的,但是最近事儿赶事儿全赶到一块儿了,项目多了起来还顺便搬了一次家,让我想起了一个段子,一个程序员为了避免长房租答应房东教他孩子学习编程^_^北漂不易,且行且珍惜~但愿每个北漂程序员都能早日财富自由,若是实在太累了就换个城市吧~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给你们示范一下~