【问题标题】:React ref anti-pattern for Popups弹出窗口的 React ref 反模式
【发布时间】:2020-02-12 13:44:32
【问题描述】:

最近我发现自己实现了一个基于 ref 的模式,这似乎与 react documentation advice 背道而驰。

模式是这样的:

type Callback = () => void;
type CallbackWrapper = {callback : Callback}

interface IWarningPopupRef{
    warn : (callback : Callback) => void;
}

interface IWarningPopupProps{
    warningText : string;
}

const WarningPopup = forwardRef<IWarningPopupRef, IWarningPopupProps>(
    const [show, setShow] = useState(false);
    const [callback, setCallback] = useState<CallbackWrapper | null>(null);
    const warn = (callback : Callback) => {
        setShow(true);
        setCallback({callback});
    }
    const acceptWarning = () => {
        setShow(false);
        setCallback(null);
        if(callback != null) callback.callback();
    }
    useImperativeHandle(ref, () => ({
        warn
    }));
    (props, ref) => {
        return (
            <div style={{
                visibility:(show)?"visible":"hidden"
            }}>
                {props.warningText}
                <button onClick={acceptWarning}>Accept</button>
            </div>
        )
    }
)

const Component : React.FC = props => {
    const warningPopupRef = useRef<IWarningPopupRef>(null);
    const doDangerButton = () => {
        warningPopupRef.current!.warn(() => {
            doDangerAction();
        });
    }
    return (
        <button onClick={doDangerButton}>Dangerous button</button>
        <WarningPopup ref={warningPopupRef} 
            warningText="Warning ! This is a dangerous button !"/>
    )
}

如果我要遵循 react 文档的建议并将状态提升到父组件,我会有这个:

interface IWarningPopupProps{
    warningText : string;
    show : boolean;
    onWarningAccept : () => void;
}

const WarningPopup : React.FC<IWarningPopupProps> = props => {
    return (
        <div style={{
            visibility:(props.show)?"visible":"hidden"
        }}>
            {props.warningText}
            <button onClick={props.onWarningAccept}>Accept</button>
        </div>
    )
} 

const Component : React.FC = props => {
    const [warningPopupShow, setWarningPopupShow] = useState(false);
    const doDangerButton = () => {
        setWarningPopupShow(true);
    }
    const acceptWarning = () => {
        setWarningPopupShow(false);
        doDangerAction();
    }
    return (
        <button onClick={doDangerButton}>Dangerous button</button>
        <WarningPopup warningText="Warning ! This is a dangerous button !"
            show={warningPopupShow}
            onWarningAccept={acceptWarning}/>
    )
}

现在我不执行上述操作,因为我担心抽象泄漏以及我的父组件必须同时处理它被创建来操作的状态和这个弹出状态。

我的理由是弹出窗口是导航流程的中断,因此应该在它自己的上下文中处理。

我是否用这种(反)模式为未来的自己设下陷阱?

【问题讨论】:

    标签: reactjs typescript design-patterns anti-patterns


    【解决方案1】:

    我赞成第二种更“React-y”的解决方案,因为:

    • 你的父母持有显示的状态是有道理的。在以后的使用中,您可能真的很高兴在弹出控制流上有更多的灵活性。
    • 您的原始模式在阅读和维护方面要复杂得多,因为您公开了一个不太明显且通常需要额外文档的命令式 API。相反,props 是简单的标准 React,更具可预测性和可测试性。
    • 引用强制父/用户绕过 React 的生命周期,强制他们检查 ref 是否在预期时间包含元素。

    最后,您可以以方便使用的方式调整弹出窗口:

    const Component : React.FC = props => {
        const [warningPopupShow, setWarningPopupShow] = useState(false);
        return (
            <button onClick={doDangerButton}>Dangerous button</button>
            <WarningPopup warningText="Warning ! This is a dangerous button !"
                show={warningPopupShow}
                onShow={setWarningPopupShow} // Simply separate "shown" updates from
                                             // acceptation action in the popup
                onWarningAccept={doDangerAction}/>
        )
    }
    

    简单、实用、惯用。

    【讨论】:

      猜你喜欢
      • 2015-02-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-10
      • 1970-01-01
      • 2020-06-16
      • 2012-07-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多