React高階組件探究
在使用React
構建項目的過程中,經常會碰到在不同的組件中需要用到相同功能的情況。不過我們知道,去這些組件中到處編寫同樣的代碼并不優雅。
在以往,為了解決以上的情況,我們會用到Mixin
這種方式來解決問題。
以下是一個最為簡單的Mixin
var defaultMixin = {
getDefaultProps: function() {
return {
name: "Allen"
}
}
}
var Component = React.createClass({
mixins: [defaultMixin],
render: function() {
return <h1>Hello, {this.props.name}</h1>
}
})
這個例子很好理解,在這里就不詳述了。要使用mixin
屬性,我們只需要簡單地在組件中加入mixins
屬性即可。
不過在React ES6中,因為種種原因,mixin
將不再被支持。
詳情可以參考以下文章:
文章中提到用高階組件
來替代mixin
。
所謂高階組件其實只是一個方法。這個方法中返回一個包裹著原組件的新組件。接下來看看它的基本用法。
高階組件基本介紹
function hoc(ComponentClass) {
return class HOC extends React.Component {
componentDidMount() {
console.log("hoc");
}
render() {
return <ComponentClass />
}
}
}
以上代碼可以看作是一個最簡的高階組件
使用起來也很簡單,比如有一個React組件。
class ComponentClass extends React.Component {
render() {
return <div></div>
}
}
export default hoc(MyComponent);
只需要將你的組件類作為參數傳入高階組件這個方法中即可。
如果采用ES7的decorator語法。則可以更簡潔
@hoc
export default class ComponentClass extends React.Component {
//...
}
簡單來說,高階組件就是一個函數,它讓你傳入一個組件類,然后返回給你一個更強大的組件類。
用更生動一點的說法,高階組件就像是一個buff……
雞血(大錘) ===> 打了雞血的大錘
@超級buff
class 賽亞人 {} //超級賽亞人get~~~
類似以上這種感覺……
高階組件中的props、ref、state
操作props###
你可以在高階組件中對ComponentClass中的props進行讀取、添加、編輯操作。
例子:
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,假設this.props中有name,那么newProps中的name會覆蓋掉this.props中的name。
通過ref訪問組件實例###
你可以通過ref訪問到組件中的實例。可以看以下例子:
function hoc(ComponentClass) {
return class HOC extends React.Component {
componentDidMount() {
console.log(this.refs.componentClass);
}
render() {
return <ComponentClass ref="componentClass" />
}
}
}
提示:React中ref還有這種回調函數的寫法: 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回調函數在ComponentClass渲染時執行,從而可以輕松拿到它的引用。
獲取state###
在高階組件中獲取state有若干種方式,下面將一一闡述。
和React父子組件交互的方式類似,你可以往ComponentClass中傳入一個函數,之后ComponentClass中通過props拿到這個函數,往里面傳入你想要的state參數。
示例如下:
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的方式應該算是高階組件的另一種運用方式
我在文章開篇提到過這句話
所謂高階組件其實只是一個方法。這個方法中返回一個包裹著原組件的新組件。接下來看看它的基本用法。
高階組件中所謂的包裹
方式主要有以下兩種:
Props Proxy: 高階組件通過ComponentClass中的props來進行相關的操作。
Inheritance Inversion: 高階組件繼承自ComponentClass。
之前我們并未用到第二種包裹方式,大部分采用的Props Proxy的方式來進行操作。接下來我們來詳細講講第二種方式。
Inheritance Inversion
Inheritance Inversion的最簡實現大概如下:
function hoc(ComponentClass) {
return class HOC extends ComponentClass {
render() {
return super.render();
}
}
}
高階組件hoc中,返回了一個繼承了原先組件類的組件。頗有一種反轉的味道。
顯而易見,你可以在這個組件類中拿到一切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]來調取ComponentClass的聲明周期或是render函數
接下來我們可以來實現一個簡單的需求。比如說一個組件中存在網絡請求,你希望在請求完成之前組件顯示loading,完成后再顯示具體內容。我們就可以運用高階組件來實現。
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(...請求); this.setState({
success: true,
data: result.data
});
}
render() {
return <div>主要內容</div>
}
}
這個例子應該很好理解。這邊就不再詳細解釋了。
Inheritance Inversion渲染劫持
渲染劫持這句話的意思很好理解。因為在高階組件中你可以控制ComponentClass的渲染輸出,因此你可以在渲染的時候做出各種各樣的事情。比如修改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>
)
}
}
以上就是一個渲染劫持的示例,我將ComponentClass中的ul標簽給移除了。你可以使用渲染劫持完成類似的需求。如果你對于React中的組件元素操作不是很了解的話,推薦您去看看官方文檔中的這一節:
React官方關于component-elements的文檔
實際案例
mobx-react
/**MyStore文件**/
import { observable } from 'mobx';
export class MyStore() {
@observable value = 1;
@action add = () => {
this.value++;
}
}
/**另一個文件**/
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當成高階組件。傳入MyComponent這個類,之后mobx-react會對其生命周期進行各種處理,并通過調用實例的forceUpdate來進行刷新實現最小粒度渲染。
算是小小安利一下mobx這個框架吧。
順便一提, mobx中提倡一份數據引用,而在redux中則更提倡immutable思想,返回新對象。
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這一生命周期,當傳來的props未發生改變時,不重新render,從而達到性能優化的目的。
以上。。。