icon
rediscover-react-new-react-documentation-in-2023

2023年2月12日星期日 下午2:35

重新認識React:在2023年裡迎來新的React文檔

React 一直都係前端開發框架中嘅頂流,其中當然有一部分原因是因為眾多大公司都有使用,從而造成名人效應。但根本嘅原因係因為 React 嘅易用性,基本上你有基礎嘅 JavaScript 同網頁開發知識就可以好快可以上手。相比起 AngularReact 有著更低的學習曲線。再加上 React 有龐大嘅社團,所以係學習同排錯時可以搵到好多資料。

React文檔的改變

現在的 React 文檔還是沿用著基於 Class Component 的教學文檔,縱使近年來都有因應著 Functional Component 或其他新特色而有所更新。但是平均的編寫時間段都是在三到五年前,而且大部分的內容都包含著 Class Component 的內容。

因為 Class Component 的方式較難維護,會隨著部件功能增加而變得更複雜。還有一些 JS 語言底層的問題,所以導致他們把開發方式轉向以 Hooks 的形式。詳情可以查看此文章

因為 Hooks 成為了開發 React 應用當中的主流方法,但是官方文檔中沒有很好地提供相關的教學。所以 React 團隊在2020年十月提出了要重新打造他們的新官方文檔、在2021年十一月提到了他們開始著手編寫 Beta 文檔、以及在2022年九月公佈已經完成了大部分的內容。

新文檔的目標是以 Hooks 開發方式出發的教學,透過一起實踐打造 React 應用,同時注重於 React 的核心思想。以及希望可以讓不同背景的開發者輕易上手。

為什麼需要React

純 JavaScript 的網頁應用都可以實現界面上的數據對應著用戶的操作而改變,只不過開發者需要手動把所有顯示相關數據的元素寫上對應的操作。這樣的開發流程其實是十分低效且容易出錯的。試想一下有十個元素正展示著一個變量的值,每當用戶進行操作改變數值時,我們需要保證有寫好這十個元素對應的數值更改。

而像是 React 這樣的 JavaScript Library可以幫我們自動更新相關用戶界面的數據,不用我們開發者把這些元素重新賦值。這意味著開發者只需要管理好數據因應用戶操作而變更的邏輯就足夠了,React 會幫我們處理用戶界面的數據。

React的特性

上面提到的數據綁定其實在其他框架中也有提供,而區分React的原因還有很多,例如 Component Based 可以提高網頁元素的重用性,以及因為每一個元部件都有自己的狀態與變量,所以我們更容易維護與測試。

Virtual DOM,因為現代的網頁追求更多的用戶交互,結果就是網頁上的元素會經常因應用戶的操作而有頻繁變動。想要用戶界面跟著數據所改變則需要存取 Real DOM,而更改 Real DOM 時會重新渲染相關的元素在熒幕上,時間需求則會跟著需要變更節點的數量而提升。由於更改 Virtual DOM 並不會觸發渲染畫面,所以效率就會大大提升。Virtual DOM 做的事情就是比較需要變動的節點,然後計算出最少次數更新 Real DOM 的操作。

DOMDocument Object Model,一個用於表示網頁資訊的樹狀數據結構。每一個節點可以視為 HTML 的一個元素,以及其相關的樣式、屬性等的數據。

簡單來說,Virtual DOM 到最後都需要修改 Real DOM。只不過相比起直接對 Real DOM 進行修改,Virtual DOM 會先得出最有效的操作去修改有變動的節點。

安裝React

就如上面提到,React 是一個 JavaScript 的函數庫。我們可以通過常見的 npm 方法安裝,也可以使用 <script> 標籤去把相關的代碼庫引入到你的網頁。

後面會提到 React 中的一個特性 JSX,使用 script tag 方式安裝的需要額外的設定,而且效能比較低。再加上後續開發會需要使用到原生 React 代碼庫以外的套件,我們可以利用 npm 來去管理安裝其他套件。所以建議使用 npm 的方式學習與開發。

方法一:script tag

這個方法是比較容易上手的,因為我們可以用平常開發純 JavaScript 項目的方式去使用 React。如果想要從純 JS 方面入手可以先試一下這個安裝操作。

<html>

  <body>
    <...>
    <div id="root"></div>
    
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>

    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script src="app.js" type="text/babel"></script>
  <body>
</html>


如果想要使用 JSX 的方式則需要在 script 標籤屬性上加上 type="text/babel"

方法二:npm

