超全面详细一条龙教程!从零搭建React项目全家桶(下篇)

6,806 阅读9分钟

在本教程的【上篇】中,已经详细说明了React项目相对基础的部分。在【下篇】中,继续讲解React进阶的部分。还没有阅读【上篇】的同学请先在我的公众号中去阅读哦~

超全面详细一条龙教程!从零搭建React项目全家桶(上篇)

在开始前,先回顾下【上篇】介绍的内容:

1 创建React-APP

2 精简项目

2.1 删除文件

2.2 简化代码

2.3 使用Fragment去掉组件外层标签

3 项目目录结构

3.1 引入全局公用样式

3.2 支持Sass/Less/Stylus

4 路由

4.1 页面构建

4.2 使用react-router-dom

4.3 路由跳转

5 组件引入

5.1 创建header组件

5.2 引入Header组件

5.3 组件传参

6 React Developer Tools浏览器插件

在本次的【下篇】中,继续分享以下内容:

先睹为快

7 Redux及相关插件

7.1 安装redux

7.2 安装react-redux

7.3 安装redux-thunk

7.4 安装浏览器Redux插件

7.5 创建store

7.6 复杂项目store分解

7.7 对接react-redux与store

7.8 启动Redux DevTools

7.9 安装使用immutable

8 Mock.js安装与使用

9 解决本地开发跨域问题

10 其他常用工具

11 附赠章节:集成Ant Design

11.1 安装Ant Design

11.2 实现按需加载

11.3 自定义主题颜色

7 Redux及相关插件

做过vue开发的同学都知道vuex,react对应的工具就是Redux,当然还有一些附属工具,比如react-redux、redux-thunk、immutable。

redux涉及内容篇幅较多,可以单独作为一次分享。本次分享篇幅有限,仅简要介绍下安装部署流程,如有看不懂的地方可先跳过或自行查阅官方文档。

7.1 安装redux

执行:

npm install  redux --save

仅安装redux也是可以使用的,但是比较麻烦。redux里更新store里的数据,需要手动订阅(subscribe)更新。可以借助另一个插件(react-redux)提高开发效率。

7.2 安装react-redux

执行:

npm install react-redux --save

react-redux允许通过connect方法,将store中的数据映射到组件的props,省去了store订阅。原state中读取store的属性改用props读取。

由于store(7.5小节)还没讲到,react-redux使用方法在7.6小节介绍。

7.3 安装redux-thunk

执行:

npm install redux-thunk --save

redux-thunk允许在actionCreators里返回函函数。这样可以把业务逻辑(例如接口请求)集中写在actionCreator.js,方便复用的同时,可以使组件的主文件更简洁。

7.4 安装浏览器Redux插件

为了更方便跟踪redux状态,建议安装chrome插件。

先科学上网,在chrome网上应用店里搜索“Redux DevTools”并安装。

安装完成后还不能直接使用,需要在项目代码中进行配置。接下来进行说明。

7.5 创建store

安装以上各种插件后,可以store用来管理状态数据了。

如果项目比较简单,只有一两个页面,可以只创建一个总store管理整体项目。目录结构参考如下:

    ├─ /src   
+   |  ├─ /store
+   |  |  ├─ actionCreators.js
+   |  |  ├─ contants.js       <-- 定义方法的常量
+   |  |  ├─ index.js
+   |  |  └─ reducer.js

以下是各文件的代码示例:

src/store/actionCreators.js:

import * as constans from './constants'

export const getData = (data) => ({
  type: constans.SET_DATA,
  data
})

src/store/contants.js:

export const SET_DATA = 'SET_DATA'

src/store/index.js:

import { createStore, applyMiddleware, compose } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'

// 这里让项目支持浏览器插件Redux DevTools
const composeEnhancers = typeof window === 'object' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose

const enhancer = composeEnhancers(
  applyMiddleware(thunk)
);

const store = createStore(
  reducer,
  enhancer
)

export default store

以上是store的核心代码,支持了Redux DevTools。同时,利用redux的集成中间件(applyMiddleware)功能将redux-thunk集成进来,最终创建了store。

src/store/reducer.js:

import * as constants from './constants'

// 初始默认的state
const defaultState = {
    myData: null
}

export default (state = defaultState, action) => {
    // 由于state是引用型,不能直接修改,否则是监测不到state发生变化的。因此需要先复制一份进行修改,然后再返回新的state。
    let newState = Object.assign({}, state)
    switch(action.type) {
        case constants.SET_DATA:
            newState.myData = action.data
            return newState
        default:
            return state
    }
}

