介绍
将高阶组件之前先讲高阶函数,所谓高阶函数,就是接受一个或多个函数作为输入,或者输出一个一个函数的函数。而高阶组件其实接收React组件作为输入,输出一个新的React组件的组件。
a higher-order component is a function that takes a component and returns a new component.
react官网对高阶组件的介绍只是从语义上予以理解,并不能真的运行起来, 下面举一个简化版的例子,结合这个例子再去看官网上给出的实际运用会更加的清楚。
运用
一个通过获取外部数据来展示评论列表的组件可能如下所示:
class CommentList extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
};
}
componentDidMount() {
// 获取评论列表
this.getComments();
}
getComments = () => {
// 模拟ajax请求获取数据
setTimeout(() => {
const comments = [
{ title: 'React是一个纯View层', description: '不擅长于和动态数据打交道,因此它不同于,也替代不了常规的MV*框架;', id: '0' },
{ title: 'React很擅长于处理组件化的页面', description: ',在页面上搭组件的形式有点像搭乐高一样,因此用上React的项目需求常规为界面组件化。另外React只支持到IE8+,就天朝的情况,是否使用React还是得稍微斟酌一番。', id: '1' },
{ title: 'React是一个纯View层', description: '不擅长于和动态数据打交道,因此它不同于,也替代不了常规的MV*框架;', id: '2' },
{ title: 'React很擅长于处理组件化的页面', description: ',在页面上搭组件的形式有点像搭乐高一样,因此用上React的项目需求常规为界面组件化。另外React只支持到IE8+,就天朝的情况,是否使用React还是得稍微斟酌一番。', id: '3' },
];
this.setState({ comments });
}, 2000);
}
toggleClick(index) {
// 点击显示详情
this.setState(prevState => ({
comments: prevState.comments.map((comment, i) => (
Object.assign({}, comment, { showDesc: i === index ? !comment.showDesc : comment.showDesc })
)),
}));
}
render() {
return (
<div>
{this.state.comments.map((comment, index) => (
<p key={comment.id}>
列表{comment.title} {comment.showDesc && comment.description}
<a onClick={this.toggleClick.bind(this, index)}>{comment.showDesc ? '收起' : '展开'}</a>
</p>
))}
</div>
);
}
}
一个展示单个博客帖子的组件,可能如下所示:
class BlogPost extends Component {
constructor(props) {
super(props);
this.state = {
blogPost: {},
};
}
componentDidMount() {
// 获取评论列表
this.getComments();
}
getComments = () => {
// 模拟ajax请求获取数据
setTimeout(() => {
const blogPost = { title: 'React是一个纯View层', description: '不擅长于和动态数据打交道,因此它不同于,也替代不了常规的MV*框架;', id: '0' };
this.setState({ blogPost });
}, 2000);
}
render() {
const { blogPost } = this.state;
return (
<div>
<h2>博客:</h2>
{
blogPost.id
&&
<p key={blogPost.id}>
{blogPost.title} {blogPost.showDesc && blogPost.description}
</p>
}
</div>
);
}
}
这个时候你会发现他们的大部分功能是一样的,那这个样能不能简化一下将公共的部分抽离出来, 这时候就需要高阶函数登场了,大概是这样写的:
function withSubscription(type) {
return (WrappedComponent) => {
return class extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
// 获取评论列表
this.getComments();
}
getComments = () => {
// 模拟ajax请求获取数据
setTimeout(() => {
if (type === 'blog') {
const blogPost = { title: 'React是一个纯View层', description: '不擅长于和动态数据打交道,因此它不同于,也替代不了常规的MV*框架;', id: '0' };
this.setState({ dataSource: blogPost });
} else if (type === 'comment') {
const comments = [
{ title: 'React是一个纯View层', description: '不擅长于和动态数据打交道,因此它不同于,也替代不了常规的MV*框架;', id: '0' },
{ title: 'React很擅长于处理组件化的页面', description: ',在页面上搭组件的形式有点像搭乐高一样,因此用上React的项目需求常规为界面组件化。另外React只支持到IE8+,就天朝的情况,是否使用React还是得稍微斟酌一番。', id: '1' },
{ title: 'React是一个纯View层', description: '不擅长于和动态数据打交道,因此它不同于,也替代不了常规的MV*框架;', id: '2' },
{ title: 'React很擅长于处理组件化的页面', description: ',在页面上搭组件的形式有点像搭乐高一样,因此用上React的项目需求常规为界面组件化。另外React只支持到IE8+,就天朝的情况,是否使用React还是得稍微斟酌一番。', id: '3' },
];
this.setState({ dataSource: comments });
}
}, 2000);
}
render() {
return (
<WrappedComponent dataSource={this.state.dataSource} {...this.props} />
);
}
}
}
}
这样的话CommentList组件和BlogPost组件就可以以一种很简洁的方式去书写
@withSubscription('comment')
export default class CommentList extends Component {
render() {
const { dataSource: comments } = this.props;
return (
<div>
<h2>评论列表:</h2>
{
comments
&&
comments.map(comment => (
<p key={comment.id}>
{comment.title} {comment.description}
</p>
))}
</div>
);
}
}
@withSubscription('blog')
export default class BlogPost extends Component {
render() {
const { dataSource: blogPost } = this.props;
return (
<div>
<h2>博客:</h2>
{
blogPost && blogPost.id
&&
<p key={blogPost.id}>
{blogPost.title} {blogPost.description}
</p>
}
</div>
);
}
}
这里只是抽离出来了一个type,如果是为了高阶函数的通用性的话,可以参考官网给出的例子去动态获取数据而不是直接在组件里分类来获取数据
个人觉得React高阶组件的特点有两个:
- 普通高阶函数的好处,可以将一些公共的组件,逻辑抽离出来,简化书写。
- 他可以通过props进行数据的传递。