我的前端故事----關於redux的一些思考


背景

我一個前端,今年第一份工作就是接手一個 APP 的開發。。。一個線下 BD 人員用的推廣 APP,為了讓我這個一天原生開發都沒有學過的人能快速開發上線,於是乎就選擇了 react-native 作為開發框架,既然主框架有了,接下來就是主要的邏輯框架的選擇了。

一直以來社區里面關於 react 的狀態管理都是推薦 Flux 思想的實踐者 Redux ,關於這個 Redux 國內已經有了很多很多的分析和講解了,自然資料也是最多,坑最少的選擇了,所以這次的開發便選擇了 Redux 全家桶來作為整個 APP 的狀態管理庫了。

疑問

熟悉 Redux 的同學一定非常熟悉這張圖了
這張經典的狀態管理流程圖清晰明確的表現出了整個 Flux 的運行流程,可以看到所有的狀態的修改都是進過了 Dispatcher 事件,能觸發 dispatcher 的也只有 action,這樣一個流程化的過程可以確保整個 store 的修改有據可查,我們來看看網上都是怎么說 flux 的好處的:

  • 數據狀態變得穩定同時行為可預測
  • 所有的數據變更都發生在store里
  • 數據的渲染是自上而下的
  • view層變得很薄,真正的組件化
  • dispatcher是單例的

可以看到,對於 flux,大家對它的評價是非常好的,的確在上面說到的方面 flux 的思路確實做的非常的好,並且讓整個狀態的改變變得規范了起來,但另外一方面,這卻讓原本靈活的 JavaScript 變得 “Java” 了起來。對於一個狀態的修改我們不得不去寫一堆模板代碼,任何狀態的修改都需要發起一個 action,然后觸發一個 dispatcher,最后才能修改到 store,並且熟悉 Redux 的同學肯定寫過這樣的代碼:

// reduces.js
let initialState = {
    userLogin: {
        name: '',
        state: '',
        payState: '',
        isDonor: false
    }
};
function login(state = initialState.userLogin, action) {
    switch (action.type) {
        case GET_CODE:
            state.state = action.fetchData.errors ? 'GET_CODE_ERROR' : 'GET_CODE_OK';
            return Object.assign({}, state, action.fetchData);
        case FETCH_OVER:
            state.payState = null;
            state.state = null;
            return Object.assign({}, state);
        case SUBMIT_LOGIN_FORM:
            state.state = action.fetchData.errors ? 'SUBMIT_LOGIN_ERROR' : 'SUBMIT_LOGIN_OK';
            state.name = action.username;
            return Object.assign({}, state, action.fetchData);
        case ADD_PAY_PASS:
            state.payState = action.fetchData.errors ? 'SUBMIT_LOGIN_ERROR' : ADD_PAY_PASS;
            return Object.assign({}, state, action.fetchData);
        case CHANGE_PWD:
            state.state = action.fetchData.errors ? 'SUBMIT_LOGIN_ERROR' : 'SUBMIT_LOGIN_OK';
            return Object.assign({}, state, action.fetchData);
        default:
            return state
    }
}

// action.js
export function handleSubmit(values) {
    const {name, pass, code} = values;
    return dispatch => {
        webApi.postApi('api/accounts/login', {
            account: name,
            password: pass,
            code: code
        }, (err, data) => {
            let fetchData = {};
            if (data.status === GLOBAL_MSG.reqSucCode) {
                fetchData = data;
            } else {
                fetchData.errors = data.errors[0];
            }
            return dispatch({type: SUBMIT_LOGIN_FORM, fetchData, username: name});
        });
    }
}

// view/login.js
handleSubmit(e) {
    e.preventDefault();
    const {form, handleSubmit} = this.props;
    form.validateFields((err, values) => {
        if (!err) {
            handleSubmit(values);
        }
    });
}

上面的 demo 代碼只是一個片段,它是一個簡單的登錄功能,可以看到,嚴格的規范導致我們需要為了修改動一個狀態就要調用起碼三個文件里面的內容,如果你的項目很大,或者狀態修改分的很細,那么你面對的就不是三個文件了,可能會是更多的文件,當然,你也可以把它們全都寫在一起。。如果你的同事不砍你的話。。。

還有一個問題,那就是異步修改的問題,這個異步發起的狀態是在 action 里面呢還是在 dispatcher 里面呢?那么就真的沒有更好的解決辦法了嘛?哪怕是語法糖也好呀。

所以,現在在 github 上面出現了很多的“最佳實踐”,每個團隊都拿出了自己的解決辦法,於是我們有了很多現成的解決方案,最后我們選擇了阿里家的 D.Va 來解決我們工作中遇到的上述問題,

解決

下面我們就來聊聊這個“最佳實踐”--Dva,來看看 dva 的 demo 是怎么寫的吧:

import React from 'react';
import dva, { connect } from 'dva';
import { Route } from 'dva/router';

// 1. Initialize
const app = dva();

// 2. Model
app.model({
  namespace: 'count',
  state: 0,
  reducers: {
    ['count/add'  ](count) { return count + 1 },
    ['count/minus'](count) { return count - 1 },
  },
});

// 3. View
const App = connect(({ count }) => ({
  count
}))(function(props) {
  return (
    <div>
      <h2>{ props.count }</h2>
      <button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button>
      <button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button>
    </div>
  );
});

// 4. Router
app.router(<Route path="/" component={App} />);

// 5. Start
app.start(document.getElementById('root'));

我們可以看到,在上述的例子中精簡了不少“啰嗦”的代碼,在正真的業務中我們其實只需要關系 view 文件和 model 文件這兩個中的內容就可以了。

5 步 4 個接口完成單頁應用的編碼,不需要配 middleware,不需要初始化 saga runner,不需要 fork, watch saga,不需要創建 store,不需要寫 createStore,然后和 Provider 綁定,等等。但卻能擁有 redux + redux-saga + ... 的所有功能。

這是 demo 下的一段話,確實,這對於我們的項目組來說是非常非常有幫助的了,減少了不少的工作量,在我開發 react-native 應用的時候,整個的開發過程變得輕松了很多,我不需要寫那么多代碼了,有了參考我可以很無腦的堆砌業務代碼了,而且不太需要關心邏輯的性能等等各種各樣的瑣事。這似乎完美的解決了我遇到的所有問題。。。

反思

可是真的是這樣的嘛?我真的需要 Redux 嘛?我究竟是需要它什么好處呢?如果我自己管理 stroe 就不可以嘛?當然是可以的了,而且最早我就是這么做的,那么我們再回過頭來看看 Flux 所帶來的那幾個好處。

首先是狀態可回溯,因為函數式編程中提到的副作用的原因,每次對 store 的修改都應該是舊 store 的一個拷貝,兩個 store 是獨立的,這樣就可以像快照一樣的保存住每個時間段 store 的狀態來了,這樣如果我們需要回滾到之前某一個狀態就會變得非常的容易,那么,這個時候就出現了一個面試時的一個考點了,深拷貝和淺拷貝的問題,很顯然,新人在對 store 做修改的時候應該都會寫過這樣的代碼吧:

var newStore = OldStore.xxx;

如果你寫過這樣的代碼,那么你會發現你依舊無法實現狀態的回溯,因為舊的 store 還是被改變了,那么有的小伙伴就說了,我可以用深拷貝呀,但是深拷貝需要在堆中重新分配內存,並且把源對象所有屬性都進行新建拷貝,才能保證拷貝后的對象與原來的對象完全隔離,互不影響。這樣做帶來的一個問題就是內存的增加,可以大膽的想象一下如果你的應用狀態非常的多,或者做了非常多的操作之后,內存會變成什么樣。

這個時候你又說了,那我可以用 immutable.js 啊,沒錯~Redux 全家桶又迎來了一位新的成員,於是你又四處去找immutable.js的資料回來看,這么不討論immutable.js的重要性,在對於不可變數據的處理上它的確是一個不錯的選擇,但反過來思考一下,我真的需要不可變數據嘛?數據可變又會怎么樣呢?這個時候你又要說了:廢話,沒有不可變數據我怎么回溯狀態!

每個業務都有自己的需求點,放在我開發的項目上,其實是不需要這個功能的,那么換句話說,在我這里,我並不需要這個功能,狀態回溯功能我個人認為是屬於一個錦上添花的功能,而不是一個剛需,當然如果你的業務需要這個特性,就當我沒說~

接下來我們聊聊狀態的改變,flux 讓我們通過 action 去觸發 dispatch 來修改狀態,這對於那些復雜巨大的項目來說是非常有必要的,因為那么多文件,那么多狀態,單靠人力去維護是非常困難的,但是這里又有個問題了,究竟多大的項目叫大項目呢?上萬行的代碼?還是上百個文件?你的項目真的達到了這個程度了嘛?如果是,那么再想想如果優化一下代碼邏輯,項目結構,還會有這么大的代碼量嗎?在這里說個不太恰當的例子,我剛參加工作的時候一個功能我能寫好幾百行,然后到了 code review 的時候那些老道的同事總能用幾十行的代碼來完成和我一樣的功能,甚至比我的還要好,那你說如果一個中小型項目全都是我這種質量的代碼去堆砌的話,它是不是就變成了一個大型項目呢?

顯然不是這樣的,一個項目的大小不單靠代碼的量去衡量,這里面有很多方面的影響,依賴繁多、功能繁瑣、邏輯復雜。這些都是大項目的特點,如果你是 bat 大廠的同學,那應該也不會看我這個了,而對於和我一樣的小廠開發來說,我們手里的項目真的是一個巨無霸嘛?我想快速迭代的需求恐怕才是第一要解決的問題了,如果代碼書寫規范,邏輯實現優雅,結構組織清晰,很大程度上你手里的項目是一個小而美的項目,在這種情況下,為什么不能換個思路呢?

前段時間我看到了 mobx.js,一個新的狀態管理庫,但思路卻完全不一樣,它沒有不可變數據的要求,它代表的是另外一種開發思路,使用 Mobx,代碼會更加清晰,也便於維護,當然前提是你的項目是一個中小型項目,其實用過 vue 的同學去看它,就會覺得相當的親切,對於 mobx 的資料現在網上也有很多了,我在這里就不做過多的介紹了,前段時間我抱着試試看的心態,嘗試着重構了app 中的部分代碼,相較於 dva 模式下的代碼,確實精簡了不少,也由此看來我的項目中充斥着大量的“冗余”代碼,從而看起來非常的龐大,通過精簡邏輯,優化結構,采用 mobx 反而更“優雅”了。

總結

雖然最后一次的重構並沒有上線我就離職了,但是也是這一次的重構讓我認識到了大家說的“最佳實踐”並不一定就是我項目的“最佳實踐”,很多時候還是要考慮自身的局限性。面對前端豐富的解決方案,如何選擇時候自己的才是我們這些開發者需要考慮的,不同的場景有不同的語言,同樣不同的業務也有不同的框架,曾經我以為可以 redux 全家桶一桶走天下的,但是現在想想還是“too young too simple sometimes native”啊。現在看看那些招聘或者求職上清一色寫的 Redux,不妨仔細想想,真的需要嗎?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM