React + Redux

  今天我们来唠唠在React一般项目中,使用Redux进行状态管理的时候,相对的如何存放reducer、action、api之类文件的结构与使用时机吧。本章默认看官们已经有初步使用过redux。

一般项目

  博主说的一般项目,指的是只需要一个state仓库来进行状态管理的项目,适合一般公司项目制作、个人学习等。这样子的项目不需要额外的combineReducers来整合你的大量state仓库,只需要一个单一state仓库来进行数据管理。

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

  那么一般体量state仓库的项目,我们应该如何比较好的整理我们的文件结构呢?

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

/src
/api
server.js
api.js
/page
/store
actionCreators.js
actionTypes.js
index.js
reducer.js
index.js

  以上是我的部分文件结构,我们来看一下内部我们是如何设计各文件结构的吧,在这个环节,我会将我之前的一个项目的这部分文件展示出来,并尽量打出详细注释,希望大家能有所收获。

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

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进行查看

store/actionTypes.js

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

  为什么我们会需要一个这样子的JS文件呢?我们难道不可以直接在「action」里使用{type: 'controlRightPage'}来作为type标识吗?

  「完全没问题」!但是这么写,总是有他的原因的,让我们来看一个简单的例子。

简单的例子

  某天,我有一个新需求,有个动作需要对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

Redux在项目中的文件结构的更多相关文章

  1. 在react+redux+axios项目中使用async/await

    Async/Await Async/Await是尚未正式公布的ES7标准新特性.简而言之,就是让你以同步方法的思维编写异步代码.对于前端,异步任务代码的编写经历了 callback 到现在流行的 Pr ...

  2. redux在react项目中的应用

    今天想跟大家分享一下redux在react项目中的简单使用 1 1.redux使用相关的安装 yarn add redux yarn add react-redux(连接react和redux) 2. ...

  3. 教你如何在React及Redux项目中进行服务端渲染

    服务端渲染(SSR: Server Side Rendering)在React项目中有着广泛的应用场景 基于React虚拟DOM的特性,在浏览器端和服务端我们可以实现同构(可以使用同一份代码来实现多端 ...

  4. 如何在非 React 项目中使用 Redux

    本文作者:胡子大哈 原文链接:https://scriptoj.com/topic/178/如何在非-react-项目中使用-redux 转载请注明出处,保留原文链接和作者信息. 目录 1.前言 2. ...

  5. 如何优雅地在React项目中使用Redux

    前言 或许你当前的项目还没有到应用Redux的程度,但提前了解一下也没有坏处,本文不会安利大家使用Redux 概念 首先我们会用到哪些框架和工具呢? React UI框架 Redux 状态管理工具,与 ...

  6. Immutable.js 以及在 react+redux 项目中的实践

    来自一位美团大牛的分享,相信可以帮助到你. 原文链接:https://juejin.im/post/5948985ea0bb9f006bed7472?utm_source=tuicool&ut ...

  7. 理解Redux以及如何在项目中的使用

    今天我们来聊聊Redux,这篇文章是一个进阶的文章,建议大家先对redux的基础有一定的了解,在这里给大家推荐一下阮一峰老师的文章: http://www.ruanyifeng.com/blog/20 ...

  8. 优雅的在React项目中使用Redux

    概念 首先我们会用到哪些框架和工具呢? React UI框架 Redux 状态管理工具,与React没有任何关系,其他UI框架也可以使用Redux react-redux React插件,作用:方便在 ...

  9. react项目中引入了redux后js控制路由跳转方案

    如果你的项目中并没有用到redux,那本文你可以忽略 问题引入 纯粹的单页面react应用中,通过this.props.history.push('/list')就可以进行路由跳转,但是加上了redu ...

随机推荐

  1. SQL——语法基础篇(上)

    用数据库的方式思考SQL是如何执行的 虽然 SQL 是声明式语言,我们可以像使用英语一样使用它,不过在 RDBMS(关系型数据库管理系统)中,SQL 的实现方式还是有差别的.今天我们就从数据库的角度来 ...

  2. 一、华为模拟器eNSP下载与安装教程

    简单介绍一下 eNSP: eNSP是一款由华为提供的免费的图形化网络仿真工具平台,它将完美呈现真实设备实景(包括华为最新的ARG3路由器和X7系列的交换机),支持大型网络模拟,让你有机会在没有真实设备 ...

  3. Golang源码分析之目录详解

    开源项目「go home」聚焦Go语言技术栈与面试题,以协助Gopher登上更大的舞台,欢迎go home~ 导读 学习Go语言源码的第一步就是了解先了解它的目录结构,你对它的源码目录了解多少呢? 目 ...

  4. paste命令-合并文件

    paste [-s] [-d " "] [file1] [file2] -s:将文件合并成行 -d:显示时的分割符 //1.txt 1 6 2 7 3 8 4 9 5 10 //2 ...

  5. Centos7 编译安装 Libmcrypt 库

    0x00 先下载 libmcrypt 库源码 libmcrypt-2.5.8.tar.gz 或者去这里 libmcrypt 下载你需要的版本. 0x01 将下载的源码解压到文件夹 tar -zxvf ...

  6. 第十一节:configParse模块

    作用:配置文件解析模块,用来增删改查配置文件内容,不区分大小写 配置文件案例: tets.ini [模块] key=value import configparser config = configp ...

  7. echarts多个数据添加多个纵坐标

    在我们echarts开发中,肯定会遇到一个问题.那就是当有多个数据且数据大小差距太大时,就会出现有些数据小到看不到的情况.所以在遇到这种情况时,我通常的解决办法就是给他多加一个坐标轴. option  ...

  8. ST表(求解静态RMQ问题)

    例题:https://www.acwing.com/problem/content/1272/ ST表类似于dp. 定义st[i][j]表示以i为起点,长度位2^j的一段区间,即[ i , i + 2 ...

  9. 1. git 本地给远程仓库创建分支 三步法

    命令如下: 1:本地创建分支dev 1 2 Peg@PEG-PC /D/home/myself/Symfony (master) $ git branch dev 2:下面是把本地分支提交到远程仓库 ...

  10. Mybatis Generator通用Join的实现

    通常,我们使用Mybatis实现join表关联的时候,一般都是通过在xml或注解里写自定义sql实现. 本文通过Mybatis Generator的插件功能新增一个JoinPlugin插件,只要在配置 ...