如何为可重用组件组织Redux状态?

时间:2017-07-28 08:08:13

标签: redux components code-reuse separation-of-concerns

TL; DR:如果一个可重用的组件有一些复杂的逻辑来管理自己的状态(想想:一个带有autocompleter,emoji等的facebook评论textarea)如何使用store,actions和reducers来管理整个网站上传播的这个组件的多个实例的状态?

考虑官方redux回购中的real-world example。 我们在其中:

  • 一个RepoPage,显示已为某个特定仓库加星标的用户列表
  • 一个UserPage,显示由特定用户加星标的回购列表
  • 一个List,它足够通用,可以显示用户或回购列表,前提是itemsrenderItem的方式。特别是RepoPage使用User组件来显示为回购标记加星标的每个用户,而UserPage使用Repo组件来显示每个已加星标的回购广告。

假设我真的希望状态的所有都在Redux中。

特别是,我希望每个RepoPage和UserPage上的每个List的状态都由Redux管理。这个例子已经由一个聪明的三级深树来处理:

  • 在顶层关键字表示它是什么类型的组件数据(在示例中称为store.pagination
  • 然后,每个特定类型的上下文都有一个分支,其中组件可以是(store.pagination.starredByUserstore.pagination. stargazersByRepo
  • 然后有与唯一上下文一样多的键(store.pagination.starredByUser[login]store.pagination. stargazersByRepo[repo]

我觉得这三个级别也对应于:组件类型,父类型,父ID。

但是,我不知道如何扩展这个想法,以处理List组件本身有很多孩子的情况,其中一个状态值得在Redux中跟踪。

特别是,我想知道如何实现一个解决方案:

  • User组件保持不变
  • Repo组件有一个切换其背景颜色的按钮
  • 每个Repo组件的状态由Redux管理

(我很高兴使用Redux的一些扩展程序,它仍然使用Reducer,但不想使用"只需将它保持在React本地状态",为了这个问题的目的)

到目前为止我的研究

  • 看起来在Elm中,Actions(消息)是代数数据类型,它们可以以这种方式嵌套,父组件可以解包"外包络"消息并将用于儿童的内部动作传递给儿童减速器(更新者)。
  • 因为Redux中的一个约定是使用字符串作为动作类型,所以上述想法的自然翻译是使用前缀,这似乎是prism(前者称为redux-elm)确实:action.type由子串组成,这些子串告诉通过组件的路径'树。 this comment中的OTOH棱镜作者tomkis解释说,Redux缺少的榆树建筑最重要的部分是行动的构成
  • 上述两种方法似乎是Reusing Reducer Logic
  • 中描述的方法的扩展版本
  • 我还没有完全理解redux-fly内部如何工作,但它似乎使用有效负载,而不是action.type来通过{{1}中的安装路径来识别组件实例这也与组件树中的路径相对应,因为它是由组件手动构造的方式
  • WinAPI,对我来说,如果你眯着眼睛,它似乎与Redux非常相似,它为每个控件使用唯一的store标识符,这样可以非常容易地检查hWnd是否适合你,并决定在哪里应该是action中的州。
  • 上述想法可能会导致Documentation suggestion/discussion: Reusing Reducer Logic 中描述的内容,其中每种类型的组件都有自己的唯一ID索引的平面子树。
  • 在上面链接的链接线程中描述的另一个想法是为特定类型的组件编写一次reducer,然后让父组件的reducer调用它(这也意味着父组件可以负责决定在哪里孩子的状态所在的商店 - 再次,这似乎与我的榆树建筑相似)
  • 一个非常有趣的讨论More on reusability for custom components,其中提案的细节与上面提到的类似,
  • 特别是上面的讨论包含用户导航的a proposition,以这种方式递归地组织存储树,组件的状态是两种分支中的子树:一个用于私有东西,另一个用于私有东西,另一个用于私有东西for" tables"子组件的子组件,其中每个子组件类具有其自己的"表",并且子的每个实例在该表中具有唯一键,其中状态以递归方式存储。允许访问这些孩子的唯一键存储在" private"部分。这与我想象的WinAPI非常类似:)
  • 来自同一线程的用户sompylasar的另一个elm-inspired proposition是使用包含儿童操作的动作作为" matrioshka"样式,在我看来模仿代数类型构造函数如何嵌套在Elm中
  • redux-subspacediscussion about Global Actions中被推荐用于棱镜,作为一个既受榆树启发又可让您采取全球行动的图书馆。

1 个答案:

答案 0 :(得分:2)

我将尝试解释一个受Elm lang启发并被移植到Typescript的想法:

我们说我们的组件非常简单,具有以下状态

interface ComponentState {
   text: string
}

可以通过以下两个操作来减少组件。

interface SetAction {
    type: 'SET_VALUE', payload: string
}

interface ResetAction {
    type: 'RESET_VALUE'
}

为这两个动作键入联合(请查看不同的打字稿联盟):

type ComponentAction = SetAction | ResetAction;

对此的减速器应该有以下签名:

function componentReducer(state: ComponentState, action: ComponentAction): ComponentState {
    // code
}

现在来"嵌入"我们需要在较大的组件中使用这个简单的组件将数据模型封装在父组件中:

interface ParentComponentState {
    instance1: ComponentState,
    instance2: ComponentState,
}

由于redux中的操作类型需要全局唯一,因此我们无法为Component实例调度单个操作,因为它将由两个实例处理。其中一个想法是使用以下技术将单个组件的操作包装到父操作中:

interface Instance1ParentAction {
    type: 'INSTNACE_1_PARENT',
    payload: ComponentAction,
}

interface Instance2ParentAction {
    type: 'INSTNACE_2_PARENT',
    payload: ComponentAction,
}

父母行动联盟将具有以下签名:

type ParentComponentAction = Instance1ParentAction | Instance2ParentAction;

这项技术最重要的是 - 父减速器:

function parentComponentReducer(state: ParentComponentState, action: ParentComponentAction): ParentComponentState {
    switch (action.type) {
        case 'INSTNACE_1_PARENT':
            return {
                ...state,
                // using component reducer
                instance1: componentReducer(state.instance1, action.payload),
            };
        //
    }
}

使用被歧视的联合会为父母和儿童减少者提供类型安全。