分享个人 Full-Stack JavaScript 项目开发经验
在构建用户界面时,除了需要对一些通用性的组件(如下拉菜单、分页等)进行封装外,往往也需要对某些常用的业务功能(如用户信息展示、网页分享等)进行封装,使可在不同模块中快速实现相同或类似的业务功能。在封装这些组件时,也需要遵循一些规则,不然可能导致其它模块难以重用类似的业务功能。或者该组件因不遵循典型的 React 数据流而影响原先的开发模式。本文将就 React 组件的封装分享一些经验和心得。
“在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。”
如果你使用过antd这样的 React UI 框架,你会注意到它绝大部分组件的控制都是通过 props 完成。通过 props 我们可以实现:
另外,有个别组件提供在数据流之外操作组件的方法。这些方法是通过 Refs 或者使用 React.forwardRef 转发 Refs 来调用(如通过 Form.create() 高阶组件创建的表单组件使用 wrappedComponentRef 回调方式来获取表单组件实例)。
“Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。”
如果选择了使用 Redux 这样的状态管理库,要获得单向数据流开发方式的好处,那么组件的封装也应该遵循单向数据流的规则,即仅通过 props 从父组件获得数据和回调方法。
Refs 提供了在单向数据流之外强制修改组件的方式,但不应该多度使用它。官方给出了以下几个适合使用 Refs 的情况:
“Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。”
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 时还有以下问题需要明确:
总结: