Redux和Calendar重复事件

Gor*_*.it 5 events json calendar repeat redux

在redux商店中存储/处理重复事件的正确方法应该是什么?

问题:假设我们有一个后端API,它通过复杂的业务逻辑生成重复事件.某些事件可能具有相同的ID.让我们说生成的输出看起来像这样:

[
  {
    "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",
  }
]
Run Code Online (Sandbox Code Playgroud)

可能的解决方案是:通过使用如下组成的附加属性uid生成唯一ID : id + # + starts_at. 这样我们就可以唯一地识别每个事件.(我现在正在使用这个)

示例:

[
  {
    "id": 1,
    "uid": "1#2017-09-12",
    "title": "Weekly meeting",
    "all_day": true,
    "starts_at": "2017-09-12",
    "ends_at": "2017-09-12"
  }
]
Run Code Online (Sandbox Code Playgroud)

我想知道是否有其他方式,也许比组成独特的id更优雅?

Gor*_*.it 0

最后,这是我实现的(仅用于演示目的 - 省略了不相关的代码):

事件根.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)
  })
Run Code Online (Sandbox Code Playgroud)

事件.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
  }
}
Run Code Online (Sandbox Code Playgroud)

还有一个数据缩减器,但由于通用实现重新用于公共列表响应,因此它链接在父缩减器中。事件数据已更新,开始/结束属性已删除,因为它由范围( ISO 8601 时间间隔字符串)组成。这可以稍后被 moment.range 使用或用“/”分割来获取开始/结束数据。我选择了范围字符串数组来简化对现有范围的检查,因为它们可能会变大。我认为在这种情况下,原始字符串比较(indexOf 或 es6 包括)会比循环复杂结构更快。

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
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,日历组件可以使用事件选择器来使用它:

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
}
Run Code Online (Sandbox Code Playgroud)

以下是 redux 开发工具中的数据结构:

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

每次获取事件时,都会更新其数据(如果有更改)并添加引用。这是获取新事件范围后 redux diff 的屏幕截图:

在此输入图像描述

希望这对某人有帮助,我只是补充一点,这仍然没有经过战斗测试,而是更多地证明了一个有效的概念。

[编辑] 顺便说一句。我可能会将其中一些逻辑移至后端,因为这样就不需要拆分/连接/删除属性。