認(rèn)識(shí)JSX
JSX是一種JavaScript的語(yǔ)法擴(kuò)展(eXtension)数苫,也在很多地方稱(chēng)之為JavaScript XML,因?yàn)榭雌鹁褪且欢蝀ML語(yǔ)法
它用于描述我們的UI界面缘挽,JSX會(huì)被解析為ReactRenderObject,所以JSX本質(zhì)就是JS對(duì)象, 所以JSX可以和JavaScript融合在一起使用
JSX其實(shí)是將HTML嵌入到JavaScript中的一種結(jié)構(gòu)語(yǔ)法
// JSX本質(zhì)就是對(duì)象,所以其可以作為變量來(lái)進(jìn)行使用
const msg = <h2>Hello World</h2>
ReactDOM.render(msg, document.getElementById('app'))
React為什么使用JSX
React認(rèn)為渲染邏輯本質(zhì)上與其他UI邏輯存在內(nèi)在耦合
- 比如UI需要綁定事件(button捂刺、a元素等等)
- 比如UI中需要展示數(shù)據(jù)狀態(tài),在某些狀態(tài)發(fā)生改變時(shí)募寨,又需要改變UI
他們之間是密不可分族展,所以React沒(méi)有講標(biāo)記分離到不同的文件中,而是將它們組合到了一起拔鹰,這個(gè)地方就是組件 (Component)
書(shū)寫(xiě)規(guī)范
- JSX的頂層只能有一個(gè)根元素仪缸,所以我們很多時(shí)候會(huì)在外層包裹一個(gè)div元素或Fragment組件
- 為了方便閱讀,我們通常在jsx的外層包裹一個(gè)小括號(hào)()列肢,但這僅僅是為了方便我們閱讀和JSX代碼換行恰画,不是必須的
- JSX中的標(biāo)簽可以是單標(biāo)簽宾茂,也可以是雙標(biāo)簽,但如果是單標(biāo)簽拴还,必須使用/來(lái)進(jìn)行閉合跨晴,也就是要使用嚴(yán)格模式來(lái)閉合標(biāo)簽
注釋
// JSX的{}中可以寫(xiě)JS語(yǔ)法,其中也就可以寫(xiě)JS注釋
{/* 這里是JSX的塊級(jí)注釋內(nèi)容 */}
{
// 這里是JS的單行注釋內(nèi)容
// 注意: //和{之間必須換行片林,否則無(wú)法正常解析}
}
嵌入值
JSX使用大括號(hào)語(yǔ)法來(lái)嵌入值端盆,在大括號(hào)中可以書(shū)寫(xiě)的是任意合法的JS變量或合法的JS表達(dá)式
變量
- 當(dāng)變量是Number、String费封、Array類(lèi)型時(shí)焕妙,可以直接顯示
- 當(dāng)變量是null、undefined弓摘、Boolean類(lèi)型時(shí)焚鹊,內(nèi)容為空
- 如果希望可以顯示null、undefined韧献、Boolean末患,那么需要轉(zhuǎn)成字符串
- 例如使用toString,String势决,拼接空字符串
- 因?yàn)閡ndefined和null是無(wú)法調(diào)用函數(shù)的阻塑,所以推薦使用拼接空字符串的方式來(lái)進(jìn)行轉(zhuǎn)換
- 對(duì)于Boolean類(lèi)型的值,無(wú)論值是true還是false果复,其都不會(huì)在界面上進(jìn)行顯示
- 對(duì)象類(lèi)型不能作為子元素(not valid as a React child)
- JSX會(huì)轉(zhuǎn)換為React.createElement陈莽,而其第三個(gè)參數(shù)children即使當(dāng)前元素所對(duì)應(yīng)的子元素
- 而children數(shù)組中是不可以有對(duì)象的
- 也就是說(shuō)對(duì)象不可以寫(xiě)在大括號(hào)語(yǔ)法中
表達(dá)式
class App extends React.Component {
constructor() {
this.state = {
firstName: 'klaus',
lastName: 'Wang',
isLogin: false
}
}
render() {
// 對(duì)象的解構(gòu),可以使用常量來(lái)進(jìn)行接收
// 因?yàn)槊恳淮谓缑嫠⑿碌臅r(shí)候虽抄,其都會(huì)重新執(zhí)行一遍render函數(shù)
// 也就是會(huì)生成一個(gè)新的函數(shù)作用域走搁,因此解構(gòu)出的變量可以使用常量來(lái)進(jìn)行接收
const { firstName, lastName, isLogin } = this.state
return (
// 雖然JSX可以使用Fragment進(jìn)行包裹(也就是使用<></>來(lái)進(jìn)行包裹)
// 但是Fragment只能在腳手架中使用,在CDN引入的時(shí)候無(wú)法使用
<div>
{/* JSX中可以書(shū)寫(xiě)合法的JS表達(dá)式 */}
<div>{ firstName + ' ' + lastName }</div>
{/*
JSX中可以編寫(xiě)三目運(yùn)算符
在實(shí)際開(kāi)發(fā)中迈窟,LoginOut和LoginIn可能對(duì)應(yīng)著是變量
為了避免變量為null或undefined的時(shí)候私植,在界面顯示出現(xiàn)bug
隨意null和undefined在JSX中一般不會(huì)顯示在界面上
*/}
<div>{ isLogin ? 'LoginOut' : 'LoginIn' }</div>
{/*
邏輯運(yùn)算符
JSX中,會(huì)出現(xiàn)許多通過(guò)邏輯運(yùn)算符來(lái)決定渲染內(nèi)容的情況
所以JSX中如果需要顯示的是boolean類(lèi)型的時(shí)候车酣,無(wú)論結(jié)果為true還是false
其值都不會(huì)顯示曲稼,以避免出現(xiàn)顯示bug
例如: isLogin && 'welcome'中 如果isLogin的值為false
那么表達(dá)式的結(jié)果為false,此時(shí)在界面上顯示false明顯是不合理的
*/}
<div>{ isLogin && 'welcome' }</div>
{/*
函數(shù)調(diào)用
JSX中可以進(jìn)行函數(shù)的調(diào)用
也可以使用一些方法湖员,例如map, filter, some等
*/}
<div>{ this.getFullName() }</div>
</div>
)
}
getFullName() {
return this.state.firstName + ' ' + this.state.lastName
}
}
ReactDOM.render(<App />, document.getElementById('app'))
綁定屬性
普通屬性
{/* 可以使用大括號(hào)語(yǔ)法來(lái)綁定普通屬性 */}
<a href={ this.state.link }>google</a>
class
{/*
class是js的關(guān)鍵字贫悄,而JSX本質(zhì)就是all in js
所以為了避免HTML書(shū)寫(xiě)和JS關(guān)鍵字沖突
JSX為對(duì)應(yīng)的HTML屬性起了別名
class -> className
for -> htmlFor
*/}
<h2 className={`titile ${this.state.isActive && 'active'}`}>Lorem</h2>
style
{/*
第一個(gè)大括號(hào)表示內(nèi)部使用JS語(yǔ)法
第二個(gè)大括號(hào)表示里面的值是一個(gè)對(duì)象
style中的屬性值 如果是字符串需要加上引號(hào)
因?yàn)槭窃趯?duì)象中,如果不加引號(hào)娘摔,會(huì)被解析為對(duì)象
*/}
<h2 style={{
color: '#fff',
backgroundColor: 'skyblue',
'font-size': '20px'
}}>Lorem</h2>
事件綁定
class App extends React.Component {
constructor() {
super()
this.state = {
count: 100
}
}
// render方法在編譯后窄坦,是通過(guò)App組件的實(shí)例對(duì)象來(lái)進(jìn)行調(diào)用的
// 所以在render函數(shù)中,this的指向是正確的
render() {
return (
<div>
<button onClick={ this.getCount }>click me</button>
</div>
)
}
getCount() {
// 在React中,對(duì)HTML生事件進(jìn)行了二次封裝
// 而HTML原生事件是所有組件都可以調(diào)用的
// 所以React在封裝HTML原生事件的時(shí)候鸭津,并不知道具體的調(diào)用者
// 所以React在執(zhí)行傳入的原生事件回調(diào)的時(shí)候彤侍,使用了`call(undefined)`
// 因此在React中 默認(rèn)情況下原生HTML事件回調(diào)中的this是undefined
// 我們?cè)谑褂玫臅r(shí)候,需要對(duì)this的指向進(jìn)行修正
console.log(this) // => undefined
}
}
解決方法1 -- 使用bind方法
{/*
使用bind進(jìn)行綁定的時(shí)候逆趋,可以返回一個(gè)擁有正確this指向的新函數(shù)
但是如果在jsx中多次調(diào)用同樣函數(shù)的時(shí)候盏阶,需要多次重復(fù)使用bind方法來(lái)修正this指向
*/}
<button onClick={ this.getCount.bind(this) }>click me</button>
<button onClick={ this.getCount.bind(this) }>click me</button>
class App extends React.Component {
constructor() {
super()
this.state = {
count: 100
}
// 我們可以在構(gòu)造函數(shù)中 一次性對(duì)某個(gè)方法進(jìn)行this的修正
// 但是這就意味著所有的函數(shù)都需要在構(gòu)造器中使用bind方法修正this指向
// 而實(shí)際開(kāi)發(fā)中,組件內(nèi)部的函數(shù)是很多的闻书,如果都在構(gòu)造器中修正this指向
// 必然會(huì)導(dǎo)致構(gòu)造函數(shù)過(guò)于繁瑣般哼,不利于維護(hù)
this.getCount = this.getCount.bind(this)
}
render() {
return (
<div>
<button onClick={ this.getCount }>click me</button>
<button onClick={ this.getCount }>click me</button>
</div>
)
}
getCount() {
console.log(this.state.count)
}
}
解決方法二 -- 使用class field
class App extends React.Component {
constructor() {
super()
this.state = {
count: 100
}
}
render() {
return (
<div>
{/* 綁定事件的時(shí)候,傳入的其實(shí)是對(duì)應(yīng)事件的引用地址 */}
<button onClick={ this.getCount }>click me</button>
</div>
)
}
// 在定義函數(shù)的時(shí)候惠窄,使用class fields定義類(lèi)的成員變量
// 并且在定義函數(shù)的時(shí)候,使用箭頭函數(shù)
// 而類(lèi)中的方法漾橙,本質(zhì)上都是通過(guò)類(lèi)的實(shí)例去進(jìn)行調(diào)用的
// 所以類(lèi)的成員變量的上層作用域是App類(lèi)
// 因此在這里杆融, 我們可以獲取正確的this指向
getCount = () => {
console.log(this.state.count)
}
}
解決方法三 -- 使用箭頭函數(shù) --- 推薦
class App extends React.Component {
constructor() {
super()
this.state = {
count: 100
}
}
render() {
return (
<div>
{/*
在調(diào)用函數(shù)的時(shí)候在外層包裹一層箭頭函數(shù)
1. 如果在事件調(diào)用的時(shí)候傳入箭頭函數(shù),其上層作用域是render函數(shù)霜运,所以擁有正確的this指向
2. 在箭頭函數(shù)的執(zhí)行體中脾歇,是去調(diào)用我們實(shí)際需要執(zhí)行的函數(shù),此時(shí)不是引用地址淘捡,而是具體的函數(shù)調(diào)用
所以使用這種方法進(jìn)行事件的綁定 便于我們進(jìn)行函數(shù)參數(shù)的傳遞
*/}
<button onClick={ () => { this.getCount() }}>click me</button>
</div>
)
}
getCount() {
console.log(this.state.count)
}
}
參數(shù)傳遞
class App extends React.Component {
constructor() {
super()
this.state = {
count: 100
}
}
render() {
return (
<div>
<button onClick={ this.handleClick }>click me</button>
{/*
使用箭頭函數(shù)修正this后藕各,默認(rèn)的事件對(duì)象是傳遞給外層的箭頭函數(shù)的
所以在箭頭函數(shù)體中進(jìn)行函數(shù)實(shí)際調(diào)用的時(shí)候,
事件對(duì)象作為第幾個(gè)參數(shù)傳入還是不進(jìn)行傳入就可以由我們自己進(jìn)行主動(dòng)控制
*/}
<button onClick={ e => { this.calcCount(2, e) }}>click me</button>
</div>
)
}
// 默認(rèn)情況下蝴光,JSX在進(jìn)行事件綁定的時(shí)候梗脾,會(huì)默認(rèn)將事件參數(shù)傳入
// ps: 這個(gè)參數(shù)并不是原生事件對(duì)象,而是React基于原生事件對(duì)象二次封裝得到的合成對(duì)象
// 原生的事件對(duì)象位于 e.nativeEvent
handleClick(e) {
console.log(e)
}
calcCount(step, e) {
console.log(this.state.count * step)
console.log(e)
}
}
條件渲染
某些情況下淀衣,界面的內(nèi)容會(huì)根據(jù)不同的情況顯示不同的內(nèi)容膘魄,或者決定是否渲染某部分內(nèi)容
class App extends React.Component {
constructor() {
super()
this.state = {
isLogin: true
}
}
// 和渲染UI相關(guān)的函數(shù)乌逐,寫(xiě)在render函數(shù)的上邊
renderText() {
// 使用if進(jìn)行條件判斷,適合于邏輯比較復(fù)雜的情況
if (this.state.isLogin) {
return '歡迎回來(lái)'
} else {
return '請(qǐng)先登錄'
}
}
render() {
return (
<div>
<h1>{ this.renderText() }</h1>
{/* 使用三目運(yùn)算符來(lái)進(jìn)行條件分支的判斷 */}
<button onClick={ () => this.changeLoginStatus() }>{ this.state.isLogin ? '退出' : '登錄' }</button>
<hr />
{/* 通過(guò)邏輯運(yùn)算符來(lái)實(shí)現(xiàn)條件分支的判斷 */}
{ this.state.isLogin && <p>您已經(jīng)成功登錄</p> }
</div>
)
}
// 和渲染UI無(wú)關(guān)创葡,但是和業(yè)務(wù)邏輯有關(guān)的函數(shù)浙踢,寫(xiě)在render函數(shù)的后邊
changeLoginStatus() {
this.setState({
isLogin: !this.state.isLogin
})
}
}
模擬v-show
class App extends React.Component {
constructor() {
super()
this.state = {
isLogin: true
}
}
render() {
return (
<div>
<button onClick={ () => this.changeLoginStatus() }>change login status</button>
<h2 style={{ display: this.state.isLogin ? 'block' : 'none' }} >登錄成功</h2>
</div>
)
}
changeLoginStatus() {
this.setState({
isLogin: !this.state.isLogin
})
}
}
列表渲染
在React中,我們可以通過(guò)map灿渴,filter洛波,slice等一系列的JavaScript函數(shù)來(lái)幫助我們實(shí)現(xiàn)列表渲染
class App extends React.Component {
constructor() {
super()
this.state = {
nums: [10, 23, 35, 122, 78, 4331, 23, 62]
}
}
render() {
return (
// 以列表形式展示出nums中所有的偶數(shù)數(shù)據(jù)
<ul>
{
this.state.nums.filter(num => num % 2 === 0).map(num => <li>{num}</li>)
}
</ul>
)
}
}