添加道具类型以渲染道具功能

时间:2021-05-31 20:35:16

标签: javascript reactjs typescript visual-studio-code react-proptypes

当使用常规的 React 组件时,有多种方法可以为组件启用智能感知(通过功能组件的 prop 类型,或 jsdoc 注释),但是如果组件使用这样的渲染 prop 模式:

return this.props.children({ config, onClickConfig })

并像这样使用:

<ConsumerComponent>
  {
    ({config, onClickConfig}) => (<button type="button" onClick={onClickConfig}>{config}</button>)
  }
</ConsumerComponent>

有什么方法可以为 config 对象的类型或 onClickConfig 函数启用智能感知吗?

我看到打字稿可以通过重用消费者中的类型来实现这一点,但是是否可以使用 jsdocs 或 prop 类型来实现?

2 个答案:

答案 0 :(得分:1)

我的猜测是很好地使用和记录。

const propTypes = {
  children: PropTypes.func.isRequired,
  // ... other proptypes
}

/**
 * @param {object} props the props object
 * @param {({ config: React.ElementType, onClickConfig: function }) => React.ElementType} props.children the render prop
 */
const ConsumerComponent = ({ children, ...props }) => ( ..... );
ConsumerComponent.propTypes = propTypes;

答案 1 :(得分:0)

问题

不幸的是,PropTypes 不允许在运行时对函数的(render prop)参数进行类型检查。如果您使用的是 Typescript,那么无效的参数类型应该会在构建时阻止编译(前提是 noEmitOnError 设置为 true)。但是,由于您尝试在运行时输入,因此您必须使用自己的自定义验证器函数来完成。

解决方案

Edit Type Checking Render Prop

点击 Open Modal 按钮会引发错误。要更正错误,请将 Line 72 更改为 boolean

虽然这个例子非常随意,因为渲染道具没有暴露,如果你将 isOpen 状态提升到 App 组件,那么它仍然需要检查 {{ isOpen 组件中的 1}} 属性类型是一个布尔值(使用 Modal 或您的自定义验证器)。

代码

App.js

PropTypes

Modal.tsx

import * as React from "react";
import Modal from "./Modal";
import "uikit/dist/css/uikit.min.css";
import "./styles.css";

const App = (): React.ReactElement => (
  <div className="app">
    <Modal>
      {({ isOpen, toggleModal }): React.ReactElement => (
        <>
          <h1 className="title">Hello!</h1>
          <p className="subtitle">
            The modal is currently {isOpen ? "visible" : "hidden"}!
          </p>
          <p className="subtitle">There are two ways to close this modal:</p>
          <ul>
            <li>Click outside of this modal in the grey overlay area.</li>
            <li>Click the close button below.</li>
          </ul>
          <button
            className="uk-button uk-button-danger uk-button-small"
            onClick={toggleModal}
          >
            Close
          </button>
        </>
      )}
    </Modal>
  </div>
);

export default App;

withTypeCheck.ts(你可以用 import * as React from "react"; import * as ReactDOM from "react-dom"; import PropTypes from "prop-types"; import withTypeCheck from "./withTypeCheck"; export type ChildrenProps = { isOpen: boolean; toggleModal: () => void; }; export type ChildFunc = ({ isOpen, toggleModal }: ChildrenProps) => React.ReactElement; export type ModalProps = { children: ChildFunc; }; /** * Reuseable Modal component * * @param children - a render prop that accepts an object of `isOpen` and `toggleModal` props * @returns a ReactElement * @example <Modal>({ isOpen, toggleModal }) => (...)}</Modal> */ const Modal = ({ children }: ModalProps): React.ReactElement => { const [isOpen, setVisible] = React.useState(false); const wrapperRef = React.useRef<HTMLDivElement>(null); const toggleModal = React.useCallback(() => { setVisible((prevState) => !prevState); }, []); const closeModal = React.useCallback( (e: Event): void => { if ( isOpen && wrapperRef.current && !wrapperRef.current.contains(e.target as Node) ) { toggleModal(); } }, [isOpen, toggleModal] ); React.useEffect(() => { document.addEventListener("click", closeModal, { capture: true }); return () => { document.removeEventListener("click", closeModal, { capture: true }); }; }, [closeModal]); return ( <> <button className="uk-button uk-button-primary uk-button-small" type="button" onClick={toggleModal} > Open Modal </button> {isOpen && ReactDOM.createPortal( <> <div className="overlay" /> <div className="window-container"> <div className="modal-container"> <div ref={wrapperRef} className="modal"> {withTypeCheck(children, { isOpen: "true", toggleModal })} </div> </div> </div> </>, document.body )} </> ); }; Modal.propTypes = { children: PropTypes.func.isRequired }; export default Modal; 代替抛出错误,但抛出它会更明显)

console.error

index.tsx

import type { ChildrenProps, ChildFunc } from "./Modal";

/**
 * Type checks a render prop function and its arguments.
 *
 * @param children - a render prop function
 * @args - an object of arugments containing `isOpen` and `toggleModal` props
 * @returns an error or ReactElement
 * @example withTypeCheck(fn, args);
 */
const withTypeCheck = (
  children: Function,
  args: ChildrenProps
): Error | ChildFunc => {
  try {
    const childrenType = typeof children;
    if (childrenType !== "function")
      throw String(
        `An invalid children prop was used inside the Modal component. Expected a function, but received ${childrenType}.`
      );

    const argTypes = typeof args;
    if (argTypes !== "object")
      throw String(
        `Invalid arguments were supplied to the children prop. Expected an object, but received ${argTypes}.`
      );

    const isOpenType = typeof args.isOpen;
    if (isOpenType !== "boolean")
      throw String(
        `An invalid 'isOpen' argument was supplied to the Modal's children prop. Expected a boolean, but received a ${isOpenType}.`
      );

    const toggleModalType = typeof args.toggleModal;
    if (toggleModalType !== "function")
      throw String(
        `An invalid 'toggleModalType' was argument supplied to the Modal's children prop. Expected a function, but received a ${toggleModalType}.`
      );

    return children(args);
  } catch (error) {
    throw error;
  }
};

export default withTypeCheck;