React之組件類型
前言
今日早讀文章由前端早讀課專欄作者@余博倫分享。
正文從這開始~
本文介紹了 React 當中有關組件的一系列概念,結合你實踐當中編寫的代碼可以更好地理解襖~
元素與組件 Element & Component
函數定義與類定義組件 Functional & Class
展示與容器組件 Presentational & Container
有狀態與無狀態組件 Stateful & Stateless
受控與非受控組件 Controlled & Uncontrolled
組合與繼承 Composition & Inheritance
元素與組件 Element & Component
元素
元素是構建React應用的最小單位。元素所描述的也就是你在瀏覽器中能夠看到的東西。根據我們在上節課中講到的內容,我們在編寫React代碼時一般用JSX來描述React元素。
在作用上,我們可以把React元素理解為DOM元素;但實際上,React元素只是JS當中普通的對象。React內部實現了一套叫做React DOM的東西,或者我們稱之為Virtual DOM也就是虛擬DOM.通過一個樹狀結構的JS對象來模擬DOM樹。
說到這裡我們可以稍微講一下,React為什麼會有這一層虛擬DOM呢?在課程介紹中我們曾經提到過,React很快、很輕。它之所以快就是因為這一套虛擬DOM的存在,React內部還實現了一個低複雜度高效率的Diff演算法,不同於以往框架,例如angular使用的臟檢查。在應用的數據改變之後,React會儘力少地比較,然後根據虛擬DOM只改變真實DOM中需要被改變的部分。React也藉此實現了它的高效率,高性能。
當然這不是虛擬DOM唯一的意義,通過這一層單獨抽象的邏輯讓React有了無限的可能,就比如react native的實現,可以讓你只掌握JS的知識也能在其他平台系統上開發應用,而不只是寫網頁,甚至是之後會出現的React VR或者React物聯網等等別的實現。
話說回來,元素也就是React DOM之中描述UI界面的最小單位。剛才我們說到了,元素其實就是普通的JS對象。不過我們用JSX來描述React元素在理解上可能有些困難,事實上,我們也可以不使用JSX 來描述:
constelement=Hello,world
// 用JSX描述就相當於是調用React的方法創建了一個對象
constelement=React.createElement( h1 ,null, Hello, world )
組件
要注意到,在React當中元素和組件是兩個不同的概念,之所以在前面講了這麼多,就是擔心大家不小心會混淆這兩個概念。首先我們需要明確的是,組件是構建在元素的基礎之上的。
React官方對組件的定義呢,是指在UI界面中,可以被獨立劃分的、可復用的、獨立的模塊。其實就類似於JS當中對function函數的定義,它一般會接收一個名為props的輸入,然後返回相應的React元素,再交給ReactDOM,最後渲染到屏幕上。
函數定義與類定義組件 Functional & Class
新版本的React里提供了兩種定義組件的方法。當然之前的React.createClass也可以繼續用,不過我們在這裡先不納入我們討論的範圍。
第一種函數定義組件,非常簡單啦,我們只需要定義一個接收props傳值,返回React元素的方法即可:
functionTitle(props){
returnHello,{props.name}
}
甚至使用ES6的箭頭函數簡寫之後可以變成這樣:
constTitle=props=>Hello,{props.name}
第二種是類定義組件,也就是使用ES6中新引入的類的概念來定義React 組件:
之後呢,根據我們在上一節課中了解到的,組件在定義好之後,可以通過JSX描述的方式被引用,組件之間也可以相互嵌套和組合。
展示與容器組件 Presentational & Container
首先是最重要的一組概念:展示組件與容器組件。同樣,在課程介紹中我們提到的,React並不是傳統的MVVM框架,它只是在V層,視圖層上下功夫。同學們應該對MVVM或MVC都有所了解,那麼既然我們的框架現在只有V層的話,在實際開發中應該如何處理數據與視圖的關係呢?
為了解決React只有V層的這個問題,更好地區分我們的代碼邏輯,展示組件與容器組件這一對概念就被引入了。這同樣也是我們在開發React應用時的最佳實踐。
我們還是先看一個具體的例子來解釋這兩個概念:
classCommentListextendsReact.Component{
constructor(props){
super(props)
this.state={comments:[]}
}
componentDidMount(){
$.ajax({
url:"/my-comments.json",
dataType: json ,
success:function(comments){
this.setState({comments:comments})
}.bind(this)
})
}
renderComment({body,author}){
return{body}—{author}
}
render(){
return{this.state.comments.map(this.renderComment)}
}
}
這是一個回複列表組件,乍看上去很正常也很合理。但實際上在開發React應用時,我們應該避免寫出這樣的組件,因為這類組件擔負的功能太多了。它只是一個單一的組件,但需要同時負責初始化state,通過ajax獲取伺服器數據,渲染列表內容,在實際應用中,可能還會有更多的功能依賴。這樣,在後續維護的時候,不管是我們要修改伺服器數據交互還是列表樣式內容,都需要去修改同一個組件,邏輯嚴重耦合,多個功能在同一個組件中維護也不利於團隊協作。
通過應用展示組件與容器組件的概念,我們可以把上述的單一組件重構為一個展示回複列表組件和回複列表容器:
// 展示組件
classCommentListextendsReact.Component{
constructor(props){
super(props)
}
renderComment({body,author}){
return{body}—{author}
}
render(){
return{this.props.comments.map(this.renderComment)}
}
}
// 容器組件
classCommentListContainerextendsReact.Component{
constructor(){
super()
this.state={comments:[]}
}
componentDidMount(){
$.ajax({
url:"/my-comments.json",
dataType: json ,
success:function(comments){
this.setState({comments:comments})
}.bind(this)
})
}
render(){
return
}
}
展示組件
主要負責組件內容如何展示
從props接收父組件傳遞來的數據
大多數情況可以通過函數定義組件聲明
容器組件
主要關注組件數據如何交互
擁有自身的state,從伺服器獲取數據,或與redux等其他數據處理模塊協作
需要通過類定義組件聲明,並包含生命周期函數和其他附加方法
那麼這樣寫具體有什麼好處呢?
解耦了界面和數據的邏輯
更好的可復用性,比如同一個回複列表展示組件可以套用不同數據源的容器組件
利於團隊協作,一個人負責界面結構,一個人負責數據交互
有狀態與無狀態組件 Stateful & Stateless
有狀態組件
意思是這個組件能夠獲取儲存改變應用或組件本身的狀態數據,在React當中也就是state,一些比較明顯的特徵是我們可以在這樣的組件當中看到對this.state的初始化,或this.setState方法的調用等等。
無狀態組件
這樣的組件一般只接收來自其他組件的數據。一般這樣的組件中只能看到對this.props的調用,通常可以用函數定義組件的方式聲明。它本身不會掌握應用的狀態數據,即使觸發事件,也是通過事件處理函數傳遞到其他有狀態組件當中再對state進行操作。
我們還是來看具體的例子比較能清楚地說明問題,與此同時,我們已經介紹了三組概念,為了防止混淆,我這裡特意使用了兩個展示組件來做示例,其中一個是有狀態組件,另一個是無狀態組件,也是為了證明,並不是所有的展示組件都是無狀態組件,所有的容器組件都是有狀態組件。再次強調一下,這是兩組不同的概念,以及對組件不同角度的劃分方式。
// Stateful Component
classStatefulLinkextendsReact.Component{
constructor(props){
super(props)
this.state={
active:false
}
}
handleClick(){
this.setState({
active:!this.state.active
})
}
render(){
return
style={{color:this.state.active? red : black }}
onClick={this.handleClick.bind(this)}
>
Stateful Link
}
}
// Stateless Component
classStatelessLinkextendsReact.Component{
constructor(props){
super(props)
}
handleClick(){
this.props.handleClick(this.props.router)
}
render(){
constactive=this.props.activeRouter===this.props.router
return(
style={{color:active? red : black }}
onClick={this.handleClick.bind(this)}
>
Stateless Link
)
}
}
classNavextendsReact.Component{
constructor(){
super()
this.state={activeRouter: home }
}
handleSwitch(router){
this.setState({activeRouter:router})
}
render(){
return(
)
}
}
上述的例子可能稍有些複雜,事實上,在React的實際開發當中,我們編寫的組件大部分都是無狀態組件。畢竟React的主要作用是編寫用戶界面。再加上ES6的新特性,絕大多數的無狀態組件都可以通過箭頭函數簡寫成類似下面這樣:
/* function SimpleButton(props) {
return
} */
constSimpleButton=props=>{props.text}
受控與非受控組件 Controlled & Uncontrolled
受控組件
一般涉及到表單元素時我們才會使用這種分類方法,在後面一節課程表單及事件處理中我們還會再次談論到這個話題。受控組件的值由props或state傳入,用戶在元素上交互或輸入內容會引起應用state的改變。在state改變之後重新渲染組件,我們才能在頁面中看到元素中值的變化,假如組件沒有綁定事件處理函數改變state,用戶的輸入是不會起到任何效果的,這也就是「受控」的含義所在。
非受控組件
類似於傳統的DOM表單控制項,用戶輸入不會直接引起應用state的變化,我們也不會直接為非受控組件傳入值。想要獲取非受控組件,我們需要使用一個特殊的ref屬性,同樣也可以使用defaultValue屬性來為其指定一次性的默認值。
我們還是來看具體的例子:
classControlledInputextendsReact.Component{
constructor(){
super()
this.state={value: Please type here... }
}
handleChange(event){
console.log( Controlled change: ,event.target.value)
this.setState({value:event.target.value})
}
render(){
return(
Controlled Component:
value={this.state.value}
onChange={(e)=>this.handleChange(e)}
/>
)
}
}
classUncontrolledInputextendsReact.Component{
constructor(){
super()
}
handleChange(){
console.log( Uncontrolled change: ,this.input.value)
}
render(){
return(
Uncontrolled Component:
defaultValue= Please type here...
ref={(input)=>this.input=input}
onChange={()=>this.handleChange()}
/>
)
}
}
通常情況下,React當中所有的表單控制項都需要是受控組件。但正如我們對受控組件的定義,想讓受控組件正常工作,每一個受控組件我們都需要為其編寫事件處理函數,有的時候確實會很煩人,比方說一個註冊表單你需要寫出所有驗證姓名電話郵箱驗證碼的邏輯,當然也有一些小技巧可以讓同一個事件處理函數應用在多個表單組件上,但生產開發中並沒有多大實際意義。更有可能我們是在對已有的項目進行重構,除了React之外還有一些別的庫需要和表單交互,這時候使用非受控組件可能會更方便一些。
組合與繼承 Composition & Inheritance
前面我們已經提到了,React當中的組件是通過嵌套或組合的方式實現組件代碼復用的。通過props傳值和組合使用組件幾乎可以滿足所有場景下的需求。這樣也更符合組件化的理念,就好像使用互相嵌套的DOM元素一樣使用React的組件,並不需要引入繼承的概念。
當然也不是說我們的代碼不能這麼寫,來看下面這個例子:
// Inheritance
classInheritedButtonextendsReact.Component{
constructor(){
super()
this.state={
color: red
}
}
render(){
return(
Inherited Button
)
}
}
classBlueButtonextendsInheritedButton{
constructor(){
super()
this.state={
color: #0078e7
}
}
}
// Composition
constCompositedButton=props=>Composited Button
constYellowButton=()=>
但繼承的寫法並不符合React的理念。在React當中props其實是非常強大的,props幾乎可以傳入任何東西,變數、函數、甚至是組件本身:
functionSplitPane(props){
return(
{props.left}
{props.right}
)
}
functionApp(){
return(
left={
}
right={
}/>
)
}
React官方也希望我們通過組合的方式來使用組件,如果你想實現一些非界面類型函數的復用,可以單獨寫在其他的模塊當中在引入組件進行使用。
點擊展開全文


