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

qbo*_*lec 13 code-reuse components separation-of-concerns redux

TL; DR:在具有管理其自己的状态有些复杂的逻辑可重用的组件的情况下(认为:与autocompleter Facebook的评论文字区域,表情符号等),一个人如何使用存储,操作和减速器进行管理的多个实例的状态这个组件遍布整个网站?

考虑官方redux回购中的真实示例.在其中我们有:

  • 一个RepoPage,它显示谁已经出演一个特别的回购协议的用户列表中,
  • 一个UserPage,显示由特定用户加星标的repos列表
  • 一个List,它足够通用,可以显示用户列表或repos,提供了方法items和方法renderItem.特别是RepoPage使用User组件来显示为回购加星标的每个用户,并UserPage使用Repo组件来显示每个加星标的回购.

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

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

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

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

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

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

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

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

我的研究到目前为止:

  • 它看起来像在榆树​​上的操作(消息)是可嵌套以这样的方式代数数据类型,即父组件可以解压缩的消息的"外信封"和到子减速器传递旨在用于儿童一个内动作(更新).
  • 因为它是终极版公约使用字符串作为动作类型,上述想法的自然转换为使用前缀,而这似乎正是棱镜(foremly称为终极版-榆树)的作用:action.type是由通过组件树告诉路径的子串.OTOH在这篇评论中,棱镜作者tomkis解释说,Redux缺少的Elm Architecture最重要的部分是动作的组合
  • 上述两种方法似乎是重用Reducer Logic中描述的方法的扩展版本
  • 我还没有完全理解redux-fly如何在内部工作,但它似乎使用有效负载,而不是action.type通过其安装路径识别组件实例,store其中也对应于组件树中的路径,因为它的方式是由组件手动构建
  • WinAPI,对我来说,如果你眯着眼睛,它似乎与Redux非常相似,它hWnd为每个控件使用唯一的标识符,这使得检查是否action适合你是非常容易的,并决定你的状态应该在哪里store.
  • 上述想法可能会导致文档建议/讨论中描述的内容:重用Reducer Logic,其中每种类型的组件都有自己的由唯一ID索引的平面子树.
  • 在上面链接所链接的螺纹descibed另一个想法是写为特定类型的部件的一次减速,然后让该父组件的减速器调用它(这也意味着,父是reponsible到决定在商店中的孩子的状态 - 再次,这似乎与我的榆树建筑相似)
  • 一个非常有趣的讨论更多关于定制组件的可重用性,其中提议的细节与上面的类似
  • 特别是上面的讨论包含用户导航的命题,以这样的方式递归地组织存储树,组件的状态是两种分支中的子树:一个用于私有东西,另一个用于子表的"表"组件,其中每个子组件类具有其自己的"表",并且每个子实例在该表中具有唯一键,其中其状态以递归方式存储.允许访问这些孩子的唯一键存储在"私人"部分.这与我想象的WinAPI真的相似:)
  • 另一榆树风格的命题由同一个线程用户sompylasar是使用含有儿童行动作为在"matrioshka"风格的有效载荷,这在我看来模拟天生类型如何代数构造函数嵌套在榆树行动
  • 关于棱镜全球行动的讨论中,推荐使用redux-subspace作为一个既受榆树启发又能让你采取全球行动的图书馆.

Tom*_*cki 5

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

假设我们有一个非常简单的组件,其状态如下

interface ComponentState {
   text: string
}
Run Code Online (Sandbox Code Playgroud)

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

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

interface ResetAction {
    type: 'RESET_VALUE'
}
Run Code Online (Sandbox Code Playgroud)

为这 2 个操作键入 union(请查看 Typescript 的 Discriminated Unions):

type ComponentAction = SetAction | ResetAction;
Run Code Online (Sandbox Code Playgroud)

用于此的减速器应具有以下签名:

function componentReducer(state: ComponentState, action: ComponentAction): ComponentState {
    // code
}
Run Code Online (Sandbox Code Playgroud)

现在要将这个简单的组件“嵌入”到更大的组件中,我们需要在父组件中封装数据模型:

interface ParentComponentState {
    instance1: ComponentState,
    instance2: ComponentState,
}
Run Code Online (Sandbox Code Playgroud)

因为 redux 中的 action 类型需要全局唯一,我们不能为 Component 实例分派单个 action,因为它将由两个实例处理。其中一个想法是使用以下技术将单个组件的动作包装到父动作中:

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

interface Instance2ParentAction {
    type: 'INSTNACE_2_PARENT',
    payload: ComponentAction,
}
Run Code Online (Sandbox Code Playgroud)

父操作联合将具有以下签名:

type ParentComponentAction = Instance1ParentAction | Instance2ParentAction;
Run Code Online (Sandbox Code Playgroud)

而这项技术最重要的事情 - 父级减速器:

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

使用 Discriminated Unions 还可以为 parent 和 child reducers 提供类型安全。