您的位置:首页 > Web前端 > React

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
的高级运用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: