ð嘿,伙计们
本文将为您提供“复合组件”模式的解释。
ðÖ为什么我要知道“复合组件”?
“复合组件”是一种模式,有助于创建可重复使用的组件相互取决于彼此。这样的模式有助于实现更好的隔离。这种方法使开发人员可以在保持代码清洁和有条理的同时轻松创建动态和交互式用户界面。这只是在Web开发中使用React和复合组件的许多好处之一。
ð - - 您能告诉我图像解释吗?
Rating
-组件应包含巨大恒星的逻辑,并易于扩展和重复使用。
RatingItem
-组件应从Rating
组件中消耗逻辑,并且不包含任何样式,以跨不同的项目或设计重复使用。因为将来可以更改UI设计,而您的星星将成为微笑或其他东西。
ð - 好的,在实施之前,让我更深入地解释Rating
组件!
我们应该在Rating
组件中存储什么?
- 评分状态在我们的情况下,范围为1至5。
- 悬停布尔值知道我们的用户是否徘徊在我们的评级组件上?
- 悬停状态从1到5创造出色的UI体验。
RatingItem
组件内将有什么?
- 没有样式,因为设计在项目中可能会有所不同,每次我们都有新设计时,更改
RatingItem
会很痛苦。 - 根据您的需求,不同的处理程序,例如
handleClick
或handleMouseLeave
。
ð�ð»让我们编码!
folder structure
contexts/RatingContext.ts
providers/RatingProvider.ts
components/Rating.tsx
components/RatingItem.tsx
components/Feedback.tsx
1)为Rating
组件创建Context
和Provider
。
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>
)
}
- 我们添加了代码来处理鼠标事件
handleClick
,handleMouseLeave
和handleMouseEnter
。 - 我们添加了常数
isCurrentRating
,isHoveredRating
,isRatingNotSet
和isFilled
,它们将用于实现所需的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。