1. 程式人生 > 實用技巧 >574 React系列(一) - 邂逅React開發

574 React系列(一) - 邂逅React開發

React系列(一) - 邂逅React開發

我個人一直喜歡使用邂逅這個詞,我希望每個人在和一個新技術接觸的時候,都是一場美麗的邂逅。

而React本身絕對是優雅和美麗的,所以從這裡開始,我們和React來一場美麗的邂逅吧!


一. 認識React

1.1. React是什麼?

React是什麼呢?相信每個做前端的人對它都或多或少有一些印象。

這裡我們來看一下官方對它的解釋:用於構建使用者介面的 JavaScript 庫。

React是什麼?

我們知道對於前端來說,主要的任務就是構建用於介面,而構建用於介面離不開三個技術:

  • HTML:構建頁面的結構
  • CSS:構建頁面的樣式
  • JavaScript:頁面動態內容和互動

那麼使用最原生的HTML、CSS、JavaScript可以構建完整的使用者介面嗎?當然可以,但是會存在很多問題

  • 比如操作DOM相容性的問題;
  • 比如過多相容性程式碼的冗餘問題;
  • 比如程式碼組織和規範的問題;

所以,一直以來前端開發人員都在需求可以讓自己開發更方便的JavaScript庫:

  • 在過去的很長時間內,jQuery是被使用最多的JavaScript庫;
  • 在過去的一份調查中顯示,全球前10,000個訪問最高的網站中,有65%使用了jQuery,是當時最受歡迎的JavaScript庫;
  • 但是越來越多的公司開始慢慢不再使用jQuery,包括程式設計師使用最多的GitHub;

現在前端領域最為流行的是三大框架:

  • Vue
  • React
  • Angular

三個框架

而Angular在國內並不是特別受歡迎,尤其是Angular目前的版本對TypeScript還有要求的情況下。

Vue和React是國內最為流行的兩個框架,而他們都是幫助我們來構建使用者介面的JavaScript庫。

  • 關於它們的對比,我會另外再寫一篇文章

1.2. React的起源

React是2013年,Facebook開源的JavaScript框架,那麼當時為什麼Facebook要推出這樣一款框架呢?

這個源於一個需求,所產生的bug:

Facebook功能需求

該功能上線之後,總是出現bug:

  • 三個訊息的數字在發生變化時,過多的操作很容易產生bug;

bug是否可以修復呢?當然可以修復,但是Facebook的工程師並不滿足於此;

他們開始思考為什麼會產生這樣的問題;

  • 在傳統的開發模式中,我們過多的去操作介面的細節;(前端、iOS、Android)

    • 比如說需要掌握和使用大量DOM的API,當然我們可以通過jQuery來簡化和適配一些API的使用;
  • 另外關於資料(狀態),往往會分散到各個地方,不方便管理和維護;

他們就去思考,是否有一種新的模式來解決上面的問題:

  • 1.以元件的方式去劃分一個個功能模組
  • 2.元件內以jsx來描述UI的樣子,以state來儲存元件內的狀態
  • 3.當應用的狀態發生改變時,通過setState來修改狀態,狀態發生變化時,UI會自動發生更新

1.3. React的特點和優勢

1.3.1. React的特點

宣告式程式設計:

  • 宣告式程式設計是目前整個大前端開發的模式:Vue、React、Flutter、SwiftUI;
  • 它允許我們只需要維護自己的狀態,當狀態改變時,React可以根據最新的狀態去渲染我們的UI介面;

宣告式程式設計

元件化開發:

  • 元件化開發頁面目前前端的流行趨勢,我們會講複雜的介面拆分成一個個小的元件;
  • 如何合理的進行元件的劃分和設計也是後面我會講到的一個重點;

元件化開發

多平臺適配:

  • 2013年,React釋出之初主要是開發Web頁面;
  • 2015年,Facebook推出了ReactNative,用於開發移動端跨平臺;(雖然目前Flutter非常火爆,但是還是有很多公司在使用ReactNative);
  • 2017年,Facebook推出ReactVR,用於開發虛擬現實Web應用程式;(隨著5G的普及,VR也會是一個火爆的應用場景);

react多平臺


1.3.2. React的優勢

React由Facebook來更新和維護,它是大量優秀程式設計師的思想結晶:

  • React的流行不僅僅侷限於普通開發工程師對它的認可,大量流行的其他框架借鑑React的思想;

Vue.js框架設計之初,有很多的靈感來自Angular和React。

  • 包括Vue3很多新的特性,也是借鑑和學習了React
  • 比如React Hooks是開創性的新功能(也是我們課程的重點)
  • Vue Function Based API學習了React Hooks的思想

Flutter的很多靈感都來自React,來自官網的一段話:(SwiftUI呢)

來自Flutter官網

  • 事實上Flutter中的Widget – Element – RenderObject,對應的就是JSX – 虛擬DOM – 真實DOM

所以React可以說是前端的先驅者,它總是會引領整個前端的潮流。


1.4. React的現狀

