复合组件模式用于创建可重复使用的额定值组件
#javascript #初学者 #react #patterns

ð嘿,伙计们

本文将为您提供“复合组件”模式的解释。

ðÖ为什么我要知道“复合组件”?

“复合组件”是一种模式,有助于创建可重复使用的组件相互取决于彼此。这样的模式有助于实现更好的隔离。这种方法使开发人员可以在保持代码清洁和有条理的同时轻松创建动态和交互式用户界面。这只是在Web开发中使用React和复合组件的许多好处之一。

ð - - 您能告诉我图像解释吗?

Rating-组件应包含巨大恒星的逻辑,并易于扩展和重复使用。

RatingItem-组件应从Rating组件中消耗逻辑,并且不包含任何样式,以跨不同的项目或设计重复使用。因为将来可以更改UI设计,而您的星星将成为微笑或其他东西。

Rating component top level explanation

ð - 好的,在实施之前,让我更深入地解释Rating组件!

我们应该在Rating组件中存储什么?

  • 评分状态在我们的情况下,范围为1至5。
  • 悬停布尔值知道我们的用户是否徘徊在我们的评级组件上?
  • 悬停状态从1到5创造出色的UI体验。

RatingItem组件内将有什么?

  • 没有样式,因为设计在项目中可能会有所不同,每次我们都有新设计时,更改RatingItem会很痛苦。
  • 根据您的需求,不同的处理程序,例如handleClickhandleMouseLeave

Compound Components implementation structure

ð�ð»让我们编码!

folder structure

contexts/RatingContext.ts
providers/RatingProvider.ts
components/Rating.tsx
components/RatingItem.tsx
components/Feedback.tsx

1)为Rating组件创建ContextProvider

contexts/RatingContext.ts

export const RatingContext = createContext({
  rating: 0,
  setRating: (_rating: number) => {},
  hoverRating: 0,
  setHoverRating: (_rating: number) => {},
  isHovering: false,
  setIsHovering: (_isHovering: boolean) => {},
});

providers/RatingProvider.ts

export const RatingProvider = RatingContext.Provider

我们正在创建Context,因为我们需要将信息从Rating传递给RatingItem,无论它是什么嵌套。

2)创建具有所需基本状态的Rating组件。

components/Rating/Rating.tsx

type Props = {
  children: ReactNode
}

export const Rating: FC<Props> = ({ children }) => {
  const [rating, setRating] = useState(0) // used to store the current rating
  const [hoverRating, setHoverRating] = useState(0) // store information about the current hovered state
  const [isHovering, setIsHovering] = useState(false) // information about is the rating hovered right now or not

  const contextValue = useMemo(
    () => ({
      rating,
      hoverRating,
      isHovering,
      setRating,
      setHoverRating,
      setIsHovering,
    }),
    [rating, hoverRating, isHovering],
  )

  return <RatingProvider value={contextValue}>{children}</RatingProvider>
}

3)让我们创建一个钩子以消耗RatingContext

import { useContext } from "react"

import { RatingContext } from "../../contexts/RatingContext"

export const useRatingContext = () => {
  const context = useContext(RatingContext)

  if (!context) {
    throw new Error("useRatingContext must be used within a RatingContext")
  }

  return context
}

ð注意:在这里,我们有一个“复合组件”逻辑。这个想法是,无论我们试图在Rating外部使用此钩子的组件使用该组件。

4)创建<RatingItem />组件。

components/RatingItem.tsx

type Props = {
  value: number
  children: JSX.Element
}

export const RatingItem = ({ value, children }: Props) => {
  const { setRating, rating, hoverRating, setHoverRating, setIsHovering } = useRatingContext()

  return (
    <span className="cursor-pointer">
      {cloneElement(children, { needed_props_here })}
    </span>
  )
}
  • prop value用于传递有关评级值的信息将代表您的RatingItem,例如1、2或0.5,具体取决于您的需求。
  • useRatingContext可访问提供商值。
  • {cloneElement(children, { neededPropsHere })}将为您的图标提供以后控制此图标UI状态所需的所有道具。我们在这里使用cloneElement API

5)通过添加其他处理程序来改善<RatingItem />组件。

