Next.js踩坑入门系列(五)— 引入状态管理redux

Next.js踩坑入门系列

写在前面

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

上面这样就真的能够了,刷新页面也没有任何问题~程序员

APP

写过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组件,这样的话每一次每个新组建外部都须要包一层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,还不知道要怎么去解决~架构

状态管理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给你们示范一下~

代码地址

相关文章
相关标签/搜索