另外在HackerRank中,2020年有一份呼叫,你更想要學習的framework(框架):

哪一個是你最想要學習的框架

國內外很多知名網站使用React開發:

image-20200608115008557

目前國內在大型公司使用React的較多:

高階前端工程師要求


二. Hello React

2.1. 原生案例實現

為了演練React,我們可以提出一個小的需求:

  • 在介面顯示一個文字:Hello World
  • 點選下方的一個按鈕,點選後文本改變為Hello React
  • 在介面顯示一個文字:Hello World
  • 點選下方的一個按鈕,點選後文本改變為Hello React

案例效果

但是,我們使用React實現之前,先使用原生程式碼來實現,這樣更加方便大家對比React和原生:

  • 當然,你也可以使用jQuery和Vue來實現,對它們分別進行對比學習

原生實現程式碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <h2 class="title"></h2>
  <button class="btn">改變文字</button>

  <script>
    // 指令式程式設計: 每做一個操作,都是給計算機(瀏覽器)一步步命令
    // 宣告式程式設計:
    // 1.定義資料
    let message = "Hello World";

    // 2.將資料顯示在h2元素中
    const titleEl = document.getElementsByClassName("title")[0];
    titleEl.innerHTML = message;

    // 3.點選按鈕,介面的資料發生改變
    const btnEl = document.getElementsByClassName("btn")[0];
    btnEl.addEventListener("click", e => {
      message = "Hello React";
      titleEl.innerHTML = message;
    })

  </script>

</body>
</html>

2.2. React開發依賴

開發React必須依賴三個庫:

  • react:包含react所必須的核心程式碼
  • react-dom:react渲染在不同平臺所需要的核心程式碼
  • babel:將jsx轉換成React程式碼的工具

第一次接觸React會被它繁瑣的依賴搞蒙,對於Vue來說,我們只是依賴一個vue.js檔案即可,但是react居然要依賴三個庫。

其實呢,這三個庫是各司其職的,目的就是讓每一個庫只單純做自己的事情:

  • 在React的0.14版本之前是沒有react-dom這個概念的,所有功能都包含在react裡。

  • 為什麼要進行拆分呢?原因就是react-native。

  • react包中包含了react和react-native所共同擁有的核心程式碼。

  • react-dom針對web和native所完成的事情不同:

    • web端:react-dom會講jsx最終渲染成真實的DOM,顯示在瀏覽器中
    • native端:react-dom會講jsx最終渲染成原生的控制元件(比如Android中的Button,iOS中的UIButton)。

babel是什麼呢?

  • Babel ,又名 Babel.js
  • 是目前前端使用非常廣泛的編輯器、轉移器。
  • 比如當下很多瀏覽器並不支援ES6的語法,但是確實ES6的語法非常的簡潔和方便,我們開發時希望使用它。
  • 那麼編寫原始碼時我們就可以使用ES6來編寫,之後通過Babel工具,將ES6轉成大多數瀏覽器都支援的ES5的語法。

React和Babel的關係:

  • 預設情況下開發React其實可以不使用babel。
  • 但是前提是我們自己使用 React.createElement 來編寫原始碼,它編寫的程式碼非常的繁瑣和可讀性差。
  • 那麼我們就可以直接編寫jsx(JavaScript XML)的語法,並且讓babel幫助我們轉換成React.createElement。
  • 後續還會講到;

所以,我們在編寫React程式碼時,這三個依賴都是必不可少的。

那麼,如何新增這三個依賴:

暫時我們直接通過CDN引入,來演練下面的示例程式:

  • 這裡有一個crossorigin的屬性,這個屬性的目的是為了拿到跨域指令碼的錯誤資訊
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

2.3. Hello World

下面我們通過一個Hello World的案例來看下如何使用React開發。

需求非常簡單:通過React,在介面上顯示一個Hello World

  • 注意:這裡我們編寫React的script程式碼中,必須新增 type="text/babel",作用是可以讓babel解析jsx的語法
<div id="app"></div>

<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<script type="text/babel">
  // 通過ReactDom物件來渲染內容
  ReactDOM.render(<h2>Hello World</h2>, document.getElementById("app"));
</script>

程式碼解析:

  • 依賴不需要多講,開發React程式碼必須新增三個依賴;

  • ReactDOM.render函式:

    • 這裡我們已經提前定義一個id為app的div
    • 這裡我們傳入了一個h2元素,後面我們就會使用React元件
    • 引數一:傳遞要渲染的內容,這個內容可以是HTML元素,也可以是React的元件
    • 引數二:將渲染的內容,掛載到哪一個HTML元素上

顯示效果:

Hello World

但是目前我們渲染的內容是定義死的,能否將其抽取到一個變數中呢?

  • 當然可以,我們可以通過{}語法來引入外部的變數或者表示式
// 將資料定義到變數中let message = "Hello World";// 通過ReactDom物件來渲染內容ReactDOM.render(<h2>{message}</h2>, document.getElementById("app"));

2.4. Hello React

