本文的目標(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è)計模式今天依然在使用,它們已成了構(gòu)建React應(yīng)用程序的標(biāo)準(zhǔn),而另一些類型的組件只會在舊的應(yīng)用和新手教學(xué)中出現(xiàn)。 在這篇文章中,我想通過層次化的方式向初學(xué)React的人介紹一下不同的React組件和React設(shè)計模式。讀完本文后,你應(yīng)當(dāng)能夠從舊的應(yīng)用和入門文章中分辨出不同類型的React組件,并能夠自信地編寫自己的現(xiàn)代React組件。 React createClass組件
我們要從React的createClass組件說起。createClass方法為開發(fā)者提供了一個工廠方法,無需編寫JavaScript類就可以創(chuàng)建React類組件。在JavaScript ES6出現(xiàn)之前,這種方法是創(chuàng)建React組件的標(biāo)準(zhǔn)方法,因為在JavaScript ES5時代還無法使用類語法: 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()工廠方法接受一個對象,該對象定義了React組件中的方法。其中,getInitialState()方法用來設(shè)置React組件的初始狀態(tài),還有必須的render()方法用來顯示JSX形式的組件。給對象傳遞更多函數(shù)即可添加更多的“方法”(如onChange())。
還可以使用生命周期方法來管理副作用。例如,為了隨時將輸入框中的值保存到瀏覽器的local storage中,可以利用componentDidUpdate()這個生命周期方法,只需要將該函數(shù)傳遞給工廠函數(shù)的對象即可。而且,local storage中的值也可以在組件接收初始狀態(tài)的時候讀出來: 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> ); }, });
每次重新加載或刷新瀏覽器時,之前在輸入框中輸入過的、保存在local storage中的初始狀態(tài)就會在組件初次mount的時候顯示出來。 注意:React的createClass方法現(xiàn)在已經(jīng)不在React的核心包中了。如果你想嘗試下,就必須要安裝另一個包:npm install create-react-class。 React Mixin React Mixin是在React提出可重用組件邏輯的高級設(shè)計方式時加入的。利用Mixin可以將React組件中的邏輯提取出來作為獨立的對象使用。在使用Mixin對象時,Mixin中的所有功能都會被引入到組件中: 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> ); }, });
在這個例子中,Mixin提供了組件的初始狀態(tài),而該初始狀態(tài)是從local storage中讀取的,并且還利用setLocalStorage()擴(kuò)展原來的組件,該函數(shù)之后會在組件中被調(diào)用。為了讓Mixin更靈活,我們可以使用一個返回函數(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了,因為它們會帶來一些負(fù)面作用。關(guān)于Mixin的細(xì)節(jié)和消亡過程可以閱讀這里(https:///blog/2016/07/13/mixins-considered-harmful.html)
React類組件 React類組件是在JavaScript ES6時引入的,因為直到ES6才支持JS類。有時候它們也被稱為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òu)造器的方法,主要用于讓React設(shè)置初始狀態(tài),或者綁定方法。還有必須的render方法用于返回JSX的輸出。React組件的所有內(nèi)部邏輯都通過類組件定義中的面向?qū)ο罄^承,從extends React.Component獲得。但是,除了這種用法之外,我并不推薦進(jìn)一步使用類繼承,相反,應(yīng)當(dāng)主要使用類組合(composition)。 注意:利用JavaScript類定義React組件時還可以使用了另一種語法,通過JavaScript ES6的箭頭函數(shù)來自動綁定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類組件提供幾個生命周期方法,用于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)用,因為函數(shù)組件已經(jīng)不遜于類組件了。
React高階組件 React高階組件(Higher-Order Components,簡稱 HOC)是一種React的高級設(shè)計模式,是替代Mixin的另一種在組件間復(fù)用邏輯的方法。如果你沒聽說過HOC,可以讀一讀我的另一篇入門文章:高階組件(https://www./gentle-introduction-higher-order-components/)。簡單來說,高階組件就是接收一個組件作為輸入,然后輸出另一個組件(并擴(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);
另一個高級React設(shè)計模式就是React Render Prop組件,通常代替React高階組件使用。我不在給出這種抽象的例子,更多內(nèi)容請查看網(wǎng)上的一些教程。
React高階組件和React Render Prop組件在今天都在被廣泛使用,盡管React函數(shù)組件和React鉤子(后文會介紹)也許對于React組件的抽象更好。不過,高階組件和Render Prop也可以用在函數(shù)組件上。 React函數(shù)組件 React函數(shù)組件等價于React類組件,但它表現(xiàn)為一個函數(shù),而不是一個類。過去函數(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ù)組件中實現(xiàn)副作用。一般來說,內(nèi)置的useEffect鉤子可以用來在任何props或state發(fā)生變化時執(zhí)行一個函數(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)中的輸入框的值改變時,該鉤子就會被執(zhí)行。當(dāng)提供給useEffect鉤子的函數(shù)被執(zhí)行時,它會利用最新的值更新local storage中的值。此外,函數(shù)組件的初始狀態(tài)也可以使用useState鉤子從local storage中讀取。
最后一點,我們可以將講個鉤子提取出來,封裝成一個自定義鉤子,這樣可以保證組件狀態(tài)永遠(yuǎn)和local storage同步。它在最后會返回一個值和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ù)組件中提取出來的,它可以用于任何其他組件,以實現(xiàn)業(yè)務(wù)邏輯的復(fù)用。它與Mixin、高階組件和Render Prop組件一樣都是高級設(shè)計模式。但是需要指出的是,React的函數(shù)組件也可以用高階組件和Render Prop組件來增強(qiáng)。
React函數(shù)組件、鉤子和類組件是目前編寫現(xiàn)代React應(yīng)用程序的標(biāo)準(zhǔn)。但是,我堅信以后函數(shù)組件和鉤子將取代類組件。屆時,類組件也許只會出現(xiàn)在舊的應(yīng)用程序和教程中,就像今天的 createClass組件和Mixin一樣。高階組件和Render Prop組件也同理,它們也會被鉤子取代。 寫在最后 所有React的組件在Pros的用法方面的理念都是一樣的,都是將信息沿著組件樹向下傳遞。但是,類組件和函數(shù)組件對于狀態(tài)和副作用的用法是不同的,還有生命周期方法和鉤子。 這篇文章介紹了所有不同種類的React組件及其用法,以及它們在歷史中的位置。最后總結(jié)一下,現(xiàn)在使用類組件、函數(shù)組件和鉤子、高階組件和Render Prop組件等高級概念是完全沒問題的。但是也應(yīng)當(dāng)了解到,舊的React應(yīng)用程序和教程也會使用一些只有以前才使用的舊組件和設(shè)計模式。
|