以上代码,我们在store设置了一个myData。如何更好地解决state修改问题,在7.8小节会提到。

7.6 复杂项目store分解

应对更多页面的项目,如果数据都集中放在一个store里,其维护成本非常高。接下来分享下如何将store分解到各个组件中。

一般来说,每个组件有自己的store,再由src下的store作为总集,集成每个组件的store。

以header和login两个组件为例,分别创建组件自己的store。

header的store目录结构如下:

    |  |  ├─ /components
    |  |  |  ├─ /header
+   |  |  |  |  ├─ /store
+   |  |  |  |  |  ├─ actionCreators.js
+   |  |  |  |  |  ├─ contants.js      
+   |  |  |  |  |  ├─ index.js
+   |  |  |  |  |  └─ reducer.js

组件store下的index.js代码如下:

import reducer from './reducer'
import * as actionCreators from './actionCreators'
import * as constants from './constants'

export { reducer, actionCreators, constants}

其实就是把组件store下的其他文件集中起来作为统一输出口。

组件store下的contants.js代码如下:

const ZONE = 'components/header/'

export const SET_DATA = ZONE + 'SET_DATA'

ZONE是用来避免与其他组件的contants重名。

同样的方式,在login下进行创建store(不再赘述)。

然后修改项目src下的总store,目录结构变动如下:

    ├─ /src   
    |  ├─ /store
-   |  |  ├─ actionCreators.js <-- 删除
-   |  |  ├─ contants.js       <--删除
    |  |  ├─ index.js
    |  |  └─ reducer.js

src/store/index.js重写如下:

import { combineReducers } from 'redux'

import { reducer as loginReducer } from '../pages/login/store'
import { reducer as headerReducer } from '../components/header/store'

const reducer = combineReducers({
    login: loginReducer,
    header: headerReducer
})

export default reducer

以上代码的作用就是把login和header的store引入,然后通过combineReducers合并在一起,并分别加上唯一的对象key值。

这样的好处非常明显:

  1. 避免各组件的store数据互相污染
  2. 组件独立维护自己的store,减少维护成本

非常建议使用这种方式维护store。

7.7 对接react-redux与store

为了方便每个组件都能使用store,而不用一遍一遍的引用store。下面来对接react-redux与store。

修改src/index.js:

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
+   import { Provider } from 'react-redux'
+   import store from './store'
    import './common/style/frame.styl'

+   const Apps = (
+       <Provider store={store}>
+           <App />
+       </Provider>
+   )

M   ReactDOM.render(Apps, document.getElementById('root'))

以上代码就是用react-redux提供的Provider,把store传给了整个App。

在需要使用store的组件中,要使用react-redux提供的connect方法对组件进行包装。

以login为例,修改src/pages/login/index.js:

    import React, { Component } from 'react'
    import Header from '../../components/header'
+   import { connect } from 'react-redux'
+   import * as actionCreators from './store/actionCreators'
    import './login.styl'

    class Login extends Component {
        render() {
            return (
                <div className="P-login">
                    <Header />
                    <h1>Login page</h1>
+                   <p>login: myData = {this.props.myData}</p>
+                   <button onClick={()=> {this.props.getData('123456')}}>更改login的myData</button>
                    <button onClick={this.gotoHome.bind(this)}>跳转Home页</button>
                </div>
            )
        }

        gotoHome() {
            this.props.history.push('/home')
        }
    }

+   // 把store中的数据映射到组件的props
+   const mapStateToProps = (state) => ({
+       myData: state.getIn(['login', 'myData']),
+   })

+   // 把store的Dispatch映射到组件的props
+   const mapDispatchToProps = (dispatch) => ({
+       getData(data) {
+           const action = actionCreators.getData(data)
+           dispatch(action)
+       }
+   })

M   export default connect(mapStateToProps, mapDispatchToProps)(Login)

最大的变化就是代码最后一行,被connect方法包装了。

然后把store里的state和dispatch都映射到了组件的props。这样可以直接通过props进行访问了,store中数据的变化会直接改变props从而触发组件的视图更新。

点击按钮后,可以看到页面中显示的myData发生了变化。

下面通过Redux DevTools进行可视化跟踪查看。

7.8 启动Redux DevTools

经过7.5小节的设置,7.4小节的Redux DevTools可以正常使用了。点击浏览器右上角的图标,在出现的面板里,可以相信地跟踪查看store里各数据的变化,非常方便。

还可以通过调试工具栏启动Redux DevTools:

具体使用方法这里不赘述了。

7.9 安装使用immutable

