如何为可重用组件组织 Redux 状态?
How to organize Redux state for reusable components?
TL;DR: 如果可重用组件有一些复杂的逻辑来管理其自身的状态(想想:带有自动完成器、表情符号等的 facebook 评论文本区域)如何一个使用 store、actions 和 reducer 来管理分布在整个网站上的该组件的多个实例的状态?
考虑官方 redux 存储库中的 real-world example。
其中有:
- a RepoPage,显示给特定回购加星标的用户列表,
- a UserPage,显示由特定用户加星标的回购列表
- a List,它足够通用,可以显示用户或存储库列表,提供
items
和 renderItem
的方式。特别是 RepoPage
使用 User
组件来显示给回购加星标的每个用户,并且 UserPage
使用 Repo
组件来显示每个已加星标的回购。
假设我真的希望 all 状态在 Redux 中。
特别是,我希望每个 RepoPage 和 UserPage 上的每个列表的状态都由 Redux 管理。这已经在例子中被一个聪明的 three-level 深树解决了:
- 在顶层,键说明它是什么类型的组件数据(在示例中称为
store.pagination
)
- 然后每个特定类型的上下文都有一个分支,组件可以在其中 (
store.pagination.starredByUser
, store.pagination. stargazersByRepo
)
- 那么有多少个唯一上下文就有多少个键(
store.pagination.starredByUser[login]
,store.pagination. stargazersByRepo[repo]
)
我感觉这三个级别也分别对应:组件类型,parent类型,parentid。
但是,我不知道如何扩展这个想法,来处理 List 组件本身有很多 children 的情况,在 Redux 中有一个值得跟踪的状态。
特别是,我想知道如何实施解决方案,其中:
User
组件保持完整
Repo
组件有一个切换其背景颜色的按钮
- 每个
Repo
组件的状态由 Redux 管理
(我很高兴使用 Redux 的一些扩展,它仍然使用 reducer,但不想使用 "just keep it in React local state",出于这个问题的目的)
我目前的研究:
- 看起来在 Elm 中,Actions(消息)是可以嵌套的代数数据类型,parent 组件可以解压消息的 "outer envelope" 并传递一个用于 child 到 child reducer(更新程序)的内部操作。
- 由于在 Redux 中使用字符串作为 action 的类型是一种惯例,因此上述想法的自然翻译是使用前缀,这似乎是 prism (foremly known as redux-elm) does: the
action.type
is comprised of substrings which tell the path through components' tree. OTOH in this comment 棱镜作者 tomkis 解释的内容Redux 缺少的 Elm 架构最重要的部分是动作的组合
- 上述两种方法似乎是 Reusing Reducer Logic
中描述的方法的扩展版本
- 我还没有完全理解 redux-fly 内部是如何工作的,但它似乎使用有效负载,而不是
action.type
通过其在 [=26= 中的安装路径来识别组件实例] 这也对应于组件树中的路径,因为它是由组件手动构建的方式
- WinAPI,如果你眯着眼睛,我觉得它与 Redux 非常相似,它为每个控件使用唯一的
hWnd
标识符,这使得检查 action
是否适合你变得非常容易,并且在 store
中决定你的状态应该在哪里。
- 上述想法可能会导致 Documentation suggestion/discussion: Reusing Reducer Logic 中描述的内容,其中每种类型的组件都有自己的扁平子树,由唯一 ID 索引。
- 上面链接的线程中描述的另一个想法是为特定类型的组件编写一次 reducer,然后让 parent 组件的 reducer 调用它(这也意味着 parent 负责决定 child 的状态在商店中的位置 - 同样,对我来说这似乎类似于 Elm Architecture)
- 一个非常有趣的讨论More on reusability for custom components,其中提出了与上述提案相似的提案细节
- 特别是上面的讨论包含 a proposition 用户导航,以递归方式组织商店树,组件的状态是两种分支中的子树:一种用于私有内容,以及另一个用于 child 组件的 "tables",其中 child 组件的每个 class 都有自己的 "table",并且 child 的每个实例都有一个table 中的唯一键,递归存储其状态。允许访问这些 children 的唯一密钥存储在 "private" 部分中。这与我对 WinAPI 的想象非常相似:)
- 来自同一个线程的用户 sompylasar 的另一个 elm-inspired proposition 是使用包含 children 的动作的动作作为 "matrioshka" 风格的有效载荷,在我看来模仿如何代数类型构造函数嵌套在 Elm
中
- redux-subspace was recommended in discussion about Global Actions 用于 prism,作为一个既 Elm-inspired 又让你拥有 glo 的图书馆所有行动。
我将尝试解释受 Elm lang 启发并已移植到 Typescript 的想法之一:
假设我们有一个非常简单的组件,其状态如下
interface ComponentState {
text: string
}
可以通过以下 2 个操作减少组件。
interface SetAction {
type: 'SET_VALUE', payload: string
}
interface ResetAction {
type: 'RESET_VALUE'
}
这 2 个操作的类型联合(请查看 Typescript 的可区分联合):
type ComponentAction = SetAction | ResetAction;
Reducer 应具有以下签名:
function componentReducer(state: ComponentState, action: ComponentAction): ComponentState {
// code
}
现在"embed"这个简单的组件在一个更大的组件中,我们需要在父组件中封装数据模型:
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;
而这项技术最重要的事情 - parent reducer:
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),
};
//
}
}
使用区分联合还为父和子减速器提供了类型安全。
TL;DR: 如果可重用组件有一些复杂的逻辑来管理其自身的状态(想想:带有自动完成器、表情符号等的 facebook 评论文本区域)如何一个使用 store、actions 和 reducer 来管理分布在整个网站上的该组件的多个实例的状态?
考虑官方 redux 存储库中的 real-world example。 其中有:
- a RepoPage,显示给特定回购加星标的用户列表,
- a UserPage,显示由特定用户加星标的回购列表
- a List,它足够通用,可以显示用户或存储库列表,提供
items
和renderItem
的方式。特别是RepoPage
使用User
组件来显示给回购加星标的每个用户,并且UserPage
使用Repo
组件来显示每个已加星标的回购。
假设我真的希望 all 状态在 Redux 中。
特别是,我希望每个 RepoPage 和 UserPage 上的每个列表的状态都由 Redux 管理。这已经在例子中被一个聪明的 three-level 深树解决了:
- 在顶层,键说明它是什么类型的组件数据(在示例中称为
store.pagination
) - 然后每个特定类型的上下文都有一个分支,组件可以在其中 (
store.pagination.starredByUser
,store.pagination. stargazersByRepo
) - 那么有多少个唯一上下文就有多少个键(
store.pagination.starredByUser[login]
,store.pagination. stargazersByRepo[repo]
)
我感觉这三个级别也分别对应:组件类型,parent类型,parentid。
但是,我不知道如何扩展这个想法,来处理 List 组件本身有很多 children 的情况,在 Redux 中有一个值得跟踪的状态。
特别是,我想知道如何实施解决方案,其中:
User
组件保持完整Repo
组件有一个切换其背景颜色的按钮- 每个
Repo
组件的状态由 Redux 管理
(我很高兴使用 Redux 的一些扩展,它仍然使用 reducer,但不想使用 "just keep it in React local state",出于这个问题的目的)
我目前的研究:
- 看起来在 Elm 中,Actions(消息)是可以嵌套的代数数据类型,parent 组件可以解压消息的 "outer envelope" 并传递一个用于 child 到 child reducer(更新程序)的内部操作。
- 由于在 Redux 中使用字符串作为 action 的类型是一种惯例,因此上述想法的自然翻译是使用前缀,这似乎是 prism (foremly known as redux-elm) does: the
action.type
is comprised of substrings which tell the path through components' tree. OTOH in this comment 棱镜作者 tomkis 解释的内容Redux 缺少的 Elm 架构最重要的部分是动作的组合 - 上述两种方法似乎是 Reusing Reducer Logic 中描述的方法的扩展版本
- 我还没有完全理解 redux-fly 内部是如何工作的,但它似乎使用有效负载,而不是
action.type
通过其在 [=26= 中的安装路径来识别组件实例] 这也对应于组件树中的路径,因为它是由组件手动构建的方式 - WinAPI,如果你眯着眼睛,我觉得它与 Redux 非常相似,它为每个控件使用唯一的
hWnd
标识符,这使得检查action
是否适合你变得非常容易,并且在store
中决定你的状态应该在哪里。 - 上述想法可能会导致 Documentation suggestion/discussion: Reusing Reducer Logic 中描述的内容,其中每种类型的组件都有自己的扁平子树,由唯一 ID 索引。
- 上面链接的线程中描述的另一个想法是为特定类型的组件编写一次 reducer,然后让 parent 组件的 reducer 调用它(这也意味着 parent 负责决定 child 的状态在商店中的位置 - 同样,对我来说这似乎类似于 Elm Architecture)
- 一个非常有趣的讨论More on reusability for custom components,其中提出了与上述提案相似的提案细节
- 特别是上面的讨论包含 a proposition 用户导航,以递归方式组织商店树,组件的状态是两种分支中的子树:一种用于私有内容,以及另一个用于 child 组件的 "tables",其中 child 组件的每个 class 都有自己的 "table",并且 child 的每个实例都有一个table 中的唯一键,递归存储其状态。允许访问这些 children 的唯一密钥存储在 "private" 部分中。这与我对 WinAPI 的想象非常相似:)
- 来自同一个线程的用户 sompylasar 的另一个 elm-inspired proposition 是使用包含 children 的动作的动作作为 "matrioshka" 风格的有效载荷,在我看来模仿如何代数类型构造函数嵌套在 Elm 中
- redux-subspace was recommended in discussion about Global Actions 用于 prism,作为一个既 Elm-inspired 又让你拥有 glo 的图书馆所有行动。
我将尝试解释受 Elm lang 启发并已移植到 Typescript 的想法之一:
假设我们有一个非常简单的组件,其状态如下
interface ComponentState {
text: string
}
可以通过以下 2 个操作减少组件。
interface SetAction {
type: 'SET_VALUE', payload: string
}
interface ResetAction {
type: 'RESET_VALUE'
}
这 2 个操作的类型联合(请查看 Typescript 的可区分联合):
type ComponentAction = SetAction | ResetAction;
Reducer 应具有以下签名:
function componentReducer(state: ComponentState, action: ComponentAction): ComponentState {
// code
}
现在"embed"这个简单的组件在一个更大的组件中,我们需要在父组件中封装数据模型:
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;
而这项技术最重要的事情 - parent reducer:
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),
};
//
}
}
使用区分联合还为父和子减速器提供了类型安全。