在軟體開發的過程中Code Reuse和可讀性一直是開發人員致力於解決的問題,在React社群中,以往出現了很多的Pattern來解決這個問題,例如Mixin,HOC等等。Render Props是React社群中裡提出的另外的一種Pattern。由React Router的Co-author Michael Jackson 提出,它與HOC等有什麼區別呢?又解決了什麼問題呢?在這篇文章來一探究竟。
問題的引出
所有的Pattern或者solution都是為了解決問題而提出的,那麼render props到底是為了解決什麼問題呢?我們先來看一個例子。例如在我們的App裡我們實現了這樣一個component
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0
};
this.handleMouseMove = this.handleMouseMove.bind(this);
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: 800 }} onMouseMove={this.handleMouseMove}>
<p>
The mouse is at ({this.state.x}, {this.state.y})
</p>
</div>
);
}
}
複製程式碼
熟悉React的同學都知道這是一個很簡單的App,他可以追蹤滑鼠位置給到自己。但是問題來了,萬一哪天你的朋友看到這個component,說這個feature很Cool,他的app裡能不能用上呢?那麼該怎麼解決這個問題呢?
Code Repeat
首先第一種想法很直接,可以把這個component做為一個父Component,另外一個Component作為一個子Component。來看程式碼。
import React from 'react';
const Dog = ({mouse}) => (
<div>{mouse.x}, {mouse.y}</div>
)
class MouseDog extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0
};
this.handleMouseMove = this.handleMouseMove.bind(this);
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: 800 }} onMouseMove={this.handleMouseMove}>
<p>
The mouse is at ({this.state.x}, {this.state.y})
<Dog mouse={mouse} />
</p>
</div>
);
}
}
複製程式碼
現在我們有了兩個Component, Dog 和 DogWithMouse,其實Dog作為DogWithMouse的子Component,那麼DogWithMouse Component就能利用滑鼠位置。好像這樣是把問題解決了,但是如果我們有另外一個Component也想要trace mouse這個feature怎麼辦呢?難道再重新這樣寫一個Component嗎?顯然這種方式是不可取的。
HOC
如果熟悉react的同學,可能會想到,這很簡單呀,我們寫一個High order Component(HOC)就行了,的確HOC可以解決這個問題,我們來看看HOC的程式碼是怎麼樣的。
import React from 'react';
const withMouse = (Component) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0
};
this.handleMouseMove = this.handleMouseMove.bind(this);
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: 800 }} onMouseMove={this.handleMouseMove}>
<Component mouse={this.state} {...this.props} />
</div>
);
}
}
}
const Dog = ({ mouse }) => <div>{mouse.x}, {mouse.y}</div>
const EnhancedDog = withMouse(Dog)
複製程式碼
在上面的程式碼裡,我們建立了一個withMouse的HOC, 這樣就把這部分邏輯抽象了出來。其他的Component想要使用我們mouse tracking的feature。只要將Component傳入HOC就能得到一個enhance過的Component。pretty cool, right?如果對於HOC不太瞭解,可以參考react 的官方文件進行了解。 HOC
HOC似乎已經完美的解決了問題,但是這種方式有沒有什麼缺點呢?似乎是有下面的一些缺點。
-
間接的props傳入。 讓我們重新看一看Dog Component,他的props傳入了mouse,但是在使用這個Component的時候,並沒有地方傳入了mouse。這是因為mouse是在HOC裡傳入的。你大概會納悶這個Mouse是從那來的。如果維護一個大專案的時候,有時候你就會發現這種間接的props傳入,是有多麼坑了。
-
命名衝突的問題。 用過react的同學不知道有沒有經歷過大量使用HOC的場景,筆者是見過多層HOC巢狀使用的專案程式碼。就像這樣:
const NewComponent = withA(withB(withC(...(Component))))
如果你用過這樣的多層巢狀的HOC,那麼就可能會發生命名衝突的問題。來看程式碼:
const withB = (Component) => {
return class extends React.Component {
render() {
return (
<div style={{ height: 800 }} >
<Component mouse={'hehe'} {...this.props} />
</div>
);
}
}
}
const EnhancedDog = withB(withMouse(Dog))
複製程式碼
在這裡定義另外的一個HOC withB。它也給props上傳了一個同樣的mouse, 如果再用它來wrap withMouse(Dog)
時,你會發現mouse tracking的feature就不work,但是React不會有任何提示,這是命名衝突的問題。如果你有一個七八層的wrapp到一起的Component,如果出現這樣的命名衝突的問題,那麼就有的debug了。
Render props
好了,既然HOC有這樣的問題,那有沒有什麼方式既可以做到code reuse,同時也可以避免HOC的這些問題呢?答案就是Render Props。Render Props是React Traning團隊提出的一種新的React中組織Component的方式,同時也是十分的簡單,我們來看上邊的例子中,如果用render props如何解決code reuse的問題。
import React from 'react';
import { render } from "react-dom";
class Mouse extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0
};
this.handleMouseMove = this.handleMouseMove.bind(this);
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: 800 }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
const DogMouse = () => (
<Mouse render={({x, y}) => (<div>this mouose is ({x}, {y})</div>})>
)
render(<DogMouse/>, document.getElementById('app'))
複製程式碼
上邊就是render props的一個簡單例子,我們看完會發現特別的簡單,沒什麼特別的。Mouse就是一個普通的Component,唯一不同的是這個Component上定義了一個叫render的prop,是一個函式。在Mouse中會將state作為引數呼叫這個函式。而建立的DogMouse同樣也是一個普通的Component,其中定義render的方法,用於定義要render什麼Component。我們看看render props和HOC相比解決了什麼問題:
-
code reuse。和HOC一樣的,render props同樣解決了code reuse的問題。把mouse tracking的feature抽象成為一個Mouse的Component,如果有另外一個Component像複用這個feature的時候,簡單的定義另外一個Component定義相應的render props就好了。
-
間接的props傳入,render props 沒有這個問題,可以清楚的看到props(x, y)是從什麼地方出入的.
-
命名衝突。利用render props是沒有Component wrapp的,所以除非定義Component時候自己命名重複,否則不會有命名衝突的問題。
從上面的例子上看,render props可以再大多數情況下替代HOC。react的官方文件上,目前也正式的介紹了render props,下次讓你想用HOC的時候,來試一試Render props把。