0%

[week 21] 初探 React:Component、JSX 語法、props 與 state 的不同

本篇為 [FE302] React 基礎 - hooks 版本 這門課程的學習筆記。如有錯誤歡迎指正!

學習目標:

 P1 我知道 React 的目的以及原理
 P1 我知道我們為什麼需要 React
 P1 我知道使用 React 跟之前使用 jQuery 的區別
 P1 我理解 state 跟 props 的不同

前言

React 版本差異

關於 React 發展歷史,其實起源於 2013 年由 Facebook 開源出來的一個 Library,直到 2015 年更新到 v15 版本後又為更多人使用。

而 React 16.8.0 則是第一個支援 Hook 的版本,這部分我們之後會再提到。到 2020 年 10 月出現 React v17,課程將以此版本為主,和 v16 基本上沒有太大的差異。

React 中一定會用到的 ES6 語法

在開始之前,可以先複習這篇:React 中一定會用到的 JavaScript 語法,主要包括下列幾個重點:

  • 樣板字面值(Template literals/Template strings)
  • 箭頭函式(arrow functions)
  • 物件屬性名稱縮寫(Shorthand property names)
  • 解構賦值(Destructuring assignment)
  • 展開語法(spread syntax)
  • 其餘語法(rest syntax)

可事先具備這些知識,或到時候再搭配 MDN 查詢用法,因為這些是之後實作 React 時一定會碰到的語法。


React:JavaScript 函式庫

我們經常會聽到前端三大框架:React、Vue、Angular,但根據 React 官網定義:

React: A JavaScript library for building user interfaces

嚴格來說,React 其實不算是一個框架,而是一個 JavaScript Libaray。通常使用 React 時會搭配其他 Libaray 使用,整個生態系結合起來就會和框架差不多。

為什麼我們需要 React?

我們可以從「React 提供了哪些功能」的角度來思考這個問題。

之所以會有 React 的誕生,可從原有的 JavaScript MVC 架構談起:Facebook 認為 MVC 模式已無法滿足擴展需求,隨著應用規模增加,系統的複雜度會成級數成長,增加開發難度以及不易進行後續維護。

因此開源出 React 這個 JavaScript Library,負責處理 MVC 的 View(介面)部分,解決思路就是「當狀態改變時,直接重新渲染畫面」,引入 Virtual DOM 的概念,透過 DOM Diff 演算法算出實際需要更新的部分,有效減少渲染次數以提高效能。

瞭解到 React 的發展過程之後,再回到「為什麼我們需要 React?」這個問題,React 的核心概念如下:

  • Component 元件化
    • 開發 React 很重要的一點,就是去思考在頁面有哪些重複性高或相似的 Element,再透過 JSX 語法將這些 Element 建立成一個 Component
    • 讓每個 Components 擁有重複性及可擴充性,以模組化的方式進行開發
  • JSX 語法
    • 透過 JSX 語法,即可將 HTML 語法轉成 JavaScript 的形式,讓我們用來建立 React elements
  • Virtual DOM
    • 因為操作 DOM 這件事,其實會耗費很大的成本;而 React 背後運行機制,就是透過比對 Virtual DOM 來避免直接操作 DOM,藉此來提升效能
    • 此外,因為建立 Virtual DOM 這一虛擬層,也讓我們能夠對程式碼進行更多操作
  • Hooks API
    • hook 其實就是 function,讓我們可以在 function component 中管理狀態和使用生命周期等功能,藉此簡化程式碼與提高重用性

可以不用 React 嗎?

由於 React 中,強調「模組化」的概念,如果是實作一個靜態網頁,或是專案規模較小、沒有模組化需求時,直接使用 HTML、CSS、JavaScript 等進行開發會更方便省時。

因此,並不是所有專案都適合用 React 進行開發,應該根據網站需求去決定合適的工具。

參考資料:

  • React 教學 - React JavaScript UI Library

初探 React

在開始前可先透過 CodeSandbox 進行練習,這是一個支援前端框架開發線上編輯器(IDE),類似複雜版的 Codepen。

點選 Create Sandbox,並選擇 React 來快速建立開發環境:

建立完成的初始畫面如下,藉由像這樣快速建立環境,也能用來幫助線上 debug:

JSX:用來建立 React 元素

簡單來說,JSX(JavaScript extension syntax)就是 HTML/XML + JavaScript。

React 提供的 JSX 語法,是透過底層的 Babel 機制,將 HTML 語法轉成 JavaScript Function 的形式,讓我們能用來建立 React elements:

const rootElement = document.getElementById("root");
ReactDOM.render(<h2>app</h2>, rootElement
);

以 mount Hello 這個 component 為例:

mount:意思是把 component 放到畫面上

function Hello() {
  // 在 Hello() 中 return 什麼,就會在畫面 render 出什麼
  return <h1>hello world!</h1>
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Hello />, rootElement
);

Hello() 這個 component 可以接收參數 props,寫在大括號中會被解讀 React 為 JavaScript 程式碼執行,例如 {JS code}

function Hello(props) {
  return <h1>Hello, {props.name}!</h1>
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Hello name="Heidi"/>, rootElement
);

ES6 的解構寫法

更常見的寫法,是把 props 參數改成 ES6 的解構寫法,也就是在大括號內傳入參數:

function Hello({name}) {
  return <h1>Hello, {name}!</h1>
}

使用 JSX 語法需注意的地方

JSX 語法和 template engine 不同,在使用上沒有那麼自由,必須遵守幾點規則,以避免寫出不合法的 JSX :

  • 沒有迴圈的概念
  • 沒有 if-else 判斷式

那麼該如何解決這種情況呢?以下有兩種解決方式,以及範例程式碼:

  • 透過三元運算子來進行判斷:適用於只有 ture/false 的情況
{todo.isDone ? '已完成' : '未完成'}
  • 透過邏輯運算子 && 的短路行為:適用於多種可能的情況
{todo.isDone && '已完成'}
{!todo.isDone && '未完成'}

JSX 特性:自動 escape

此外,根據官方文件,JSX 語法能夠預防 Injection Attacks,提供 escape 功能。

如果真的想要 render 出 innerHTML,則需透過 dangerouslysetinnerhtml 這個冗長的標籤,一般而言不會使用這個方法。

但需要注意,如果在 a 連結標間中,有使用者輸入的區塊,例如:

<a href={todo.content}>click me!</a>

這時若被惡意輸入 javascript:alert() 程式碼,點擊 a 連結就會執行該 JS 程式碼。這其實因為 React 沒有跳脫冒號,造成的 click based XSS:

防範方式有兩種:

  • 不要在 a 標籤內放入使用者輸入
  • 加上 encodeURIComponent() 語法,把字串轉化成 escape 格式的字符串
<a href={window.encodeURIComponent(todo.content)}>click me!</a>

常見的 component:Counter

最後再以常見的 Counter component 為例:

