日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Redux的全家桶与最佳实践

發(fā)布時間:2025/3/8 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redux的全家桶与最佳实践 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨角獸企業(yè)重金招聘Python工程師標準>>>


image.png


Redux 的第一次代碼提交是在 2015 年 5 月底(也就是一年多前的樣子),那個時候 React 的最佳實踐還不是明晰,作為一個 View 層,有人會用 backbone 甚至是 angular 和它搭配,也有人覺得這層 View 功能已經(jīng)足夠強大,簡單地搭配一些 utils 就直接上。后來便有了 FLUX 的演講,React 社區(qū)開始注意到這種新的類似函數(shù)式編程的理念,Redux 也作為 FLUX 的一種變體開始受到關(guān)注,再后來順理成章地得到 React 的『欽點』,作者也加入了 Facebook 從事 React 的開發(fā)。生態(tài)圈經(jīng)過了這一年的成熟,現(xiàn)在很多第三方庫已經(jīng)非常完善,所以這里想介紹一下目前 Redux 的一些最佳實踐。

1. 復習一下 Redux 的基本概念

首先我們復習一下 Redux 的基本概念, 如果你已經(jīng)很熟悉了,就直接跳過這一章吧。

Redux 把界面視為一種狀態(tài)機,界面里的所有狀態(tài)、數(shù)據(jù)都可以由一個狀態(tài)樹來描述。所以對于界面的任何變更都簡化成了狀態(tài)機的變化:

(State, Input) => NewState

