1. 程式人生 > 實用技巧 >575 React系列二 - 核心JSX語法一

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);
  }
}