国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

如何設(shè)計redux state結(jié)構(gòu)

huangjinnan / 3092人閱讀

摘要:例如下拉框的顯示與關(guān)閉。如何設(shè)計結(jié)構(gòu)在使用的過程中,我們都會使用的方式,將我們的拆分到不同的文件當(dāng)中,通常會遵循高內(nèi)聚方便使用的原則,按某個功能模塊頁面來劃分。

為什么使用redux

使用react構(gòu)建大型應(yīng)用,勢必會面臨狀態(tài)管理的問題,redux是常用的一種狀態(tài)管理庫,我們會因為各種原因而需要使用它。

不同的組件可能會使用相同的數(shù)據(jù),使用redux能更好的復(fù)用數(shù)據(jù)和保持?jǐn)?shù)據(jù)的同步

react中子組件訪問父組件的數(shù)據(jù)只能通過props層層傳遞,使用redux可以輕松的訪問到想要的數(shù)據(jù)

全局的state可以很容易的進(jìn)行數(shù)據(jù)持久化,方便下次啟動app時獲得初始state

dev tools提供狀態(tài)快照回溯的功能,方便問題的排查

但并不是所有的state都要交給redux管理,當(dāng)某個狀態(tài)數(shù)據(jù)只被一個組件依賴或影響,且在切換路由再次返回到當(dāng)前頁面不需要保留操作狀態(tài)時,我們是沒有必要使用redux的,用組件內(nèi)部state足以。例如下拉框的顯示與關(guān)閉。

常見的狀態(tài)類型

react應(yīng)用中我們會定義很多state,state最終也都是為頁面展示服務(wù)的,根據(jù)數(shù)據(jù)的來源、影響的范圍大致可以將前端state歸為以下三類:

Domain data: 一般可以理解為從服務(wù)器端獲取的數(shù)據(jù),比如帖子列表數(shù)據(jù)、評論數(shù)據(jù)等。它們可能被應(yīng)用的多個地方用到,前端需要關(guān)注的是與后端的數(shù)據(jù)同步、提交等等。

UI state: 決定當(dāng)前UI如何展示的狀態(tài),比如一個彈窗的開閉,下拉菜單是否打開,往往聚焦于某個組件內(nèi)部,狀態(tài)之間可以相互獨立,也可能多個狀態(tài)共同決定一個UI展示,這也是UI state管理的難點。

App state: App級的狀態(tài),例如當(dāng)前是否有請求正在loading、某個聯(lián)系人被選中、當(dāng)前的路由信息等可能被多個組件共同使用到狀態(tài)。

如何設(shè)計state結(jié)構(gòu)

在使用redux的過程中,我們都會使用modules的方式,將我們的reducers拆分到不同的文件當(dāng)中,通常會遵循高內(nèi)聚、方便使用的原則,按某個功能模塊、頁面來劃分。那對于某個reducer文件,如何設(shè)計state結(jié)構(gòu)能更方便我們管理數(shù)據(jù)呢,下面列出幾種常見的方式:

1.將api返回的數(shù)據(jù)直接放入state

這種方式大多會出現(xiàn)在列表的展示上,如帖子列表頁,因為后臺接口返回的數(shù)據(jù)通常與列表的展示結(jié)構(gòu)基本一致,可以直接使用。

2.以頁面UI來設(shè)計state結(jié)構(gòu)

如下面的頁面,分為三個section,對應(yīng)開戶中、即將流失、已提交審核三種不同的數(shù)據(jù)類型。

因為頁面是展示性的沒有太多的交互,所以我們完全可以根據(jù)頁面UI來設(shè)計如下的結(jié)構(gòu):

tabData: {
    opening: [{
        userId: "6332",
        mobile: "1858849****",
        name: "test1",
        ...
    }, ...],
    missing: [],
    commit: [{
        userId: "6333",
        mobile: "1858849****",
        name: "test2",
        ...
    }, ... ]
}

這樣設(shè)計比較方便我們將state映射到頁面,拉取更多數(shù)據(jù)只需要將新數(shù)據(jù)簡單contact進(jìn)對應(yīng)的數(shù)組即可。對于簡單頁面,這樣是可行的。

