avatar

cooyue

博客网站 - 科越

  • 首页
  • 友链
Home Redux 在项目中的文件结构
文章

Redux 在项目中的文件结构

Posted 2020-05-6 Updated 2024-06- 7
By Administrator
46~59 min read

Redux 在项目中的文件结构

  今天我们来唠唠在「React」一般项目中,使用「Redux」进行状态管理的时候,相对的如何「reder」、「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是一个对象,里面有两个对象方法分别是get和post,返回的都是一个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/6844904147100123144

前端
前端 React
License:  CC BY 4.0
Share

Further Reading

Jan 3, 2025

Nextjs 实现国际化翻译 - App Router 模式解决方案

Nextjs 实现国际化翻译 - App Router 模式解决方案 老板:最近我们网站想部署到国外去啊,我们加一个国际化翻译的功能,O 不 OK? 我:接一下谷歌翻译嘛,啪的一下,很快嗷! 老板:忘了说了,这个翻译最好能自己配置、没有配置文件的话需要兜底、变量插值,如果可以的话,页面的 title

Aug 13, 2023

通过 Node 中间层,实现后端微服务架构中的服务发现和负载均衡

通过 Node 中间层,实现后端微服务架构中的服务发现和负载均衡 要详细解释服务发现和负载均衡的意义,首先我们一定要从从「什么是微服务」,以及「微服务架构的意义」开始讲起 什么是微服务? 微服务架构是一种软件架构风格,它将一个大型的、复杂的应用程序,拆分成多组小型的、独立的服务单元,这些服务单元可以

Aug 9, 2023

三年初心,前端之路:从实习到负责人,我的成长与探索

三年初心,前端之路:从实习到负责人,我的成长与探索 按 20 年入职开始算的话,目前的公司差不多已经待了三年吧,现在已经毕业两年多了,自从 21 年原来的负责人离职后,就一直作为这边的基础设施组的前端 owner,一直带着两三个实习转正的同学负责目前公司的 CICD 平台和一些公司前端的基础建设。这

OLDER

Input 输入框添加内部图标

NEWER

使用 React hooks 转化 class 的一些思考

Recently Updated

  • Nextjs 实现国际化翻译 - App Router 模式解决方案
  • 通过 Node 中间层,实现后端微服务架构中的服务发现和负载均衡
  • 三年初心,前端之路:从实习到负责人,我的成长与探索
  • 记录一次前端做请求负载处理的思考
  • 从零开始教你使用 storybook + rollup 搭建一个属于自己的 React UI 组件库

Trending Tags

工程 Flutter React JavaScript 面经 前端 负载均衡

Contents

©2025 cooyue. Some rights reserved.

Using the Halo theme Chirpy