React高階組件探究
在使用React
構(gòu)建項(xiàng)目的過程中,經(jīng)常會(huì)碰到在不同的組件中需要用到相同功能的情況。不過我們知道,去這些組件中到處編寫同樣的代碼并不優(yōu)雅。
在以往,為了解決以上的情況,我們會(huì)用到Mixin
這種方式來解決問題。
以下是一個(gè)最為簡(jiǎn)單的Mixin
var defaultMixin = {
getDefaultProps: function() {
return {
name: "Allen"
}
}
}
var Component = React.createClass({
mixins: [defaultMixin],
render: function() {
return <h1>Hello, {this.props.name}</h1>
}
})
這個(gè)例子很好理解,在這里就不詳述了。要使用mixin
屬性,我們只需要簡(jiǎn)單地在組件中加入mixins
屬性即可。
不過在React ES6中,因?yàn)榉N種原因,mixin
將不再被支持。
詳情可以參考以下文章:
文章中提到用高階組件
來替代mixin
。
所謂高階組件其實(shí)只是一個(gè)方法。這個(gè)方法中返回一個(gè)包裹著原組件的新組件。接下來看看它的基本用法。
高階組件基本介紹
function hoc(ComponentClass) {
return class HOC extends React.Component {
componentDidMount() {
console.log("hoc");
}
render() {
return <ComponentClass />
}
}
}
以上代碼可以看作是一個(gè)最簡(jiǎn)的高階組件
使用起來也很簡(jiǎn)單,比如有一個(gè)React組件。
class ComponentClass extends React.Component {
render() {
return <div></div>
}
}
export default hoc(MyComponent);
只需要將你的組件類作為參數(shù)傳入高階組件這個(gè)方法中即可。
如果采用ES7的decorator語法。則可以更簡(jiǎn)潔
@hoc
export default class ComponentClass extends React.Component {
//...
}
簡(jiǎn)單來說,高階組件就是一個(gè)函數(shù),它讓你傳入一個(gè)組件類,然后返回給你一個(gè)更強(qiáng)大的組件類。
用更生動(dòng)一點(diǎn)的說法,高階組件就像是一個(gè)buff……
雞血(大錘) ===> 打了雞血的大錘
@超級(jí)buff
class 賽亞人 {} //超級(jí)賽亞人get~~~
類似以上這種感覺……
高階組件中的props、ref、state
操作props###
你可以在高階組件中對(duì)ComponentClass中的props進(jìn)行讀取、添加、編輯操作。
例子:
function hoc(ComponentClass) {
return class HOC extends React.Component {
render() {
const newProps = {
name: "cqm",
value: "testData",
}
return <ComponentClass {...this.props} {...newProps} />
}
}
}
高階組件中能夠通過this.props直接獲取到ComponentClass中的props,假設(shè)this.props中有name,那么newProps中的name會(huì)覆蓋掉this.props中的name。
通過ref訪問組件實(shí)例###
你可以通過ref訪問到組件中的實(shí)例。可以看以下例子:
function hoc(ComponentClass) {
return class HOC extends React.Component {
componentDidMount() {
console.log(this.refs.componentClass);
}
render() {
return <ComponentClass ref="componentClass" />
}
}
}
提示:React中ref還有這種回調(diào)函數(shù)的寫法: ref={(dom) => this.dom = dom} 依稀記得好像在哪里看到過,說這種寫法更新更好
你也可以使用另一種方式:
function hoc(ComponentClass) {
return class HOC extends React.Component {
getDom(dom) {
console.log(dom);
}
render() {
return <ComponentClass ref={(dom) => this.getDom(dom)} />
}
}
}
ref回調(diào)函數(shù)在ComponentClass渲染時(shí)執(zhí)行,從而可以輕松拿到它的引用。
獲取state###
在高階組件中獲取state有若干種方式,下面將一一闡述。
和React父子組件交互的方式類似,你可以往ComponentClass中傳入一個(gè)函數(shù),之后ComponentClass中通過props拿到這個(gè)函數(shù),往里面?zhèn)魅肽阆胍膕tate參數(shù)。
示例如下:
function hoc(ComponentClass) {
return class HOC extends React.Component {
getState(state) {
console.log(state);
}
render() {
const newProps = {
getState: (state) => this.getState(state)
};
return <ComponentClass {...newProps} />
}
}
}
@hoc
export default class ComponentClass extends React.Component {
state = {
value = ""
};
render() {
return (
<div>
<input onChange={(z) => this.props.getState(z.target.value)} />
</div>
);
}
}
理論上你還可以通過this.refs.componentClass.state
這種方式來獲取全部的state(不過我一般不這么干)。
接下來用到的獲取state的方式應(yīng)該算是高階組件的另一種運(yùn)用方式
我在文章開篇提到過這句話
所謂高階組件其實(shí)只是一個(gè)方法。這個(gè)方法中返回一個(gè)包裹著原組件的新組件。接下來看看它的基本用法。
高階組件中所謂的包裹
方式主要有以下兩種:
Props Proxy: 高階組件通過ComponentClass中的props來進(jìn)行相關(guān)的操作。
Inheritance Inversion: 高階組件繼承自ComponentClass。
之前我們并未用到第二種包裹方式,大部分采用的Props Proxy的方式來進(jìn)行操作。接下來我們來詳細(xì)講講第二種方式。
Inheritance Inversion
Inheritance Inversion的最簡(jiǎn)實(shí)現(xiàn)大概如下:
function hoc(ComponentClass) {
return class HOC extends ComponentClass {
render() {
return super.render();
}
}
}
高階組件hoc中,返回了一個(gè)繼承了原先組件類的組件。頗有一種反轉(zhuǎn)的味道。
顯而易見,你可以在這個(gè)組件類中拿到一切ComponentClass的props、state。
代碼如下:
function hoc(ComponentClass) {
return class HOC extends ComponentClass {
componentDidMount() {
console.log(this.state);
}
render() {
return super.render();
}
}
}
@hoc
export default class ComponentClass extends React.Component {
state = {
id: 0,
value: "hahaha"
};
render() {
return <div>ComponentClass</div>
}
}
你也可以通過super.[lifecycle]來調(diào)取ComponentClass的聲明周期或是render函數(shù)
接下來我們可以來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的需求。比如說一個(gè)組件中存在網(wǎng)絡(luò)請(qǐng)求,你希望在請(qǐng)求完成之前組件顯示loading,完成后再顯示具體內(nèi)容。我們就可以運(yùn)用高階組件來實(shí)現(xiàn)。
function hoc(ComponentClass) {
return class HOC extends ComponentClass {
render() {
if (this.state.success) {
return super.render()
}
return <div>Loading...</div>
}
}
}
@hoc
export default class ComponentClass extends React.Component {
state = {
success: false,
data: null
};
async componentDidMount() {
const result = await fetch(...請(qǐng)求); this.setState({
success: true,
data: result.data
});
}
render() {
return <div>主要內(nèi)容</div>
}
}
這個(gè)例子應(yīng)該很好理解。這邊就不再詳細(xì)解釋了。
Inheritance Inversion渲染劫持
渲染劫持這句話的意思很好理解。因?yàn)樵诟唠A組件中你可以控制ComponentClass的渲染輸出,因此你可以在渲染的時(shí)候做出各種各樣的事情。比如修改props,修改render的組件樹等等。
以下示例用以演示修改render方法輸出的組件樹
function hoc(ComponentClass) {
return class HOC extends ComponentClass {
render() {
const elementTree = super.render();
elementTree.props.children = elementTree.props.children.filter((z) => {
return z.type !== "ul" && z;
}
const newTree = React.cloneElement(elementTree);
return newTree;
}
}
}
@hoc
export default class ComponentClass extends React.Component {
render() {
const divStyle = {
width: '100px',
height: '100px',
backgroundColor: 'red'
};
return (
<div>
<p style={{color: 'brown'}}>啦啦啦</p>
<ul>
<li>1</li>
<li>2</li>
</ul>
<h1>哈哈哈</h1>
</div>
)
}
}
以上就是一個(gè)渲染劫持的示例,我將ComponentClass中的ul標(biāo)簽給移除了。你可以使用渲染劫持完成類似的需求。如果你對(duì)于React中的組件元素操作不是很了解的話,推薦您去看看官方文檔中的這一節(jié):
React官方關(guān)于component-elements的文檔
實(shí)際案例
mobx-react
/**MyStore文件**/
import { observable } from 'mobx';
export class MyStore() {
@observable value = 1;
@action add = () => {
this.value++;
}
}
/**另一個(gè)文件**/
import MyStore from './MyStore';
const store = new MyStore();
@observer
export default class MyComponent extends React.Component {
render() {
return (
<div>
<p>這只青蛙的壽命{store.value}</p>
<button onClick={store.add}>+1s</button>
</div>
);
}
}
可以把observer當(dāng)成高階組件。傳入MyComponent這個(gè)類,之后mobx-react會(huì)對(duì)其生命周期進(jìn)行各種處理,并通過調(diào)用實(shí)例的forceUpdate來進(jìn)行刷新實(shí)現(xiàn)最小粒度渲染。
算是小小安利一下mobx這個(gè)框架吧。
順便一提, mobx中提倡一份數(shù)據(jù)引用,而在redux中則更提倡immutable思想,返回新對(duì)象。
pure-render-decorator
@pureRender
export default class MyComponent extends React.Component {
static propTypes = {
name: React.PropTypes.string,
};
render() {
return (
<div>{this.props.name}</div>
);
}
}
pureRender中重新定義了shouldComponentUpdate這一生命周期,當(dāng)傳來的props未發(fā)生改變時(shí),不重新render,從而達(dá)到性能優(yōu)化的目的。
以上。。。