export const RatingItem = ({ value, children }: Props) => {
  const { setRating, rating, hoverRating, setHoverRating, setIsHovering } = useRatingContext()

  // new code
  const handleMouseEnter = () => {
    setHoverRating(value)
    setIsHovering(true)
  }

  const handleMouseLeave = () => {
    setHoverRating(0)
    setIsHovering(false)
  }

  const handleClick = () => {
    if (rating === value) {
      setRating(0)

      return;
    }

    setRating(value)
  }

  const isCurrentRating = rating === value
  const isHoveredRating = hoverRating === value
  const isRatingNotSet = rating === 0
  const isFilled = isRatingNotSet || isCurrentRating || isHoveredRating

  return (
    <span
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      onClick={handleClick}
      className="cursor-pointer"
    >
// end of new code
      {cloneElement(children, { isFilled, isChoose: isCurrentRating })}
    </span>
  )
}
  • 我们添加了代码来处理鼠标事件handleClickhandleMouseLeavehandleMouseEnter
  • 我们添加了常数isCurrentRatingisHoveredRatingisRatingNotSetisFilled,它们将用于实现所需的UI模式。 此部分可能会根据您的设计而有所不同!

6)我们创建了几乎所有所需的逻辑来显示我们的评分。

components/Feedback/feedback.tsx

export const Feedback = () => {
    return (
        <Rating>
            <div className={styles.rating}>
              <RatingItem value={1}>
                <IconVeryBad />
              </RatingItem>
              <RatingItem value={2}>
                <IconBad />
              </RatingItem>
              <RatingItem value={3}>
                <IconNeutral />
              </RatingItem>
              <RatingItem value={4}>
                <IconGood />
              </RatingItem>
              <RatingItem value={5}>
                <IconVeryGood />
              </RatingItem>
            </div>
        </Rating>
    )
}

7)但是,如果我们需要在Feedback组件中知道现在的评分是什么怎么办?

components/Rating.tsx

type Props = {
  children: ReactNode
  onRatingChange?: (rating: number) => void // new prop onRatingChange
}

export const Rating: FC<Props> = ({ children, onRatingChange }) => {
  const [rating, setRating] = useState(0)
  const [hoverRating, setHoverRating] = useState(0)
  const [isHovering, setIsHovering] = useState(false)

  const contextValue = useMemo(
    () => ({
      rating,
      hoverRating,
      isHovering,
      setRating,
      setHoverRating,
      setIsHovering,
    }),
    [rating, hoverRating, isHovering],
  )

  useEffect(() => { 
    onRatingChange?.(rating) // a prop function be called each time rating is changing
  }, [rating])

  return <RatingProvider value={contextValue}>{children}</RatingProvider>
}

  • onRatingChange-用户更改评分时将调用的函数。

8)使用Rating组件

component/RatingFeedback.tsx

export const RatingFeedback = () => {
  const [isShowText, setShowText] = useState(false)

  const handleRatingChange = (rating: number) => {
    if (rating > 0) {
      setShowText(true)
      setRating(rating)
    } else {
      setShowText(false)
      setRating(0)
    }
  }

  return (
    <>
      <Rating onRatingChange={handleRatingChange}>
        <div className="flex justify-between w-full pointer-events-auto background-color-300 mt-4 max-w-[300px]">
          <RatingItem value={1}>
            <IconVeryBad />
          </RatingItem>
          <RatingItem value={2}>
            <IconBad />
          </RatingItem>
          <RatingItem value={3}>
            <IconNeutral />
          </RatingItem>
          <RatingItem value={4}>
            <IconGood />
          </RatingItem>
          <RatingItem value={5}>
            <IconVeryGood />
          </RatingItem>
        </div>
      </Rating>
      {isShowText && (
        <label>
            Please write to us
            <textarea/>
        </label>
      )}
    </>
  )
}
  • handleRatingChange负责显示用户选择评级时
  • 的文本字段

ð摘要

在本文中,我们证明了使用“复合组件”用于使用React创建评级系统的使用。复合组件允许您通过组合简单和可重复使用的组件来创建复杂的UI组件。这种方法改善了代码组织,使其更容易维护和扩展。我们使用的示例显示了如何创建既有用户友好又可定制的评级系统。

要测试代码并参见行动中的示例,您可以按照以下链接:https://codesandbox.io/p/sandbox/compound-components-for-rating-y6getu