Перестаньте создавать свои компоненты таким образом ❌

Перестаньте создавать свои компоненты таким образом ❌

Изображение на обложке для Прекратите создавать компоненты пользовательского интерфейса, подобные этому❌

Действительно, все рады абстрагировать общую логику в повторно используемые компоненты. Но простые и неаккуратные абстракции тоже могут иметь неприятные последствия.Конечно, это уже другая тема.Сегодня мы собираемся обсудить, как проектировать действительно многоразовые компоненты.

Обычно мы абстрагируем компоненты, определяя некоторые параметры. И, скорее всего, вы также видели так называемые «многоразовые» компоненты с более чем 50 параметрами! Такие компоненты в конечном итоге станут сложными в использовании и обслуживании, а также приведут к проблемам с производительностью и трудно отслеживаемым ошибкам.

Добавить параметр для удовлетворения новых требований не так просто, как написать еще один кусок ifлогики , в итоге вы добавите много кода, а компонент станет очень большим и сложным в обслуживании.

Однако, если мы будем осторожны при проектировании абстрактных компонентов, мы сможем написать компоненты, которые действительно просты в использовании и сопровождении, без глупых ошибок и не будут слишком сложными, чтобы быть недоступными для пользователей.

У Kent C dodd's есть глубокий анализ этой проблемы, см.: Simply React

Как выглядит повторно используемый компонент?

Вот LoginFormModalкомпонент , который абстрагирует модальные окна для форм входа и регистрации. Сам компонент не такой уж сложный, принимает всего несколько свойств, но он очень негибкий. В нашем приложении может быть создано много модальных окон, поэтому нам нужен более гибкий компонент.

<LoginFormModal
  onSubmit={handleSubmit}
  modalTitle="Modal title"
  modalLabelText="Modal label (for screen readers)"
  submitButton={<button>Submit form</button>}
  openButton={<button>Open Modal</button>}
/>

复制代码

最后,我们将创建可以像这样被使用的组件:

<Modal>
  <ModalOpenButton>
    <button>Open Modal</button>
  </ModalOpenButton>
  <ModalContents aria-label="Modal label (for screen readers)">
    <ModalDismissButton>
      <button>Close Modal</button>
    </ModalDismissButton>
    <h3>Modal title</h3>
    <div>Some great contents of the modal</div>
  </ModalContents>
</Modal>
复制代码

但是,这并不是从代码量上看起来更复杂了。我们已将控制组件行为的能力赋予给了组件的使用者而不是创建者,这称为控制反转。它肯定比我们现有的 LoginFormModal 组件有更多的代码,但它更简单,更灵活,适合我们未来的用例,而且不会变得更加复杂。

例如,考虑这样一种情况:我们不想只渲染表单,而是想要渲染我们喜欢的任何内容。我们的 Modal 支持这一点,但 LoginFormModal 需要接受一个新的参数。或者,如果我们希望关闭按钮显示在内容的下方,该怎么办?我们需要一个名为 renderCloseBelow 的特殊参数。但是对于我们的 Modal,这显而易见可以轻松做到。你只需将 ModalCloseButton 组件移动到所需的位置即可。

更加灵活,更少的接口暴露。

这种模型称为复合组件 - 多个组件组合成所需的 UI。典型的例子是 HTML 中的 <select><option>

它广泛用于许多实际的库中,例如:

让我们创建第一个复合组件,同时创建一个可重用的 modal

创建我们的第一个复合组件

import * as React from 'react'
import VisuallyHidden from '@reach/visually-hidden'

/* Here the Dialog and CircleButton is a custom component 
Dialog is nothing button some styles applied on reach-dialog 
component provided by @reach-ui */
import {Dialog, CircleButton} from './lib'

const ModalContext = React.createContext()
//this helps in identifying the context while visualizing the component tree
ModalContext.displayName = 'ModalContext'

function Modal(props) {
  const [isOpen, setIsOpen] = React.useState(false)

  return <ModalContext.Provider value={[isOpen, setIsOpen]} {...props} />
}

function ModalDismissButton({children: child}) {
  const [, setIsOpen] = React.useContext(ModalContext)
  return React.cloneElement(child, {
    onClick: () => setIsOpen(false),
  })
}

