解剖react組件的多種寫法與演進

目前,react組件有三種寫法,分別是es5的createClass寫法,es6的class寫法,以及stateless(無狀態組件)寫法。

下面由淺入深來細說這三種寫法。涵蓋了生命周期,反向數據流,es6/7等知識。


一,原始的createClass寫法

對于寫react組件,很多人第一印象往往是createClass,這是因為createClass是react組件最原始的寫法,基本每個學react的人都是接觸這種寫法過來的。

createClass寫法是基于es5,它實際上是React對象的一個頂層API,它只接受一個配置對象作為參數,如下:

var React= require('react');var ReactDOM= require('react-dom');var AppComponent= React.createClass( {這是一個react組件配置對象} );

1,下面來說說組件配置對象。

首先,這個配置對象必須有一個render()方法,并且這個render()方法必須返回由閉合標簽包裹的html片段。

render()方法的作用在于渲染DOM。

如下:

varAppComponent = React.createClass({render:function(){return(

返回值最外層必須是閉合標簽

)? }})

一個最為簡單react組件就這樣完成了。這個組件只配置了一個render函數,你還可以有更多的配置。

react組件的調用也很簡單:

// 調用react組件

最終結果如下:


返回值最外層必須是閉合標簽

2,添加組件生命周期方法

react組件本身除了render()方法以外,還有諸多生命周期方法。這些生命周期方法如下:

# 初始化階段getDefaultProps()// 組件類創建的時候調用getInitialState()// 組件掛載之前調用, 定義state初始值componentWillMount()// 組件即將被裝載、渲染到頁面上componentDidMount()// 組件掛載后調用# 組件運行階段componentWillReceiveProps()// 組件將要接收到新屬性的時候調用shouldComponentUpdate()// 組件接受到新屬性或者新狀態的時候調用componentWillUpdate()// 新屬性或state 更新前調用componentDidUpdate()// 更新完成后調用# 組件銷毀階段componentWillUnmount()// 組件銷毀之前調用

那么,這些生命周期方法是不是必須的呢?

沒有一個是必須的,也就是說是否使用,使用哪些生命周期方法,全憑你自身需求來決定。

但通常你會用到下面這幾個:

getInitialState()// 定義你的初始statecomponentDidMount()// 在render()方法之后調用,也就是執行這個方法前DOM已經渲染完成,// 此時可以使用這個方法來載入數據,也就是發送ajax請求數據

那么,這些方法是如何應用的呢?

很簡單,使用方法和render()方法一樣,在組件配置對象中定義即可。組件配置對象實際上就是一個組件屬性和方法的集合。

接著上面的例子:

varAppComponent = React.createClass({getDefaultProps:function(){// 一些邏輯},getInitialState:function(){// 一些邏輯},componentWillMount:function(){// 一些邏輯},componentDidMount:function(){// 一些邏輯},componentWillReceiveProps:function(){// 一些邏輯},shouldComponentUpdate:function(){// 一些邏輯},? ……? render:function(){return(

返回值最外層必須是閉合標簽

)? }})

至此,完成了一個略完整的組件。

為什么說是略完整?因為還沒有添加組件驗證器,也就是propTypes。

3,添加組件驗證器propTypes

propTypes是一個對象,主要用于驗證傳入組件的props屬性是否符合你在propTypes對象中設定的類型 ,如果不符合,組件會報錯。通常在開發環境中使用。

下面我們來給組件添加驗證器,在組件內配置propTypes屬性即可。以上面的例子為例。

varAppComponent = React.createClass({propTypes: {data: React.PropTypes.array,// 驗證傳入的data是不是數組,如果不是則會報錯loadding: React.PropTypes.bool,// 驗證loadding是否為trueloadData: React.PropTypes.func,// 驗證loadData是否為函數類型},getDefaultProps:function(){// 一些邏輯},getInitialState:function(){// 一些邏輯},? ……? render:function(){return(

返回值最外層必須是閉合標簽

)? }})

于是,組件驗證器添加完成。

props屬性是如何傳入呢?如下:

所有傳入的屬性都可以在組件內通過this.props對象訪問到。

4, 自定義方法

上面講解了react組件自帶的一些方法和屬性。那么除了組件自帶的,我們可不可以給組件自定義我們需要的方法呢?

答案必然是肯定的。光自帶的方法是無法滿足需求的,往往我們需要自定義一些方法來處理很多的邏輯。

自定義方法和組件自帶方法一樣,都是在組件配置對象內定義。以上面的粟子,再來舉個粟子:

