在React官網(wǎng)文檔學(xué)習(xí)React HOC背桐,整個(gè)看了一遍還是云里霧里的募书,于是按照官網(wǎng)文檔,自己動(dòng)手實(shí)踐一下捶障。官網(wǎng)地址:React 高階組件
定義:高階組件就是一個(gè)函數(shù)是鬼,且該函數(shù)接受一個(gè)組件作為參數(shù)猿棉,并返回一個(gè)新的組件
使用高階組件(HOC)解決交叉問題
假設(shè)有兩個(gè)組件,CommentList組件從外部數(shù)據(jù)源訂閱數(shù)據(jù)并渲染評論列表屑咳,BlogPost組件是一個(gè)訂閱單個(gè)博客文章的組件萨赁,該組件遵循類似的模式,即在componentDidMount中添加事件處理函數(shù)訂閱數(shù)據(jù)兆龙,在componentWillUnmount中清除事件處理函數(shù)杖爽,兩個(gè)組件的事件處理函數(shù)內(nèi)容相同。兩個(gè)組件的區(qū)別在于:從數(shù)據(jù)源訂閱的數(shù)據(jù)不同紫皇,并且渲染格式不同慰安。(代碼見React官網(wǎng))
由此,可以將兩個(gè)組件中相同的邏輯部分提取到一個(gè)高階組件聪铺,該高階組件能夠創(chuàng)建類似 CommonList 和 BlogPost 從數(shù)據(jù)源訂閱數(shù)據(jù)的組件 化焕。該組件接受一個(gè)子組件作為其中的一個(gè)參數(shù),并從數(shù)據(jù)源訂閱數(shù)據(jù)作為props屬性傳入子組件铃剔。該高階組件命名為WithSubscription撒桨。
import DataSource from '../DataSource'
let withSubscription = (WrappedComponent, selectData) => {
// ……返回另一個(gè)新組件……
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props)
};
}
componentDidMount() {
// ……注意訂閱數(shù)據(jù)……
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props)
});
}
render() {
// ……使用最新的數(shù)據(jù)渲染組件
// 注意此處將已有的props屬性傳遞給原組件
const style = {
'marginBottom':'30px'
}
return(
<div style={style}>
<div>This is a HOC Component...</div>
<WrappedComponent data={this.state.data} {...this.props} />
</div>
);
}
};
}
export default withSubscription;
高階組件既不會(huì)修改原組件,也不會(huì)使用繼承復(fù)制原組件的行為键兜。相反凤类,高階組件是通過將原組件包裹(wrapping)在容器組件(container component)里面的方式來組合(composes) 使用原組件。高階組件就是一個(gè)沒有副作用的純函數(shù)普气。
高階組件接收容器組件的所有props屬性以及一個(gè)新的 data屬性谜疤,并將從數(shù)據(jù)源訂閱的數(shù)據(jù)用 data 屬性渲染輸出內(nèi)容。高階組件并不關(guān)心數(shù)據(jù)是如何以及為什么被使用,而被包裹組件也不關(guān)心數(shù)據(jù)來自何處夷磕。
高階組件使用
./pages/index.js
import React from 'react'
import HOCList from '../components/HOCList';
import CommentList from '../components/CommentList';
import BlogPost from '../components/BlogPost';
import withSubscription from '../components/WithSubscription/index'
const CommentListWithSubscription = withSubscription(
CommentList,
(DataSource) => DataSource.getComments()
);
const BlogPostWithSubscription = withSubscription(
BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id)
);
export default class extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
render() {
const style = {
width:'100%',
'text-align': 'center',
title:{
color:'red'
}
}
return (
<div style={style}>
<h1 style={style.title}>hello hoc</h1>
<CommentListWithSubscription />
<BlogPostWithSubscription />
</div>
);
}
}
CommentListWithSubscription
的第一個(gè)參數(shù)是包裹組件(wrapped component)履肃,第二個(gè)參數(shù)會(huì)從 DataSource和當(dāng)前props即高階組件的props屬性中檢索需要的數(shù)據(jù)。
當(dāng) CommentListWithSubscription 和 BlogPostWithSubscription 渲染時(shí), 會(huì)向CommentList 和 BlogPost 傳遞一個(gè) data props屬性坐桩,該 data屬性的數(shù)據(jù)包含了從 DataSource 檢索的最新數(shù)據(jù)尺棋。
官網(wǎng)的示例代碼不完全,為了更好地看到運(yùn)行結(jié)果撕攒,對代碼做一些修改:
- 另外創(chuàng)建數(shù)據(jù)源DataSource:
./components/DataSource.js
let DataSource = {
getComments: () => {
return [
'comment1', 'comment2', 'comment3'
]
},
getBlogPost: () => {
return 'BlogPost Contents';
},
addChangeListener: () => { console.log('addChangeListener') },
removeChangeListener: () => { console.log('removeChangeListener') },
}
export default DataSource;
- 兩個(gè)被包裹組件只負(fù)責(zé)對數(shù)據(jù)源的展示:
//.components/BlogPost/index.js
import React from 'react'
import { Input,Button } from 'antd'
const { TextArea } = Input;
export default class extends React.Component {
render() {
return (
<div>
<TextArea value={this.props.data} />
</div>
);
}
}
//.components/BlogPCommentListst/index.js
import React from 'react'
export default class extends React.Component {
render() {
return (
<div>
{this.props.data.map((value) => (
<div comment={value} key={value} >{value}</div>
))}
</div>
);
}
}
運(yùn)行結(jié)果:http://localhost:3000/