3.State范式化(normalize)

很多情況下,處理的數(shù)據(jù)都是嵌套或互相關(guān)聯(lián)的。例如,一個群列表,由很多群組成,每個群又包含很多個用戶,一個用戶可以加入多個不同的群。這種類型的數(shù)據(jù),我們可以方便用如下結(jié)構(gòu)表示:

const Groups = [
    {
        id: "group1",
        groupName: "連線電商",
        groupMembers: [
            {
                id: "user1",
                name: "張三",
                dept: "電商部"
            },
            {
                id: "user2",
                name: "李四",
                dept: "電商部"
            },
        ]
    },
    {
        id: "group2",
        groupName: "連線資管",
        groupMembers: [
            {
                id: "user1",
                name: "張三",
                dept: "電商部"
            },
            {
                id: "user3",
                name: "王五",
                dept: "電商部"
            },
        ]
    }
]

這種方式,對界面展示很友好,展示群列表,我們只需遍歷Groups數(shù)組,展示某個群成員列表,只需遍歷相應(yīng)索引的數(shù)據(jù)Groups[index],展示某個群成員的數(shù)據(jù),繼續(xù)索引到對應(yīng)的成員數(shù)據(jù)GroupsgroupIndex即可。
但是這種方式有一些問題:

存在很多重復(fù)數(shù)據(jù),當(dāng)某個群成員信息更新的時候,想要在不同的群之間進(jìn)行同步比較麻煩。

嵌套過深,導(dǎo)致reducer邏輯復(fù)雜,修改深層的屬性會導(dǎo)致代碼臃腫,空指針的問題

redux中需要遵循不可變更新模式,更新屬性往往需要更新組件樹的祖先,產(chǎn)生新的引用,這會導(dǎo)致跟修改數(shù)據(jù)無關(guān)的組件也要重新render。

為了避免上面的問題,我們可以借鑒數(shù)據(jù)庫存儲數(shù)據(jù)的方式,設(shè)計出類似的范式化的state,范式化的數(shù)據(jù)遵循下面幾個原則:

不同類型的數(shù)據(jù),都以“數(shù)據(jù)表”的形式存儲在state中

“數(shù)據(jù)表” 中的每一項條目都以對象的形式存儲,對象以唯一性的ID作為key,條目本身作為value。

任何對單個條目的引用都應(yīng)該根據(jù)存儲條目的 ID 來索引完成。

數(shù)據(jù)的順序通過ID數(shù)組表示。

上面的示例范式化之后如下:

{
    groups: {
        byIds: {
            group1: {
                id: "group1",
                groupName: "連線電商",
                groupMembers: ["user1", "user2"]
            },
            group2: {
                id: "group2",
                groupName: "連線資管",
                groupMembers: ["user1", "user3"]
            }
        },
        allIds: ["group1", "group2"]
    },
    members: {
        byIds: {
            user1: {
                id: "user1",
                name: "張三",
                dept: "電商部"
            },
            user2: {
                id: "user2",
                name: "李四",
                dept: "電商部"
            },
            user3: {
                id: "user3",
                name: "王五",
                dept: "電商部"
            }
        },
        allIds: []
    }
}

與原來的數(shù)據(jù)相比有如下改進(jìn):

因為數(shù)據(jù)是扁平的,且只被定義在一個地方,更方便數(shù)據(jù)更新

檢索或者更新給定數(shù)據(jù)項的邏輯變得簡單與一致。給定一個數(shù)據(jù)項的 type 和 ID,不必嵌套引用其他對象而是通過幾個簡單的步驟就能查找到它。

每個數(shù)據(jù)類型都是唯一的,像用戶信息這樣的更新僅僅需要狀態(tài)樹中 “members > byId > user” 這部分的復(fù)制。這也就意味著在 UI 中只有數(shù)據(jù)發(fā)生變化的一部分才會發(fā)生更新。與之前的不同的是,之前嵌套形式的結(jié)構(gòu)需要更新整個 groupMembers數(shù)組,以及整個 groups數(shù)組。這樣就會讓不必要的組件也再次重新渲染。

