Skip to main content

元件 Prop Types 結合 JSDoc 註記 with VSC TS powered

前情提要

  • 現階段專案導入 TypeScript 成本過高,使用 JSDoc 加上 VSC 內部的 TS server,可以滿足許多的型別提示
  • 元件有沒有型別註記,對於 refactor props name 會有影響,如果沒有註記,進行 refactor props name 會無法對使用該元件的其他檔案一併修改,只會修改元件內部本身,詳見 如何 rename React 元件的 props - across all files

元件的 propTypes & JSDoc 註記

純 JS 專案需要 JSDoc 和 Prop Types 來輔助,但是要定義一次 PropTypes,又要寫一次 JSDoc,總覺得好像有點重工了

這篇文章的目的是想以 Prop Types 作為基礎來註記型別,額外想詳細註記的部分再使用 JSDoc,並將兩者結合起來

import PropTypes, { InferProps } from 'prop-types'
import clsx from 'clsx'
import { makeStyles } from '@material-ui/core/styles'
import { Button, darken } from '@material-ui/core'

const useStyles = makeStyles(() => ({
buttonBasic: {
fontSize: '13px',
borderRadius: '5px',
padding: '5px 10px',
border: '1px solid #fff',
lineHeight: '1',
minWidth: '0px',
},
outlined: {
color: '#666',
borderColor: '#666',
backgroundColor: '#fff',
'&:hover': {
backgroundColor: darken('#fff', 0.05),
borderColor: darken('#666', 0.1),
},
},
contained: {
color: '#fff',
borderColor: '#6399d1',
backgroundColor: '#6399d1',
'&:hover': {
backgroundColor: darken('#6399d1', 0.2),
borderColor: darken('#6399d1', 0.2),
},
},
}))

/**
* @typedef {Object} ExtendProp
* @property {(event: Event) => void} onClick
* @property {'contained'|'outlined'} variant
*/

/**
* @typedef {InferProps<typeof ButtonModalPropTypes> & ExtendProps} ButtonModalProps
*/

// 主要用於 PopupModal 的按鈕 : 確認、取消 等
/** @param {ButtonModalProps} props */
export default function ButtonModal({
onClick,
variant,
text,
disabled,
testId,
className,
}) {
const classes = useStyles()

// 預設傳入 event, 保持共用元件自由度
const handleClick = (event) => {
if (onClick && !disabled) {
onClick(event)
}
}

return (
<Button
className={clsx(classes.buttonBasic, {
[classes[variant]]: variant,
[classes.disabled]: disabled,
[className]: className,
})}
variant={variant}
onClick={handleClick}
disabled={disabled}
data-testid={testId}
>
{text}
</Button>
)
}

ButtonModal.defaultProps = {
variant: 'contained',
text: '',
testId: null,
onClick: () => {},
disabled: false,
className: '',
}

export const ButtonModalPropTypes = {
variant: PropTypes.oneOf(['contained', 'outlined']),
text: PropTypes.string,
testId: PropTypes.string,
onClick: PropTypes.func,
disabled: PropTypes.bool,
className: PropTypes.string,
}

ButtonModal.propTypes = ButtonModalPropTypes

Export Prop Types 定義

export const ButtonModalPropTypes = {
variant: PropTypes.oneOf(['contained', 'outlined']),
text: PropTypes.string,
testId: PropTypes.string,
onClick: PropTypes.func,
disabled: PropTypes.bool,
className: PropTypes.string,
}

這一步主要是 export 出去 prop types 定義,有引用 Button 的大元件可以直接複用,也方便 JSDoc 取用 TS 型別

JSDoc 組合技

/**
* @typedef {Object} ExtendProp
* @property {(event: Event) => void} onClick
* @property {'contained'|'outlined'} variant
*/

/**
* @typedef {InferProps<typeof ButtonModalPropTypes> & ExtendProp} ButtonModalProp
*/

重點說明

  • InferProps<typeof ButtonModalPropTypes> ,是使用 typeofButtonModalPropTypes 自動推論的型別變成 type,並放進 InferProps 裡面
    • 就可以作為 props 的註記,這樣就不用寫 prop-types 又要完整寫一次元件本身的 JSDoc
  • 對於 prop-type 無法細部註記的 callback function 或其他 enum,放在 ExtendProps 註記
  • {InferProps<typeof ButtonModalPropTypes> & ExtendProp} 把兩者合成在一起,形成新的型別
  • 後續其他引用的元件也可以引用 ButtonModalProp 作為 JSDoc 註記
/**
* @param {Object} props
* @param {import('components/units/button/ButtonModal').ButtonModalProp['onClick']} props.onConfirm
* @param {import('components/units/button/ButtonModal').ButtonModalProp['onClick']} props.onCancel
*/

function Footer({ onConfirm, onCancel }) {
const classes = useStyles()
return (
<div className={classes.footerButtonWrapper}>
<ButtonModal
variant="contained"
text="確定"
onClick={onConfirm}
testId={globalTestIds.BUTTON_CONFIRM}
/>
<ButtonModal
variant="outlined"
text="取消"
onClick={onCancel}
testId={globalTestIds.BUTTON_CANCEL}
/>
</div>
)
}

其他有引用按鈕的元件,就可以引用 ButtonModalProp 型別內部的 property 定義

Reference

javascript - React and JSDoc - How to document a React component properly? - Stack Overflow

javascript - How to extend a typedef parameter in JSDOC? - Stack Overflow

Support typedef inheritance with JSDoc · Issue #20077 · microsoft/TypeScript · GitHub

如何 rename React 元件的 props - across all files