内容简介:原本打算至少一周一篇的,可是最近事儿赶事儿全赶到一起了,项目多了起来还顺便搬了一次家,让我想起了一个段子,一个程序员为了不长房租答应房东教他孩子学习编程^_^北漂不易,且行且珍惜~希望每一个北漂程序员都能早日财富自由,如果实在太累了就换个城市吧~上一讲有关路由的坑还是没填明白,原本params路由自认为已经没问题了,不过最近在测试的时候,发现进入系统的时候是没问题的,但是如果在params路由页面进行刷新,会404页面。所以,继续fix~上面这样就真的可以了,刷新页面也没有任何问题~
- (一)Hello Next.js
- (二)添加Antd && CSS
- (三)目录重构&&再谈路由
- (四)Next.js中期填坑
- (五)引入状态管理Redux
- (六) Next.js其他知识大杂烩
- ... 陆续更新
写在前面
原本打算至少一周一篇的,可是最近事儿赶事儿全赶到一起了,项目多了起来还顺便搬了一次家,让我想起了一个段子,一个 程序员 为了不长房租答应房东教他孩子学习编程^_^北漂不易,且行且珍惜~希望每一个北漂程序员都能早日财富自由,如果实在太累了就换个城市吧~
填坑
上一讲有关路由的坑还是没填明白,原本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这么高级的功能我们还需要请求一下数据~:smile:。正好有个用户列表页,我们这里使用下面这个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目录结构,争取让项目更加合理一些~
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【CuteJavaScript】Angular6入门项目(3.编写服务和引入RxJS)
- Python算法引入
- Java内存泄漏的引入
- React 中引入 Angular 组件
- TensorFlow 引入 Swift 会怎样?
- 现有iOS工程引入Flutter
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
产品经理的20堂必修课
徐建极 / 人民邮电出版社 / 2013-9-1 / 59.00元
《产品经理的20堂必修课》以作者八年的产品经理工作实践为基础,通过系统的理论结合丰富的实例的方法,全面地总结了作为一名互联网产品经理所应掌握的知识。 《产品经理的20堂必修课》分为三大部分。 讲产品:深入剖析互联网产品成功的要素,分别从需求导向、简单原则、产品运营、战略布局等维度,分析如何让产品在残酷的互联网竞争中脱颖而出。 讲方法:着重分析优秀的产品团队运作的工作方法和程序,详......一起来看看 《产品经理的20堂必修课》 这本书的介绍吧!