當前位置:
首頁 > 科技 > 解剖react組件的多種寫法與演進

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

前言

8月8號,還記得這特殊的日子嗎?今日早讀文章由@sessionboy授權分享。

正文從這開始~

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

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

一,原始的createClass寫法

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

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

varReact=require( react );

varReactDOM=require( react-dom );

varAppComponent=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()// 定義你的初始state

componentDidMount()// 在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是否為true

loadData:React.PropTypes.func,// 驗證loadData是否為函數類型

},

getDefaultProps:function(){

// 一些邏輯

},

getInitialState:function(){

// 一些邏輯

},

……

render:function(){

return(

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

)

}

})

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

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

data=[]

loadding={true}

loadData={函數}

/>

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

4, 自定義方法

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

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

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

varAppComponent=React.createClass({

propTypes:{

data:React.PropTypes.array,// 驗證傳入的data是不是數組,如果不是則會報錯

loadding:React.PropTypes.bool,// 驗證loadding是否為true

loadData: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的新組件AppComponent

constructor(props){

super(props)// 調用super父類構造函數改變this指向

}

//這是一個基於es6的react組件

render(){

return

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

}

}

或者

importReact,{Component}from react ;// 解構

classAppComponentextendsComponent{// 定義一個繼承於react頂層Component的新組件AppComponent

constructor(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(){

returnhello

}

}

# es6寫法,有2種

// 寫法一

classAppComponentextendsReact.Component{

……

componentDidMount(){

}

renderSubmit(){

}

……

render(){

returnhello

}

}

/*

提示:

componentDidMount(){

}

這種寫法相當於es5的:

componentDidMount: function(){

}

*/

# 寫法二,使用箭頭函數,似乎高逼格一些

classAppComponentextendsReact.Component{

……

componentDidMount=()=>{

}

renderSubmit=()=>{

}

……

render=()=>{

returnhello

}

}

# 提示: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是否為true

loadData:React.PropTypes.func,// 驗證傳入的loadData是否為函數類型

}

如何獲取props屬性?

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

data={[]}

loadding={true}

loadData={函數}

/>

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

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

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

# 第一種,在內部展開獲取

constAppComponent=(props)=>{

const{data,loadding,loadData}=props;// 通過es6的對象解構賦值的寫法,直接從props獲取到

/*

上面等同於:

const data = props.data;

const loadding = props.loadding;

const loadData = 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功能。 下面來個例子:

# 父組件

importChildComponentfrom ./ChildComponent ;// 引入子組件

classAppComponentextendsReact.Component{

state={

msg:null,

content:null// 初始狀態

}

changeLoad(content){// 這個方法是傳遞給子組件調用的,並且子組件會傳遞content過來

this.setState({

msg: 反向傳遞成功 ,

content// { content } 等同於 { content: content }

})

}

render(){

return

{this.state.content}

msg={msg}

changeLoad={this.changeLoad}// 調用子組件並傳遞msg以及changeLoad方法

/>

}

}

# 子組件

constChildComponent=({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.js

importListTableComponentfrom /components/ListTable ;

importItemTableComponentfrom /components/ItemTable ;

classAppComponentextendsReact.Component{

state={

loadding:false

}

render=()=>{

const{list,item}=this.props;

consthanddleLoad=()=>{

// ToDo

}

constlistProps={list,handdleLoad};

constitemProps={item,handdleLoad};

return

{...listProps}// 『...』 是es7的展開屬性運算符,

/>

{...itemProps}

/>

}

}

# 展示組件一

// /components/ListTable.js

constListTable=({list,handdleLoad})=>{

return

……

{list.coutry}

{list.address}

……

做一些事情

}

# 展示組件二

// /components/ItemTable.js

constItemTable=({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應用更上一層樓。

關於本文

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端早讀課 的精彩文章:

漫談前端體系建設
關於載入設計,你要知道的8種策略和4種樣式
事件代理:模式 or 反模式?
2017 年了,這麼多前端框架,你會怎樣選擇?
高性能視差滾動

TAG:前端早讀課 |

您可能感興趣

React Native 彈出框組件
一文詳解 React 組件類型
組件化系統快速應用之SpringBoot實踐,這些你都了解嗎?
對select組件的封裝
學習es7的Decorator(順帶寫個react高階組件)
Kubernetes組件
一個製作Web圖案的組件css-doodle
Google更新Flutter,添加網頁開發工具組件
組件、Prop和State
Spring核心——Stereotype組件與Bean掃描
推薦 9 個樣式化組件的 React UI 庫
Mozilla Firefox 將很快獲得包含 Tor 模式的擴展組件
Nintendo Labo 推出全新 Switch VR 組件
最全面跨組件數據Apache Atlas實現跨組件沿襲Apache Hadoop
容器化分散式日誌組件ExceptionLess的Angular前端UI
總結:iview(基於vue.js的開源ui組件)學習的一些坑
Nike VaporMax 氣墊組件是如何製成的?
Nike VaporMax氣墊組件是如何製成的?
Windows系統的JScript組件被曝存在一個0day RCE
Vue.js 組件中的v-on綁定自定義事件理解