腳本之家
你與百萬開發(fā)者在一起
本文的目標(biāo)是讓開發(fā)者清晰地了解 React 組件類型,哪些在現(xiàn)代 React 應(yīng)用中依然在使用,以及為何一些類型現(xiàn)在不再使用了。
作者 | Robin Wieruch
譯者 | 彎月
責(zé)編 | 屠敏
出品 | CSDN(ID:CSDNNews)
以下為譯文:
盡管React從2013年發(fā)布到現(xiàn)在并沒有引入太多重大改變,但不同類型的React組件也出現(xiàn)了不少。一些組件類型和組件設(shè)計(jì)模式今天依然在使用,它們已成了構(gòu)建React應(yīng)用程序的標(biāo)準(zhǔn),而另一些類型的組件只會(huì)在舊的應(yīng)用和新手教學(xué)中出現(xiàn)。
在這篇文章中,我想通過層次化的方式向初學(xué)React的人介紹一下不同的React組件和React設(shè)計(jì)模式。讀完本文后,你應(yīng)當(dāng)能夠從舊的應(yīng)用和入門文章中分辨出不同類型的React組件,并能夠自信地編寫自己的現(xiàn)代React組件。
01
React createClass組件
我們要從React的createClass組件說起。createClass方法為開發(fā)者提供了一個(gè)工廠方法,無需編寫JavaScript類就可以創(chuàng)建React類組件。在JavaScript ES6出現(xiàn)之前,這種方法是創(chuàng)建React組件的標(biāo)準(zhǔn)方法,因?yàn)樵贘avaScript ES5時(shí)代還無法使用類語法:
var App = React.createClass({
getInitialState: function() {
return {
value: '',
};
},
onChange: function(event) {
this.setState({ value: event.target.value });
},
render: function() {
return (
<div>
<h1>Hello React 'createClass' Component!</h1>
<input
value={this.state.value}
type='text'
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
},
});
createClass()工廠方法接受一個(gè)對象,該對象定義了React組件中的方法。其中,getInitialState()方法用來設(shè)置React組件的初始狀態(tài),還有必須的render()方法用來顯示JSX形式的組件。給對象傳遞更多函數(shù)即可添加更多的“方法”(如onChange())。
還可以使用生命周期方法來管理副作用。例如,為了隨時(shí)將輸入框中的值保存到瀏覽器的local storage中,可以利用componentDidUpdate()這個(gè)生命周期方法,只需要將該函數(shù)傳遞給工廠函數(shù)的對象即可。而且,local storage中的值也可以在組件接收初始狀態(tài)的時(shí)候讀出來:
var App = React.createClass({
getInitialState: function() {
return {
value: localStorage.getItem('myValueInLocalStorage') || '',
};
},
componentDidUpdate: function() {
localStorage.setItem('myValueInLocalStorage', this.state.value);
},
onChange: function(event) {
this.setState({ value: event.target.value });
},
render: function() {
return (
<div>
<h1>Hello React 'createClass' Component!</h1>
<input
value={this.state.value}
type='text'
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
},
});
每次重新加載或刷新瀏覽器時(shí),之前在輸入框中輸入過的、保存在local storage中的初始狀態(tài)就會(huì)在組件初次mount的時(shí)候顯示出來。
注意:React的createClass方法現(xiàn)在已經(jīng)不在React的核心包中了。如果你想嘗試下,就必須要安裝另一個(gè)包:npm install create-react-class。
React Mixin
React Mixin是在React提出可重用組件邏輯的高級設(shè)計(jì)方式時(shí)加入的。利用Mixin可以將React組件中的邏輯提取出來作為獨(dú)立的對象使用。在使用Mixin對象時(shí),Mixin中的所有功能都會(huì)被引入到組件中:
var localStorageMixin = {
getInitialState: function() {
return {
value: localStorage.getItem('myValueInLocalStorage') || '',
};
},
setLocalStorage: function(value) {
localStorage.setItem('myValueInLocalStorage', value);
},
};
var App = React.createClass({
mixins: [localStorageMixin],
componentDidUpdate: function() {
this.setLocalStorage(this.state.value);
},
onChange: function(event) {
this.setState({ value: event.target.value });
},
render: function() {
return (
<div>
<h1>Hello React 'createClass' Component with Mixin!</h1>
<input
value={this.state.value}
type='text'
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
},
});
在這個(gè)例子中,Mixin提供了組件的初始狀態(tài),而該初始狀態(tài)是從local storage中讀取的,并且還利用setLocalStorage()擴(kuò)展原來的組件,該函數(shù)之后會(huì)在組件中被調(diào)用。為了讓Mixin更靈活,我們可以使用一個(gè)返回函數(shù)的對象:
function getLocalStorageMixin(localStorageKey) {
return {
getInitialState: function() {
return { value: localStorage.getItem(localStorageKey) || '' };
},
setLocalStorage: function(value) {
localStorage.setItem(localStorageKey, value);
},
};
}
var App = React.createClass({
mixins: [getLocalStorageMixin('myValueInLocalStorage')],
...
});
不過,現(xiàn)代React應(yīng)用程序已經(jīng)不再使用Mixin了,因?yàn)樗鼈儠?huì)帶來一些負(fù)面作用。關(guān)于Mixin的細(xì)節(jié)和消亡過程可以閱讀這里(https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)
02
React類組件
React類組件是在JavaScript ES6時(shí)引入的,因?yàn)橹钡紼S6才支持JS類。有時(shí)候它們也被稱為React ES6類組件。至少有了JavaScript ES6之后,就不需要使用React的createClass方法了。JS自己終于支持類了:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
};
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.setState({ value: event.target.value });
}
render() {
return (
<div>
<h1>Hello React ES6 Class Component!</h1>
<input
value={this.state.value}
type='text'
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
}
}
使用JavaScript類編寫的React Component有個(gè)類似于類構(gòu)造器的方法,主要用于讓React設(shè)置初始狀態(tài),或者綁定方法。還有必須的render方法用于返回JSX的輸出。React組件的所有內(nèi)部邏輯都通過類組件定義中的面向?qū)ο罄^承,從extends React.Component獲得。但是,除了這種用法之外,我并不推薦進(jìn)一步使用類繼承,相反,應(yīng)當(dāng)主要使用類組合(composition)。
注意:利用JavaScript類定義React組件時(shí)還可以使用了另一種語法,通過JavaScript ES6的箭頭函數(shù)來自動(dòng)綁定React組件中的方法:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
};
}
onChange = event => {
this.setState({ value: event.target.value });
};
render() {
return (
<div>
<h1>Hello React ES6 Class Component!</h1>
<input
value={this.state.value}
type='text'
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
}
}
React類組件提供幾個(gè)生命周期方法,用于mount、update和unmount等。比如前面的local storage的例子,可以在生命周期方法中以副作用的方式來執(zhí)行這些操作——即,將輸入框中的最新值保存到local storage中,而在構(gòu)造函數(shù)中可以根據(jù)local storage的值來設(shè)置初始狀態(tài):
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem('myValueInLocalStorage') || '',
};
}
componentDidUpdate() {
localStorage.setItem('myValueInLocalStorage', this.state.value);
}
onChange = event => {
this.setState({ value: event.target.value });
};
render() {
return (
<div>
<h1>Hello React ES6 Class Component!</h1>
<input
value={this.state.value}
type='text'
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
}
}
利用this.state、this.setState()和生命周期方法,React類組件中的狀態(tài)管理和副作用可以寫在一起。React類組件到現(xiàn)在依然在廣泛使用,盡管后文即將介紹的React函數(shù)組件在現(xiàn)代React應(yīng)用程序中得到了更廣泛的應(yīng)用,因?yàn)楹瘮?shù)組件已經(jīng)不遜于類組件了。
React高階組件
React高階組件(Higher-Order Components,簡稱 HOC)是一種React的高級設(shè)計(jì)模式,是替代Mixin的另一種在組件間復(fù)用邏輯的方法。如果你沒聽說過HOC,可以讀一讀我的另一篇入門文章:高階組件(https://www.robinwieruch.de/gentle-introduction-higher-order-components/)。簡單來說,高階組件就是接收一個(gè)組件作為輸入,然后輸出另一個(gè)組件(并擴(kuò)展其功能)的組件。我們利用前面的例子來看看,怎樣才能將功能提取到可復(fù)用的高階組件中。
const withLocalStorage = localStorageKey => Component =>
class WithLocalStorage extends React.Component {
constructor(props) {
super(props);
this.state = {
[localStorageKey]: localStorage.getItem(localStorageKey),
};
}
setLocalStorage = value => {
localStorage.setItem(localStorageKey, value);
};
render() {
return (
<Component
{...this.state}
{...this.props}
setLocalStorage={this.setLocalStorage}
/>
);
}
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { value: this.props['myValueInLocalStorage'] || '' };
}
componentDidUpdate() {
this.props.setLocalStorage(this.state.value);
}
onChange = event => {
this.setState({ value: event.target.value });
};
render() {
return (
<div>
<h1>
Hello React ES6 Class Component with Higher-Order Component!
</h1>
<input
value={this.state.value}
type='text'
onChange={this.onChange}
/>
<p>{this.state.value}</p>
</div>
);
}
}
const AppWithLocalStorage = withLocalStorage('myValueInLocalStorage')(App);
另一個(gè)高級React設(shè)計(jì)模式就是React Render Prop組件,通常代替React高階組件使用。我不在給出這種抽象的例子,更多內(nèi)容請查看網(wǎng)上的一些教程。
React高階組件和React Render Prop組件在今天都在被廣泛使用,盡管React函數(shù)組件和React鉤子(后文會(huì)介紹)也許對于React組件的抽象更好。不過,高階組件和Render Prop也可以用在函數(shù)組件上。
03
React函數(shù)組件
React函數(shù)組件等價(jià)于React類組件,但它表現(xiàn)為一個(gè)函數(shù),而不是一個(gè)類。過去函數(shù)組件沒有狀態(tài)也無法使用副作用,因此它們被稱為“無狀態(tài)函數(shù)組件”,但自從React鉤子出現(xiàn)后,函數(shù)組件就復(fù)活了。
React鉤子給函數(shù)組件帶來了狀態(tài)和副作用。React不僅帶有各種內(nèi)置的鉤子,還允許創(chuàng)建自定義的鉤子。我們來看看前面的類組件的例子怎樣改寫成函數(shù)組件:
const App = () => {
const [value, setValue] = React.useState('');
const onChange = event => setValue(event.target.value);
return (
<div>
<h1>Hello React Function Component!</h1>
<input value={value} type='text' onChange={onChange} />
<p>{value}</p>
</div>
);
};
這段代碼僅在輸入框上演示了函數(shù)組件。由于要捕獲輸入框的值,就需要使用組件狀態(tài),因此這里用到了內(nèi)置的React.useState鉤子。
React鉤子還可以在函數(shù)組件中實(shí)現(xiàn)副作用。一般來說,內(nèi)置的useEffect鉤子可以用來在任何props或state發(fā)生變化時(shí)執(zhí)行一個(gè)函數(shù):
const App = () => {
const [value, setValue] = React.useState(
localStorage.getItem('myValueInLocalStorage') || '',
);
React.useEffect(() => {
localStorage.setItem('myValueInLocalStorage', value);
}, [value]);
const onChange = event => setValue(event.target.value);
return (
<div>
<h1>Hello React Function Component!</h1>
<input value={value} type='text' onChange={onChange} />
<p>{value}</p>
</div>
);
};
這段代碼演示了useEffect鉤子的用法,每次狀態(tài)中的輸入框的值改變時(shí),該鉤子就會(huì)被執(zhí)行。當(dāng)提供給useEffect鉤子的函數(shù)被執(zhí)行時(shí),它會(huì)利用最新的值更新local storage中的值。此外,函數(shù)組件的初始狀態(tài)也可以使用useState鉤子從local storage中讀取。
最后一點(diǎn),我們可以將講個(gè)鉤子提取出來,封裝成一個(gè)自定義鉤子,這樣可以保證組件狀態(tài)永遠(yuǎn)和local storage同步。它在最后會(huì)返回一個(gè)值和setter函數(shù),供函數(shù)組件使用:
const useStateWithLocalStorage = localStorageKey => {
const [value, setValue] = React.useState(
localStorage.getItem(localStorageKey) || '',
);
React.useEffect(() => {
localStorage.setItem(localStorageKey, value);
}, [value]);
return [value, setValue];
};
const App = () => {
const [value, setValue] = useStateWithLocalStorage(
'myValueInLocalStorage',
);
const onChange = event => setValue(event.target.value);
return (
<div>
<h1>Hello React Function Component!</h1>
<input value={value} type='text' onChange={onChange} />
<p>{value}</p>
</div>
);
};
由于這段代碼是從函數(shù)組件中提取出來的,它可以用于任何其他組件,以實(shí)現(xiàn)業(yè)務(wù)邏輯的復(fù)用。它與Mixin、高階組件和Render Prop組件一樣都是高級設(shè)計(jì)模式。但是需要指出的是,React的函數(shù)組件也可以用高階組件和Render Prop組件來增強(qiáng)。
React函數(shù)組件、鉤子和類組件是目前編寫現(xiàn)代React應(yīng)用程序的標(biāo)準(zhǔn)。但是,我堅(jiān)信以后函數(shù)組件和鉤子將取代類組件。屆時(shí),類組件也許只會(huì)出現(xiàn)在舊的應(yīng)用程序和教程中,就像今天的 createClass組件和Mixin一樣。高階組件和Render Prop組件也同理,它們也會(huì)被鉤子取代。
04
寫在最后
所有React的組件在Pros的用法方面的理念都是一樣的,都是將信息沿著組件樹向下傳遞。但是,類組件和函數(shù)組件對于狀態(tài)和副作用的用法是不同的,還有生命周期方法和鉤子。
這篇文章介紹了所有不同種類的React組件及其用法,以及它們在歷史中的位置。最后總結(jié)一下,現(xiàn)在使用類組件、函數(shù)組件和鉤子、高階組件和Render Prop組件等高級概念是完全沒問題的。但是也應(yīng)當(dāng)了解到,舊的React應(yīng)用程序和教程也會(huì)使用一些只有以前才使用的舊組件和設(shè)計(jì)模式。
聯(lián)系客服