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

576 React系列二 - 核心JSX語法二

一. 條件渲染

某些情況下,介面的內容會根據不同的情況顯示不同的內容,或者決定是否渲染某部分內容:

  • 在vue中,我們會通過指令來控制:比如v-if、v-show;
  • 在React中,所有的條件判斷都和普通的JavaScript程式碼一致;

常見的條件渲染的方式有哪些呢?


1.1. 條件判斷語句

一種方式是當邏輯較多時,通過條件判斷:

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      isLogin: true
    }
  }

  render() {
    let titleJsx = null;
    if (this.state.isLogin) {
      titleJsx = <h2>歡迎回來~</h2>
    } else {
      titleJsx = <h2>請先登入~</h2>
    }

    return (
      <div>
        {titleJsx}
      </div>
    )
  }
}

~

當然,我們也可以將其封裝到一個獨立的函式中:

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      isLogin: true
    }
  }

  render() {
    return (
      <div>
        {this.getTitleJsx()}
      </div>
    )
  }

  getTitleJsx() {
    let titleJsx = null;
    if (this.state.isLogin) {
      titleJsx = <h2>歡迎回來~</h2>
    } else {
      titleJsx = <h2>請先登入~</h2>
    }
    return titleJsx;
  }
}

1.2. 三元運算子

另外一種實現條件渲染的方法就是三元運算子:condition ? true : false;

三元運算子適用於沒有太多邏輯的程式碼:只是根據不同的條件直接返回不同的結果

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      isLogin: true
    }
  }

  render() {
    return (
      <div>
        <h2>{this.state.isLogin ? "歡迎回來~": "請先登入~"}</h2>
        <button onClick={e => this.loginBtnClick()}>{this.state.isLogin ? "退出": "登入"}</button>
      </div>
    )
  }

  loginBtnClick() {
    this.setState({
      isLogin: !this.state.isLogin
    })
  }
}

1.3. 與運算子&&

在某些情況下,我們會遇到這樣的場景:

  • 如果條件成立,渲染某一個元件;
  • 如果條件不成立,什麼內容也不渲染;

如果我們使用三元運算子,是如何做呢?

{this.state.isLogin ? <h2>{this.state.username}</h2>: null}

其實我們可以通過邏輯與&&來簡化操作:

{this.state.isLogin && <h2>{this.state.username}</h2>}

1.4. v-show效果

針對一個HTML原生,渲染和不渲染之間,如果切換的非常頻繁,那麼會相對比較損耗效能:

  • 在開發中,其實我們可以通過display的屬性來控制它的顯示和隱藏;
  • 在控制方式在vue中有一個專門的指令:v-show;
  • React沒有指令,但是React會更加靈活(靈活帶來的代價就是需要自己去實現);

我來看一下如何實現:

render() {
    const { isLogin, username } = this.state;
    const nameDisplay = isLogin ? "block": "none";

    return (
      <div>
        <h2 style={{display: nameDisplay}}>{username}</h2>
        <button onClick={e => this.loginBtnClick()}>{isLogin ? "退出": "登入"}</button>
      </div>
    )
  }

二. jsx列表渲染

2.1. 列表渲染

真實開發中我們會從伺服器請求到大量的資料,資料會以列表的形式儲存:

  • 比如歌曲、歌手、排行榜列表的資料;
  • 比如商品、購物車、評論列表的資料;
  • 比如好友訊息、動態、聯絡人列表的資料;

在React中並沒有像Vue模組語法中的v-for指令,而且需要我們通過JavaScript程式碼的方式組織資料,轉成JSX:

  • 很多從Vue轉型到React的同學非常不習慣,認為Vue的方式更加的簡潔明瞭;
  • 但是React中的JSX正是因為和JavaScript無縫的銜接,讓它可以更加的靈活;
  • 另外我經常會提到React是真正可以提高我們編寫程式碼能力的一種方式;

如何展示列表呢?

  • 在React中,展示列表最多的方式就是使用陣列的map高階函式;

陣列的map函式語法如下:

  • callback:生成新陣列元素的函式,使用三個引數:

    • currentValue

      callback 陣列中正在處理的當前元素。

    • index可選

      callback 陣列中正在處理的當前元素的索引。

    • array可選

      map 方法呼叫的陣列。

  • thisArg可選:執行 callback 函式時值被用作this

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
 // Return element for new_array 
}[, thisArg])

