元件 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>
,是使用typeof
把ButtonModalPropTypes
自動推論的型別變成 type,並放進InferProps
裡面- 就可以作為 props 的註記,這樣就不用寫
prop-types
又要完整寫一次元件本身的 JSDoc
- 就可以作為 props 的註記,這樣就不用寫
- 對於
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