575 React系列二 - 核心JSX語法一
一. ES6的class
雖然目前React開發模式中更加流行hooks,但是依然有很多的專案依然是使用類元件(包括AntDesign庫中);
但是有很多的同學對ES6中的類不太熟悉,所以這裡我還是補充一下;
1.1. 類的定義
在ES6之前,我們通過function來定義類,但是這種模式一直被很多從其他程式語言(比如Java、C++、OC等等)轉到JavaScript的人所不適應。
原因是,大多數面向物件的語言,都是使用class關鍵字來定義類的。
而JavaScript也從ES6開始引入了class關鍵字,用於定義一個類。
ES6之前定義一個Person類:
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.running = function() { console.log(this.name + this.age + "running"); } var p = new Person("why", 18); p.running();
轉換成ES6中的類如何定義呢?
-
類中有一個constructor構造方法,當我們通過new關鍵字呼叫時,就會預設執行這個構造方法
-
- 構造方法中可以給當前物件新增屬性
-
類中也可以定義其他方法,這些方法會被放到Person類的prototype上
class Person { constructor(name, age) { this.name = name; this.age = age; } running() { console.log(this.name + this.age + "running"); } } const p = new Person("why", 18); p.running();
另外,屬性也可以直接定義在類中:
- height和address是直接定義在類中
class Person {
height = 1.88;
address = "北京市";
constructor(name, age) {
this.name = name;
this.age = age;
}
studying() {
console.log(this.name + this.age + "studying");
}
}
1.2. 類的繼承
繼承是面向物件的一大特性,可以減少我們重複程式碼的編寫,方便公共內容的抽取(也是很多面向物件語言中,多型的前提)。
ES6中增加了extends關鍵字來作為類的繼承。
我們先寫兩個類沒有繼承的情況下,它們存在的重複程式碼:
- Person類和Student類
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
running() {
console.log(this.name, this.age, "running");
}
}
class Student {
constructor(name, age, sno, score) {
this.name = name;
this.age = age;
this.sno = sno;
this.score = score;
}
running() {
console.log(this.name, this.age, "running");
}
studying() {
console.log(this.name, this.age, this.sno, this.score, "studing");
}
}
我們可以使用繼承來簡化程式碼:
- 注意:在constructor中,子類必須通過super來呼叫父類的構造方法,對父類進行初始化,否則會報錯。
class Student1 extends Person {
constructor(name, age, sno, score) {
super(name, age);
this.sno = sno;
this.score = score;
}
studying() {
console.log(this.name, this.age, this.sno, this.score, "studing");
}
}
const stu1 = new Student1("why", 18, 110, 100);
stu1.studying();
二. 案例練習
2.1. 列表展示
真實開發中,我們的資料通常會從伺服器獲取,比較常見的是獲取一個列表資料,儲存到一個數組中進行展示
- 比如現在有一個電影列表,我們如何通過React進行展示呢?
我們還是通過一個元件來完成:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: ["星際穿越", "大話西遊", "盜夢空間", "少年派"]
}
}
render() {
// var movieLis = [];
// for (var i in this.state.movies) {
// movieLis.push((<li>{this.state.movies[i]}</li>));
// }
return (
<div>
<h2>電影列表</h2>
<ul>
{
this.state.movies.map((item, index) => {
return (<li>{item}</li>)
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
2.2. 計數器案例
電影列表的案例中並沒有互動,我們再來實現一個計數器的案例:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>當前計數:{this.state.counter}</h2>
<button onClick={this.increment.bind(this)}>+1</button>
<button onClick={this.decrement.bind(this)}>-1</button>
</div>
)
}
increment() {
this.setState({
counter: this.state.counter+1
})
}
decrement() {
this.setState({
counter: this.state.counter-1
})
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
三. JSX語法解析
3.1. 認識JSX的語法
我們先來看一段程式碼:
-
這段element變數的宣告右側賦值的標籤語法是什麼呢?
-
- 它不是一段字串(因為沒有使用引號包裹),它看起來是一段HTML原生,但是我們能在js中直接給一個變數賦值html嗎?
- 其實是不可以的,如果我們將
type="text/babel"
去除掉,那麼就會出現語法錯誤; - 它到底是什麼呢?其實它是一段jsx的語法;
<script type="text/babel">
const element = <h2>Hello World</h2>
ReactDOM.render(element, document.getElementById("app"));
</script>
JSX是什麼?
- JSX是一種JavaScript的語法擴充套件(eXtension),也在很多地方稱之為JavaScript XML,因為看起就是一段XML語法;
- 它用於描述我們的UI介面,並且其完全可以和JavaScript融合在一起使用;
- 它不同於Vue中的模組語法,你不需要專門學習模組語法中的一些指令(比如v-for、v-if、v-else、v-bind);
為什麼React選擇了JSX?
-
React認為渲染邏輯本質上與其他UI邏輯存在內在耦合
-
- 比如UI需要繫結事件(button、a原生等等);
- 比如UI中需要展示資料狀態,在某些狀態發生改變時,又需要改變UI;
-
他們之間是密不可分,所以React沒有將標記分離到不同的檔案中,而是將它們組合到了一起,這個地方就是元件(Component);
-
- 當然,後面我們還是會繼續學習更多元件相關的東西;
-
在這裡,我們只需要知道,JSX其實是嵌入到JavaScript中的一種結構語法;
JSX的書寫規範:
-
JSX的頂層只能有一個根元素,所以我們很多時候會在外層包裹一個div原生(或者使用後面我們學習的Fragment);
-
為了方便閱讀,我們通常在jsx的外層包裹一個小括號(),這樣可以方便閱讀,並且jsx可以進行換行書寫;
-
JSX中的標籤可以是單標籤,也可以是雙標籤;
-
- 注意:如果是單標籤,必須以/>結尾;
JSX的本質,我們後面再來討論;
3.2. JSX嵌入表示式
如果我們jsx中的內容是動態的,我們可以通過表示式來獲取:
- 書寫規則:{表示式}
- 大括號內可以是變數、字串、陣列、函式呼叫等任意js表示式;
3.2.1. jsx中的註釋
jsx是嵌入到JavaScript中的一種語法,所以在編寫註釋時,需要通過JSX的語法來編寫:
<div>
{/* 我是一段註釋 */}
<h2>Hello World</h2>
</div>
3.2.2. JSX嵌入變數
-
情況一:當變數是Number、String、Array型別時,可以直接顯示
-
情況二:當變數是null、undefined、Boolean型別時,內容為空;
-
- 如果希望可以顯示null、undefined、Boolean,那麼需要轉成字串;
- 轉換的方式有很多,比如toString方法、和空字串拼接,String(變數)等方式;
-
情況三:物件型別不能作為子元素(not valid as a React child)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "why",
age: 18,
hobbies: ["籃球", "唱跳", "rap"],
test1: null,
test2: undefined,
flag: false,
friend: {
name: "kobe",
age: 40
}
}
}
render() {
return (
<div>
{/* 我是一段註釋 */}
<h2>Hello World</h2>
</div>
<div>
{/* 1.可以直接顯示 */}
<h2>{this.state.name}</h2>
<h2>{this.state.age}</h2>
<h2>{this.state.hobbies}</h2>
{/* 2.不顯示 */}
<h2>{this.state.test1}</h2>
<h2>{this.state.test1 + ""}</h2>
<h2>{this.state.test2}</h2>
<h2>{this.state.test2 + ""}</h2>
<h2>{this.state.flag}</h2>
<h2>{this.state.flag + ""}</h2>
{/* 3.不顯示 */}
<h2>123{this.state.friend}</h2>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById("app"));
~
補充:為什麼null、undefined、Boolean在JSX中要顯示為空內容呢?
原因是在開發中,我們會進行很多的判斷;
- 在判斷結果為false時,不顯示一個內容;
- 在判斷結果為true時,顯示一個內容;
這個時候,我們可以編寫如下程式碼:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
flag: false
}
}
render() {
return (
<div>
{this.state.flag ? <h2>我是標題</h2>: null}
{this.state.flag && <h2>我是標題</h2>}
</div>
)
}
}
3.3.3. JSX嵌入表示式
JSX中,也可以是一個表示式。
這裡我們演練三個,其他的大家在開發中靈活運用:
- 運算表示式
- 三元運算子
- 執行一個函式
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "kobe",
lastName: "bryant",
age: 20
}
}
render() {
return (
<div>
{/* 運算表示式 */}
<h2>{this.state.firstName + " " + this.state.lastName}</h2>
{/* 三元運算子 */}
<h2>{this.state.age >= 18 ? "成年人": "未成年人"}</h2>
{/* 執行一個函式 */}
<h2>{this.sayHello("kobe")}</h2>
</div>
)
}
sayHello(name) {
return "Hello " + name;
}
}
3.3.4. jsx繫結屬性
很多時候,描述的HTML原生會有一些屬性,而我們希望這些屬性也是動態的:
-
比如元素都會有title屬性
-
比如img元素會有src屬性
-
比如a元素會有href屬性
-
比如元素可能需要繫結class
-
- 注意:繫結class比較特殊,因為class在js中是一個關鍵字,所以jsx中不允許直接寫class
- 寫法:使用className替代
-
比如原生使用內聯樣式style
-
- style後面跟的是一個物件型別,物件中是樣式的屬性名和屬性值;
- 注意:這裡會講屬性名轉成駝峰標識,而不是連線符-;
我們來演示一下屬性的繫結:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "你好啊",
imgUrl: "https://upload.jianshu.io/users/upload_avatars/1102036/c3628b478f06.jpeg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240",
link: "https://www.baidu.com",
active: false
}
}
render() {
return (
<div>
<h2 title={this.state.title}>Hello World</h2>
<img src={this.state.imgUrl} alt=""/>
<a href={this.state.link} target="_blank">百度一下</a>
<div className={"message " + (this.state.active ? "active": "")}>你好啊</div>
<div className={["message", (this.state.active ? "active": "")].join(" ")}>你好啊</div>
<div style={{fontSize: "30px", color: "red", backgroundColor: "blue"}}>我是文字</div>
</div>
)
}
}
3.3. jsx事件監聽
3.3.1. 和原生繫結區別
如果原生DOM原生有一個監聽事件,我們可以如何操作呢?
- 方式一:獲取DOM原生,新增監聽事件;
- 方式二:在HTML原生中,直接繫結onclick;
我們這裡演練一下方式二:
btnClick()
這樣寫的原因是onclick繫結的後面是跟上JavaScript程式碼;
<button onclick="btnClick()">點我一下</button>
<script>
function btnClick() {
console.log("按鈕發生了點選");
}
</script>
在React中是如何操作呢?
我們來實現一下React中的事件監聽,這裡主要有兩點不同
- React 事件的命名採用小駝峰式(camelCase),而不是純小寫;
- 我們需要通過{}傳入一個事件處理函式,這個函式會在事件發生時被執行;
class App extends React.Component {
render() {
return (
<div>
<button onClick={this.btnClick}>點我一下(React)</button>
</div>
)
}
btnClick() {
console.log("React按鈕點選了一下")
}
}
3.3.2. this繫結問題
在事件執行後,我們可能需要獲取當前類的物件中相關的屬性:
-
比如我們這裡列印:
this.state.message
-
- 但是這裡會報錯:
Cannot read property 'state' of undefined
- 原因是this在這裡是undefined
- 但是這裡會報錯:
-
如果我們這裡直接列印this,也會發現它是一個undefined
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "你好啊,李銀河"
}
}
render() {
return (
<div>
<button onClick={this.btnClick}>點我一下(React)</button>
</div>
)
}
btnClick() {
console.log(this);
console.log(this.state.message);
}
}
~
為什麼是undefined呢?
- 原因是
btnClick
函式並不是我們主動呼叫的,而且當button發生改變時,React內部呼叫了btnClick
函式; - 而它內部呼叫時,並不知道要如何繫結正確的this;
如何解決this的問題呢?
方案一:bind給btnClick顯示繫結this
在傳入函式時,我們可以主動繫結this:
- 這裡我們主動將btnClick中的this通過bind來進行繫結(顯示繫結)
- 那麼之後React內部呼叫btnClick函式時,就會有一個this,並且是我們繫結的this;
<button onClick={this.btnClick.bind(this)}>點我一下(React)</button>
但是呢,如果我有兩個函式都需要用到btnClick的繫結:
- 我們發現
bind(this)
需要書寫兩遍;
<button onClick={this.btnClick.bind(this)}>點我一下(React)</button>
<button onClick={this.btnClick.bind(this)}>也點我一下(React)</button>
這個我們可以通過在構造方法中直接給this.btnClick繫結this來解決:
- 注意檢視
constructor
中我們的操作:this.btnClick = this.btnClick.bind(this);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "你好啊,李銀河"
}
this.btnClick = this.btnClick.bind(this);
}
render() {
return (
<div>
<button onClick={this.btnClick}>點我一下(React)</button>
<button onClick={this.btnClick}>也點我一下(React)</button>
</div>
)
}
btnClick() {
console.log(this);
console.log(this.state.message);
}
}
~
方案二:使用 ES6 class fields 語法
你會發現我這裡將btnClick的定義變成了一種賦值語句:
- 這是ES6中給類定義屬性的方法,稱之為class fields語法;
- 因為這裡我們賦值時,使用了箭頭函式,所以在當前函式中的this會去上一個作用域中查詢;
- 而上一個作用域中的this就是當前的物件;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "你好啊,李銀河"
}
}
render() {
return (
<div>
<button onClick={this.btnClick}>點我一下(React)</button>
<button onClick={this.btnClick}>也點我一下(React)</button>
</div>
)
}
btnClick = () => {
console.log(this);
console.log(this.state.message);
}
}
~
方案三:事件監聽時傳入箭頭函式(推薦)
因為 onClick
中要求我們傳入一個函式,那麼我們可以直接定義一個箭頭函式傳入:
- 傳入的箭頭函式的函式體是我們需要執行的程式碼,我們直接執行
this.btnClick()
; this.btnClick()
中通過this來指定會進行隱式繫結,最終this也是正確的;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "你好啊,李銀河"
}
}
render() {
return (
<div>
<button onClick={() => this.btnClick()}>點我一下(React)</button>
<button onClick={() => this.btnClick()}>也點我一下(React)</button>
</div>
)
}
btnClick() {
console.log(this);
console.log(this.state.message);
}
}
3.3.3. 事件引數傳遞
在執行事件函式時,有可能我們需要獲取一些引數資訊:比如event物件、其他引數
情況一:獲取event物件
- 很多時候我們需要拿到event物件來做一些事情(比如阻止預設行為)
- 假如我們用不到this,那麼直接傳入函式就可以獲取到event物件;
class App extends React.Component {
constructor(props) {
render() {
return (
<div>
<a href="http://www.baidu.com" onClick={this.btnClick}>點我一下</a>
</div>
)
}
btnClick(e) {
e.preventDefault();
console.log(e);
}
}
~
情況二:獲取更多引數
- 有更多引數時,我們最好的方式就是傳入一個箭頭函式,主動執行的事件函式,並且傳入相關的其他引數;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
names: ["衣服", "鞋子", "褲子"]
}
}
render() {
return (
<div>
<a href="http://www.baidu.com" onClick={this.aClick}>點我一下</a>
{
this.state.names.map((item, index) => {
return (
<a href="#" onClick={e => this.aClick(e, item, index)}>{item}</a>
)
})
}
</div>
)
}
aClick(e, item, index) {
e.preventDefault();
console.log(item, index);
console.log(e);
}
}