一般開發網頁應用時,我們都需要不同的套件去提高開發效率(例如測試伺服器、代碼格式、排錯工具、CSS 庫等),而npm 則是最多人使用的套件管理器。最直接的用處則是對於多人、以及在不同裝置上開發,開發者不需要把需要的套件都上傳到網上,只需要把套件的名稱和版本號記錄到一個文檔。每次要從新設定工作環境時,套件管理器就會根據文檔上的套件快速安裝下來。

請確保電腦上已安裝了 node,因為 npm 是跟 node 捆綁在一起的。按這裡安裝。

另外,一般開發網站時,需要在本地環境去建立伺服器去測試網頁。我們可以選擇安裝單獨的套件去實現,但更常見的做法是使用一些項目建構工具,例如vitecreate-react-appcreate-next-app。這裡會使用 vite 作為例子。因為 vite 不僅是提供 React 的項目建構,還有其他框架的,所以根據我們要使用的技術去選擇即可。

在終端上打上以下指令:

# terminal
> npm create vite@latest

√ Project name: » First-React-App
√ Select a framework: » React
√ Select a variant: » JavaScript

等運行後就可以發現項目已經創建成功,如果沒有創建的話可以留意報錯的訊息。

# 新創建項目的結構

├─First-React-App
│  ├─public
│  └─src
│      └─assets
│      └─App.css
│      └─App.jsx
│      └─index.css
│      └─main.jsx
│  └─.gitignore
│  └─index.html
│  └─package.json
│  └─vite.config.js

認識React代碼

上面方法二提到的項目建構工具都會有生成預設的頁面代碼和樣式,讓開發者可以直接使用模板進行開發。我們也可以趁這個機會看一下 React 應用的代碼是怎樣的。

# /src/main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

去使用 React 的代碼接口,我們需要選取首頁 HTML 中的一個元素(通常慣例都是 id 為 "root")。接下來就是用 ReactDOM 物件的 createRoot()render() 去接管操控網頁的 DOM

像是 <App /> 這樣的類 HTML 標籤並不是真正的 HTML,而是我們下面會提到的 JSX 語法。

組件 - Component

React 裡一個最核心的概念就是組件,組件追求著把網站頁面上的不同部分分開編寫。好處就是可以在同一頁面和不同頁面上重複使用這些組件,例如網頁中的 headerfooterbutton 等的共用元素 / 區域。

除了重用性,組件的形式幫助開發者可以專注在一個區域上的代碼。如果用戶界面上的元素出錯了,只需要去相關的組件文件排錯即可。另外組件也可以方便測試人員對個別界面測試。

函數式組件

Functional Component 是新版 React 為了解決 Class Component 過於複雜的問題而推出的,主要是讓整體代碼更加簡潔。函數式組件是 Stateless 和沒有生命週期的,但是藉助不同的 Hooks 即可以做到 Class Component 一樣的效果。

State 可以理解為一個受到監聽的物體,裡面會存放著用戶界面需要展示的資料。React 會一直留意著這個物體,每當物體裡的變量改變後就會重新渲染頁面。

我們會使用 JSX 來寫組件,寫好就可以讓其他組件或是上面的 ReactDOM.createRoot().render() 裡使用。就像是一層包著一層的 HTML 標籤一樣,在背後其實也是一層層的 JS 物件。就像是這個 <App/> 組件:

# /src/App.jsx
# 簡化後的一些預設代碼

function App() {
  return (
    <div className="App">
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src="/vite.svg" className="logo" alt="Vite logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </div>
  )
}

export default App
當我們完成這個 App 函數式組件後就可以用 export 語法傳去其他檔案裡使用,就像是 /src/main.jsx 一樣。

JSX語法

JSX 的全稱是 JavaScript XML,是一種 JavaScript 的擴充語法。如果想要在純 JS 上使用則需要安裝相關的插件,上面提到的項目建構工具一般在背後都有安裝插件去識別。JSX 可以讓我們在 JavaScript 中使用 HTML 語法,使用 JSX 語法有好有壞,主要看開發者是否喜歡這種寫法。

Babel 會在背後把 JSX 轉換成 React.createElement(type, props, ...children) 的代碼。

JSX的特色

由於 JSX 是一種 JavaScript 的擴充語法,所以我們也可以在裡面使用 JS 的表達式。例如運算操作、數組循環等高效的方式來去編寫動態 HTML 元素,就像其他後端語言的模板引擎,可以透過原生語言的表達式去動態拼接出 HTML 代碼。