這其中切分成了三個階段:

  • action
  • reducer
  • store
  • 所謂的 action,就是用一個對象描述發(fā)生了什么,Redux 中一般使用一個純函數(shù),即 actionCreator 來生成 action 對象。

    // actionCreator => action // 這是一個純函數(shù),只是簡單地返回 action function somethingHappened(data){return {type: 'foo',data: data} }

    隨后這個 action 對象和當前的狀態(tài)樹 state 會被傳入到 reducer 中,產(chǎn)生一個新的 state

    //reducer(action, state) => newState function reducer(action, state){switch(action.type){case 'foo':return { data: data };default:return state;} }

    store 的作用就是儲存 state,并且監(jiān)聽其變化。
    簡單地說就是你可以這樣產(chǎn)生一個 store :

    import { createStore } from 'redux' //這里的 reducer 就是剛才的 Reducer 函數(shù) let store = createStore(reducer);

    然后你可以通過 dispatch 一個 action 來讓它改變狀態(tài):

    store.getState();//{} store.dispatch(somethingHappened('aaa')); store.getState(); // { data: 'aaa'}

    好了,這就是 Redux 的全部功能。對的,它就是如此簡單,以至于它本體只有 3KB 左右的代碼,因為它只是實現(xiàn)了一個簡單的狀態(tài)機而已,任何稍微有點編程能力的人都能很快寫出這個東西。至于和 React 的結(jié)合,則需要 react-redux 這個庫,這里我們就不講怎么用了。

    2. Redux的一些痛點

    大體上,Redux 的數(shù)據(jù)流是這樣的:

    界面 => action => reducer => store => react => virtual dom => 界面

    每一步都很純凈,看起來很美好對吧?對于一些小小的嘗試性質(zhì)的 DEMO 來說確實很美好。但其實當應(yīng)用變得越來越大的時候,這其中存在諸多問題:

  • 如何優(yōu)雅地寫異步代碼?(從簡單的數(shù)據(jù)請求到復雜的異步邏輯)
  • 狀態(tài)樹的結(jié)構(gòu)應(yīng)該怎么設(shè)計?
  • 如何避免重復冗余的 actionCreator?
  • 狀態(tài)樹中的狀態(tài)越來越多,結(jié)構(gòu)越來越復雜的時候,和 react 的組件映射如何避免混亂?
  • 每次狀態(tài)的細微變化都會生成全新的 state 對象,其中大部分無變化的數(shù)據(jù)是不用重新克隆的,這里如何提高性能?
  • 你以為我會在下面一一介紹這些問題是怎么解決的?還真不是,這里大部分問題的回答都可以在官方文檔中看到: 技巧 | Redux 中文文檔 ,文檔里講得已經(jīng)足夠詳細(有些甚至詳細得有些啰嗦了)。所以下面只挑 Redux 生態(tài)圈里幾個比較成熟且流行的組件來講講。

    3. Redux 異步控制

    官方文檔里介紹了一種很樸素的異步控制中間件 redux-thunk (如果你還不了解中間件的話請看 Middleware | Redux 中文文檔 ,事實上 redux-thunk 的代碼很簡單,簡單到只有幾行代碼:

    function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);}; }

    它其實只干了一件事情,判斷 actionCreator 返回的是不是一個函數(shù),如果不是的話,就很普通地傳給下一個中間件(或者 reducer);如果是的話,那么把 dispatch 、 getStateextraArgument 作為參數(shù)傳入這個函數(shù)里,實現(xiàn)異步控制。

    比如我們可以這樣寫:

    //普通action function foo(){return {type: 'foo',data: 123} }//異步action function fooAsync(){return dispatch => {setTimeout(_ => dispatch(123), 3000);} }

    但這種簡單的異步解決方法在應(yīng)用變得復雜的時候,并不能滿足需求,反而會使 action 變得十分混亂。

    舉個比較簡單的例子,我們現(xiàn)在要實現(xiàn)『圖片上傳』功能,用戶點擊開始上傳之后,顯示出加載效果,上傳完畢之后,隱藏加載效果,并顯示出預覽圖;如果發(fā)生錯誤,那么顯示出錯誤信息,并且在2秒后消失。

    用普通的 redux-thunk 是這樣寫的:

    function upload(data){return dispatch => {// 顯示出加載效果dispatch({ type: 'SHOW_WAITING_MODAL' });// 開始上傳api.upload(data).then(res => {// 成功,隱藏加載效果,并顯示出預覽圖dispatch({ type: 'PRELOAD_IMAGES', data: res.images });dispatch({ type: 'HIDE_WAITING_MODAL' });}).catch(err => {// 錯誤,隱藏加載效果,顯示出錯誤信息,2秒后消失dispatch({ type: 'SHOW_ERROR', data: err });dispatch({ type: 'HIDE_WAITING_MODAL' });setTimeout(_ => dispatch({ type: 'HIDE_ERROR' }), 2000);})} }

    這里的問題在于,一個異步的 upload action 執(zhí)行過程中會產(chǎn)生好幾個新的 action,更可怕的是這些新的 action 也是包含邏輯的(比如要判斷是否錯誤),這直接導致異步代碼中到處都是 dispatch(action) ,是很不可控的情況。如果還要進一步考慮取消、超時、隊列的情況,就更加混亂了。

    所以我們需要更強大的異步流控制,這就是 GitHub - yelouafi/redux-saga: An alternative side effect model for Redux apps 。下面我們來看看如果換成 redux-saga 的話會怎么樣:

    import { take, put, call, delay } from 'redux-saga/effects' // 上傳的異步流 function *uploadFlow(action) {// 顯示出加載效果yield put({ type: 'SHOW_WAITING_MODAL' });// 簡單的 try-catchtry{const response = yield call(api.upload, action.data);yield put({ type: 'PRELOAD_IMAGES', data: response.images });yield put({ type: 'HIDE_WAITING_MODAL' });}catch(err){yield put({ type: 'SHOW_ERROR', data: err });yield put({ type: 'HIDE_WAITING_MODAL' });yield delay(2000);yield put({ type: 'HIDE_ERROR' });} }function* watchUpload() {yield* takeEvery('BEGIN_REQUEST', uploadFlow) }

    是不是規(guī)整很多呢?redux-saga 允許我們使用簡單的 try-catch 來進行錯誤處理,更神奇的是竟然可以直接使用 delay 來替代 setTimeout 這種會造成回調(diào)和嵌套的不優(yōu)雅的方法。

    本質(zhì)上講,redux-sage 提供了一系列的『副作用(side-effects)方法』,比如以下幾個:

  • put (產(chǎn)生一個 action)
  • call (阻塞地調(diào)用一個函數(shù))
  • fork (非阻塞地調(diào)用一個函數(shù))
  • take (監(jiān)聽且只監(jiān)聽一次 action)
  • delay (延遲)
  • race (只處理最先完成的任務(wù))
  • 并且通過 Generator 實現(xiàn)對于這些副作用的管理,讓我們可以用同步的邏輯寫一個邏輯復雜的異步流。

    下面這個例子出自于 官方文檔 ,實現(xiàn)了一個對于請求的隊列,即讓程序同一時刻只會進行一個請求,其它請求則排隊等待,直到前一個請求結(jié)束:

    import { buffers } from 'redux-saga'; import { take, actionChannel, call, ... } from 'redux-saga/effects';function* watchRequests() {// 1- 創(chuàng)建一個針對請求事件的 channelconst requestChan = yield actionChannel('REQUEST');while (true) {// 2- 從 channel 中拿出一個事件const {payload} = yield take(requestChan);// 3- 注意這里我們使用的是阻塞的函數(shù)調(diào)用yield call(handleRequest, payload);} }function* handleRequest(payload) { ... }

    更多關(guān)于 redux-saga 的內(nèi)容,請參考 Read Me | redux-saga (中文文檔: 自述 | Redux-saga 中文文檔 )。

    4. 提高 selector 的性能

    把 react 與 redux 結(jié)合的時候,react-redux 提供了一個極其重要的方法: connect ,它的作用就是選取 redux store 中的需要的 state 與 dispatch , 交由 connect 去綁定到 react 組件的 props 中:

    import { connect } from 'react-redux'; import { toggleTodo } from '../actions' import TodoList from '../components/TodoList'// 我們需要向 TodoList 中注入一個名為 todos 的 prop // 它通過以下這個函數(shù)從 state 中提取出來: const mapStateToProps = (state) => {// 下面這個函數(shù)就是所謂的selectortodos: state.todos.filter(i => i.completed)// 其它props... }const mapDispatchToProps = (dispatch) => {onTodoClick: (id) => {dispatch(toggleTodo(id))} }// 綁定到組件上 const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps )(TodoList)export default VisibleTodoList

    在這里需要指定哪些 state 屬性被注入到 component 的 props 中,這是通過一個叫 selector 的函數(shù)完成的。

    上面這個例子存在一個明顯的性能問題,每當組件有任何更新時都會調(diào)用一次 state.todos.filter 來計算 todos ,但我們實際上只需要在 state.todos 變化時重新計算即可,每次更新都重算一遍是非常不合適的做法。下面介紹的這個 reselect 就能幫你省去這些沒必要的重新計算。

    你可能會注意到, selector 實際上就是一個『 純函數(shù)』

    selector(state) => some props

    而純函數(shù)是具有可緩存性的,即對于同樣的輸入?yún)?shù),永遠會得到相同的輸出值 (如果對這個不太熟悉的同學可以參考 JavaScript函數(shù)式編程 ,reselect 的原理就是如此,每次調(diào)用 selector 函數(shù)之前,它會判斷參數(shù)與之前緩存的是否有差異,若無差異,則直接返回緩存的結(jié)果,反之則重新計算:

    import { createSelector } from 'reselect';var state = {a: 100 }var naiveSelector = state => state.a;// mySelector 會緩存輸入 a 對應(yīng)的輸出值 var mySelector = createSelector(naiveSelector, a => {console.log('做一次乘法!!!');return a * a;} )console.log(mySelector(state)); // 第一次計算,需要做一次乘法 console.log(mySelector(state)); // 輸入值未變化,直接返回緩存的結(jié)果 console.log(mySelector(state)); // 同上 state.a = 5; // 改變 a 的值 console.log(mySelector(state)); // 輸入值改變,做一次乘法 console.log(mySelector(state)); // 輸入值未變化,直接返回緩存的結(jié)果 console.log(mySelector(state)); // 同上

    上面的輸出值是:

    做一次乘法!!! 10000 10000 10000 做一次乘法!!! 25 25 25

    之前那個關(guān)于 todos 的范例可以這樣改,就可以避免 todos 數(shù)組被重復計算的性能問題:

    import { createSelector } from 'reselect'; import { connect } from 'react-redux'; import { toggleTodo } from '../actions' import TodoList from '../components/TodoList'const todoSelector = createSelector(state => state.todos,todos => todos.filter(i => i.completed) )const mapStateToProps = (state) => {todos: todoSelector// 其它props... }const mapDispatchToProps = (dispatch) => {onTodoClick: (id) => {dispatch(toggleTodo(id))} }// 綁定到組件上 const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps )(TodoList)export default VisibleTodoList

    更多可以參考 GitHub - reactjs/reselect: Selector library for Redux

    5. 減少冗余代碼

    redux 中的 action 一般都類似這樣寫:

    function foo(data){return {type: 'FOO',data: data} }//或者es6寫法: var foo = data => ({ type: 'FOO', data})

    當應(yīng)用越來越大之后,action 的數(shù)量也會大大增加,為每個 action 對象顯式地寫上 type 和 data 或者其它屬性會造成大量的代碼冗余,這一塊是完全可以優(yōu)化的。

    比如我們可以寫一個最簡單的 actionCreator:

    function actionCreator(type){return function(data){return {type: type,data: data}} }var foo = actionCreator('FOO'); foo(123); // {type: 'FOO', data: 123}

    redux-actions 就可以為我們做這樣的事情,除了上面這種樸素的做法,它還有其它比較好用的功能,比如它提供的 createActions 方法可以接受不同類型的參數(shù),以產(chǎn)生不同效果的 actionCreator,下面這個范例來自官方文檔:

    import { createActions } from 'redux-actions';const { actionOne, actionTwo, actionThree } = createActions({// 函數(shù)類型ACTION_ONE: (key, value) => ({ [key]: value }),// 數(shù)組類型ACTION_TWO: [(first) => first, // payload(first, second) => ({ second }) // meta],// 最簡單的字符串類型 }, 'ACTION_THREE');actionOne('key', 1)); //=> //{ // type: 'ACTION_ONE', // payload: { key: 1 } //}actionTwo('Die! Die! Die!', 'It\'s highnoon~'); //=> //{ // type: 'ACTION_TWO', // payload: ['Die! Die! Die!'], // meta: { second: 'It\'s highnoon~' } //}actionThree(76); //=> //{ // type: 'ACTION_THREE', // payload: 76, //}

    更多可以參考 GitHub - acdlite/redux-actions: Flux Standard Action utilities for Redux.

    轉(zhuǎn)載于:https://my.oschina.net/cllgeek/blog/1584693

    總結(jié)

    以上是生活随笔為你收集整理的Redux的全家桶与最佳实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。