通常我們接口返回的數(shù)據(jù)都是嵌套形式的,要將數(shù)據(jù)范式化,我們可以使用Normalizr這個庫來輔助。
當(dāng)然這樣做之前我們最好問自己,我是否需要頻繁的遍歷數(shù)據(jù),是否需要快速的訪問某一項數(shù)據(jù),是否需要頻繁更新同步數(shù)據(jù)。

更進(jìn)一步

對于這些關(guān)系數(shù)據(jù),我們可以統(tǒng)一放到entities中進(jìn)行管理,這樣root state,看起來像這樣:

{
    simpleDomainData1: {....},
    simpleDomainData2: {....}
    entities : {
        entityType1 : {byId: {}, allIds},
        entityType2 : {....}
    }
    ui : {
        uiSection1 : {....},
        uiSection2 : {....}
    }
}

其實上面的entities并不夠純粹,因為其中包含了關(guān)聯(lián)關(guān)系(group里面包含了groupMembers的信息),也包含了列表的順序信息(如每個實體的allIds屬性)。更進(jìn)一步,我們可以將這些信息剝離出來,讓我們的entities更加簡單,扁平。

{
    entities: {
        groups: {
            group1: {
                id: "group1",
                groupName: "連線電商",
            },
            group2: {
                id: "group2",
                groupName: "連線資管",
            }
        },
        members: {
            user1: {
                id: "user1",
                name: "張三",
                dept: "電商部"
            },
            user2: {
                id: "user2",
                name: "李四",
                dept: "電商部"
            },
            user3: {
                id: "user3",
                name: "王五",
                dept: "電商部"
            }
        }
    },
    
    groups: {
        gourpIds: ["group1", "group2"],
        groupMembers: {
            group1: ["user1", "user2"],
            group2: ["user2", "user3"]
        }
    }
}

這樣我們在更新entity信息的時候,只需操作對應(yīng)entity就可以了,添加新的entity時則需要在對應(yīng)的對象如entities[group]中添加group對象,在groups[groupIds]中添加對應(yīng)的關(guān)聯(lián)關(guān)系。

enetities.js

const ADD_GROUP = "entities/addGroup";
const UPDATE_GROUP = "entities/updateGroup";
const ADD_MEMBER = "entites/addMember";
const UPDATE_MEMBER = "entites/updateMember";

export const addGroup = entity => ({
    type: ADD_GROUP,
    payload: {[entity.id]: entity}
})

export const updateGroup = entity => ({
  type: UPDATE_GROUP,
  payload: {[entity.id]: entity}
})

export const addMember = member => ({
  type: ADD_MEMBER,
  payload: {[member.id]: member}
})

export const updateMember = member => ({
  type: UPDATE_MEMBER,
  payload: {[member.id]: member}
})

_addGroup(state, action) {
  return state.set("groups", state.groups.merge(action.payload));
}

_addMember(state, action) {
  return state.set("members", state.members.merge(action.payload));
}

_updateGroup(state, action) {
  return state.set("groups", state.groups.merge(action.payload, {deep: true}));
}

_updateMember(state, action) {
  return state.set("members", state.members.merge(action.payload, {deep: true}))
}

const initialState = Immutable({
  groups: {},
  members: {}
})

export default function entities(state = initialState, action) {
  let type = action.type;

  switch (type) {
    case ADD_GROUP:
      return _addGroup(state, action);
    case UPDATE_GROUP:
      return _updateGroup(state, action);
    case ADD_MEMBER:
      return _addMember(state, action);
    case UPDATE_MEMBER:
      return _updateMember(state, action);
    default:
      return state;
  }
}

可以看到,因為entity的結(jié)構(gòu)大致相同,所以更新起來很多邏輯是差不多的,所以這里可以進(jìn)一步提取公用函數(shù),在payload里面加入要更新的key值。

export const addGroup = entity => ({
  type: ADD_GROUP,
  payload: {data: {[entity.id]: entity}, key: "groups"}
})

export const updateGroup = entity => ({
  type: UPDATE_GROUP,
  payload: {data: {[entity.id]: entity}, key: "groups"}
})

export const addMember = member => ({
  type: ADD_MEMBER,
  payload: {data: {[member.id]: member}, key: "members"}
})

export const updateMember = member => ({
  type: UPDATE_MEMBER,
  payload: {data: {[member.id]: member}, key: "members"}
})

function normalAddReducer(state, action) {
  let payload = action.payload;
  if (payload && payload.key) {
    let {key, data} = payload;
    return state.set(key, state[key].merge(data));
  }
  return state;
}

function normalUpdateReducer(state, action) {
  if (payload && payload.key) {
    let {key, data} = payload;
    return state.set(key, state[key].merge(data, {deep: true}));
  }
}

export default function entities(state = initialState, action) {
  let type = action.type;

  switch (type) {
    case ADD_GROUP:
    case ADD_MEMBER:
      return normalAddReducer(state, action);
    case UPDATE_GROUP:    
    case UPDATE_MEMBER:
      return normalUpdateReducer(state, action);
    default:
      return state;
  }
}
將loading狀態(tài)抽離到根reducer中,統(tǒng)一管理

在請求接口時,通常會dispatch loading狀態(tài),通常我們會在某個接口請求的reducer里面來處理響應(yīng)的loading狀態(tài),這會使loading邏輯到處都是。其實我們可以將loading狀態(tài)作為根reducer的一部分,多帶帶管理,這樣就可以復(fù)用響應(yīng)的邏輯。

const SET_LOADING = "SET_LOADING";

export const LOADINGMAP = {
  groupsLoading: "groupsLoading",
  memberLoading: "memberLoading"
}

const initialLoadingState = Immutable({
  [LOADINGMAP.groupsLoading]: false,
  [LOADINGMAP.memberLoading]: false,
});

const loadingReducer = (state = initialLoadingState, action) => {
  const { type, payload } = action;
  if (type === SET_LOADING) {
    return state.set(key, payload.loading);
  } else {
    return state;
  }
}

const setLoading = (scope, loading) => {
  return {
    type: SET_LOADING,
    payload: {
      key: scope,
      loading,
    },
  };
}

// 使用的時候
store.dispatch(setLoading(LOADINGMAP.groupsLoading, true));

這樣當(dāng)需要添加新的loading狀態(tài)的時候,只需要在LOADINGMAP和initialLoadingState添加相應(yīng)的loading type即可。
也可以參考dva的實現(xiàn)方式,它也是將loading存儲在根reducer,并且是根據(jù)model的namespace作為區(qū)分,

它方便的地方在于將更新loading狀態(tài)的邏輯被提取到plugin中,用戶不需要手動編寫更新loading的邏輯,只需要在用到時候使用state即可。plugin的代碼也很簡單,就是在鉤子函數(shù)中攔截副作用。

function onEffect(effect, { put }, model, actionType) {
    const { namespace } = model;

  return function*(...args) {
    yield put({ type: SHOW, payload: { namespace, actionType } });
    yield effect(...args);
    yield put({ type: HIDE, payload: { namespace, actionType } });
  };
}
其他

對于web端應(yīng)用,我們無法控制用戶的操作路徑,很可能用戶在直接訪問某個頁面的時候,我們store中并沒有準(zhǔn)備好數(shù)據(jù),這可能會導(dǎo)致一些問題,所以有人建議以page為單位劃分store,舍棄掉部分多頁面共享state的好處,具體可以參考這篇文章,其中提到在視圖之間共享state要謹(jǐn)慎,其實這也反映出我們在思考是否要共享某個state時,思考如下幾個問題:

有多少頁面會使用到該數(shù)據(jù)

每個頁面是否需要多帶帶的數(shù)據(jù)副本

改動數(shù)據(jù)的頻率怎么樣

參考文章

https://www.zhihu.com/questio...
https://segmentfault.com/a/11...
https://hackernoon.com/shape-...
https://medium.com/@dan_abram...
https://medium.com/@fastphras...
https://juejin.im/post/59a16e...
http://cn.redux.js.org/docs/r...
https://redux.js.org/recipes/...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/109428.html

