react服务端渲染框架Next.js踩坑(二)

这节课我们一起来完成项目的架构 源码github

react服务端渲染框架Next.js踩坑(一)
react服务端渲染框架Next.js踩坑(二)
react服务端渲染框架Next.js踩坑(三)
react服务端渲染框架Next.js踩坑(四)

一、引入less

为了更方便的处理我们的样式,我们选择使用less来处理样式,并使用css modules。css modules很容易学,因为它的规则少,同时又非常有用,可以保证某个组件的样式,不会影响到其他组件。
安装less npm install less @zeit/next-less --save,然后更改一下next.config.js,配置css modules很简单,cssModules设置为true就OK了。

// next.config.js
const path = require('path')
const withLess = require('@zeit/next-less')

if (typeof require !== 'undefined') {
  require.extensions['.less'] = () => {}
}
module.exports = withLess({
  cssModules: true, // 开启css modules
  cssLoaderOptions: {
    importLoaders: 1,
    localIdentName: '[local]___[hash:base64:5]',
  },
  webpack(config) {
    const eslintRule = {
      enforce: 'pre',
      test: /.(js|jsx)$/,
      loader: 'eslint-loader',
      exclude: [
        path.resolve(__dirname, '/node_modules'),
      ],
    }
    config.module.rules.push(eslintRule)
    return config
  },
})
复制代码

在pages目录下新建一个style.less文件

.container{
  background-color: red;
}
复制代码

在pages/index.js中引入style.less,cssModules调用样式的时候不需要直接填写class名,直接通过引入的style调用样式 className={style.container}

// pages/index.js
import React from 'react'
import style from './style.less'

const Home = () => {
  return (
    <div className={style.container}>hello world</div>
  )
}

export default Home
复制代码

二、引入redux

写过react的同学应该对它都不陌生,按照官方教程一顿复制粘贴基本都能用。那如何在next中使用redux呢?next本身集成了项目入口,但是我们需要自定义入口,这样我们才能在这里配置redux和引入公用的header和footer组件。
在pages目录新建_app.js文件,安装reduxnpm install redux react-redux redux-thunk next-redux-wrapper --save

// pages/_app.js
import App, { Container } from 'next/app'
import React from 'react'
import { Provider } from 'react-redux'
import withRedux from 'next-redux-wrapper'
import makeStore from '../store'


class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {}
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }
    return { pageProps }
  }

  componentDidMount() {}

  render() {
    const { Component, pageProps, store } = this.props
    return (
      <Container>
        <Provider store={store}>
          <Component {...pageProps} />
        </Provider>
      </Container>
    )
  }
}

export default withRedux(makeStore)(MyApp);
复制代码

然后在根目录下创建store文件夹,store里创建index.js 和 reducer.js。

一个项目里我们会创建很多个页面,如果我们把所有页面的 reducer 全部放在一起的话,很明显不利于我们代码的简洁性,也不利于我们后期维护,所以我们需要分拆各个页面的reducer,为每个页面独立创建自己的 reducer,然后在/store/reducer.js里整合。拆分之后的 reducer 都是相同的结构(state, action),并且各自独立管理该页面 state 的更新。

Redux 提供了 combineReducers 去实现这个模式。

// ~/store/reducer.js
import { combineReducers } from 'redux'

const reducer = combineReducers({
});

export default reducer;
复制代码

以后我们每创建一个reducer的时候,只需要在这里引入,并且添加到 combineReducers 里就可以使用他了。

// ~/store/index.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import reducer from './reducer'

const exampleInitialState = { }
const makeStore = (initialState = exampleInitialState) => {
  return createStore(reducer, initialState, applyMiddleware(thunk));
};

export default makeStore;
复制代码

基本redux结构配置好了,后面我们在写页面的时候再慢慢完善。

三、自定义服务端路由server.js

在next.js中是根据文件夹名来确定路由,但是出现动态路由也就是后面带参数比如/a/:id的时候就会出现问题了,虽然我们可以在 getInitialProps 方法中获取到 id 参数,但是这样会和 /a 路由冲突,并且很不优雅。所以我们需要自定义服务端路由。
在根目录创建server.js文件,安装express npm install express --save

