React页面:我不想帮组件管理它的状态

677 阅读2分钟

让组件管理自己的状态?这句话听起来似乎是一件理所当然的事情,但是让我们看以下例子:

// MyPage.tsx
const MyPage: React.FC = () => {
    const [modalVisible, setModalVisible] = setState<boolean>(false)
    
    const toggleModal = () => {
        setModalVisible(!modalVisible)
    }
    
    return (
        <div>
            <Button onClick={toggleModal}>点击</Button>
            <MyModal visible={modalVisible} />
        </div>
    )
}


// MyModal.tsx
consy MyModal: React.React.FC = ({visible}) => {
    
    return <Modal visible={visible}>
        <!-- JSX.Element -->
    </Modal>
}
export default MyModal

为了方便理解,我们称 MyPage 为页面组件,ButtonMyModal 等为基础/业务组件,一个页面由多个基础/业务组件构成。

上面代码是一个很简单的例子:MyPage 包含 ButtonMyModal 两个组件,并且拥有 modalVisible 这个状态来控制 MyModal 的可见状态。

上面的代码在功能上完全没有问题,但是本文的观点是既然 visibleMyModal 组件自身的状态,那就应该让 MyModal 自身去维护,而不是在 MyPage 里面设置一个状态去进行管理。

如果你同意上述观点,就可以继续往下面看了,不然的话可以在评论区留下你的观点,或者点个赞再离开😁。

useImperativeHandle & forwardRef

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用。

国际惯例,先上代码:

// MyPage.tsx
const MyPage: React.FC = () => {

    const modalRef = useRef(null);

    const toggleModal = () => {
        modalRef.current.toggle()
    }
    
    return (
        <div>
            <Button onClick={toggleModal}>点击</Button>
            <MyModal ref={modalRef} />
        </div>
    )
}

// MyModal.tsx
consy MyModal: React.ForwardRefRenderFunction = (props, ref) => {

    const [visible, setVisible] = useState<boolean>(false)
    
    useImperativeHandle(ref, ({
        toggle: () => {
            setVisible(!visible)
        }
    }))
    
    return <Modal visible={visible}>
        <!-- JSX.Element -->
    </Modal>
}

export default React.forwardRef(MyModal)
  • forwardRef

有两个作用,分别是将 ref 从父组件转发到子组件的 DOM 元素上以及让子组件接受 propsref 作为参数。

  • useImperativeHandle

useImperativeHandle 限制了暴露给父组件调用的方法,比如上述例子,MyPage 只能调用 MyModal 的 toggle 方法。这使得父组件可以不用关注子组件的内部实现,而只需要关注简单的几个方法的作用;同时,子组件状态在内部闭环,符合组件开发的开闭原则。

总结

useEffectuseCallback 等 API 相比,useImperativeHandle(通常和 forwardRef 一起使用!!!) 这个 Hook API 可能收到的关注比较少,但是它在一些公用组件开发中却起到非常大的作用:组件管理自身状态以及公开必要的方法给到外部调用。

本文只是从一个简单的例子进行介绍,还有很多不全面的地方,欢迎大家指正。更详细的 API 介绍大家可以查看官方文档,里面有一个 input 相关的更简洁的例子,可以帮助大家快速理解 API 的使用和作用。