相關(guān)文章

  • Redux進(jìn)階系列2: 如何合理地設(shè)計ReduxState

    摘要:設(shè)計一個好的并非易事,本文先從設(shè)計時最容易犯的兩個錯誤開始介紹,然后引出如何合理地設(shè)計。錯誤以為設(shè)計的依據(jù)以為設(shè)計的依據(jù),往往是一個對應(yīng)一個子,的結(jié)構(gòu)同返回的數(shù)據(jù)結(jié)構(gòu)保持一致或接近一致。至此,的結(jié)構(gòu)設(shè)計完成。 Redux是一個非常流行的狀態(tài)管理解決方案,Redux應(yīng)用執(zhí)行過程中的任何一個時刻,都是一個狀態(tài)的反映。可以說,State 驅(qū)動了Redux邏輯的運轉(zhuǎn)。設(shè)計一個好的State并非...

    劉明 評論0 收藏0
  • Redux概念之二: Redux的三大原則

    摘要:就是應(yīng)用程序領(lǐng)域的狀態(tài),它是類型中的模型的設(shè)計的概念,這設(shè)計是由架構(gòu)而來的,在原本的架構(gòu)中是允許多個的結(jié)構(gòu),簡化為只有單一個。的設(shè)計中是與中的相比,它們之間有一些類似的設(shè)計。 Redux里的強(qiáng)硬規(guī)則與設(shè)計不少,大部份都會與FP(函數(shù)式程序開發(fā))、改進(jìn)原本的Flux架構(gòu)設(shè)計有關(guān)。Redux官網(wǎng)文檔上的三大基本原則,主要是因為有可能怕初學(xué)者不理解Redux中的一些限制或設(shè)計,所以先寫出來說...

    dingda 評論0 收藏0
  • 干貨 | React技術(shù)棧耕耘 —— Redux

    摘要:作者小滬江前端開發(fā)工程師本文為原創(chuàng)文章,有不當(dāng)之處歡迎指出。于是,單一數(shù)據(jù)源規(guī)則實施起來,是規(guī)定用的頂層容器組件的來存儲單一對象樹,同時交給來管理。顧名思義,當(dāng)更新時,的回調(diào)函數(shù)會更新視圖層,以達(dá)到訂閱的效果。 作者:小boy (滬江web前端開發(fā)工程師)本文為原創(chuàng)文章,有不當(dāng)之處歡迎指出。轉(zhuǎn)載請注明出處。文章示例代碼:https://github.com/ikcamp/rea... ...

    LdhAndroid 評論0 收藏0
  • 簡析React 和 Redux 的特點和關(guān)系

    摘要:這對復(fù)雜問題定位是有好處的。同時,也是純函數(shù),與的是純函數(shù)呼應(yīng)。強(qiáng)約束約定,增加了內(nèi)聚合性。通過約定和全局的理解,可以減少的一些缺點。約定大于配置也是框架的主要發(fā)展方向。 React+Redux非常精煉,良好運用將發(fā)揮出極強(qiáng)勁的生產(chǎn)力。但最大的挑戰(zhàn)來自于函數(shù)式編程(FP)范式。在工程化過程中,架構(gòu)(頂層)設(shè)計將是一個巨大的挑戰(zhàn)。要不然做出來的東西可能是一團(tuán)亂麻。說到底,傳統(tǒng)框架與rea...

    iOS122 評論0 收藏0
  • Vuex — The core of Vue application

    摘要:個人看來,一個狀態(tài)管理的應(yīng)用,無論是使用,還是,最困難的部分是在的設(shè)計。中,并沒有移除,而是改為用于觸發(fā)。也是一個對象,用于注冊,每個都是一個用于返回一部分的。接受一個數(shù)組或?qū)ο螅鶕?jù)相應(yīng)的值將對應(yīng)的綁定到組件上。 系列文章: Vue 2.0 升(cai)級(keng)之旅 Vuex — The core of Vue application (本文) 從單頁應(yīng)用(SPA)到服務(wù)器...

    Aldous 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<