❝做者:米卡react
❞
今天咱们来唠唠在「React」通常项目中,使用「Redux」进行状态管理的时候,相对的如何「reder」、「action」、「api」之类文件的结构与使用时机吧。本章默认看官们已经有初步使用过「redux」。ios
博主说的通常项目,指的是只须要一个「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
// 必要的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
// 控制左侧导航栏伸出收入
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」能把键盘扣掉。)
// 导入你建立的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
的写法也很会很直观。
// 导入你建立的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」来给你们举一个例子。
// 必要的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]。
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
是一个对象,里面有两个对象方法分别是get
和post
,返回的都是一个Promise
对象。
// 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」的项目中是另外一个不一样的写法,若是你们感兴趣能够在评论区@我。对我讲的可能不够清晰的地方,也能够留下您的邮箱,我将这章的部分的代码发送给你,方便您进行试验与测试。
我是米卡
Redux的createStore实现: https://juejin.im/post/5eaee8e96fb9a04381514bab
本文使用 mdnice 排版