按照我們最初的案例,我們已經實現了Hello World,但是我們希望點選一個按鈕後,修改為Hello React


2.4.1. 錯誤的方式

下面的程式碼是我們正常的執行邏輯,但是會報錯:

  • 原因是預設情況下 ReactDOM.render 會覆蓋掛載到的app原生中的所有內容;
  • 所以在執行完 ReactDOM.render 之後,就不存在button原生了;
<body>
  
  <div id="app">
    <button class="change-btn">改變文字</button>
  </div>

  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <script type="text/babel">
    // 將資料定義到變數中
    let message = "Hello World";

    // 通過ReactDom物件來渲染內容
    ReactDOM.render(<h2>{message}</h2>, document.getElementById("app"));

    // 獲取btn
    const btnEl = document.getElementsByClassName("change-btn")[0];
    btnEl.addEventListener("click", (e) => {
      console.log(e);
    })
  </script>
</body>

2.4.2. 正確的方式

雖然可以實現,但是整個程式碼的流暢過於繁瑣

<body>

  <div id="app">
  </div>

  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <script type="text/babel">
    // 將資料定義到變數中
    let message = "Hello World";

    // 通過ReactDom物件來渲染內容
    render();

    // 定義一個執行的函式
    function btnClick() {
      message = "Hello React";
      render();
    }

    function render() {
      ReactDOM.render((
        <div>
          <h2>{message}</h2>
          <button onClick={btnClick}>改變文字</button>
        </div>
      ), document.getElementById("app"));
    }
  </script>
</body>

2.4.3. 元件的方式

整個邏輯其實可以看做一個整體,那麼我們就可以將其封裝成一個元件:

  • 我們說過 ReactDOM.render 第一引數是一個HTML原生或者一個元件;
  • 所以我們可以先將之前的業務邏輯封裝到一個元件中,然後傳入到 ReactDOM.render 函式中的第一個引數;

在React中,如何封裝一個元件呢?

  • 這裡我們暫時使用類的方式封裝元件:

    • render當中返回的jsx內容,就是之後React會幫助我們渲染的內容
    • 1.定義一個類,繼承自React.Component
    • 2.實現當前元件的render函式

具體的程式碼如下:

class App extends React.Component {
  render() {
    return (<h2>Hello World</h2>)
  }
}

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

如果我們的Hello World是依賴變數的,並且會根據按鈕的點選而改變呢?這裡涉及到幾個核心點

1.資料在哪裡定義

  • 在元件中的資料,我們可以分成兩類:

    • 參與介面更新的資料:當資料變數時,需要更新元件渲染的內容
    • 不參與介面更新的資料:當資料變數時,不需要更新將組建渲染的內容
  • 參與介面更新的資料我們也可以稱之為是參與資料流,這個資料是定義在當前物件的state中

    • 我們可以通過在建構函式中 this.state = {定義的資料}
  • 當我們的資料發生變化時,我們可以呼叫 this.setState 來更新資料,並且通知React進行update操作

    • 在進行update操作時,就會重新呼叫render函式,並且使用最新的資料,來渲染介面

2.事件繫結中的this

  • 在類中直接定義一個函式,並且將這個函式繫結到html原生的onClick事件上,當前這個函式的this指向的是誰呢?

  • 預設情況下是undefined

    • 很奇怪,居然是undefined;
    • 因為在正常的DOM操作中,監聽點選,監聽函式中的this其實是節點物件(比如說是button物件);
    • 這次因為React並不是直接渲染成真實的DOM,我們所編寫的button只是一個語法糖,它的本質React的Element物件;
    • 那麼在這裡發生監聽的時候,react給我們的函式繫結的this,預設情況下就是一個undefined;
  • 我們在繫結的函式中,可能想要使用當前物件,比如執行 this.setState 函式,就必須拿到當前物件的this

    • 我們就需要在傳入函式時,給這個函式直接繫結this
    • 類似於下面的寫法:<button onClick={this.changeText.bind(this)}>改變文字</button>

我們一起來看一下程式碼是如何實現的:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <div id="app">
  </div>

  <!-- 新增React的依賴 -->
  <!-- <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> -->

  <script src="./react/react.development.js"></script>
  <script src="./react/react-dom.development.js"></script>
  <script src="./react/babel.min.js"></script>


  <!-- 開始開發 -->
  <script type="text/babel">
    // 封裝App元件
    class App extends React.Component {
      constructor() {
        super();
        // this.message = "Hello World";
        this.state = {
          message: "Hello World"
        }
      }

      render() {
        return (
          <div>
            <h2>{this.state.message}</h2>
            <button onClick={this.btnClick.bind(this)}>改變文字</button>
          </div>
        )
      }

      btnClick() {
        // this.message = "Hello React";
        // this.state.message = "Hello React";
        // console.log(this.state);
        this.setState({
          message: "Hello React"
        })
      }
    }

    // 渲染元件
    ReactDOM.render(<App />, document.getElementById("app"));
  </script>

</body>

</html>