我有一个 ParentComponent
具有稍微复杂的状态和逻辑(主要是为了使 UI 更容易),它可以被描述为某种形式。
现在,在用户输入所有输入后,他需要按下一个按钮。按钮及其动作取决于当前状态:
- 如果用户尚未登录,按钮会建议用户登录。
- 如果他们“使用其他平台的帐户”,按钮会要求他更改帐户。
- 如果帐户没有执行“主要”操作的权限,按钮建议请求所需的权限。
- 等等...
自然,我可以将所有这些逻辑和验证转储到 ParentComponent
并完成它(以及可维护性和可读性)。但是我想将上述每个步骤分解成它自己的组件(可能使用本地钩子和 Redux 的一些全局状态),因为有条件地调用钩子是不受欢迎的,并且我在一个组件中需要的钩子与另一个组件的钩子冲突。
所以我面临以下问题:
- 我有多个子组件,每个子组件都返回
- 按钮,如果用户需要做某事。
null
,否则(用户满足条件,所以一切正常)。
- 我想以预定义的顺序一个接一个地渲染所述组件。
- 如果子组件呈现给某些东西,则显示它。
- 如果组件渲染到
null
,我想渲染下一个子级。
这类似于 https://refactoring.guru/design-patterns/chain-of-responsibility 这正是我想做的,但我不明白这种方法如何映射到 React 组件和渲染。
回答1
我设法提出了允许执行以下操作的实现:
<ChainOfResponsibility>
<EnsureUserHasLoggedIn>
<EnsureBalanceIsLoaded>
<BuyItemButton>
</ChainOfResponsibility>
如果 <EnsureUserHasLoggedIn>
认为用户需要登录,它将只呈现“登录”按钮而不呈现其他任何内容。否则,它将渲染链中的下一个“片段”。
interface Props extends ChainOfResponsibilityPiece {}
const EnsureUserHasLoggedIn: React.FC<Props> = ({ next }) => {
const userHasLoggedIn = /* Arbitrary condition. */ true
if (!userHasLoggedIn) {
return <Button>Sign In</Button>
}
// Render the next piece in chain.
return <>{next}</>
}
这种方法的好处是重新渲染以前的子组件将触发父组件的渲染(ChainOfResponsibility)。反过来,这将再次渲染整个链,从一开始就确保所有不变量。
如果需要,这里是代码:
import React from 'react'
export interface ChainOfResponsibilityPiece {
next?: React.ReactNode
}
interface ChainOfResponsibilityProps {
children: React.ReactNode
}
const ChainOfResponsibility: React.FC<ChainOfResponsibilityProps> = ({ children }) => {
// Safety.
const childrenArray = React.Children.toArray(children).filter((child) => React.isValidElement(child))
// No children -> nothing to render.
if (childrenArray.length === 0) {
return null
}
// Build children with references to the next child.
const childrenWithNext = []
childrenArray.reverse().forEach((child, childIndex) => {
if (!React.isValidElement(child)) {
throw new Error(`Children must be valid React elements, got ${child}`)
}
childrenWithNext.push(
React.cloneElement(child, {
// SAFETY: We want either next child or `undefined` – out-of-bounds index returns `undefined`.
next: childrenWithNext[childIndex - 1], // Reversed index.
}),
)
})
// Render. Children are reversed, so start with the last one.
return <>{childrenWithNext[childrenWithNext.length - 1]}</>
}
export default ChainOfResponsibility