Next.js踩坑入门系列
- (一) Hello Next.js
- (二) 添加Antd && CSS
- (三) 目录重构&&再谈路由
- (四) Next.js中期填坑
- (五) 引入状态管理Redux
- (六) 再次重构目录
- (七) 其他相关知识
写在前面
原本打算至少一周一篇的,可是最近事儿赶事儿全赶到一起了,项目多了起来还顺便搬了一次家,让我想起了一个段子,一个程序员为了不长房租答应房东教他孩子学习编程^_^北漂不易,且行且珍惜希望每一个北漂程序员都能早日财富自由,如果实在太累了就换个城市吧
填坑
上一讲有关路由的坑还是没填明白,原本params路由自认为已经没问题了,不过最近在测试的时候,发现进入系统的时候是没问题的,但是如果在params路由页面进行刷新,会404页面。所以,继续fix~
// 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);
});
上面这样就真的可以了,刷新页面也没有任何问题~
APP
写过react SPA的大家应该基本都用过redux,按照官方教程一顿复制粘贴基本都能用,需要注意的就是redux会创建一个全局唯一的store包在整个应用的最外层。喏,这个是redux官方的示例:
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文件,不好意思其他名字不可以,然后写上如下代码,就可以啦~
// /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就需要按照它制定的规则来~
重构Layout
前几篇文章说了,整个系统的架构大概就是上下布局,顶部导航栏是固定的,所以抽离出来了一个Layout组件,这样的话每一次每一个新组建外部都需要包一层Layout并且需要手动传title,才能正确展示,有了APP这个组件我们就可以来重构一下Layout,这样就不需要每个页面都包一层Layout了~
// 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部分就抽离出来了。越来越有规范化的系统样子了~
这里说一点我的感想,因为Next帮我们做了很多配置的东西,所以在写起来的时候就是需要按照它的约定俗成的规则,比如路由,APP,静态资源这种。我觉得这样写有好处也有坏处吧,仁者见仁智者见智,至少我是挺喜欢的,因为出问题了看文档很快就会解决,其他的自行配置的SSR框架就会因人而异的出现各种莫名bug,还不知道要怎么去解决~
状态管理Redux准备
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文件。因此我们一个一个来:
store
// /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来简单地实现了,最后的视线效果如下图:
actions
// /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};
}
reducer
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
上面两个内容还没有涉及到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给大家示范一下~
今天的文章Next.js 踩坑入门系列(五)— 引入状态管理 redux分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/18799.html