2017-08-26 80 views
5

應該在redux存儲中存儲/處理重複事件的正確方式是什麼?Redux和日曆重複事件

問題:假設我們有一個後端API,通過複雜的業務邏輯生成重複事件。一些事件可能具有相同的ID。讓我們說,產生輸出看起來是這樣的:id + # + starts_at

[ 
    { 
    "id": 1, 
    "title": "Weekly meeting", 
    "all_day": true, 
    "starts_at": "2017-09-12", 
    "ends_at": "2017-09-12" 
    }, 
    { 
    "id": 3, 
    "title": "Daily meeting1", 
    "all_day": false, 
    "starts_at": "2017-09-12", 
    "ends_at": "2017-09-12", 
    }, 
    { 
    "id": 3, 
    "title": "Daily meeting1", 
    "all_day": false, 
    "starts_at": "2017-09-13", 
    "ends_at": "2017-09-13", 
    }, 
    { 
    "id": 3, 
    "title": "Daily meeting1", 
    "all_day": false, 
    "starts_at": "2017-09-14", 
    "ends_at": "2017-09-14", 
    } 
] 

可能的解決辦法是:由具有這樣組成的附加屬性UID生成唯一的ID。這樣我們可以唯一地識別每個事件。 (我現在用的這個權利)

[ 
    { 
    "id": 1, 
    "uid": "1#2017-09-12", 
    "title": "Weekly meeting", 
    "all_day": true, 
    "starts_at": "2017-09-12", 
    "ends_at": "2017-09-12" 
    } 
] 

我不知道是否有一些其他的方式,也許不必由唯一的ID更優雅?

回答

0

這到底是我實現什麼(僅用於演示的目的 - 省略無關的代碼):

eventRoot.js:

import { combineReducers } from 'redux' 
import ranges from './events' 
import ids from './ids' 
import params from './params' 
import total from './total' 

export default resource => 
    combineReducers({ 
    ids: ids(resource), 
    ranges: ranges(resource), 
    params: params(resource) 
    }) 

events.js:

import { GET_EVENTS_SUCCESS } from '@/state/types/data' 

export default resource => (previousState = {}, { type, payload, requestPayload, meta }) => { 
    if (!meta || meta.resource !== resource) { 
    return previousState 
    } 
    switch (type) { 
    case GET_EVENTS_SUCCESS: 
     const newState = Object.assign({}, previousState) 
     payload.data[resource].forEach(record => { 
     // ISO 8601 time interval string - 
     // http://en.wikipedia.org/wiki/ISO_8601#Time_intervals 
     const range = record.start + '/' + record.end 
     if (newState[record.id]) { 
      if (!newState[record.id].includes(range)) { 
      // Don't mutate previous state, object assign is only a shallow copy 
      // Create new array with added id 
      newState[record.id] = [...newState[record.id], range] 
      } 
     } else { 
      newState[record.id] = [range] 
     } 
     }) 
     return newState 
    default: 
     return previousState 
    } 
} 

還有一個數據簡化器,但由於泛型實現將它重新用於常用列表響應,所以它在父級簡化器中已鏈接。事件數據被更新,開始/結束屬性被刪除,因爲它由範圍組成(ISO 8601 time interval string)。這可以稍後由moment.range使用或由'/'分割以獲取開始/結束數據。我選擇了一系列範圍字符串來簡化對現有範圍的檢查,因爲它們可能會變大。在這種情況下,我認爲原始字符串比較(indexOf或es6 includes)比循環複雜結構要快。

data.js(精簡版):

import { END } from '@/state/types/fetch' 
import { GET_EVENTS } from '@/state/types/data' 