// ~server.js
const express = require('express')
const next = require('next')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare()
  .then(() => {
    const server = express()
    // 页面
    server.get('/', (req, res) => {
      return app.render(req, res, '/home', req.query)
    })

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen(port, (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
  })
复制代码

当然我们的启动文件也要改,package.json 里的 scripts 改成

"scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
}
复制代码

这样路由 / 就会指向到 pages文件夹下的home目录。
我们在pages目录下创建home文件夹,并且把pages下的 index.js 和 style.less移动到home下,这个以后就是我们的首页了。移完后我们pages的目录结构如下。

|-- pages
    |-- home
        |-- index.js
        |-- style.less
    |-- _app.js
复制代码

在命令行运行 npm run dev 程序,浏览器打开http://localhost:3000

四、配置页面的redux

home文件夹创建好了,我们来为首页添加redux。在home目录下创建store文件夹,store创建 index.js、reducer.js、action.js和constants.js。index.js是用来暴露出reducer和action,方便我们的调用。constants.js是用来存放 action 的type常量,以后我们就可以直接在这里维护 action 的type常量,避免出现写错type的低级错误。
首先是reducer

// ~pages/home/store/reducer.js
import * as constants from './constants'

const defaultState = {
  homeData: '我是首页',
}

export default (state = defaultState, action) => {
  switch (action.type) {
    case constants.CHANGE_HOME_DATA:
      return Object.assign({}, state, {
        homeData: action.data,
      })
    default:
      return state;
  }
}
复制代码

我们定义一个改变 homeData 的reducer,如果action.type 等于 constants.CHANGE_HOME_DATA 常量我们就改变 state 里 homeData 的值。
用过redux的同学都知道,在 reducer 里原先的 state 是不允许被改变的,所以我们这里使用 Object.assign 进行深拷贝。

// ~pages/home/store/action.js
import * as constants from './constants'

/* eslint-disable import/prefer-default-export */
export const changeHomeData = (data) => {
  return {
    type: constants.CHANGE_HOME_DATA,
    data,
  }
}
复制代码

定义一个 action 来管理reducer,传入 type 和 data 给reducer。

当然别忘记创建type常量 constants.CHANGE_HOME_DATA

// ~pages/home/store/constants.js

/* eslint-disable import/prefer-default-export */
export const CHANGE_HOME_DATA = 'home/CHANGE_HOME_DATA'
复制代码
// ~pages/home/store/index.js
import reducer from './reducer'
import * as actionCreators from './action'
import * as constants from './constants'

export { reducer, actionCreators, constants }
复制代码

把reducer,actionCreators和constants暴露出来,方便其他页面引用。

接下来需要在根目录的reducer进行耦合

// ~store/reducer
import { combineReducers } from 'redux'
import { reducer as homeReducer } from '../pages/home/store'

const reducer = combineReducers({
  home: homeReducer,
});

export default reducer;
复制代码

这样我们就创建好了 home 页面的 store,我们只需要 state.home.homeData 就可以引用 homeData 数据。
既然创建好了,那我们怎么在home是用呢?编辑~pages/home/index.js

// ~pages/home/index.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import style from './style.less'
import { actionCreators } from './store'

class Home extends Component {
  static async getInitialProps() {
    return { }
  }

  render() {
    const { homeData, changeHome } = this.props
    return (
      <div>
        <div className={style.container}>{homeData}</div>
        <button type="button" onClick={() => { changeHome() }}>改变homeData</button>
      </div>
    )
  }
}

const mapState = (state) => {
  return {
    homeData: state.home.homeData,
  }
}

const mapDispatch = (dispatch, props) => {
  return {
    changeHome() {
      const data = '我改变了'
      dispatch(actionCreators.changeHomeData(data));
    },
  }
}

export default connect(mapState, mapDispatch)(Home)
复制代码

next.js的redux使用和 react的redux使用一模一样,通过 mapState 获得state, mapDispatch来定义方法,在mapDispatch可以通过dispatch触发action,然后action传给reducer参数,使其进行相关操作。 重启服务后,页面就会出现

点击按钮后变成

四、优化

上面我们自定义了服务端路由server.js,这里有一个问题,我们访问 http://localhost:3000/ 时候会出现首页,但是如果我们访问 http://localhost:3000/home 也是会访问到同一个页面,因为next的文件路由我们并没有禁止掉,所以我们需要修改一下 next.config.js,新增一个字段 useFileSystemPublicRoutes: false

// next.config.js
const path = require('path')
const withLess = require('@zeit/next-less')

if (typeof require !== 'undefined') {
  require.extensions['.less'] = () => {}
}
module.exports = withLess({
  useFileSystemPublicRoutes: false,
  cssModules: true,
  cssLoaderOptions: {
    importLoaders: 1,
    localIdentName: '[local]___[hash:base64:5]',
  },
  webpack(config) {
    const eslintRule = {
      enforce: 'pre',
      test: /.(js|jsx)$/,
      loader: 'eslint-loader',
      exclude: [
        path.resolve(__dirname, '/node_modules'),
      ],
    }
    config.module.rules.push(eslintRule)
    return config
  },
})
复制代码

重启后再访问 http://localhost:3000/home 就无法访问到页面了。

五、总结

至此我们完成了项目的基本架构,在接下去的章节我们开始学习写具体的页面。当前的目录结构

|-- next-cnode
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintrc
    |-- next.config.js
    |-- package.json
    |-- server.js
    |-- components
    |-- store
    |   |-- index.js
    |   |-- reducer.js
    |-- pages
    |   |-- home
    |       |-- store
    |           |-- index.js
    |           |-- reducer.js
    |           |-- action.js
    |           |-- constants.js
    |       |-- index.js
    |       |-- style.less
    |   |-- _app.js
    |-- static
复制代码

转载于:https://juejin.im/post/5c9dbdeaf265da60ce37bad1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值