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. Linux Mint(Ubuntu)如何管理开机自动启动项?

    Linux Mint自带了一个简洁的开机自启管理应用,使用方法也很简单: 依次点击“Menu”==>“控制中心”==>“个人”==>“启动应用程序”,界面如图所示: 上面打勾的就是系 ...

  2. C语言折半查找法练习题冒泡排序

    C语言折半查找法练习题 折半查找法: 折半查找法是效率较高的一种查找方法.假设有已经按照从小到大的顺序排列好的五个整数num[0]~num[4],要查找的数是key,其基本思想是: 设查找数据的范围下 ...

  3. Python-气象-大气科学-可视化绘图系列(一)——利用xarray读取netCDF文件并画图(代码+示例)

    本文原创链接:https:////www.cnblogs.com/zhanling/p/12192978.html 1 import numpy as np import xarray as xr i ...

  4. Persona & User Scenario

    Persona: Tom:男,21岁,大学生,周末经常和同学们一起出去吃饭.唱歌.打球.郊游,期间会时不时拍一些照片以作纪念,长期积累的照片数量较多且内容繁杂,很少对照片进行整理: Alisa:女,2 ...

  5. Xshell远程连接Linux系统

    一般来说我们连接Linux,会使用到一些远程连接工具 比如:Xshell和Xftp Xshell:远程连接linux系统 Xftp:远程在Linux系统中上传或下载文件 Xshell和Xftp百度云链 ...

  6. A Bug's Life POJ 2492

    D - A Bug's Life 二分图 并查集 BackgroundProfessor Hopper is researching the sexual behavior of a rare spe ...

  7. 装机摸鱼日记01--DDR3AMD专用内存+QHQF(6400T)试水

    前些日子在某鱼入手了两条AMD专用内存,宏想的DDR3-1600MHz-8G内存和一块高贵阿苏斯Z170-P-D3主板,然后某宝600多买了一颗QHQF(当然也可以玩QHQJ,更便宜,估计三百多),准 ...

  8. Springboot:静态资源加载(七)

    WebMvc自动配置: 搜索WebMvcAutoConfiguration自动装配类: 第一种方式通过webjars加载静态资源: https://www.webjars.org(通过maven加载依 ...

  9. 【高频 Redis 面试题】Redis 事务是否具备原子性?

    一.Redis 事务的实现原理 一个事务从开始到结束通常会经历以下三个阶段: 1.事务开始 客户端发送 MULTI 命令,服务器执行 MULTI 命令逻辑. 服务器会在客户端状态(redisClien ...

  10. 微信小程序填坑---小程序支付

    因为公司刚刚重新做了网站,所以也吧公众号和小程序提上了日程,在公众号里面没有什么问题,直接按照官方文档进行代码编写.调试,然后就解决了公众号内支付的问题. 因为小程序提供了<webview> ...