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

資訊專欄INFORMATION COLUMN

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

huangjinnan / 3099人閱讀

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

為什么使用redux

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

因?yàn)轫撁媸钦故拘缘臎]有太多的交互,所以我們完全可以根據(jù)頁面UI來設(shè)計(jì)如下的結(jié)構(gòu):

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

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

3.State范式化(normalize)

很多情況下,處理的數(shù)據(jù)都是嵌套或互相關(guān)聯(lián)的。例如,一個(gè)群列表,由很多群組成,每個(gè)群又包含很多個(gè)用戶,一個(gè)用戶可以加入多個(gè)不同的群。這種類型的數(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: "電商部"
            },
        ]
    }
]

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

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

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

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

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

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

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

任何對(duì)單個(gè)條目的引用都應(yīng)該根據(jù)存儲(chǔ)條目的 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):

因?yàn)閿?shù)據(jù)是扁平的,且只被定義在一個(gè)地方,更方便數(shù)據(jù)更新

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

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

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

更進(jìn)一步

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

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

其實(shí)上面的entities并不夠純粹,因?yàn)槠渲邪岁P(guān)聯(lián)關(guān)系(group里面包含了groupMembers的信息),也包含了列表的順序信息(如每個(gè)實(shí)體的allIds屬性)。更進(jìn)一步,我們可以將這些信息剝離出來,讓我們的entities更加簡(jiǎn)單,扁平。

{
    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"]
        }
    }
}

這樣我們?cè)诟耬ntity信息的時(shí)候,只需操作對(duì)應(yīng)entity就可以了,添加新的entity時(shí)則需要在對(duì)應(yīng)的對(duì)象如entities[group]中添加group對(duì)象,在groups[groupIds]中添加對(duì)應(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;
  }
}

可以看到,因?yàn)閑ntity的結(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)一管理

在請(qǐng)求接口時(shí),通常會(huì)dispatch loading狀態(tài),通常我們會(huì)在某個(gè)接口請(qǐng)求的reducer里面來處理響應(yīng)的loading狀態(tài),這會(huì)使loading邏輯到處都是。其實(shí)我們可以將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,
    },
  };
}

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

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

它方便的地方在于將更新loading狀態(tài)的邏輯被提取到plugin中,用戶不需要手動(dòng)編寫更新loading的邏輯,只需要在用到時(shí)候使用state即可。plugin的代碼也很簡(jiǎn)單,就是在鉤子函數(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 } });
  };
}
其他

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

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

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

改動(dòng)數(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)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

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

相關(guān)文章

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

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

    劉明 評(píng)論0 收藏0
  • Redux概念之二: Redux的三大原則

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

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

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

    LdhAndroid 評(píng)論0 收藏0
  • 簡(jiǎn)析React 和 Redux 的特點(diǎn)和關(guān)系

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

    iOS122 評(píng)論0 收藏0
  • Vuex — The core of Vue application

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

    Aldous 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<