作者:胡子大哈
原文鏈接: http://huziketang.com/books/react/lesson1
看了 react.js 小書 第一階段的內(nèi)容,邊看邊做一點記錄,整理了一些知識點。僅供學(xué)習(xí)。
6.使用 JSX 描述 UI 信息
JSX 原理
React.js 把 JavaScript 的語法擴展了一下,讓 JavaScript 語言能夠支持這種直接在 JavaScript 代碼里面編寫類似 HTML 標(biāo)簽結(jié)構(gòu)的語法,這樣寫起來就方便很多了。編譯的過程會把類似 HTML 的 JSX 結(jié)構(gòu)轉(zhuǎn)換成 JavaScript 的對象結(jié)構(gòu)。
使用 React 和 JSX 的時候一定要經(jīng)過編譯的過程。
這里再重復(fù)一遍:所謂的 JSX 其實就是 JavaScript 對象。
JSX 是 JavaScript 語言的一種語法擴展,長得像 HTML,但并不是 HTML。JSX 在編譯的時候會變成相應(yīng)的 JavaScript 對象描述。
react-dom 負責(zé)把這個用來描述 UI 信息的 JavaScript 對象變成 DOM 元素,并且渲染到頁面上。
可以想象有一個叫 react-canvas 可以幫我們把 UI 渲染到 canvas 上,或者是有一個叫 react-app 可以幫我們把它轉(zhuǎn)換成原生的 App(實際上這玩意叫 ReactNative)。
7.組件的 render 方法
React.js 中一切皆組件,用 React.js 寫的其實就是 React.js 組件。
條件返回 JSX 的方式在 React.js 中很常見,組件的呈現(xiàn)方式隨著數(shù)據(jù)的變化而不一樣,你可以利用 JSX 這種靈活的方式隨時組合構(gòu)建不同的頁面結(jié)構(gòu)。
8.組件的組合、嵌套和組件樹
class Title extends Component {
render () {
return (
<h1>React 小書</h1>
)
}
}
class Header extends Component {
render () {
return (
<div>
<Title />
</div>
)
}
}
我們可以直接在 Header 標(biāo)簽里面直接使用 Title 標(biāo)簽。就像是一個普通的標(biāo)簽一樣。
這樣可復(fù)用性非常強,我們可以把組件的內(nèi)容封裝好,然后靈活在使用在任何組件內(nèi)。
自定義的組件都必須要用大寫字母開頭,普通的 HTML 標(biāo)簽都用小寫字母開頭。
組件可以和組件組合在一起,組件內(nèi)部可以使用別的組件。就像普通的 HTML 標(biāo)簽一樣使用就可以。這樣的組合嵌套,最后構(gòu)成一個所謂的組件樹.
9.事件監(jiān)聽
在 React.js 里面監(jiān)聽事件是很容易的事情,你只需要給需要監(jiān)聽事件的元素加上屬性類似于 onClick、onKeyDown 這樣的屬性。
class Title extends Component {
handleClickOnTitle () {
console.log('Click on title.')
}
render () {
return (
<h1 onClick={this.handleClickOnTitle}>React 小書</h1>
)
}
}
在 React.js 不需要手動調(diào)用瀏覽器原生的addEventListener進行事件監(jiān)聽。React.js 幫我們封裝好了一系列的on*的屬性,而且你不需要考慮不同瀏覽器兼容性的問題,React.js 都幫我們封裝好這些細節(jié)了。
React.js 封裝了不同類型的事件,具體可見官方文檔:合成事件。另外要注意的是,這些事件屬性名都必須要用駝峰命名法。
沒有經(jīng)過特殊處理的話,這些on*的事件監(jiān)聽只能用在普通的 HTML 的標(biāo)簽上,而不能用在組件標(biāo)簽上。也就是說,<Header onClick={…} />這樣的寫法不會有什么效果的。這一點要注意,但是有辦法可以做到這樣的綁定,以后我們會提及?,F(xiàn)在只要記住一點就可以了:這些on*的事件監(jiān)聽只能用在普通的 HTML 的標(biāo)簽上,而不能用在組件標(biāo)簽上。
event 對象
和普通瀏覽器一樣,事件監(jiān)聽函數(shù)會被自動傳入一個 event 對象,這個對象和普通的瀏覽器 event 對象所包含的方法和屬性都基本一致。
不同的是 React.js 中的 event 對象并不是瀏覽器提供的,而是它自己內(nèi)部所構(gòu)建的。
class Title extends Component {
handleClickOnTitle (e) {
console.log(e.target.innerHTML)
}
render () {
return (
<h1 onClick={this.handleClickOnTitle}>React 小書</h1>
)
}
}
每次點擊的時候就會打印“React 小書”。
關(guān)于事件中的 this
一般在某個類的實例方法里面的 this 指的是這個實例本身。
如果你想在事件函數(shù)當(dāng)中使用當(dāng)前的實例,你需要手動地將實例方法 bind 到當(dāng)前實例上再傳入給 React.js。
class Title extends Component {
handleClickOnTitle (e) {
console.log(this)
}
render () {
return (
<h1 onClick={this.handleClickOnTitle.bind(this)}>React 小書</h1>
)
}
}
bind會把實例方法綁定到當(dāng)前實例上,然后我們再把綁定后的函數(shù)傳給 React.js 的onClick事件監(jiān)聽。
總結(jié):
- 為 React 的組件添加事件監(jiān)聽是很簡單的事情,你只需要使用 React.js 提供了一系列的 on* 方法即可。
- React.js 會給每個事件監(jiān)聽傳入一個 event 對象,這個對象提供的功能和瀏覽器提供的功能一致,而且它是兼容所有瀏覽器的。
- React.js 的事件監(jiān)聽方法需要手動 bind 到當(dāng)前實例,這種模式在 React.js 中非常常用。
10.組件的 state 和 setState
一個組件的顯示形態(tài)是可以由它數(shù)據(jù)狀態(tài)和配置參數(shù)決定的。React.js 的state就是用來存儲這種可變化的狀態(tài)的。
setState方法由父類 Component 所提供。當(dāng)我們調(diào)用這個函數(shù)的時候,React.js 會更新組件的狀態(tài) state ,并且重新調(diào)用 render 方法,然后再把 render 方法所渲染的最新的內(nèi)容顯示到頁面上。
setState 接受函數(shù)參數(shù)
這里還有要注意的是,當(dāng)你調(diào)用 setState 的時候,React.js 并不會馬上修改 state。而是把這個對象放到一個更新隊列里面,稍后才會從隊列當(dāng)中把新的狀態(tài)提取出來合并到 state 當(dāng)中,然后再觸發(fā)組件更新。
React.js 會把上一個 setState 的結(jié)果傳入一個函數(shù),你就可以使用該結(jié)果進行運算、操作,然后返回一個對象作為更新 state 的對象。
...
handleClickOnLikeButton () {
this.setState((prevState) => {
return { count: 0 }
})
this.setState((prevState) => {
return { count: prevState.count + 1 } // 上一個 setState 的返回是 count 為 0,當(dāng)前返回 1
})
this.setState((prevState) => {
return { count: prevState.count + 2 } // 上一個 setState 的返回是 count 為 1,當(dāng)前返回 3
})
// 最后的結(jié)果是 this.state.count 為 3
}
...
這樣就可以達到上述的利用上一次 setState 結(jié)果進行運算的效果。
setState 合并
上面我們進行了三次 setState,但是實際上組件只會重新渲染一次,而不是三次;這是因為在 React.js 內(nèi)部會把 JavaScript 事件循環(huán)中的消息隊列的同一個消息中的 setState 都進行合并以后再重新渲染組件。
你只需要記住的是:在使用 React.js 的時候,并不需要擔(dān)心多次進行 setState 會帶來性能問題。
11.配置組件的 props
如果想讓組件能適應(yīng)不同場景下的需求,我們就要讓組件具有一定的“可配置”性。
React.js 的props就可以幫助我們達到這個效果。每個組件都可以接受一個 props 參數(shù),它是一個對象,包含了所有你對這個組件的配置。
render () {
const likedText = this.props.likedText || '取消'
const unlikedText = this.props.unlikedText || '點贊'
return (
<button onClick={this.handleClickOnLikeButton.bind(this)}>
{this.state.isLiked ? likedText : unlikedText}
</button>
)
}
如何把 props 傳進去呢?在使用一個組件的時候,可以把參數(shù)放在標(biāo)簽的屬性當(dāng)中,所有的屬性都會作為 props 對象的鍵值:
class Index extends Component {
render () {
return (
<div>
<LikeButton likedText='已贊' unlikedText='贊' />
</div>
)
}
}
JSX 的表達式插入可以在標(biāo)簽屬性上使用。所以其實可以把任何類型的數(shù)據(jù)作為組件的參數(shù),包括字符串、數(shù)字、對象、數(shù)組、甚至是函數(shù)等等。
<LikeButton wordings={{likedText: '已贊', unlikedText: '贊'}} />
現(xiàn)在我們把 likedText 和 unlikedText 這兩個參數(shù)封裝到一個叫 wordings 的對象參數(shù)內(nèi),然后傳入點贊組件中。
render () {
const wordings = this.props.wordings || {
likedText: '取消',
unlikedText: '點贊'
}
return (
<button onClick={this.handleClickOnLikeButton.bind(this)}>
{this.state.isLiked ? wordings.likedText : wordings.unlikedText}
</button>
)
}
一個組件的行為、顯示形態(tài)都可以用 props 來控制,可以達到很好的可配置性。
默認配置 defaultProps
上面的組件默認配置我們是通過||操作符來實現(xiàn)。這種需要默認配置的情況在 React.js 中非常常見,所以 React.js 也提供了一種方式defaultProps,可以方便的做到默認配置。
class LikeButton extends Component {
static defaultProps = {
likedText: '取消',
unlikedText: '點贊'
}
constructor () {
super()
this.state = { isLiked: false }
}
handleClickOnLikeButton () {
this.setState({
isLiked: !this.state.isLiked
})
}
render () {
return (
<button onClick={this.handleClickOnLikeButton.bind(this)}>
{this.state.isLiked
? this.props.likedText
: this.props.unlikedText}
</button>
)
}
}
props 不可變
props 一旦傳入進來就不能改變。
你不能改變一個組件被渲染的時候傳進來的 props。React.js 希望一個組件在輸入確定的 props 的時候,能夠輸出確定的 UI 顯示形態(tài)。
但這并不意味著由 props 決定的顯示形態(tài)不能被修改。組件的使用者可以主動地通過重新渲染的方式把新的 props 傳入組件當(dāng)中,這樣這個組件中由 props 決定的顯示形態(tài)也會得到相應(yīng)的改變。
總結(jié):
- 為了使得組件的可定制性更強,在使用組件的時候,可以在標(biāo)簽上加屬性來傳入配置參數(shù)。
- 組件可以在內(nèi)部通過 this.props 獲取到配置參數(shù),組件可以根據(jù) props 的不同來確定自己的顯示形態(tài),達到可配置的效果。
- 可以通過給組件添加類屬性 defaultProps 來配置默認參數(shù)。
- props 一旦傳入,你就不可以在組件內(nèi)部對它進行修改。但是你可以通過父組件主動重新渲染的方式來傳入新的 props,從而達到更新的效果。
12.state vs props
state 的主要作用是用于組件保存、控制、修改自己的可變狀態(tài)。state 在組件內(nèi)部初始化,可以被組件自身修改,而外部不能訪問也不能修改。你可以認為 state 是一個局部的、只能被組件自身控制的數(shù)據(jù)源。state 中狀態(tài)可以通過 this.setState 方法進行更新,setState 會導(dǎo)致組件的重新渲染。
props 的主要作用是讓使用該組件的父組件可以傳入?yún)?shù)來配置該組件。它是外部傳進來的配置參數(shù),組件內(nèi)部無法控制也無法修改。除非外部組件主動傳入新的 props,否則組件的 props 永遠保持不變。
state 和 props 有著千絲萬縷的關(guān)系。它們都可以決定組件的行為和顯示形態(tài)。但是它們的職責(zé)其實非常明晰分明:state 是讓組件控制自己的狀態(tài),props 是讓外部對組件自己進行配置。
沒有 state 的組件叫無狀態(tài)組件(stateless component),設(shè)置了 state 的叫做有狀態(tài)組件(stateful component)。因為狀態(tài)會帶來管理的復(fù)雜性,我們盡量多地寫無狀態(tài)組件,盡量少地寫有狀態(tài)的組件。
React.js 非常鼓勵無狀態(tài)組件,在 0.14 版本引入了函數(shù)式組件 —— 一種定義不能使用 state 組件,你可以理解函數(shù)式組件就是一種只能接受 props 和提供 render 方法的類組件。
13.渲染列表數(shù)據(jù)
渲染存放 JSX 元素的數(shù)組
JSX 的表達式插入 {} 里面可以放任何數(shù)據(jù),如果我們往 {} 里面放一個存放 JSX 元素的數(shù)組會怎么樣?
class Index extends Component {
render () {
return (
<div>
{[
<span>React.js </span>,
<span>is </span>,
<span>good</span>
]}
</div>
)
}
}
到瀏覽器中,你在頁面上會看到:“React.js is good”。
React.js 把插入表達式數(shù)組里面的每一個 JSX 元素一個個羅列下來,渲染到頁面上。如果你往 {} 放一個數(shù)組,React.js 會幫你把數(shù)組里面一個個元素羅列并且渲染出來。
使用 map 渲染列表數(shù)據(jù)
const users = [
{ username: 'Jerry', age: 21, gender: 'male' },
{ username: 'Tomy', age: 22, gender: 'male' },
{ username: 'Lily', age: 19, gender: 'female' },
{ username: 'Lucy', age: 20, gender: 'female' }
]
class Index extends Component {
render () {
const usersElements = [] // 保存每個用戶渲染以后 JSX 的數(shù)組
for (let user of users) {
usersElements.push( // 循環(huán)每個用戶,構(gòu)建 JSX,push 到數(shù)組中
<div>
<div>姓名:{user.username}</div>
<div>年齡:{user.age}</div>
<div>性別:{user.gender}</div>
<hr />
</div>
)
}
return (
<div>{usersElements}</div>
)
}
}
這里用了一個新的數(shù)組 usersElements,然后循環(huán) users 數(shù)組,為每個 user 構(gòu)建一個 JSX 結(jié)構(gòu),然后 push 到 usersElements 中。然后直接用表達式插入,把這個 userElements 插到 return 的 JSX 當(dāng)中。
但我們一般不會手動寫循環(huán)來構(gòu)建列表的 JSX 結(jié)構(gòu),可以直接用 ES6 自帶的map。
class Index extends Component {
render () {
return (
<div>
{users.map((user) => {
return (
<div>
<div>姓名:{user.username}</div>
<div>年齡:{user.age}</div>
<div>性別:{user.gender}</div>
<hr />
</div>
)
})}
</div>
)
}
}
這樣的模式在 JavaScript 中非常常見,一般來說,在 React.js 處理列表就是用 map 來處理、渲染的。
對于用表達式套數(shù)組羅列到頁面上的元素,都要為每個元素加上 key 屬性,這個 key 必須是每個元素唯一的標(biāo)識。
14.實戰(zhàn)分析:評論功能
我們遵循一個原則:如果一個文件導(dǎo)出的是一個類,那么這個文件名就用大寫開頭。四個組件類文件導(dǎo)出都是類,所以都是大寫字母開頭。
類似于<input />、<select />、<textarea> 這些元素的 value 值被 React.js 所控制、渲染的組件,在 React.js 當(dāng)中被稱為受控組件(Controlled Component)。對于用戶可輸入的控件,一般都可以讓它們成為受控組件,這是 React.js 所推崇的做法。