Redux在项目中的文件结构

做者:米卡react

React + Redux

  今天咱们来唠唠在React通常项目中,使用Redux进行状态管理的时候,相对的如何rederactionapi之类文件的结构与使用时机吧。本章默认看官们已经有初步使用过reduxios

通常项目

  博主说的通常项目,指的是只须要一个state仓库来进行状态管理的项目,适合通常公司项目制做、我的学习等。这样子的项目不须要额外的combineReducers来整合你的state仓库,只一个单一state仓库来进行数据管理。web

  值得注意的是,这里的大量state并非不知足redux单一仓库原则,只不过因为涉及的数据较多,且因为需求分割、功能分割,致使每一个state仓库须要储存在不一样的文件,方便寻找及修改,同时减小单个state仓库体量,可以有效减小空间占用,避免每次store.getState(),都会取一次巨大的state仓库。npm

  那么通常体量state仓库的项目,咱们应该如何比较好的整理咱们的文件结构呢?redux

  首先当须要进行仓库的state修改时,咱们会修改哪里呢?reducer须要修改,action须要修改,action的请求type也须要修改,那么咱们能够将其统一放置在store文件夹下,将其做为一个总体,这样当须要修改时,可以很快的定位到该位置。axios

/src
    /api
        server.js
        api.js
    /page
    /store
        actionCreators.js
        actionTypes.js
        index.js
        reducer.js
    index.js  
复制代码

  以上是个人部分文件结构,咱们来看一下内部咱们是如何设计各文件结构的吧,在这个环节,我会将我以前的一个项目的这部分文件展现出来,并尽可能打出详细注释,但愿你们能有所收获。后端

  OK,那我们先从最基本的入口文件src/index.js开始吧。api

index.js(入口文件)

// 必要的React导入
import React from 'react';
import ReactDOM from 'react-dom';
// 必要的React导入
import { createStore } from 'redux'
// 建立好的store
import store from './store'
// 建立好的router
import MyRouter from './pages/router'
 ReactDOM.render( // 绑定到整个项目 <MyRouter store={store} />, document.getElementById('root') ); 复制代码

  这是整个项目的入口文件,很是的简洁,当你须要回看项目的时候,查看有关redux的部分能够直接进入store/index.js进行查看app

store/actionTypes.js

// 控制左侧导航栏伸出收入
export const CONTROL_LEFT_PAGE = 'controlLeftPage';
// 控制右侧主题栏伸出收入
export const CONTROL_RIGHT_PAGE = 'controlRightPage';
复制代码

  为何咱们会须要一个这样子的JS文件呢?咱们难道不能够直接在action里使用{type: 'controlRightPage'}来做为type标识吗?dom

  彻底没问题!可是这么写,老是有他的缘由的,让咱们来看一个简单的例子。

简单的例子

  某天,我有一个新需求,有个动做须要对state里面的数据进行修改,须要在reducer里默认一个string做为type,为"setInputVal"。很正常的需求,那咱们开始咯?咱们在reducer里面须要写一个相似如下结构的东西。

// 这是一个reducer
export default (state = defaultState, action) => {
    let newState;
    switch (action.type) {
        case 'setInputVal':
            newState = JSON.parse(JSON.stringify(state));
            newState.leftPageFlag = action.flag;
            return newState;
    }
    // 若是没有对应的action.type,返回的是未修改的state
    return state;
}
复制代码

  相对应的,咱们在action里会有一个相似如下结构的东西。

// 这是一个action
export const setInput = (val) => ({
    type: 'setInputValue',
    val
})
复制代码

  表面看上去并无什么差错,可是当你手误输入错了你的type,上方我就模拟了咱们coding时的一个错误,会出现什么呢?对的,这个地方并不会报错,你的程序将会正常的执行,可是你会发现项目里关于这个功能的操做是无效的,因而你一遍一遍的查看你的每一行逻辑代码,最后发现,嗯,我这行写错了,改过来就行了。

  咱们再来看看假如使用自定义的参数来保存type会发生什么?

// 这是一个自定义的type参数
export const SET_INPUT_VAL = 'setInputVal';
复制代码
// 这是一个reducer
export default (state = defaultState, action) => {
    let newState;
    switch (action.type) {
        case SET_INPUT_VAL:
            newState = JSON.parse(JSON.stringify(state));
            newState.leftPageFlag = action.flag;
            return newState;
    }
    // 若是没有对应的action.type,返回的是未修改的state
    return state;
}
复制代码
// 这是一个action
export const setInput = (val) => ({
    type: SET_INPUT_VAL,
    val
})
复制代码

  这是你们可能发现了,你会发现你很是难有出错的机会由于type建立的时候是使用常量定义的,整个程序只使用一次setInputVal,后续你将他各类重命名也是与整个程序彻底没有关系的。

  而假如你将SET_INPUT_VAL写错成了SET_INPUT_VALUE,你的程序会告诉你,SET_INPUT_VALUE is not defined。这句话是有多么的美妙,毕竟coding都是会有人为上的差错的,可是当你出错的时候有东西为你指明了修改的方向你会以为很是舒服,人生又有了方向 (博主也常常为一个变量名或者string修改BUG能把键盘扣掉。)

store/reducer.js

// 导入你建立的type
import { CONTROL_LEFT_PAGE, CONTROL_RIGHT_PAGE } from './actionTypes'
/** * 这是一个state仓库 **/
const defaultState = {
    // 左侧导航flag
    leftPageFlag: false,
    // 右侧导航flag
    rightPageFlag: false,
};
 // 这是你的reducer,得到默认仓库或传入一个仓库,根据action.type来进行相应修改 export default (state = defaultState, action) => { let newState; switch (action.type) { // 这里的CONTROL_LEFT_PAGE其实就是一个本身定义的string类型的字符串 case CONTROL_LEFT_PAGE: newState = JSON.parse(JSON.stringify(state)); newState.leftPageFlag = action.flag; return newState; case CONTROL_RIGHT_PAGE: newState = JSON.parse(JSON.stringify(state)); newState.rightPageFlag = action.flag; return newState; } return state; } 复制代码

  将此state仓库放在这个地方的缘由是由于,修改reducer的时候,会有一个关于state的参照,能够清楚的看到本身但愿修改的是上面的state的哪一部分。同时switch——case的写法也很会很直观。

store/actionCreators.js

// 导入你建立的type
import { CONTROL_LEFT_PAGE, CONTROL_RIGHT_PAGE } from './actionTypes'
// api文件,这块想解释的是redux-thunk的做用
import { getStarArticlesApi } from '../api/api'
// 导入你的store
import store from '../store'
 //工厂模式  /** * 控制左侧导航栏伸出收入 **/ export const controlLeftPage = (flag) => ({ type: CONTROL_LEFT_PAGE, // 传入的参数,进入reducer后根据这个逻辑进行state的修改 flag })  /** * 控制右侧主题栏伸出收入 **/ export const controlRightPage = (flag) => ({ type: CONTROL_RIGHT_PAGE, // 传入的参数,进入reducer后根据这个逻辑进行state的修改 flag })  /** * 获取明星文章 **/ // 注意,这个getStarArticles并非真正的action,只是做为一个包容异步操做后进行action的一个函数。 export const getStarArticles = (req) => { return (dispatch) => { // getStarArticles执行后,进行http请求 getStarArticlesApi(req).then(res => { // 请求完毕,将结果经过action传给reducer const action = getStarArticlesBack(res); dispatch(action); }).catch(err => { // 报错 console.log(err); }) } }  /** * 获取明星文章的回调 **/ export const getStarArticlesBack = (res) => ({ type: GET_STAR_ARTICLES, // 传入的参数,进入reducer后根据这个逻辑进行state的修改 res })  复制代码

  actionCreators是建立你的action的地方,当你须要增长action的时候你均可以在actionTypes.js文件中定义type后,在这个文件定义你的action

  若是是须要在动做中执行http请求的话,redux自己是不可以作到这一点的,因此咱们引入了redux-thunk这个npm库,它容许咱们在dispatch action前对action作一些处理,好比一些异步操做(http请求),因此这部分我留了getStarArticles这个action来给你们举一个例子。

store/index.js

// 必要的redux方法
import { createStore, applyMiddleware, compose } from 'redux';
// 个人一个reducer
import reducer from './reducer'
// redux-thunk是请求的中间件,当咱们在讲action部分会提到它
import thunk from 'redux-thunk' 
 //加强函数 一步方法,执行两个函数 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;  //中间件  const enhancer = composeEnhancers(applyMiddleware(thunk));  // 整合store const store = createStore( reducer, /* preloadedState, */ enhancer );  // 导出 export default store;  复制代码

  index.js文件的话其实基本逻辑是固定的,这个文件的做用是整合reducer(可能还有中间件thunk),并将其做为一个store对象输出的,这里的store若是你们不够了解的话能够移步个人另外一个博文:Redux的createStore实现[1]

api/server.js

import axios from 'axios'
import qs from 'qs'
 let http = { post: '', get: '' }  http.post = function (api, data) { let params = qs.stringify(data); return new Promise((resolve, reject) => { axios.post(api, params).then(res => { resolve(res.data) }).catch(err => { reject(err.data) }) }) }  http.get = function (api, data) { let params = qs.stringify(data); return new Promise((resolve, reject) => { axios.get(api, params).then(res => { resolve(res.data) }).catch(err => { reject(err.data) }) }) } export default http  复制代码

  这个位置没有作过多的注释,由于这个实际上是我我的的一个对自身而言比较熟悉的axios封装,因此这部分你们只要知道,导出的http是一个对象,里面有两个对象方法分别是getpost,返回的都是一个Promise对象。

api/api.js

// http对象,里面有两个对象方法分别是get和post
import http from './server'
// 获取明星文章, 非详情, 带长度
export const getStarArticlesApi = p => http.post('/getStarArticles', p);
// 得到组别数量及组别种类名
export const getAllGroupLengthApi = p => http.post('/getAllGroupLength', p);
复制代码

  这种api写法有两个好处,第一其实和定义type是一个缘由,能够避免出现api写错的状况,第二,当你定义的时候能够没有必要肯定你须要传给后端的是一个什么样的数据类型,直接使用p就能够直接代替你想传的全部值,方便你的初始定义。

  这个地方和TypeScript的思想实际上是背道而驰的,由于TS但愿你明肯定义这个位置的详细类型,而你使用的是JS,那么就不须要对这里进行限制。因此这里在TS+react+redux的项目中是另外一个不一样的写法,若是你们感兴趣能够在评论区@我。对我讲的可能不够清晰的地方,也能够留下您的邮箱,我将这章的部分的代码发送给你,方便您进行试验与测试。

  我是米卡

Reference

[1]

Redux的createStore实现: https://juejin.im/post/5eaee8e96fb9a04381514bab

本文使用 mdnice 排版

相关文章
相关标签/搜索