※瀏覽器漏洞挖掘思路
※【第1041期】關鍵請求
※如何進入BATJ前端部?StuQ免費送你價值3000元進階課!
※微交互:APP設計的秘密
※ES6的工廠函數
TAG:前端早讀課 |
※一文詳解 React 組件類型
※opencv Mat類型和BYTE*指針類型互轉
※Redis 數據類型
※python基礎之變數類型number(math模塊)
※solidity之地址類型
※Hibernate 映射枚舉Enum 類型的屬性
※TypeScript基礎之高級類型的可辨識聯合(Discriminated Unions)
※Adobe Reader類型混淆導致代碼執行漏洞分析
※TypeScript 2.8引入條件類型
※「Python」Chapter1 變數和簡單數據類型
※scala學習-泛型、界定、形變、this.type、複合類型、抽象類型
※Chrome 66 新特性:CSS 類型對象模型,非同步剪貼板 API,AudioWorklet,等
※Perl 數據類型
※「Good idea/solution」 類型作文審題
※澳大利亞 Bunjil Place,高難度木曲線營造類型場所/fjmt
※Google出品的Python代碼靜態類型分析器:Pytype
※類型轉換運算符 運算符重載 operator new operator delete
※Kotlin 基本數據類型
※python中list,array,mat,tuple大小及類型
※Shopify賬號有哪些類型?Shopify賬號類型&賬號許可權