昨天我們看完 styled-components
今天來看 Component 裡面的結構
以 ProgramCoinModal 為例
import { Button } from '@chakra-ui/react'
// ~中間省略~
const StyledBody = styled.div`
padding: 2rem;
`
const StyledTitle = styled.div`
display: box;
box-orient: vertical;
line-clamp: 2;
max-height: 3rem;
overflow: hidden;
color: var(--gray-darker);
font-size: 18px;
font-weight: bold;
line-height: 1.5rem;
text-overflow: ellipsis;
`
const StyledPeriod = styled.span`
color: ${props => props.theme['@primary-color']};
font-size: 14px;
`
const StyledCurrency = styled.span`
color: var(--gray-darker);
font-size: 18px;
font-weight: bold;
`
const ProgramCoinModal: React.VFC<
ModalProps & {
renderTrigger?: React.VFC<{
setVisible: React.Dispatch<React.SetStateAction<boolean>>
}>
programId: string
periodAmount: number
periodType: PeriodType
projectPlanId: string
}
> = ({ renderTrigger, programId, periodAmount, periodType, projectPlanId, ...props }) => {
const { formatMessage } = useIntl()
const history = useHistory()
const { currentMember, currentMemberId } = useAuth()
const { program } = useProgram(programId)
const [visible, setVisible] = useState(false)
const { enrolledProgramIds } = useEnrolledProgramIds(currentMemberId || '')
const isEnrolled = enrolledProgramIds.includes(programId)
const targetProgramPlan = program?.plans.find(
programPlan => programPlan.periodAmount === periodAmount && programPlan.periodType === periodType,
)
const { orderChecking, check, placeOrder, orderPlacing } = useCheck({
productIds: targetProgramPlan ? [`ProgramPlan_${targetProgramPlan.id}`] : [],
discountId: 'Coin',
shipping: null,
options: targetProgramPlan
? { [`ProgramPlan_${targetProgramPlan.id}`]: { parentProductId: `ProjectPlan_${projectPlanId}` } }
: {},
})
const isPaymentAvailable =
!orderChecking &&
sum(check.orderProducts.map(orderProduct => orderProduct.price)) ===
sum(check.orderDiscounts.map(orderDiscount => orderDiscount.price))
const handlePay = () => {
placeOrder('perpetual', {
name: currentMember?.name || currentMember?.username || '',
phone: '',
email: currentMember?.email || '',
})
.then(taskId => {
history.push(`/tasks/order/${taskId}`)
})
.catch(handleError)
}
return (
<>
{renderTrigger && renderTrigger({ setVisible })}
<Modal
width="24rem"
footer={null}
centered
destroyOnClose
visible={visible}
bodyStyle={{ padding: 0 }}
onCancel={() => setVisible(false)}
{...props}
>
<CustomRatioImage width="100%" ratio={9 / 16} src={program?.coverUrl || EmptyCover} />
<StyledBody>
<StyledTitle className="mb-3">{program?.title}</StyledTitle>
<div className="d-flex align-items-center justify-content-between mb-4">
<StyledPeriod>
{formatMessage(productMessages.programPackage.label.availableForLimitTime, {
amount: periodAmount,
unit:
periodType === 'D'
? formatMessage(commonMessages.unit.day)
: periodType === 'W'
? formatMessage(commonMessages.unit.week)
: periodType === 'M'
? formatMessage(commonMessages.unit.monthWithQuantifier)
: periodType === 'Y'
? formatMessage(commonMessages.unit.year)
: formatMessage(commonMessages.unit.unknown),
})}
</StyledPeriod>
{targetProgramPlan?.listPrice && (
<StyledCurrency>
<PriceLabel listPrice={targetProgramPlan.listPrice} currencyId={targetProgramPlan.currency.id} />
</StyledCurrency>
)}
</div>
<Button
colorScheme="primary"
isFullWidth
isDisabled={orderChecking || !isPaymentAvailable}
isLoading={orderChecking || orderPlacing}
onClick={() => {
isEnrolled ? window.confirm(formatMessage(commonMessages.alert.isEnrolled)) && handlePay() : handlePay()
}}
>
{formatMessage(commonMessages.button.useCoin)}
</Button>
</StyledBody>
</Modal>
</>
)
}
export default ProgramCoinModal
專案中會把 styled-components 放在最上方
而我以前都會放在最下方,因人和團對而異
在 styled-components 下方就是 component 本體
由於是使用 typescript,所以每個變數都需要加上 type
component 分為 VFC 和 FC
VFC props 不會有 children 而 FC 則會有,使用上時需要注意
每個傳入的 props 也都需要加上 type
在 compoent 內,最上方通常會放 useIntl,專門處理多國語系
下面會根據需求,可能會使用 useHistory, useAuth, useApp 等系統相關會用到的變數
接著如果需要取資料庫的資料的需求,則會放 custom hooks
再下面則會放入 useState 用於元件的狀態管理
handle function 會放在宣告變數的下方
最後則是 useEffect
return 就是這個元件顯示的本體
明天我繼續看 return 的內部