const cacheDuration = 10 * 60 * 1000 // ten minutes 
const addRecords = (newRecords = [], oldRecords, isEvent) => { 
    // prepare new records and timestamp them 
    const newRecordsById = newRecords.reduce((prev, record) => { 
    if (isEvent) { 
     const { start, end, ...rest } = record 
     prev[record.id] = rest 
    } else { 
     prev[record.id] = record 
    } 
    return prev 
    }, {}) 
    const now = new Date() 
    const newRecordsFetchedAt = newRecords.reduce((prev, record) => { 
    prev[record.id] = now 
    return prev 
    }, {}) 
    // remove outdated old records 
    const latestValidDate = new Date() 
    latestValidDate.setTime(latestValidDate.getTime() - cacheDuration) 
    const oldValidRecordIds = oldRecords.fetchedAt 
    ? Object.keys(oldRecords.fetchedAt).filter(id => oldRecords.fetchedAt[id] > latestValidDate) 
    : [] 
    const oldValidRecords = oldValidRecordIds.reduce((prev, id) => { 
    prev[id] = oldRecords[id] 
    return prev 
    }, {}) 
    const oldValidRecordsFetchedAt = oldValidRecordIds.reduce((prev, id) => { 
    prev[id] = oldRecords.fetchedAt[id] 
    return prev 
    }, {}) 
    // combine old records and new records 
    const records = { 
    ...oldValidRecords, 
    ...newRecordsById 
    } 
    Object.defineProperty(records, 'fetchedAt', { 
    value: { 
     ...oldValidRecordsFetchedAt, 
     ...newRecordsFetchedAt 
    } 
    }) // non enumerable by default 
    return records 
} 

const initialState = {} 
Object.defineProperty(initialState, 'fetchedAt', { value: {} }) // non enumerable by default 

export default resource => (previousState = initialState, { payload, meta }) => { 
    if (!meta || meta.resource !== resource) { 
    return previousState 
    } 
    if (!meta.fetchResponse || meta.fetchStatus !== END) { 
    return previousState 
    } 
    switch (meta.fetchResponse) { 
    case GET_EVENTS: 
     return addRecords(payload.data[resource], previousState, true) 
    default: 
     return previousState 
    } 
} 

這可隨後被與事件選擇一個日曆組件:

const convertDateTimeToDate = (datetime, timeZoneName) => { 
    const m = moment.tz(datetime, timeZoneName) 
    return new Date(m.year(), m.month(), m.date(), m.hour(), m.minute(), 0) 
} 

const compileEvents = (state, filter) => { 
    const eventsRanges = state.events.list.ranges 
    const events = [] 
    state.events.list.ids.forEach(id => { 
    if (eventsRanges[id]) { 
     eventsRanges[id].forEach(range => { 
     const [start, end] = range.split('/').map(d => convertDateTimeToDate(d)) 
     // You can add an conditional push, filtered by start/end limits 
     events.push(
      Object.assign({}, state.events.data[id], { 
      start: start, 
      end: end 
      }) 
     ) 
     }) 
    } 
    }) 
    return events 
} 

這裏是數據結構中的樣子終極版開發工具:

https://i.imgur.com/5nzrG6e.png

EAC h事件被提取,他們的數據被更新(如果有改變)並且引用被添加。這裏是差異的終極版的截圖獲取新活動範圍:

enter image description here

希望這有助於有人,我就補充說,這還不是戰鬥測試,但更多的是那的工作的一個概念證明。

[編輯]順便說一句。我可能會將一些邏輯移動到後端,因爲那樣就不需要拆分/連接/刪除屬性。

1

您目前的解決方案存在一個可能的缺陷。如果兩個事件的idstart_id會相同,會發生什麼?您的域名可能出現這種情況嗎?

因此,我通常在這種情況下使用this nice lib。它產生了很短的唯一ID,它具有一些很好的屬性,例如保證不會相交,變得不可預測等等。

還問問自己,你是否真的需要唯一的ID在你的情況。看起來你的後端無論如何都無法區分事件,那爲什麼要麻煩呢? Redux商店將愉快地保留您的活動事件,無需uid

+0

