reactjs - React 组件的类似中间件(责任链)的呈现

我有一个 ParentComponent 具有稍微复杂的状态和逻辑(主要是为了使 UI 更容易),它可以被描述为某种形式。

现在,在用户输入所有输入后,他需要按下一个按钮。按钮及其动作取决于当前状态:

  • 如果用户尚未登录,按钮会建议用户登录。
  • 如果他们“使用其他平台的帐户”,按钮会要求他更改帐户。
  • 如果帐户没有执行“主要”操作的权限,按钮建议请求所需的权限。
  • 等等...

自然,我可以将所有这些逻辑和验证转储到 ParentComponent 并完成它(以及可维护性和可读性)。但是我想将上述每个步骤分解成它自己的组件(可能使用本地钩子和 Redux 的一些全局状态),因为有条件地调用钩子是不受欢迎的,并且我在一个组件中需要的钩子与另一个组件的钩子冲突。

所以我面临以下问题:

  1. 我有多个子组件,每个子组件都返回
  • 按钮,如果用户需要做某事。
  • null,否则(用户满足条件,所以一切正常)。
  1. 我想以预定义的顺序一个接一个地渲染所述组件。
  2. 如果子组件呈现给某些东西,则显示它。
  3. 如果组件渲染到 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

相似文章

随机推荐

最新文章