varAppComponent = React.createClass({propTypes: {data: React.PropTypes.array,// 驗證傳入的data是不是數組,如果不是則會報錯loadding: React.PropTypes.bool,// 驗證loadding是否為trueloadData: React.PropTypes.func,// 驗證loadData是否為函數類型},getInitialState:function(){// 一些邏輯},componentDidMount:function(){// ajax請求},renderSubmit:function(e){// 自定義的方法,用于處理表單提交e. stopPropagation();// 一些表單提交處理邏輯}? ……? render:function(){return(

// 調用自定義方法

)? }})

至此,一個完整的組件基本完成。

createClass方法的劣勢

現階段已不推薦使用createClass方法來創建組件,準確地說它已經過時了。

基于es5,終究是要被淘汰的

過于臃腫,組件性能略差

二,class組件寫法,也就是使用es6的寫法

1,創建class組件

class寫法是目前比較推薦的寫法,另一種最為推薦的寫法是stateless組件,后面會講到。

這里所說的class是es6的class特性。

react巧妙地運用了這一特性。因為它有一個非常棒的API,那就是extends(繼承)。

我們先來回顧下如何使用class定義一個類,如下:

classAppComponent{ }

這樣就定義了一個AppComponent類。

再來回顧下,如何繼承另一個類,如下:

classAppComponentextendsAPP{ }

這樣AppComponent類就繼承了APP類,AppComponent就可以訪問到APP類的屬性和方法。

眾所周知,react有一個頂層組件,也就是React.Component,它本身封裝了諸多屬性和方法,那么,我們可以使用class來繼承它,從而使它的屬性和方法能夠共享給我們自定義的一個類,從而形成一個新的組件。如下:

importReactfrom'react';classAppComponentextendsReact.Component{// 定義一個繼承于react頂層Component的新組件AppComponentconstructor(props){super(props)// super,調用父類構造函數改變this指向}//這是一個基于es6的react組件render(){return

? ? ? ? ? ? 返回值最外層必須是閉合標簽? ? ?

? }}或者importReact,{Component}? from'react';// 解構classAppComponentextendsComponent{// 定義一個繼承于react頂層Component的新組件AppComponentconstructor(props){super(props)// super,調用父類構造函數改變this指向}//這是一個基于es6的react組件render(){return

? ? ? ? ? ? 返回值最外層必須是閉合標簽? ? ?

? }}# 上面兩種寫法同理,只是第二種使用了es6的另一個特性,對象的解構,將Component從React對象中解構出來。

至此,一個基于es6的react組件誕生了。

2,class組件與createClass組件的比較

class組件的用法和createClass組件的用法基本一致,它同樣有生命周期,有驗證器,同樣可以自定義方法等等。

createClass組件到class組件的變遷,實際上是es5到es6的變遷,基本上是語法上的變遷和功能上的拓展。組件的本質是沒有改變的,只是寫法稍有不同。

下面來說說這些不同點。

創建組件的方式不同

一個是使用es5封裝的createClass方法來創建,一個是使用es6的class特性來創建。

上面已經詳細闡述過,這點就不過多闡述了。

定義初始state的方式不同

createClass組件是使用getInitialState方法來定義初始state的,如下:

varAppComponent = React.createClass({? ? getInitialState:function(){return{? ? ? ? ? ? loadding:false,? ? ? ? ? ? isshow:false,? ? ? ? ? ? data:null……? ? ? }? ? ? ? ? }? })

class組件定義初始state則大不相同,可分為兩種,如下:

# 第一種,構造函數內定義,這是es6的實現classAppComponentextendsReact.Component{? constructor(props){super(props);// 調用父類的構造函數,改變this指向this.state = {? ? ? ? loadding:false,? ? ? ? isshow:false,? ? ? ? data:null,? ? ? ? ……? ? ? }? }? ……}# 第二種,直接定義靜態屬性,這是es7的實現,更為簡單,實用classAppComponentextendsReact.Component{? state = {? ? ? ? loadding:false,? ? ? ? isshow:false,? ? ? ? data:null……? }? ? ……}# es6規定是只有靜態方法,沒有靜態屬性的。但是es7可以定義靜態屬性,可通過babel轉換實現。# 要使用es7也很簡單,通常安裝和配置babel-preset-stage-0即可使用所有的es6/7新特性

react組件內es5和es6的不同寫法

直接舉個粟子說明,以class組件為例:

# es5寫法classAppComponentextendsReact.Component{? componentDidMount: function(){? }? renderSubmit: function(){? }? ……? render: function(){return

hello

? }}# es6寫法,有2種// 寫法一classAppComponentextendsReact.Component{? ? ……? ? componentDidMount(){? ? }? ? renderSubmit(){? }? ……? render(){return

hello