使用表達式時需要放在大括號裡 - { },不然就會報錯。以下是使用表達式的例子:

function Greeting() {
  const name = 'KaLok';
  return (
    <div>
      <h1>Greeting Component</h1>
      <h2>{`Hello! I'm ${name}`}</h2>
    </div>
  )
}

更加常見的方法是使用 ES6 的數組 .map() 去循環渲染出動態數量的資料:

function CountryList() {
  const countries = ['US', 'UK', 'Brazil', 'Mexico'];

  return (
    <div className="container">
      <ul>
      {
        countries.map(country => {
          return <li key={country}>{country}</li>
        })
      }
      </ul>
    </div>
  )
}

這樣就可以根據數據的內容長度去動態渲染元素,可以看到在循環渲染的 <li> 標籤的屬性上有一個 keykey 是用於給 React 知道元素的唯一性,這樣就可以判斷出最有效的 DOM 修改方法。

JSX與HTML的命名分別

雖然 JSX 想讓開發者就像編寫 HTML 一樣,但是也是有一點點差別的。例如 classHTML 裡的屬性名稱,可以在 JavaScript 上則是建立 class 模板的關鍵字,所以就跟隨著原生 JavaScript 使用 DOM API 的方法 className。或者 <label> 標籤的屬性 for,也是 JavaScript 上的關鍵字。所以跟隨原生的方法則是 htmlFor

不同組件傳遞數據

前面提到 React 的核心思想就是把不同功能的部分分離出來成多個只負責一個功能的組件,所以在不同的組件中傳遞數據成了一個必備的技能。組件的關係大致可以分成父組件、子組件和相鄰組件。

不過需要知道的是數據傳遞的方式。React 的數據傳遞為單方向從上而下的模式,也就是數據會從父組件傳到子組件。

React 把傳入組件的數據稱為屬性 - props,以函數式組件來說,就是宣告函數時放在函數參數的位置。基本上就是一個 JavaScript 物件。組件的屬性就像 HTML 的屬性一樣,在標籤內以鍵和值的方式傳遞,可以從以下的例子中了解:

function App() {
  <GreetingWithName name={'React'} />
  <GreetingWithName name={'Vue'} />
  <GreetingWithName name={'Angular'} />
}

function GreetingWithName(props) {
  <div>
    <p>{`Hello, I\'m ${props.name}`}</p>
  </div>
}
因為父組件傳入的 props 是唯讀的,子組件不能夠直接修改 props 的值,如果子組件需要修改傳入數據則需要由父組件傳入修改變量的函數、再通過調用父組件的函數來改變數值。

狀態 - State

如果我們嘗試寫函數修改變量的話,會發現用戶界面並沒有更新,數據還是顯示著原來的數據。這是因為 React 並不會主動更新所有組件內的數值變化。我們需要使用 Hooks 來創建能讓 React 對應變動時重新渲染組件的變量(官方名稱為 State 狀態)。

通過 useState,我們可以在函數式組件也使用上 state 的功能。只要我們調用更改 state 的函數,該組件就會被重新渲染出來。

import { useState } from 'react';

function App() {
  const [name, setName] = useState();

  <GreetingWithName name={name} />
  <input name="name" onChange(e => setName(e.target.value)) />
}

useState 的函數會返回一個數值和修改數值的函數。值得注意的是,數值只適用於讀取用,想要修改數字需要調用函數來實現。

我們可以看到在 <input> 標籤裡寫上了 React 提供的 Event Listener,其實總體寫法跟 HTML的差不多。每當我們在輸入框裡打字或其他操作就會觸發 Event,然後就會調用修改 state 的函數,React 也會隨之而然的重新渲染這個組件。

總結

上面提到的都是一些 React 的基礎,內容也加上了很多我的個人補充。如果想要實際寫一些東西出來的話,可以參考官方文檔上的入門小項目,基本上涵蓋了這篇筆記的主要內容。只不過我花了很多時間在一些細節或是背後的邏輯,導致篇幅較長主題不多。

參考資料

  1. React Docs BETA - https://beta.reactjs.org/
  2. [Beta] Translating New React Docs #4135 - https://github.com/reactjs/reactjs.org/issues/4135
  3. 4 Reasons Why You Should Prefer Vite Over Create-React-App - https://semaphoreci.com/blog/vite
  4. Functional vs Class-Components in React | Quick Guide - https://www.xenonstack.com/blog/functional-vs-class-components