iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0

昨天我們看完 styled-components
今天來看 Component 裡面的結構

component structure

以 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 的內部


上一篇
Component (1)
下一篇
Component (3)
系列文
從 Open Source 專案學習 React 開發 - 以 lodestar-app 為例30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Dylan
iT邦新手 1 級 ‧ 2022-09-30 22:59:49

VFC 在 react 18 好像被移掉了

我要留言

立即登入留言