- 元のアドレス:このようなUIコンポーネントの構築を停止する❌
- 原作者:Harsh Choudhary
- 翻訳元:ナゲッツ翻訳プロジェクト
- この記事へのパーマリンク:github.com/xitu/gold-m…
- 翻訳者:ザビエル
- 校正者:PingHGao、tinnkm
このようなコンポーネントの構築をやめる❌
実際、誰もが共通論理を再利用可能なコンポーネントに抽象化して喜んでいます。しかし、単純でずさんな抽象化も裏目に出る可能性があります。もちろん、これは別のトピックです。今日は、真に再利用可能なコンポーネントを設計する方法について説明します。
通常、いくつかのパラメーターを定義することによってコンポーネントを抽象化します。また、50を超えるパラメータを持ついわゆる「再利用可能な」コンポーネントも見たことがあるでしょう。このようなコンポーネントは、パフォーマンスの問題や追跡が難しいバグをもたらす一方で、使用と保守が困難になることになります。
新しい要件を満たすためにパラメーターを追加することは、if
ロジック。多くのコードを追加することになり、コンポーネントが非常に大きくなり、保守が困難になります。
ただし、抽象コンポーネントの設計に注意すれば、非常に使いやすく、保守しやすく、愚かなバグがなく、複雑すぎてユーザーが禁止できないコンポーネントを作成できます。
Kent C dodd'sには、この問題の詳細な分析があります。「SimplyReact 」を参照してください。
再利用可能なコンポーネントはどのように見えますか?
これは、ログインフォームと登録フォームのモーダルを抽象化する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
ModalOpenButton
和 ModalCloseButton
设置其子按钮的 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。
如果你喜欢这篇博客,那关注我吧!我正计划分享更多习惯的内容。
进一步了解我: Harsh choudhary
欢迎阅读其他博客 测试你的 hook 或者 如何编写自定义 hook.
ナゲッツ翻訳プロジェクトは、高品質のインターネット技術記事を翻訳するコミュニティです。記事のソースは、ナゲッツに関する英語の共有記事です。コンテンツは、 Android、iOS、フロントエンド、バックエンド、ブロックチェーン、製品、デザイン、人工知能、その他の分野をカバーしています。より高品質の翻訳を見たい場合は、引き続きナゲッツ翻訳計画、公式に注意を払ってください。 Weibo、およびZhihu列。