GX博客

分享个人 Full-Stack JavaScript 项目开发经验

浅谈React组件的封装

在构建用户界面时,除了需要对一些通用性的组件(如下拉菜单、分页等)进行封装外,往往也需要对某些常用的业务功能(如用户信息展示、网页分享等)进行封装,使可在不同模块中快速实现相同或类似的业务功能。在封装这些组件时,也需要遵循一些规则,不然可能导致其它模块难以重用类似的业务功能。或者该组件因不遵循典型的 React 数据流而影响原先的开发模式。本文将就 React 组件的封装分享一些经验和心得。


“在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。”

props

如果你使用过antd这样的 React UI 框架,你会注意到它绝大部分组件的控制都是通过 props 完成。通过 props 我们可以实现:

  • UI 状态的控制
  • 组件逻辑功能的配置
  • 传递展示数据
  • 传递回调函数

另外,有个别组件提供在数据流之外操作组件的方法。这些方法是通过 Refs 或者使用 React.forwardRef 转发 Refs 来调用(如通过 Form.create() 高阶组件创建的表单组件使用 wrappedComponentRef 回调方式来获取表单组件实例)。


“Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。”

Refs

如果选择了使用 Redux 这样的状态管理库,要获得单向数据流开发方式的好处,那么组件的封装也应该遵循单向数据流的规则,即仅通过 props 从父组件获得数据和回调方法。

Refs 提供了在单向数据流之外强制修改组件的方式,但不应该多度使用它。官方给出了以下几个适合使用 Refs 的情况:

  • 管理焦点,文本选择或媒体播放(这些状态控制比较适合处于数据流之外)。
  • 触发强制动画。
  • 集成第三方 DOM 库(这时需要通过 Refs 获取特定的 DOM 节点)。

“Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。”

Refs 转发

Refs 不是 props,它不会透传下去。像 key 属性一样,其被 React 进行了特殊处理。所以,如果你对 HOC 添加 ref,该 ref 将引用最外层的容器组件,而不是被包裹的组件。

React.forwardRef方法可以用于转发 ref 属性:

const C = React.forwardRef((props, ref) =>
    (<input type="text" ref={ref}/>)
);

const B = React.forwardRef((props, ref) =>
    (<C ref={ref}/>)
);

class A extends React.Component {

    setTextInputRef = (element) => {
        this.textInput = element;
    };

    focusTextInput = () => {
        this.textInput.focus();
    };

    componentDidMount() {
        this.focusTextInput();
    }

    render() {
        return (<B ref={this.setTextInputRef}/>);
    }
}

不通过 React.forwardRef 我们也可以把 ref 作为常规 props 传递(不过这样就不能使用约定的 ref 属性了):

const C = (props) => (<input type="text" ref={props.forwardedRef}/>);

const B = (props) => (<C forwardedRef={props.rootRef}/>);

class A extends React.Component {

    setTextInputRef = (element) => {
        this.textInput = element;
    };

    focusTextInput = () => {
        this.textInput.focus();
    };

    componentDidMount() {
        this.focusTextInput();
    }

    render() {
        return (<B rootRef={this.setTextInputRef}/>);
    }
}

使用 Refs 时还有以下问题需要明确:

  • ref 回调函数会在 componentDidMount 和 componentDidUpdate 生命周期函数前被调用。
  • 如果 ref 回调函数是以内联函数的方式定义的,由于每次渲染时会创建一个新的函数实例,所以在更新过程中它会被执行两次,第一次传入的是 null。

总结:

  1. 为了保持数据流的一致,props 应该是父组件与子组件交互的唯一方式,只有在少数需要在数据流外强制修改子组件的情况下才使用 Refs。
  2. 业务性的组件封装要考虑整体业务的重用和扩展性,不然相似的业务功能,别人可能也无法使用这个封装的组件,那就失去了封装的意义。

版权声明:

本文为博主原创文章,若需转载,须注明出处,添加原文链接。

https://leeguangxing.cn/blog_post_48.html