在7.5小节,提到了store里不能直接修改state,因为state是引用类型,直接修改可能导致监测不到数据变化。

immutable.js从字面上就可以明白,immutable的意思是“不可改变的”。使用immutable创建的数据是不可改变的,对immutable数据的任何修改都会返回一个新的immutable数据,不会改变原始immutable数据。

immutable.js提供了很多方法,非常方便修改对象或数组类型的引用型数据。

安装immutable和redux-immutable,执行:

npm install immutable redux-immutable --save

然后对代码进行改造:

src/store/reducer.js:

-   import { combineReducers } from 'redux'
+   import { combineReducers } from 'redux-immutable'
    ...(略)

以上代码就是把combineReducers换成redux-immutable里的。

然后修改src/pages/login/store/reducer.js

    import * as constants from './constants'
+   import { fromJS } from 'immutable'

M   const defaultState = fromJS({
        myData: null
M   })

+   const getData = (state, action) => {
+       return state.set('myData', action.data)
+   }

    export default (state = defaultState, action) => {
        switch(action.type) {
            case constants.SET_DATA:
M               return getData(state, action)
            default:
                return state
        }
    }

immutable的介入,就是利用fromJS方法,把原始的JS类型转化为immutable类型。

由于state已经是immutable类型了,可以使用immutable的set方法进行数据修改,并返回一个新的state。代码简洁很多,不需要手动通过Object.assign等方法去复制再处理了。

header组件的代码修改同理不再赘述。

immutable还有很多其他非常使用方法,具体请参阅官方文档:

immutable-js.github.io/immutable-j…

8 Mock.js安装与使用

在开发过程中,为了方便前端独自调试接口,经常使用Mock.js拦截Ajax请求,并返回预置好的数据。本小节介绍下如何在react项目中使用Mock.js。

执行安装:

npm install mockjs --save

在src下新建mock.js,代码如下:

import Mock from 'mockjs'

const domain = '/api/'

// 模拟getData接口
Mock.mock(domain + 'getData', function () {
    let result = {
      code: 200,
      message: 'OK',
      data: 'test'
    }
    return result
})

然后在src/index.js中引入mock.js:

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import { Provider } from 'react-redux'
    import store from './store'
+   import './mock'
    import './common/style/frame.styl'

    ...(略)

如此简单。这样,在项目中请求/api/getData的时候,就会被Mock.js拦截,并返回mock.js中写好的数据。

9 解决本地开发跨域问题

在react开发环境中,默认启动的是3000端口,而后端API服务可能在本机的80端口,这样在ajax请求的时候会出现跨域问题。可以借助http-proxy-middleware工具实现反向代理。

执行安装:

npm install http-proxy-middleware --save-dev

在src下创建setupProxy.js,代码如下:

const proxy = require('http-proxy-middleware');
module.exports = function (app) {
    app.use(
        '^/api',
        proxy({
            target: 'http://localhost',
            changeOrigin: true
        })
    )
}

这代码的意思就是,只要请求地址是以"/api"开头,那就反向代理到http://localhost域名下,跨域问题解决!大家可以根据实际需求进行修改。

※注意:setupProxy.js设置后,一定要重启项目才生效。

10 其他常用工具

  1. Axios - Ajax请求工具

【官网】github.com/axios/axios

【安装命令】

npm install axios --save
  1. better-scroll - 页面原生滚动体验效果工具

【官网】ustbhuangyi.github.io/better-scro…

【安装命令】

npm install better-scroll --save
  1. react-transition-group - CSS3动画组合工具

【官网】github.com/reactjs/rea…

【安装命令】

npm install react-transition-group --save
  1. react-loadable - 动态加载组件工具

【官网】www.npmjs.com/package/rea…

【安装命令】

yarn add react-loadable

11 附赠章节:集成Ant Design

Ant Design是非常好用的前端UI库,很多项目都使用了Ant Design。

【官网】

ant.design/index-cn

本章节内容基于上述章节将create-react-app进行eject后集成Ant Design。

具体内容请移步至我的微信公众号阅读^_^

11.1 安装Ant Design

11.2 实现按需加载

11.3 自定义主题颜色

请关注微信公众号「卧梅又闻花」,查阅《超全面详细一条龙教程!从零搭建React项目全家桶(下篇)》

项目GitHub

本次分享涉及的项目代码已全部上传至GitHub,有需要的同学可前往自行下载:

github.com/Yuezi32/rea…

本次React全家桶分享到这里就全部结束啦,欢迎在微信公众号私信与我交流。

欢迎关注我的个人微信公众号,随时获取最新文章。