首页 文章

Redux:如何跟踪数据是否在本地修改?

提问于
浏览
2

我知道这是一个意见问题,而且很长,但我在Redux中遇到一个很好的解决方案时遇到了麻烦 .

我正在构建一个关卡编辑器,我希望向用户显示数据是否已被修改,因为它已持久保存到服务器 . 首先,考虑一下数据:

chapters: [{
    id: 1,
    levelIds: [ 2 ]
}],
levels: [{
     id: 2,
     entityIds: [ 4, 5 ]
}],
entities: [{
     id: 4, position:...
}, {
     id: 5, position:...
}]

章节有多个级别,级别有多个实体 . 在关卡编辑器中,您可以将完整章节编辑为一个项目,因此,如果任何实体或级别发生更改,则整个章节将被视为未保存 .

I want to track if the user has made any changes to the data since it was last persisted to the server. 如果某些内容发生了变化,我想在章节名称旁边显示 * . 标准:

  • 跟踪未保存(未保留到服务器)状态

  • 状态必须与撤消/重做系统一起使用

  • 如果某些"nested"数据被更改(如实体位置),则顶级章节必须知道它未保存,而不是实体本身

我已经探索了一些选项,我会试着说明为什么我不确定是否有任何解决方案比其他解决方案更好 .

选项1:在每章上存储“未保存”标志

此解决方案涉及存储"unsaved"标志,可能在单独的reducer中,在任何修改时设置为 true ,并在章节保存到服务器时设置为 false .

Problems

  • 我需要跟踪很多动作,所以这有点冗长 . 我还需要手动跟踪哪些动作实际修改了章节 . 它可能看起来像:

function isUnsavedReducer( state = {}, action ):Object {
    switch( action.type ) {
        case CHANGE_ENTITY_POSITION:
        case CHANGE_ENTITY_NAME
        ...etc
        case CHANGE_LEVEL_TITLE: {
            return {
                ...state,
                [ action.chapterId ]: true
            };
        }
    }
}
  • 大多数动作都不知道 chapterId . 例如,如果我移动实体,则操作看起来像 { entityId: 2, position: newPosition } . 如果我走这条路线,我想我必须将 chapterId 添加到所有动作中,即使它们不修改章节?

选项2:跟踪保存的最后一章对象

从表面上看,这看起来更简单 . 只要数据持久化,只需存储当前内存中的章节对象:

function lastSavedReducer( state = {}, action ):Object {
    switch( action.type ) {
        case SAVE_CHAPTER: {
            return {
                ...state,
                [ action.chapterId ]: action.chapter
            };
        }
    }
}

然后在视图中检查当前数据是否未保存,这是一个严格的相等检查:

{ lastSaved[ currentChapterId ] === this.props.chater ? 'Saved' : 'Unsaved' }

Problems:

  • 与上面的问题#2相同 . 当我使用redux动作修改实体位置时,我不必修改我的所有Reducer,如chapterReducer以返回一个新对象(即使实际上没有任何更改) . 我也可以存储"last persisted entities"对象,但由于所有实体都保存在一个商店中,我无法跟踪未保存的章节,只是未保存的内容 .

有一个明显的解决方案我错过了吗?我应该修改我的数据存储方式还是我的减速器设置?我的Reducer中的规范化数据以及可以设置为“未保存”的许多可能操作使我不确定最佳前进方式 . 你有没有实现类似的东西,已经知道陷阱或最佳前进方向?

1 回答

  • 0

    问题是您已将数据标准化,并且此数据充当应用程序的真实来源 .

    为什么您希望尚未保存的版本事件直接修改此规范化数据?

    • 如果不从后端重新获取保存状态,很难恢复到已保存状态(不应该要求)

    • 这很令人困惑,因为我们不知道是否保存了规范化数据(您当前的问题)

    最佳解决方案:在列表中保留未保存的操作,但在保存之前不要运行它们

    您可以在商店列表中累积版本操作,而不是直接修改规范化数据 .

    在渲染时,您可以使用此列表投影Redux存储状态,因此存储状态不会更改,但您仍可以连接到当前版本状态的组件 . 此技术与事件源和快照非常相似 .

    想象一下你的商店状态如下:

    const storeState = {
        unsavedEditionActions: [a1,a2,a3],
        normalizedData: {
          chapters: [{
            id: 1,
            levelIds: [ 2 ]
          }],
          levels: [{
             id: 2,
             entityIds: [ 4, 5 ]
          }],
          entities: [{
             id: 4, position:...
          }, {
             id: 5, position:...
          }]
        }
    }
    

    如果某个组件需要获得版本状态和原始状态,您可以直接将其缩减为 mapStateToProps

    let Component = ...
    
    Component = connect(state => {
    
      return {
        normalizedData: state.normalizedData,
        normalizedDataEdited: state.unsavedEditionActions.reduce(myNormalizedDataReducer,state.normalizedData)
      }
    
    })(Component)
    

    您的组件现在将接收当前保存的标准化数据和当前草稿版数据 . 从那时起,您就可以实现您的魔力和浅薄 - 比较2个DB的所有内容,以了解已编辑的内容 .

    保存时,清空列表并将其应用于规范化数据 . 取消时,您只需清空此列表(无需重新获取,因为您在版本完整之前保持状态) .

    我建议使用Reselect和ImmutableJS以获得更好的性能,但没有它也应该可以正常工作 .

    请注意,这种维护事件列表的技术也是许多人用于乐观更新的方法(即:在用户操作后立即提供反馈,无需等待服务器批准,但如果服务器执行,则能够恢复到先前的状态不批准改变) . 一些有用的链接:

    请注意,您不必在 connect 中减少动作列表,您也可以直接在您的商店中保存2个条目:normalizedData / normalizedDataEdited .


    其他解决方案:构建图表以从标准化数据进行编辑,并比较initialGraph!= editedGraph

    代替直接修改版本上的规范化数据,您可以构建一个临时的非规范化图形 . 因此,您可以构建此图形的副本,编辑副本,并将副本与原始图形进行浅层比较,以查看它是否已被修改 .

    function levelEditionReducer( state = {}, action ) {
        switch( action.type ) {
            case LEVEL_DATA_LOADED:
              const levelData = action.payload.levelData;
              return {
                initialGraph: levelData,
                editedGraph: levelData,
              }  
            case CHANGE_ENTITY_NAME
              const newEditedGraph = someFunctionToEditEntityName(state.editedGraph,action);
              return (newEditedGraph == state.editedGraph) ? state : { ...state, editedGraph: newEditionState }
            ......
        }
    }
    

    然后在版本之后,您可以看到 state.initialGraph != state.editedGraph 并显示保存按钮和草稿状态 .

    请注意,只有在非常谨慎地处理不可变数据结构时,这种浅层比较才有效,并确保不更新任何不必要的内容 . 如果 someFunctionToEditEntityName 返回与输入相同的状态,则reducer返回完全相同的状态非常重要,因为该操作实际上没有改变任何东西!

    使用ES6编写这种代码并不简单,但如果你使用其他库,如Immutable或updeep或类似的东西,如果你试图将一个属性设置为一个已经拥有的值的属性,它可能会使操作短路并使一定要给你原来的对象 .

    另请注意,您会尽早发现缺少护理,因为保存按钮会显示,但不应该显示:)所以您可能不会错过 .

    最终保存数据后,您可以将保存的图形合并到标准化数据中

相关问题