? }}/*

提示:

componentDidMount(){

}

這種寫法相當于es5的:

componentDidMount: function(){

}

*/# 寫法二,使用箭頭函數,似乎高逼格一些classAppComponentextendsReact.Component{? ? ……? ? componentDidMount=()=>{? ? }? ? renderSubmit=()=>{? }? ……? render=()=>{return

hello

? }}# 提示:class嚴格來講不是一個對象,class內定義的屬性和方法并不需要用逗號','隔開。

三, stateless組件寫法

前言

所謂stateless組件,也就是無狀態組件。

這種react組件有一個特點,它沒有生命周期方法,沒有render方法,連state也沒有,this也沒有,也不需要實例化。

為什么需要這樣的組件?

很多時候,從業務上考慮,我們的某些組件只用于純UI展示,并沒有涉及到生命周期,也不需要setState,

但是react組件本身依然存在生命周期方法等一大堆組件本身的設定,然而這些設定我們根本不需要用到的,它們的存在造成了資源浪費,多余,和臃腫。

另外組件實例化是需要占用內存,消耗性能的。

因此,考慮到不同業務需求,后來react增加了stateless組件的支持。

stateless組件本質是一個函數,它沒有生命周期,也不需要實例化,沒有this指向, 更輕盈,性能更加好。

這種組件是所有react組件中性能最好的組件類型。官方也推薦多用這種組件。

stateless組件的定義

stateless組件本質是一個帶有返回值的函數,而且必須是使用閉合標簽包裹的返回值。

下面來舉個粟子:

constAppComponent =(props) =>{// 一些邏輯return

這是一個干凈純潔的stateless組件

}

這樣就定義了一個常見的stateless組件。發現沒,這就是一個函數。

有沒有覺得非常干凈,整潔,高逼格了許多?

stateless組件的使用和前面兩種是一樣的,直接來個粟子:

同樣,你還可以給它加驗證器:

AppComponent.propTypes= {? ? data: React.PropTypes.array,// 驗證傳入的data是不是數組,如果不符合則會報錯loadding: React.PropTypes.bool,// 驗證傳入的loadding是否為trueloadData: React.PropTypes.func,// 驗證傳入的loadData是否為函數類型}

如何獲取props屬性?

首先來看看如何在調用組件時傳入props屬性:

上面組件調用時傳入了data, loadding, loadData三個props屬性。這些傳入的屬性最終都會被收集到組件的props對象里面。

需要注意的是,stateless組件的props是通過傳參傳進去的,因為它本身是一個函數,沒有this

而獲取props屬性的方式也五花八門,下面直接看個粟子:

# 第一種,在內部展開獲取constAppComponent= (props) =>{const{ data, loadding, loadData } = props; // 通過es6的對象解構賦值的寫法,直接從props獲取到? ? /*? ? 上面等同于:constdata = props.data;constloadding = props.loadding;constloadData = props.loadData;? ? */? ? ……return

? ? ? 這是一個干凈純潔的stateless組件?

? }# 第二種,高逼格一些,直接從參數里解構出來,如下:constAppComponent= ({ data, loadding, loadData }) =>{? ? ……return

? ? ? 這是一個干凈純潔的stateless組件?

? }? 或者,如果參數較多,可以寫得優雅一些:constAppComponent= ({? ? data,? ? loadding,? ? loadData? }) =>{? ? ……return

? ? ? 這是一個干凈純潔的stateless組件?

? }

使用反向數據流來實現setState功能

stateless組件本身沒有生命周期,也無法設置state,那是否意味著stateless組件不能使用setState功能呢?

答案是可以實現,不過是間接使用。實現的方式是——反向數據流。

有兩個前提條件,一個是必須依賴一個父組件,二是這個父組件不是stateless組件,它有生命周期。

在實踐之前,我們先來解決一個疑問,react明明是單向綁定,何來反向數據流?

的確react本身是單向綁定,是自上而下傳遞信息的,也就是從父組件傳遞給子組件,逐級向下傳遞。

如果要實現反向數據流,那么就意味著要實現自下而上傳遞信息,也就是要實現子組件向父組件傳遞信息。

那么,如何做到子組件向父組件傳遞信息?

下面是實現原理:

在父組件中定義一個方法,把這個方法傳遞給子組件,由子組件去觸發這個方法,并且以函數傳參的方式,把需要的信息傳遞給父組件。

這樣就實現了自下而上傳遞的反向數據流。

那么,我們可以使用這個原理來實現stateless組件的setState功能。

下面來個例子:

# 父組件import ChildComponent from'./ChildComponent';// 引入子組件classAppComponentextendsReact.Component{? state = {? ? msg:null,? ? content:null// 初始狀態}? changeLoad(content){// 這個方法是傳遞給子組件調用的,并且子組件會傳遞content過來this.setState({? ? ? msg:'反向傳遞成功',? ? ? content// { content } 等同于 { content: content }})? }? render(){return

? ? ? {this.state.content }? ?



? } }# 子組件const ChildComponent =({ msg, changeLoad })=>{? conststr="我是一段文字,子組件把我這段文字傳給了它的父組件,并在父組件中展示我";return

? { msg?msg:null}? ? ? 點我傳遞str給父組件 // 調用父組件的changeLoad方法并傳遞str給父組件

}

這個例子很簡單。父組件定義了msg和content兩個初始state, 并且定義了一個changeLoad函數,這個函數是傳遞給子組件,由子組件去觸發的。

子組件觸發的時候傳遞新的content給父組件, changeLoad函數接受到新的content就會通過setState功能去更新舊的content,以及msg提示。

此時父組件就會展示子組件傳遞過來的content,并傳遞msg通知子組件。msg就是被改變的props屬性。

這樣不僅實現了正向傳遞,也實現了反向傳遞。

四,不同組件的最佳應用方式

寫react組件,推薦使用class組件和stateless組件。createClass組件盡量少用或者不用。

下面來說說這三種組件的應用場景。

createClass

已不推薦使用,這里不再多講。但你仍需要了解它,因為你可能會接觸到一些舊項目,或者一些舊的開源項目,這些項目大都采用createClass寫法。

再加上es6受限于兼容性問題而尚未普及,所以你可能接觸到較多的createClass組件,你有必要去了解它。

class組件和stateless組件互為替補

這兩種組件通常要搭配使用,互為替補,這是react組件最好的應用方式。

在實踐前,先來講講兩個概念,分別是——容器組件,展示組件。

也就是Container Component和 Presentational Component

所謂Container Component(容器組件),簡單來講就是它通常是作為一個父組件,它底下領著一群子組件。容器組件(父組件)的作用在于,給子組件傳遞數據,并且定義邏輯方法傳遞給子組件。

子組件不參與或少參與業務邏輯處理,它主要負責接收和展示容器組件傳遞過來的數據,以及調用傳遞過來的邏輯方法。

而這里所說的子組件,通常就是展示型組件,也就是Presentational Component。

這里為什么說class組件和stateless組件搭配使用,互為替補,是react組件的最好應用方式呢?

首先,stateless組件沒有生命周期,無實例化,性能最好。而展示組件通常只需要做數據展示,和邏輯方法調用,它并不需要使用到生命周期方法。

這不正和stateless組件最為契合嗎?

于是,stateless組件,通常用作于展示組件。

再來說說class組件,它有生命周期,再搭配es6/7語法,它可以處理眾多復雜的業務邏輯。

而容器組件通常只專注于處理業務邏輯,需要使用生命周期,對于數據的展示則交給展示組件。這正和class組件最為契合。于是,通常class組件作為容器組件。

這兩種組件,各自分工,互為替補,是react組件最好的應用方式。

下面是一個例子:

# 容器組件// /containers/index.jsimport ListTableComponent from'/components/ListTable';import ItemTableComponent from'/components/ItemTable';classAppComponentextendsReact.Component{? state = {? ? ? ? loadding:false}render =()=>{const{list, item } = this.props;consthanddleLoad = ()=>{// ToDo}constlistProps = {list, handdleLoad };constitemProps = { item, handdleLoad };return


}}# 展示組件一//? /components/ListTable.jsconstListTable = ({list,handdleLoad })=>{return

? ? ? ? ? ……? ? ? ? ? ? {list.coutry}? ? ? ? ? ? {list.address}? ? ? ? ? ……? ? ? 做一些事情? ?

}# 展示組件二//? /components/ItemTable.jsconstItemTable = ({ item,handdleLoad })=>{return

? ? ? ? ? ……? ? ? ? ? ? {item.name}? ? ? ? ? ? {item.age}? ? ? ? ? ……? ? ? 做一些事情? ?

}

這個例子中,容器組件index.js載入了兩個子組件,這兩個是展示組件,容器組件定義了handdleLoad方法,并從props拿到數據list和item兩個數據源,再組織成listProps和itemProps兩個props屬性對象,并把他們分別傳給ListTable和ItemTable兩個展示組件。

這兩個展示組件從props中拿到傳過來的數據,并在render方法中展示出來,并不需要處理過多業務邏輯。

總結

react組件的演進,依賴于js語法的演進,隨著es6/7的到來,react組件的寫法變得多樣,且更為高效便捷。

因此,多結合es6/7語法,多使用stateless組件,可使你的react應用更上一層樓。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容