首先感謝您的重播,在排除故障時聽到其他人的意見是很好的。如果'id'和'start_id'恰好相同(在我的情況下),我可以告訴它是同一個事件。假設你有10個不同的學生課程,每天重複。在我的情況下,我選擇了添加訂閱時填充的動態MM表。在那之前,事件是動態呈現的。通過這種方式,我不會污染數據庫中無效的條目/關係。其他選項是爲每個事件發生創建一個記錄(並且這可能需要一段時間的客戶端) –

1

也許沒有太大的改進(如果有的話),但只是使用JSON.stringify檢查重複項可能會使獨特的id過時。

const existingEvents = [ 
    { 
    "id": 3, 
    "title": "Daily meeting1", 
    "all_day": false, 
    "starts_at": "2017-09-14", 
    "ends_at": "2017-09-14", 
    } 
]; 

const duplicate = { 
    "id": 3, 
    "title": "Daily meeting1", 
    "all_day": false, 
    "starts_at": "2017-09-14", 
    "ends_at": "2017-09-14", 
}; 

const eventIsDuplicate = (existingEvents, newEvent) => { 
    const duplicate = 
    existingEvents.find(event => JSON.stringify(event) == JSON.stringify(newEvent)); 
    return typeof duplicate != 'undefined'; 
}; 

console.log(eventIsDuplicate(existingEvents, duplicate)); // true 

我想這隻會是最好的現有解決方案,如果由於某種原因,你會希望將所有的獨特邏輯上的客戶端。

+0

好的,重點不在於保持唯一性,就像正確處理存儲中的數據一樣,並且可能有一個規範化的數據通過重新使用相同的UID在其他地方使用。有我的情況下組成的密鑰的作品,否則 總是有一個選項,可以添加ends_at到組合鍵,即使結束日期已經改變,這將使事件符合證明(在我的情況下,這些數據是完全相等的..僅...開始日期發生了變化) –

1

據我瞭解你給出的例子,似乎只要事件的細節發生變化,服務器就發送一個特定的事件。

如果是這樣,並且您想要跟蹤對事件的更改,您的可能形狀可能是一個包含事件的所有字段以保存當前數據的對象數組,而history屬性是一個數組所有以前的(或最近的n個)事件對象以及接收它們的時間戳。這就是減速器的外觀,只存儲每個事件的最近五次事件更改。我期待的行動有一個​​屬性,它具有您的標準event屬性和時間戳屬性,這可以在動作創建者中輕鬆完成。

const event = (state = { history: [] }, action) => { 
    switch (action.type) { 
    case 'EVENT_FETCHED': 
     return ({ 
     ...action.payload.event, 
     history: [...state.history, action.payload].slice(-5), 
     }); 
    default: 
     return state; 
    } 
    }; 


const events = (state = { byID: {}, IDs: [] }, action) => { 
    const id = action.payload.event.ID; 
    switch (action.type) { 
    case 'EVENT_FETCHED': 
     return id in state.byID 
     ? { 
      ...state, 
      byID: { ...state.byID, [id]: event(state.byID[id], action) }, 
     } 
     : { 
      byID: { ...state.byID, [id]: event(undefined, action) }, 
      IDs: [id], 
     }; 
    default: 
     return state; 
    } 
}; 

這樣做,你不需要任何唯一的ID。如果我誤解了你的問題,請告訴我。

編輯:這是Redux文檔中pattern的輕微擴展,用於存儲以前的事件。

+0

事情是我需要保留starts_at,這對於具有相同ID的一些事件是不同的(其他所有事件都是相同的)。你的實現只是將所有事件作爲獨立數組收集。規範化應該通過使用ID(或UID)來完成,您可以獲得確切的事件並將其重用於代碼的其他部分。我的實現已經有效,所以它不是一個讓它工作的問題,而是更多的如何優化它,以及這些案例的標準(如果存在的話)。 –

+0

當事件數據發生變化(除了start_at之外的任何事情)時,應該將更改傳播到具有相同ID的所有事件。 –