【问题标题】:add prop types to render props function添加道具类型以渲染道具功能
【发布时间】:2021-08-19 03:02:23
【问题描述】:

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

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

并像这样使用:

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

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

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

【问题讨论】:

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


    【解决方案1】:

    问题

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

    解决方案

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

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

    代码

    App.js

    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;
    

    Modal.tsx

    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;
    

    withTypeCheck.ts(你可以用console.error替换抛出错误,但抛出会更明显)

    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;
    

    index.tsx

    import * as React from "react";
    import * as ReactDOM from "react-dom";
    import App from "./App";
    
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      document.getElementById("root")
    );
    

    【讨论】:

    • 这很有帮助,但我试图在编译时拥有此功能。我知道道具类型仅用于在运行时检查类型,但 vscode 仍然在编译时为它们生成类型并使用它来提供智能感知。我在运行时了解自定义验证器函数,但正在寻找某种方式在编译时声明它,以便智能感知可以正常工作
    【解决方案2】:

    我的猜测是同时使用两者并做好记录。

    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;
    

    【讨论】:

    • 这很有帮助。我能够从中获得灵感,并将函数的参数公开给消费者
    猜你喜欢
    • 2021-07-04
    • 2020-11-16
    • 1970-01-01
    • 2016-05-06
    • 1970-01-01
    • 2018-02-17
    • 2020-03-10
    • 2021-12-22
    • 1970-01-01
    相关资源
    最近更新 更多