Am Ende ist das, was ich implementiert habe (zu Demonstrationszwecken nur - nicht verwandter Code ist weggelassen):
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
}
}
Es gibt auch einen Datenreduzierer, der jedoch aufgrund der generischen Implementierung, die für allgemeine Listenantworten wiederverwendet wird, im übergeordneten Reduzierer verknüpft ist. Ereignisdaten werden aktualisiert und die Start-/Endeigenschaft wird entfernt, da sie nach Bereich (ISO 8601 time interval string) zusammengesetzt ist. Dies kann später von moment.range verwendet oder durch '/' geteilt werden, um Start-/Enddaten zu erhalten. Ich habe mich für ein Array von Bereichszeichenfolgen entschieden, um das Überprüfen vorhandener Bereiche zu vereinfachen, da diese möglicherweise größer werden. Ich denke, dass der primitive String-Vergleich (indexOf oder es6) schneller ist als das Schleifen komplexer Strukturen in solchen Fällen.
data.js (abgespeckte Version):
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
}
}
Dies kann dann durch eine Kalenderkomponente mit Ereignis-Selektor verwendet werden:
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
}
Und hier ist, wie sich die Datenstruktur in der aussieht redux Entwickler-Tools:
Eac h Zeit die Ereignisse abgerufen werden, ihre Daten aktualisiert werden (wenn es eine Änderung gibt) und Referenzen hinzugefügt werden. Hier ist ein Screenshot von redux diff nach neuen Ereignissen holen reichen:
Hope this jemand hilft, werde ich nur hinzufügen, dass dies noch nicht getestet Schlacht ist aber mehr ein Beweis für ein Konzept, das funktioniert.
[Bearbeiten] BTW. Ich werde wahrscheinlich etwas von dieser Logik in das Backend verschieben, da dann keine Eigenschaften geteilt/hinzugefügt/gelöscht werden müssen.
Nun erst einmal vielen Dank für die Wiederholung, es ist gut zu hören andere Leute Meinungen bei der Fehlersuche. Wenn 'id' und 'start_id' zufällig identisch sind (in meinem Fall), kann ich sagen, dass es dasselbe Ereignis ist. Nehmen wir an, Sie haben 10 verschiedene Klassen, die sich täglich wiederholen. In meinem Fall habe ich mich für eine dynamische MM-Tabelle entschieden, die beim Hinzufügen eines Abonnements ausgefüllt wird. Bis dahin werden Ereignisse dynamisch dargestellt. Auf diese Weise verschmutze ich DB nicht mit ungültigen Einträgen/Relationen. Eine andere Möglichkeit wäre, einen Datensatz für jedes Ereignis zu erstellen (und das kann eine Weile dauern) –