function ModalOpenButton({children: child}) {
  const [, setIsOpen] = React.useContext(ModalContext)
  return React.cloneElement(child, {
    onClick: () => setIsOpen(true),
})
}

function ModalContentsBase(props) {
  const [isOpen, setIsOpen] = React.useContext(ModalContext)
  return (
    <Dialog isOpen={isOpen} onDismiss={() => setIsOpen(false)} {...props} />
  )
}

function ModalContents({title, children, ...props}) {
  return (
    //we are making generic reusable component thus we allowed user custom styles
   //or any prop they want to override
    <ModalContentsBase {...props}>
      <div>
        <ModalDismissButton>
          <CircleButton>
            <VisuallyHidden>Close</VisuallyHidden>
            <span aria-hidden>×</span>
          </CircleButton>
        </ModalDismissButton>
      </div>
      <h3>{title}</h3>
      {children}
    </ModalContentsBase>
  )
}

export {Modal, ModalDismissButton, ModalOpenButton, ModalContents}
复制代码

耶!我们实现了很多的逻辑了,现在可以使用上面的组件,例如:

<Modal>
     <ModalOpenButton>
         <Button>Login</Button>
     </ModalOpenButton>
     <ModalContents aria-label="Login form" title="Login">
         <LoginForm
            onSubmit={register}
            submitButton={<Button>Login</Button>}
          />
      </ModalContents>
  </Modal>
复制代码

现在代码更具可读性和灵活性了。

элегантный, великолепный код

允许用户传递自己定义的 onClickHandler

ModalOpenButtonModalCloseButton 设置其子按钮的 onClick 事件,以便我们可以打开和关闭模态框。但是,如果这些组件的用户想要在用户单击按钮时执行某些操作(除了打开/关闭模态框之外)(例如:触发分析业务),该怎么办?

我们创建一个 callAll 方法,它执行传递给它的所有方法,如下:

callAll(() => setIsOpen(false), ()=>console.log("I ran"))
复制代码

我从 Kent 的 Epic React workshop 中学到了这一点。这太聪明了,我喜欢它。

const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args))
复制代码

让我们在我们的组件中使用它:

function ModalDismissButton({children: child}) {
  const [, setIsOpen] = React.useContext(ModalContext)
  return React.cloneElement(child, {
    onClick: callAll(() => setIsOpen(false), child.props.onClick),
  })
}

function ModalOpenButton({children: child}) {
  const [, setIsOpen] = React.useContext(ModalContext)
  return React.cloneElement(child, {
    onClick: callAll(() => setIsOpen(true), child.props.onClick),
  })
}
复制代码

这让我们可以通过将 onClickHandler 传递给我们的自定义按钮来使用,如下所示:

<ModalOpenButton>
  <button onClick={() => console.log('sending data to facebook ;)')}>Open Modal</button>
</ModalOpenButton>
复制代码

Празднующий парень

总结

不要匆忙地就进行组件的抽象,也不要把一切都留给参数。也许它现在是一个简单的组件,但你不知道将来需要实现哪些用例,不要认为这是时间和可维护性之间的权衡,复杂性可能会呈指数级增长。

在 React 中发挥复合组件的优势,让你的生活更轻松。

另外,请查看 Kent 的 Epic React Course,在那里我了解到了复合组件模式(Compound Components)等等。

关于我,我叫 Harsh,我喜欢写代码。我从 16 岁开始就一直在做这件事。在使用 React 构建 Web 应用程序时,我感到宾至如归。我目前正在学习 Remix

如果你喜欢这篇博客,那关注我吧!我正计划分享更多习惯的内容。

Twitter
Linkedin

进一步了解我: Harsh choudhary

欢迎阅读其他博客 测试你的 hook 或者 如何编写自定义 hook.

Nuggets Translation Project – это сообщество, которое занимается переводом высококачественных технических статей из статей являются англоязычные статьи о Nuggets. Контент охватывает Android , iOS , интерфейс , серверную часть , блокчейн , продукты , дизайн , искусственный интеллект и др. Если вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать внимание на план перевода Nuggets , официальный Weibo и колонка Zhihu .

Ich denke du magst

Origin juejin.im/post/7085634227177259022
Empfohlen
Rangfolge