解剖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綁定自定義事件理解