這一節的內容會比較多,如果是剛剛入門的新同學一時半會兒可能會接受不了,而且基本都屬于理論知識。愛掏網 - it200.com如果你在閱讀時發現理解有困難也不需要灰心,可以把本篇教程當作隨時可供查閱的文檔,等到你在實踐中積累了一定的代碼量之后再回過頭來閱讀文章就會感覺非常輕松啦。愛掏網 - it200.com
- 元素與組件 Element & Component
- 函數定義與類定義組件 Functional & Class
- 展示與容器組件 Presentational & Container
- 有狀態與無狀態組件 Stateful & Stateless
- 受控與非受控組件 Controlled & Uncontrolled
- 組合與繼承 Composition & Inheritance
元素
元素是構建React應用的最小單位。愛掏網 - it200.com元素所描述的也就是你在瀏覽器中能夠看到的東西。愛掏網 - it200.com根據我們在上節課中講到的內容,我們在編寫React代碼時一般用JSX來描述React元素。愛掏網 - it200.com
在作用上,我們可以把React元素理解為DOM元素;但實際上,React元素只是JS當中普通的對象。愛掏網 - it200.comReact內部實現了一套叫做React DOM的東西,或者我們稱之為Virtual DOM也就是虛擬DOM。愛掏網 - it200.com通過一個樹狀結構的JS對象來模擬DOM樹。愛掏網 - it200.com
說到這里我們可以稍微講一下,React為什么會有這一層虛擬DOM呢?在課程介紹中我們曾經提到過,React很快、很輕。愛掏網 - it200.com它之所以快就是因為這一套虛擬DOM的存在,React內部還實現了一個低復雜度高效率的Diff
算法,不同于以往框架,例如Angular使用的臟檢查。愛掏網 - it200.com在應用的數據改變之后,React會盡力少地比較,然后根據虛擬DOM只改變真實DOM中需要被改變的部分。愛掏網 - it200.comReact也藉此實現了它的高效率,高性能。愛掏網 - it200.com
當然這不是虛擬DOM唯一的意義,通過這一層單獨抽象的邏輯讓React有了無限的可能,就比如React Native的實現,可以讓你只掌握JS的知識也能在其他平臺系統上開發應用,而不只是寫網頁,甚至是之后會出現的React VR或者React物聯網等等別的實現。愛掏網 - it200.com
話說回來,元素也就是React DOM之中描述UI界面的最小單位。愛掏網 - it200.com剛才我們說到了,元素其實就是普通的JS對象。愛掏網 - it200.com不過我們用JSX來描述React元素在理解上可能有些困難,事實上,我們也可以不使用JSX來描述:
const element = Hello, world
;
// 用JSX描述就相當于是調用React的方法創建了一個對象
const element = React.createElement('h1', null, 'Hello, world');
組件
要注意到,在React當中元素和組件是兩個不同的概念,之所以在前面講了這么多,就是擔心大家不小心會混淆這兩個概念。愛掏網 - it200.com首先我們需要明確的是,組件是構建在元素的基礎之上的。愛掏網 - it200.com
React官方對組件的定義呢,是指在UI界面中,可以被獨立劃分的、可復用的、獨立的模塊。愛掏網 - it200.com其實就類似于JS當中對function
函數的定義,它一般會接收一個名為props
的輸入,然后返回相應的React元素,再交給ReactDOM
,最后渲染到屏幕上。愛掏網 - it200.com
新版本的React里提供了兩種定義組件的方法。愛掏網 - it200.com當然之前的React.createClass
也可以繼續用,不過我們在這里先不納入我們討論的范圍。愛掏網 - it200.com
第一種函數定義組件,非常簡單啦,我們只需要定義一個接收props
傳值,返回React元素的方法即可:
function Title(props) {
return Hello, {props.name}
}
甚至使用ES6的箭頭函數簡寫之后可以變成這樣:
const Title = props => Hello, {props.name}
第二種是類定義組件,也就是使用ES6中新引入的類的概念來定義React組件:
class Title extends React.Component {
render() {
return Hello, {this.props.name}
}
}
之后呢,根據我們在上一節課中了解到的,組件在定義好之后,可以通過JSX描述的方式被引用,組件之間也可以相互嵌套和組合。愛掏網 - it200.com
接下來我們還會介紹一些更深入的關于組件概念,現在聽起來可能會比較抽象枯燥,不過接下來要介紹的這幾個概念在之后的課程中都是會被應用到的,同學們也可以根據自己的實際情況,在閱讀完后續的課程之后,再返回來看,相信一定會對你理解React有所幫助。愛掏網 - it200.com
首先是最重要的一組概念:展示組件與容器組件。愛掏網 - it200.com同樣,在課程介紹中我們提到的,React并不是傳統的MVVM框架,它只是在V
層,視圖層上下功夫。愛掏網 - it200.com同學們應該對MVVM或MVC都有所了解,那么既然我們的框架現在只有V
層的話,在實際開發中應該如何處理數據與視圖的關系呢?
為了解決React只有V
層的這個問題,更好地區分我們的代碼邏輯,展示組件與容器組件這一對概念就被引入了。愛掏網 - it200.com這同樣也是我們在開發React應用時的最佳實踐。愛掏網 - it200.com
我們還是先看一個具體的例子來解釋這兩個概念:
class CommentList extends React.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)}
}
}
這是一個回復列表組件,乍看上去很正常也很合理。愛掏網 - it200.com但實際上在開發React應用時,我們應該避免寫出這樣的組件,因為這類組件擔負的功能太多了。愛掏網 - it200.com它只是一個單一的組件,但需要同時負責初始化state
,通過ajax
獲取服務器數據,渲染列表內容,在實際應用中,可能還會有更多的功能依賴。愛掏網 - it200.com這樣,在后續維護的時候,不管是我們要修改服務器數據交互還是列表樣式內容,都需要去修改同一個組件,邏輯嚴重耦合,多個功能在同一個組件中維護也不利于團隊協作。愛掏網 - it200.com
通過應用展示組件與容器組件的概念,我們可以把上述的單一組件重構為一個展示回復列表組件和回復列表容器:
// 展示組件
class CommentList extends React.Component {
constructor(props) {
super(props)
}
renderComment({body, author}) {
return {body}—{author}
}
render() {
return {this.props.comments.map(this.renderComment)}
}
}
// 容器組件
class CommentListContainer extends React.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
}
}
像這樣回復列表如何展示與如何獲取回復數據的邏輯就被分離到兩個組件當中了。愛掏網 - it200.com我們再來明確一下展示組件和容器組件的概念:
展示組件
- 主要負責組件內容如何展示
- 從
props
接收父組件傳遞來的數據 - 大多數情況可以通過函數定義組件聲明
容器組件
- 主要關注組件數據如何交互
- 擁有自身的
state
,從服務器獲取數據,或與Redux等其他數據處理模塊協作 - 需要通過類定義組件聲明,并包含生命周期函數和其他附加方法
那么這樣寫具體有什么好處呢?
- 解耦了界面和數據的邏輯
- 更好的可復用性,比如同一個回復列表展示組件可以套用不同數據源的容器組件
- 利于團隊協作,一個人負責界面結構,一個人負責數據交互
有狀態組件
意思是這個組件能夠獲取儲存改變應用或組件本身的狀態數據,在React當中也就是state
,一些比較明顯的特征是我們可以在這樣的組件當中看到對this.state
的初始化,或this.setState
方法的調用等等。愛掏網 - it200.com
無狀態組件
這樣的組件一般只接收來自其他組件的數據。愛掏網 - it200.com一般這樣的組件中只能看到對this.props
的調用,通常可以用函數定義組件的方式聲明。愛掏網 - it200.com它本身不會掌握應用的狀態數據,即使觸發事件,也是通過事件處理函數傳遞到其他有狀態組件當中再對state
進行操作。愛掏網 - it200.com
我們還是來看具體的例子比較能清楚地說明問題,與此同時,我們已經介紹了三組概念,為了防止混淆,我這里特意使用了兩個展示組件來做示例,其中一個是有狀態組件,另一個是無狀態組件,也是為了證明,并不是所有的展示組件都是無狀態組件,所有的容器組件都是有狀態組件。愛掏網 - it200.com再次強調一下,這是兩組不同的概念,以及對組件不同角度的劃分方式。愛掏網 - it200.com
// 有狀態組件
class StatefulLink extends React.Component {
constructor(props) {
super(props)
this.state = {
active: false
}
}
handleClick() {
this.setState({
active: !this.state.active
})
}
render() {
return
Stateful Link
}
}
// 無狀態組件
class StatelessLink extends React.Component {
constructor(props) {
super(props)
}
handleClick() {
this.props.handleClick(this.props.router)
}
render() {
const active = this.props.activeRouter === this.props.router
return (
Stateless Link
)
}
}
class Nav extends React.Component {
constructor() {
super()
this.state={activeRouter: 'home'}
}
handleSwitch(router) {
this.setState({activeRouter: router})
}
render() {
return (
)
}
}
上述的例子可能稍有些復雜,事實上,在React的實際開發當中,我們編寫的組件大部分都是無狀態組件。愛掏網 - it200.com畢竟React的主要作用是編寫用戶界面。愛掏網 - it200.com再加上ES6的新特性,絕大多數的無狀態組件都可以通過箭頭函數簡寫成類似下面這樣:
// function SimpleButton(props) {
// return
// }
const SimpleButton = props =>
受控組件
一般涉及到表單元素時我們才會使用這種分類方法,在后面一節課程表單及事件處理中我們還會再次談論到這個話題。愛掏網 - it200.com受控組件的值由props
或state
傳入,用戶在元素上交互或輸入內容會引起應用state
的改變。愛掏網 - it200.com在state
改變之后重新渲染組件,我們才能在頁面中看到元素中值的變化,假如組件沒有綁定事件處理函數改變state
,用戶的輸入是不會起到任何效果的,這也就是“受控”的含義所在。愛掏網 - it200.com
非受控組件
類似于傳統的DOM表單控件,用戶輸入不會直接引起應用state
的變化,我們也不會直接為非受控組件傳入值。愛掏網 - it200.com想要獲取非受控組件,我們需要使用一個特殊的ref
屬性,同樣也可以使用defaultValue
屬性來為其指定一次性的默認值。愛掏網 - it200.com
我們還是來看具體的例子:
class ControlledInput extends React.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 (
)
}
}
class UncontrolledInput extends React.Component {
constructor() {
super()
}
handleChange() {
console.log('Uncontrolled change:',this.input.value)
}
render() {
return (
)
}
}
通常情況下,React當中所有的表單控件都需要是受控組件。愛掏網 - it200.com但正如我們對受控組件的定義,想讓受控組件正常工作,每一個受控組件我們都需要為其編寫事件處理函數,有的時候確實會很煩人,比方說一個注冊表單你需要寫出所有驗證姓名電話郵箱驗證碼的邏輯,當然也有一些小技巧可以讓同一個事件處理函數應用在多個表單組件上,但生產開發中并沒有多大實際意義。愛掏網 - it200.com更有可能我們是在對已有的項目進行重構,除了React之外還有一些別的庫需要和表單交互,這時候使用非受控組件可能會更方便一些。愛掏網 - it200.com
前面我們已經提到了,React當中的組件是通過嵌套或組合的方式實現組件代碼復用的。愛掏網 - it200.com通過props
傳值和組合使用組件幾乎可以滿足所有場景下的需求。愛掏網 - it200.com這樣也更符合組件化的理念,就好像使用互相嵌套的DOM元素一樣使用React的組件,并不需要引入繼承的概念。愛掏網 - it200.com
當然也不是說我們的代碼不能這么寫,來看下面這個例子:
// Inheritance
class InheritedButton extends React.Component {
constructor() {
super()
this.state = {
color: 'red'
}
}
render() {
return (
)
}
}
class BlueButton extends InheritedButton {
constructor() {
super()
this.state = {
color: '#0078e7'
}
}
}
// Composition
const CompositedButton = props =>
const YellowButton = () =>
但繼承的寫法并不符合React的理念。愛掏網 - it200.com在React當中props
其實是非常強大的,props
幾乎可以傳入任何東西,變量、函數、甚至是組件本身:
function SplitPane(props) {
return (
{props.left}
{props.right}
)
}
function App() {
return (
}
right={
} />
)
}
React官方也希望我們通過組合的方式來使用組件,如果你想實現一些非界面類型函數的復用,可以單獨寫在其他的模塊當中在引入組件進行使用。愛掏網 - it200.com