我們來演練一下之前的案例:

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      movies: ["盜夢空間", "大話西遊", "流浪地球", "少年派", "食神", "美人魚", "海王"]
    }
  }

  render() {
    return (
      <div>
        <h2>電影列表</h2>
        <ul>
          {
            this.state.movies.map(item => {
              return <li>{item}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

ReactDOM.render(<App/>, document.getElementById("app"));

2.2. 陣列處理

很多時候我們在展示一個數組中的資料之前,需要先對它進行一些處理:

  • 比如過濾掉一些內容:filter函式
  • 比如擷取陣列中的一部分內容:slice函式

比如我當前有一個數組中存放了一系列的數字:[10, 30, 120, 453, 55, 78, 111, 222]

案例需求:獲取所有大於50的數字,並且展示前3個數組

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      numbers: [10, 30, 120, 453, 55, 78, 111, 222]
    }
  }

  render() {
    return (
      <div>
        <h2>數字列表</h2>
        <ul>
          {
            this.state.numbers.filter(item => item >= 50).slice(0, 3).map(item => {
              return <li>{item}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

ReactDOM.render(<App/>, document.getElementById("app"));

2.3. 列表的key

我們會發現在前面的程式碼中只要展示列表都會報一個警告:

列表展示警告

這個警告是告訴我們需要在列表展示的jsx中新增一個key。

至於如何新增一個key,為什麼要新增一個key,這個我們放到後面講解setState時再來討論;


三. JSX原理解析

3.1. JSX轉換本質

實際上,jsx 僅僅只是 React.createElement(component, props, ...children) 函式的語法糖。

  • 所有的jsx最終都會被轉換成React.createElement的函式呼叫。

React.createElement在原始碼的什麼位置呢?

~

React.createElement原始碼

createElement需要傳遞三個引數:

  • 引數一:type

    • 當前ReactElement的型別;
    • 如果是標籤元素,那麼就使用字串表示 “div”;
    • 如果是元件元素,那麼就直接使用元件的名稱;
  • 引數二:config

    • 所有jsx中的屬性都在config中以物件的屬性和值的形式儲存
  • 引數三:children

    • 存放在標籤中的內容,以children陣列的方式進行儲存;
    • 當然,如果是多個元素呢?React內部有對它們進行處理,處理的原始碼在下方

對children進行的處理:

  • 從第二個引數開始,將其他所有的引數,放到props物件的children中
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
  props.children = children;
} else if (childrenLength > 1) {
  const childArray = Array(childrenLength);
  for (let i = 0; i < childrenLength; i++) {
    childArray[i] = arguments[i + 2];
  }
  if (__DEV__) {
    if (Object.freeze) {
      Object.freeze(childArray);
    }
  }
  props.children = childArray;
}

真實的轉換過程到底長什麼樣子呢?我們可以從多個角度來檢視。


3.1.1. Babel官網檢視

我們知道預設jsx是通過babel幫我們進行語法轉換的,所以我們之前寫的jsx程式碼都需要依賴babel。

在這裡我們編寫一些jsx程式碼,來檢視執行後的結果:

<div className="app">
  <div className="header">
    <h1 title="標題">我是網站標題</h1>
  </div>
  <div className="content">
    <h2>我是h2元素</h2>
    <button onClick={e => console.log("+1")}>+1</button>
    <button onClick={e => console.log("+1")}>-1</button>
  </div>
  <div className="footer">
    <p>我是網站的尾部</p>
  </div>
</div>

~

babel轉換


3.1.2. 編寫createElement

還有一種辦法是我們自己來編寫React.createElement程式碼:

class App extends React.Component {
  constructor(props) {
  render() {
    /*#__PURE__*/
    const result = React.createElement("div", {
      className: "app"
    }, /*#__PURE__*/React.createElement("div", {
      className: "header"
    }, /*#__PURE__*/React.createElement("h1", {
      title: "\u6807\u9898"
    }, "\u6211\u662F\u7F51\u7AD9\u6807\u9898")), /*#__PURE__*/React.createElement("div", {
      className: "content"
    }, /*#__PURE__*/React.createElement("h2", null, "\u6211\u662Fh2\u5143\u7D20"), /*#__PURE__*/React.createElement("button", {
      onClick: e => console.log("+1")
    }, "+1"), /*#__PURE__*/React.createElement("button", {
      onClick: e => console.log("+1")
    }, "-1")), /*#__PURE__*/React.createElement("div", {
      className: "footer"
    }, /*#__PURE__*/React.createElement("p", null, "\u6211\u662F\u7F51\u7AD9\u7684\u5C3E\u90E8")));
    return result;
  }
}

ReactDOM.render(React.createElement(App, null) , document.getElementById("app"));

上面的整個程式碼,我們就沒有通過jsx來書寫了,介面依然是可以正常的渲染。

另外,在這樣的情況下,你還需要babel相關的內容嗎?不需要了

  • 所以,type="text/babel"可以被我們刪除掉了;
  • 所以,<script src="../react/babel.min.js"></script>可以被我們刪除掉了;

3.2. 虛擬DOM

3.2.1. 虛擬DOM的建立過程

我們通過 React.createElement 最終創建出來一個 ReactElement物件:

return ReactElement(
  type,
  key,
  ref,
  self,
  source,
  ReactCurrentOwner.current,
  props,
);

這個ReactElement物件是什麼作用呢?React為什麼要建立它呢?

  • 原因是React利用ReactElement物件組成了一個JavaScript的物件樹;
  • JavaScript的物件樹就是大名鼎鼎的虛擬DOM(Virtual DOM);

如何檢視ReactElement的樹結構呢?

  • 我們可以將之前的jsx返回結果進行列印;
  • 注意下面程式碼中我打jsx的列印;
render() {
  const jsx = (
    <div className="app">
      <div className="header">
        <h1 title="標題">我是網站標題</h1>
      </div>
      <div className="content">
        <h2>我是h2元素</h2>
        <button onClick={e => console.log("+1")}>+1</button>
        <button onClick={e => console.log("+1")}>-1</button>
      </div>
      <div className="footer">
        <p>我是網站的尾部</p>
      </div>
    </div>
  )
  console.log(jsx);
  return jsx;
}

列印結果,在瀏覽器中檢視:

ReactElement物件結構

而ReactElement最終形成的樹結構就是Virtual DOM;

整體的轉換過程如下:


3.2.2. 為什麼採用虛擬DOM

為什麼要採用虛擬DOM,而不是直接修改真實的DOM呢?

  • 很難跟蹤狀態發生的改變:原有的開發模式,我們很難跟蹤到狀態發生的改變,不方便針對我們應用程式進行除錯;
  • 操作真實DOM效能較低:傳統的開發模式會進行頻繁的DOM操作,而這一的做法效能非常的低;

DOM操作效能非常低:

首先,document.createElement本身創建出來的就是一個非常複雜的物件;

其次,DOM操作會引起瀏覽器的迴流和重繪,所以在開發中應該避免頻繁的DOM操作;


這裡我們舉一個例子:

比如我們有一組陣列需要渲染:[0, 1, 2, 3, 4],我們會怎麼做呢?

<ul>
  <li>0</li>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>

後來,我們又增加了5條資料:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for (var i=5; i<10; i++) {
  var li = document.createElement("li");
  li.innerHTML = arr[i];
  ul.appendChild(li);
}

上面這段程式碼的效能怎麼樣呢?非常低效

  • 因為我們通過 document.createElement 建立元素,再通過 ul.appendChild(li) 渲染到DOM上,進行了多次DOM操作;
  • 對於批量操作的,最好的辦法不是一次次修改DOM,而是對批量的操作進行合併;(比如可以通過DocumentFragment進行合併);

虛擬DOM幫助我們從指令式程式設計轉到了宣告式程式設計的模式

React官方的說法:Virtual DOM 是一種程式設計理念。

在這個理念中,UI以一種理想化或者說虛擬化的方式儲存在記憶體中,並且它是一個相對簡單的JavaScript物件,我們可以通過ReactDOM.render讓 虛擬DOM真實DOM同步起來,這個過程中叫做協調(Reconciliation);

這種程式設計的方式賦予了React宣告式的API:你只需要告訴React希望讓UI是什麼狀態,React來確保DOM和這些狀態是匹配的。

你不需要直接進行DOM操作,只可以從手動更改DOM、屬性操作、事件處理中解放出來;

關於虛擬DOM的一些其他內容,在後續的學習中還會再次講到。