React学习之进阶终临高阶组件(二十一)
2017-03-22 17:04
435 查看
HOC高级组件在
React是一种比较先进的重用组件的方法,高级组件不是
React的
API,它是一种从
React的组件方式中合并而成的一种模式,所以可以说
HOC不是组件,而是一个处理模式
准确地来说,
HOC组件是一个处理组件并且返回新组件的函数,但是这个函数又是一个纯函数,遵循函数式编程规范。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
就是说,
HOC就是将一个组件变为另一个组件。
HOC在第三方
React库中是非常普遍的,比如
Redux的
connect和Relay中的
createContainer,这两个函数后面会提到一点他们的大概实现方式和使用形式。
接下来,将讨论的是
HOC的用处和怎么去实现它
1.构造联系
之前我提到的mixins就是一种构造父子关系方式,但是
mixins会存在一定程度坏处。所以我们有必要用另外一种方式来处理这种关系。
组件是
React复用代码的基本单位,然而,你会发现一些实现的方式并不适合传统的组件。
如下:
class CommentList extends React.Component { constructor() { super(); this.handleChange = this.handleChange.bind(this); this.state = { // "DataSource"是一个全局的数据处理类,用来处理数据的,可以不要在意其中的细节 comments: DataSource.getComments() }; } componentDidMount() { //监听数据变化 DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { // 清除监听事件 DataSource.removeChangeListener(this.handleChange); } handleChange() { // 当数据发生变化时触发事件 this.setState({ comments: DataSource.getComments() }); } render() { return ( <div> {this.state.comments.map((comment) => ( <Comment comment={comment} key={comment.id} /> ))} </div> ); } }
这其中的DataSource用到了观察者模式,在组件中进行事件的订阅。我们再看下面这份代码
class BlogPost extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { blogPost: DataSource.getBlogPost(props.id) }; } componentDidMount() { DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ blogPost: DataSource.getBlogPost(this.props.id) }); } render() { return <TextBlock text={this.state.blogPost} />; } }
两份代码相似但是有不同,他们最终渲染出的结果是不同的,但是有三点是相同的
在挂载时,给
DataSource监听了一个事件
在事件内部,如果
DataSource内部的数据发生变化的时候,会调用
setState来进行更新
在卸载时,会移除掉监听在
DataSource上的事件
可以想象的是,当我们构建大型的应用程序的时候,通过订阅
DataSource然后通过
setState去更新状态,这些步骤我们都可以抽象出来,形成一个函数,然后在多个组件中分享使用,这就是高级组件的用法。
我们可以写一个创建组件的函数,像CommentList和blogPost那样去订阅DataSource。这个函数接受一个组件作为参数来监听事件。
如下:
const CommentListWithSubscription = withSubscription( CommentList, (DataSource) => DataSource.getComments() ); const BlogPostWithSubscription = withSubscription( BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id) });
其中第一个参数是一个组件,第二参数则是我们在
DataSource数据进行改变时需要执行的操作。
// This function takes a component... function withSubscription(WrappedComponent, selectData) { //返回一个新构建的组件 return class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { //进行事件绑定 DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { return <WrappedComponent data={this.state.data} {...this.props} />; } }; }
这个
HOC不会对输入的组件做任何修改,也不做任何继承赋值的行为,它就相当于是在原来的组件的基础上进行扩展,这就是用函数式编程的好处,一般都说是纯函数零副作用。
HOC并不知道你干了什么,它只是调用你想要调用的回调函数,然后进行数据处理,没有直接跟数据进行交互处理,它根本不担心你干了什么,你的数据从哪里来。
从
widthSubscription大体上看,这是一个普通的不能再普通的函数,我们可以自行的为这函数进行扩充,比如:为内部存取的数据,换一个不名字,不用
data而用自己特定的,又或是,可以增加
shouldComponentUpdate来实现对一些组件不执行更新等等,都是可以的。
2.不要试图修改原始的组件,使用组合方法
在HOC中拒绝去修改原始数据
function logProps(InputComponent) { InputComponent.prototype.componentWillReceiveProps(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); } // 对原组件进行的扩展,修改了原组件 return InputComponent; } const EnhancedComponent = logProps(InputComponent);
上面的代码就存在几个非常明显的问题了,一个就是原组件在函数中进行改变然后返回,更关键的就是,如果我们的组件中已经写好了一个
componentWillReceiveProps函数处理,但是在
logProps会被覆盖掉,很明显这不是我们想要的。
所以要避免这种事情,我们就可以采用上面已经提到的包容组件的方式,就是重新创建一个新的组件,让这个组件包裹传递进来的组件,而传递进来的组件则在
render中渲染出去。
function logProps(WrappedComponent) { return class extends React.Component { componentWillReceiveProps(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); } render() { return <WrappedComponent {...this.props} />; } } }
上面的代码就变得非常正确了。
3.通过包容组件来处理props
不相关
就之前的说到的东西,我们可以知道,HOC模式给组件进行扩展的方式是,通过一个临时的组件将该组件包括起来然后返回出去
包容组件和被包容的组件通过一个
props来进行传递,这一步通常是在
render中。
render() { // 分离出额外的属性 const { extraProp, ...passThroughProps } = this.props; // 执行的函数 const injectedProp = someStateOrInstanceMethod; // 包裹组件 return ( <WrappedComponent injectedProp={injectedProp} {...passThroughProps} /> ); }
4.最大化可组合性
不是所有的HOC都会接受一个回调函数进行处理,有时只有一个参数,就是要被包裹的组件
const NavbarWithRouter = withRouter(Navbar);
又或者是如同
Relay.createContainer用一个
config来进行配置
const CommentWithRelay = Relay.createContainer(Comment, config);
又或者是
Redux的
connect函数
const ConnectedComment = connect(commentSelector, commentActions)(Comment);
如果上面看不懂,可以看下面的分步:
const enhance = connect(commentListSelector, commentListActions); const ConnectedComment = enhance(CommentList);
connect就是一个高阶函数,返回的就是一个高阶组件,但是这也许让人疑惑,但是这是一个好方式,通过调用不断将需要的参数传递进去,内部是函数式编程规范进行处理的。
建议大家可以好好的学习一下函数式编程。
这里还有很多实用方法,之后会更新,个人还不是特别会,需要不断实践,才能更新。
下一篇将讲
React的高级运用
相关文章推荐
- React学习之进阶非控制型组件(十四)
- React进阶学习之组件的解耦之道
- React学习——ListView组件
- 【JAVASCRIPT】React学习-如何构建一个组件
- React Native学习-控制横竖屏第三方组件:react-native-orientation
- reactjs学习笔记2--组件的介绍
- ReactJS学习系列课程(props 组件属性)
- 【HTML5&CSS3进阶学习01】气泡组件的实现
- react学习笔记之组件生命周期
- ReactNative学习-滑动查看图片第三方组件react-native-swiper
- Android进阶学习-复合组件自定义View(1)
- React-Native通过登录界面学习TextInput组件
- 【JAVASCRIPT】React学习- 数据流(组件通信)
- react native组件学习之listview
- react native组件学习(三)
- React Native学习-调取摄像头第三方组件:react-native-image-picker
- React学习笔记(6)---组件协同使用介绍
- react-native组件学习(二)
- react-native开源组件react-native-wechat学习
- React学习笔记—组件复用