❝做者:米卡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 排版