內容簡介
本內容是基於實實在在的專案開發提煉出來的解決方案,手把手教你如何在React中使用Redux來進行資料管理,該案例可以直接在專案中使用;本文中不會介紹原理什麼之類的,網上一大堆。本內容適合有react開發基礎的人學習,希望對大家有幫助。
目錄結構
- 1 按型別分
這裡的型別指的是一個檔案在專案中充當的角色型別,即這個檔案是一個component,還是一個container,或者是一個reducer等,充當component、container、action、reducer等不同角色的檔案,分別放在不同的資料夾下,這也是Redux官網示例所採用的專案結構。這種結構如下所示:當專案比較小時按型別來分是完全沒問題的,當專案越來越龐大你會發現要修改某資料結構時很麻煩。actions/ a.js b.js components/ a1.jsx a2.jsx b1.jsx constainers/ a.jsx b.jsx reducers/ a.js b.js index.js複製程式碼
- 2 按功能分
我們在專案開發中總結了一種比較合適的目錄結構,components裡存放所有的view元件和元件對應的樣式檔案,constainers裡存放所有的容器元件,為了開發效率和減少檔案數量,我們把容器元件的action和reducer放在同一個檔案(XXXRedux.js)裡面。這種結構如下所示::顯而易見採用這樣的目錄結構要修改某一個元件時很方便,每一個容器元件對應的action、reducer、component、css都同一個檔案目錄下,操作很方便。components/ componentA/ a.jsx a.less componentB/ b.jsx b.less constainers/ constainer1/ X.jsx X.less XRedux.js constainer2/ XX.jsx XX.less XXRedux.js複製程式碼
開發步驟
因為涉及到公司資料安全問題,真實專案是沒法給各位看官look look了;以下都用經典的todolist程式碼做展示。
1 設計reducer
設計store有很多講究,不能把服務給的資料結構直接作為store,也不能按照view結構來設計store;對於初學者來說是一件很煩惱的事,社群很多都推薦按照設計資料庫表結構來設計store,但是很多以專案交付為目標的不可能做得這麼標準,所以大家都儘量向標準看齊-_-。
曾經在文章看到設計store的標準,給大家參考參考:- 1) 把整個應用的狀態按照領域(Domain)分成若干子State,子State之間不能儲存重複的資料。
- 2) State以鍵值對的結構儲存資料,以記錄的key/ID作為記錄的索引,記錄中的其他欄位都依賴於索引。
- 3)State中不能儲存可以通過已有資料計算而來的資料,即State中的欄位不互相依賴。
在業務比較複雜的情況下強力建議大家使用immutable,在專案初期還沒有感覺,但是專案越來越大,業務越來越複雜時就能完全感受到它的好處了。
以下程式碼是一個reducer檔案,我們把該reducer需要的所有type定義在一個常量types物件中,initialState是最初state沒有資料時顯示的預設資料。
const types = {
SELECT_ENTRY: 'select_entry',
CREATE_NEW_ENTRY: 'create_new_entry',
EDIT_ENTRY: 'edit_entry',
CANCEL_EDIT: 'cancel_edit',
UPDATE_ENTRY_LIST: 'update_entry_list',
UPDATE_SAVED_ENTRY: 'updadte_saved_entry'
}
const initialState = {
selectedId: null,
isEditing: false,
big: '測試',
};
export default function editor(state = initialState, action) {
switch (action.type) {
case types.SELECT_ENTRY:
return Object.assign({}, state, { selectedId: action.id });
case types.CREATE_NEW_ENTRY:
return Object.assign({}, state, { selectedId: null, isEditing: true });
case types.EDIT_ENTRY:
return Object.assign({}, state, { selectedId: action.id, isEditing: true });
case types.CANCEL_EDIT:
return Object.assign({}, state, { isEditing: false });
default:
return state;
}
}複製程式碼
- 2 action
為了快速開發,方便修改和減少檔案數量,把action也寫在reducer裡面;一般每reducer裡面程式碼最多也不會超過400行,如果程式碼量太多你應該考慮你元件是否寫得有問題了;大家把storage操作就當作是與後臺服務的互動吧。
可能已有人注意到使用dispatch分發action時已經使用非同步資料流了,是的,但是使用非同步操作之前還是需要在Redux裡新增一些非同步操作中間價,不用捉雞,這個後面程式碼會講到。(注意⚠️)在真正的專案開發時候因為業務需要,所以store沒有這麼簡單;很多時候互動都是在props裡獲取的物件(變數非引用無所謂),若要對從props裡獲取的物件進行某些業務操作時一定要深拷貝一份進行操作,否則你會遇到意想不到的結果。
export function selectEntry(id) {
return { type: types.SELECT_ENTRY, id };
}
export function createNewEntry() {
return { type: types.CREATE_NEW_ENTRY };
}
export function editEntry(id) {
return { type: types. EDIT_ENTRY, id };
}
export function cancelEdit() {
return { type: types.CANCEL_EDIT };
}
function updateEntryList(items) {
return { type: types.UPDATE_ENTRY_LIST, items };
}
export function deleteEntry(id) {
return dispatch => {
storage.deleteEntry(id)
.then(() => storage.getAll())
.then((items) => dispatch(updateEntryList(items)));
};
}
export function fetchEntryList() {
return dispatch => {
storage.getAll()
.then(items => dispatch(updateEntryList(items)));
};
}
function updateSavedEntry(id) {
return { type: types.UPDATE_SAVED_ENTRY, id };
}
export function saveEntry(item) {
const { title, content, id } = item;
return dispatch => {
if (id) {
// 更新流程
storage.updateEntry(id, title, content)
.then(() => dispatch(updateSavedEntry(id)))
.then(() => storage.getAll())
.then(items => dispatch(updateEntryList(items)));
} else {
// 建立流程
storage.insertEntry(title, content)
.then(inserted => dispatch(updateSavedEntry(inserted.id)))
.then(() => storage.getAll())
.then(items => dispatch(updateEntryList(items)));
}
};
}複製程式碼
3 Redux 與元件
在專案中使用Redux做資料管理時,我們把元件分為容器元件和展示元件,一般容器元件就負責資料是這麼更新的,不會包含任何Virtual DOM的修改和組合以及元件樣式;而展示元件是view相關的,一般會寫成無狀態元件,但是在專案中很多時候展示元件需要有生命週期,但是建議儘量使用無狀態元件。import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; import CreateBar from '../CreateBar'; import List from '../List'; import ItemShowLayer from '../ItemShowLayer'; import ItemEditor from '../ItemEditor'; import './style.scss'; /** * Deskmark是一個容器元件 */ class Deskmark extends React.Component { constructor(props) { super(props); } static defaultProps = {} static propTypes = { state: PropTypes.object.isRequired, actions: PropTypes.object.isRequired, } componentDidMount() { this.props.actions.fetchEntryList(); } render() { const { state, actions } = this.props; const { isEditing, selectedId } = state.editor; const items = state.items; const item = items.find( ({ id }) => id === selectedId ); const mainPart = isEditing ? ( <ItemEditor item={item} onSave={actions.saveEntry} onCancel={actions.cancelEdit} /> ) : ( <ItemShowLayer item={item} onEdit={actions.editEntry} onDelete={actions.deleteEntry} /> ); return ( <section className="deskmark-component"> <nav className="navbar navbar-fixed-top navbar-dark bg-inverse"> <a className="navbar-brand" href="#">Deskmark App</a> </nav> <div className="container"> <div className="row"> <div className="col-md-4 list-group"> <CreateBar onClick={actions.createNewEntry} /> <List items={items} onSelect={actions.selectEntry} /> </div> {mainPart} </div> </div> </section> ); } } /** * mapStateToProps可以過濾該元件需要的資料 * @param {*} state Redux管理的store */ function mapStateToProps(state) { const { state, actions } = state; return { state, actions }; } export default connect(mapStateToProps)(Deskmark);複製程式碼
4 入口檔案
app.js檔案是專案的入口檔案,也是在React中使用Redux的最關鍵的一步,該檔案只需要配置一次,以後都可以拿來直接用。外掛說明
- 1) redux
- 2) react-redux 該外掛讓Redux在React中的使用爽得不要不要的,在React中只需要使用一個元件(Provider)和一個高價函式(connect)就可以用Redux來管理React的資料了。
- 3) react-router 把router的資料給Redux管理
- 4) redux-thunk 可以在Redux中使用非同步操作,網上也有很多類似比較牛X非同步中間價
大家如果有興趣可以看看redux-arena,專案中沒有使用過,也就不做過多介紹了。
1) app.js
import React from 'react'; import ReactDom from 'react-dom'; import {Provider} from 'react-redux'; import Deskmark from '../containers/Main/index'; import mainStore from '../containers/Main/store'; import { Router, Route, hashHistory, browserHistory } from 'react-router'; //獲取store const store = mainStore(); ReactDom.render( <Provider store={store}> <Router history={hashHistory}> <Route path="/" component={Deskmark} /> </Router> </Provider>, document.getElementById('react-content') );複製程式碼
2) store.js
import { createStore, applyMiddleware, compose } from 'redux'; import thunkMiddleware from 'redux-thunk'; import rootReducer from './mainReducer'; //thunkMiddleware非同步操作中介軟體 const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore); //建立一個store export default function mainStore() { const store = createStoreWithMiddleware(rootReducer, compose(applyMiddleware(thunkMiddleware)) ); return store; }複製程式碼
3) mainReducer.js
import { combineReducers } from 'redux'; //combineReducers把多個子reducer合併成一個reducer const rootReducer = combineReducers({ editor, index, items }); export default rootReducer;複製程式碼
好了,到此為止在React中使用Redux就告一段落,謝謝閱讀,如果有問題可以一起討論,若文中有錯誤歡迎批評指出或者給出建議。