function Counter() {
  // useState() 會建立一個陣列 state, 傳入參數為初始值
  const [value, setValue] = React.useState(1);
  function handleClick() {
    // 透過呼叫 setValue() 來更動 state
    setValue(value + 1);
  }
  // 建立 onClick 監聽事件
  return <button onClick={handleClick}>{value}</button>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(
<Counter />, rootElement
);

透過點擊 button 觸發事件來改變 state,React 再根據 state 內容來 render 出畫面:


React 的思考模式跟以前的思考模式有什麼不一樣?

React 最大的不同,在於多了 Component 和 State 這兩個概念。

Component 元件

這和以往的思考模式其實很不一樣,像是在切好的 UI 畫面上,將每個 element 新增各種功能;或是以 MVC 架構進行開發。

而在 React 中,一個元件(component)是 React 的最小單位,再透過 Props 來設定屬性或資料。由於所有東西或介面都是由元件所組成,強調的是 UI 元件的封裝性、共用性及擴展性。

React State

在 React 是「透過資料的狀態,決定是否重新渲染畫面」,這和以往直接更改操作 DOM 元素不同;而是以類似間接的方式,透過 React 演算法比對 Virtual DOM 來決定是否更新真實 DOM。

也就是說,Component 是透過資料狀態,來決定是否更新 UI 畫面,而 Component 中有兩種資料來源:

  1. 外部傳進 Component 的 Props
  2. Component 內部的 State

每當 React 偵測到 Props 或 State 有改變時,就會自動重新渲染。

剛開始對這種以 Component 和 State 為核心概念的寫法很不習慣,但卻也有點熟悉的感覺,就像以前在實作前後端分離的留言板或 Todo List 時很像。可能也因為 function component,又和透過功能區分的模組化很類似,但需要考慮到該如何更改 State 來呈現畫面。


State 跟 Props 的差別在哪裡?

State 和 Props 都是 JavaScript 物件,我們在前面有提到,當這兩者之一有改變時,會觸發 React 重新渲染畫面。兩者差別在於:

State

  • 在 Component 內部被管理,類似於 function 中的宣告變數
  • 是 Component 本身的狀態,只有該 Component 能透過 setState 變更 state,這部分我們會在之後的 Hooks 詳細介紹

Props

  • 從外部傳進 Component,類似於 function 的參數
  • 由於 React 單向資料流的特性,Props 是父層由上往下傳遞給子層
  • Props 不能被接收的子層修改,但也可能是父層的 State,可透過 setState 進行變更,再把更新的 State 值做為新的 Props 傳遞給子層

包在標籤中間的 props:children

在 React component 中,包在標籤中間的東西,稱為 children,children 也是一個 props。

以 Todoitem 為例,這裡的 children 指的就是 Watch a movie

function Todoitem({n, children}) {
  return <h1>Todo {n}: {children}</h1>
}

const rootElement = document.getElementById("root");
ReactDOM.render(
<Todoitem n={1}>
  Watch a movie
</Todoitem>, rootElement
);

參考資料:

  • React State - React 教學Tutorial - Fooish 程式技術
  • React Day5 - state 與 setState
  • React的props與state
  • 【DAY 04】React!說,Props是誰啊?
  • 【Day 6】Child Component && Props

環境建置:create-react-app

React 環境建置可分為兩種:

  • 自己從頭開始做
    • 使用 React + Webpack 打包 + Bable 編譯 + Webpack Dev Server
  • 使用現成的套件
    • create-react-app,由 React 官方提供

這裡會以現成的程式來做示範,詳細說明可參考 facebook/create-react-app

安裝指令

依照下方指令安裝相關套件並啟動:

$ npx create-react-app my-app
$ cd my-app
$ npm start

成功運行後,就會在 localhost 開一個 server:

在開始專案之前,首先要閱讀 README.md 的說明,還有查看 package.json 確認安裝了哪些套件等訊息。

再來是 src\index.js 檔案,和我們在 CodeSandbox 看到的內容非常類似:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// 與資料收集、效能有關
import reportWebVitals from './reportWebVitals';

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

reportWebVitals();
  • React.StrictMode:代表嚴格模式中,會像 ESLint 進行檢查與警告,但有時為了偵測,可能會呼叫兩次,造成和想像中不同的結果,因此建議不加上
  • <App />:render 出 App conponent,也就是 render 的畫面

可在 src\App.js 檔案,修改要 render 的畫面:

function App() {
  return (
    <div className="App">
      Hello World!
    </div>
  );
}

export default App;

重整頁面即可看到結果:

結論

到這裡我們瞭解 React 重要的概念 Component,以及如何使用 JSX 語法來建立 React 元素。

此外,除了可以利用線上編輯器 CodeSandbox,也可以在本地端安裝官方提供的 create-react-app 套件,來快速建置 React 開發環境。