React 入門 1 - Element, Function Component, & props


Posted by urlun0404 on 2022-10-24

接下來要認識 Components。

Component 在 React 裡面分為 Function ComponentClass Component,我目前的規畫是先熟悉Function Component的寫法,再去瞭解Class Components,會這樣規劃是基於以下幾個原因:

  1. 雖然以前大家都是用Class Component,但React 在16.8版推出Hook以後,現在專案開發的趨勢是使用 Function Component 這種比較簡潔的寫法;
  2. 就我所知,學習 Class Component 需要認識跟生命週期(Lifetime)有關的方法、比較複雜,所以先從Function Components下手;
  3. 考慮到舊專案可能還在用Class Component,所以之後還是有必要學習!

不過在認識Component以前,還是先快速認識一下React語法裡面真正的最小組成單位,Element。


Element

React對於Element的解釋是這樣子的:

  1. Element是React App的最小組成單位;
  2. 任何Element本質上都是一個JavaScript的物件。

所以,具體來說什麼是Element?假設現在先用純HTML語法寫出一個具有class屬性的h1標籤,語法如下:

<h1 class="redTitle">Hello!</h1>

在JavaScript操作DOM時,通常稱呼這個h1標籤為 object(物件);在React裡則稱為 element

若要使用React語法創造這樣的h1元素,必須使用 React.createElement() 方法,改成下面這樣的語法:

const element = React.createElement("h1",{className: "redTitle", "Hello!"});

// 會產生像下面這樣的物件
{
    "h1",             // tag name
    "redTitle",       // class (HTML attribute),
    "Hello!"          // content
}

想像一下,如果每一個element都像這樣寫,在開發時反而變得更複雜,也不容易除錯,因此第0篇時有提到,React提供類似HTML語法的擴充語法JSX,讓我們可以便利快速地去產生element,所以用JSX語法改寫上面的Element:

const element = <h1 className="redTitle">Hello!</h1>;

這裡唯一要注意的是,原來HTML的class屬性要記得改成 className(因為class是JavaScript的關鍵字)。


Function Component and props

這邊並只先專注於介紹Function Component,同時也介紹Component的重要性質-props。

Component(元件)是指至少一個以上Element組成的獨立物件,Components之間可以彼此互不干擾地運作,也能多個Components組合起來形成網頁介面,總之使用Component有以下幾個性質:

  1. Components可以各自獨立更新:意思是若網頁裡有好幾個Components,若只有其中一個Component的值改變,可以只更新這個Component;

  2. 前面所提到的Element可以是原生的DOM物件 <div></div><img/>等,也能是一個自製Component (User-Defined Component),例如 Function Component 和 Class Component。這裡要注意的是,為了區隔原生DOM物件和自製Component,自製Component都要以大寫開頭,例如:

    const nativeDomElement = <div>這是原生DOM物件所產生的Element</div>;
    const userDefinedElement = <Hello />;    // 這個Element是一個名為Hello的自製Component
    
  3. React官方建議太過複雜的JSX語法可以拆成多個Component再去組合

  4. Component可以擁有自己的屬性(properties),在React稱為 props

Function Component

Function Component顧名思義就是用JavaScript function 定義一個Component。

例如下方程式碼就是定義一個跟成績有關、名為 Grade 的function component,最後使用 ReactDOM.render() 方法將這個Component渲染到網頁畫面上。

// function component
function Grade(){
    return <div>Math</div>;
}

// Render
ReactDOM.render(<Grade/>, getElementById('root'));


props

前面有提到Component可以有自己的屬性(properties),在React裡面這些屬性構成一個名為 props 的屬性物件。props的語法有點像是HTML的attribute,但你可以根據需求自行決定這個Component擁有哪些props?並且將props傳遞到Component裡面去處理。

先用前面的Grade Component舉個例子,假設今天希望讓學生自行輸入數學成績,可以透過props去傳遞學生所輸入的值,再動態地顯示在畫面上,範例如下:

// function component
function Grade(props){
    return (
        <div>
          <h1>{props.subject}</h1>
          <h3>Your {props.subject} grade is {props.grade} point!</h3>
        </div>
    );
}

// Render
ReactDOM.render( <Grade subject="Math" grade={prompt("Enter your Math grade:")} />
    ,
    getElementById('root')
);

首先,我們用類似HTML attribute的語法,在Grade Component 寫上想要傳遞的 props,如 subject="Math"grade={prompt("Enter your Math grade:")}

注意這裡的grade屬性用到的是JavaScript的prompt方法,所以用大括號 {} 包住JS語法,告訴JSX要寫入的是JS語法,而不是像"Math" 單純的字串(string)。

接著將props物件傳遞到 fucntion Grade(props){...},並且同樣以JavaScript取得物件屬性值的語法寫下 {props.subject} 獲得屬性值。這裡要特別注意的是,return 只能回傳一個值,但今天希望同時回傳科目名稱(props.subject)和成績(subject.grade),就必須使用一組<div></div>(或者改以一組 [<React.Fragement></React.Fragement>](https://reactjs.org/docs/fragments.html)<></>)去包住其他元素(<h1><h3>)。

如果希望學生一次國文、數學和英文成績,我們能藉由Component可以重複利用的性質,重複使用Grade Component,讓學生輸入三個科目的成績再回傳呢?

這裡我們特別再做一個Student Component去整理代表三個科目的三個 Grade Components,並且渲染Student Component,程式碼如下:

// function component
function Grade(props){
    return (
        <div>
          <h1>{props.subject}</h1>
          <h3>Your {props.subject} grade is {props.grade} point!</h3>
        </div>
    );
}

function Student(props){
    return(
        <div>
            <Grade subject="Math" grade={props.math}/>
            <Grade subject="Chinese" grade={props.chinese}/>
            <Grade subject="English" grade={props.english}/>
        </div>
    );
}

// Render
ReactDOM.render(
    <Student
      chinese={prompt("Enter your Chinese grade:")}
      math={prompt("Enter your Math grade:")}
      english={prompt("Enter your English grade:")}
    />
    ,
    getElementById('root')
);

附上線上程式碼執行結果:
Live Demo

最後補充 props 的值僅供讀取 (Read-Only),傳遞後不能修改,例如下面程式碼就是錯的:

function Sum(props){
    return props.total -= props.subtraction;
}

但網頁常常需要一些可讓使用者改變的效果,如果希望改變元素值的話,則要使用到state


重點整理

  1. Function Component 的 return 只能回傳一個Element;要回傳多個Elements,必須用一組Element(常用<div></div>、或 <React.Fragement></React.Fragement><></>)包住要多個Elements再回傳,例如 return (<div> <h1></h1><img/> </div>) 或是 return (<> <h1></h1><img/> </>)
  2. Component 可以重複利用、相互組合,因此太過複雜的語法或Element建議分成多個Component再組合。
  3. Component 可以有自己的屬性-props,props 能夠動態傳遞屬性值到Component裡面作處理或顯示在畫面上。
  4. props 不能改變傳遞的屬性值,若要改變值則必須用到 state


補充:<Fragement>
前面有提到function component只能回傳一組JSX標籤,原因在於function component實際是一個函式(function),函式只能回傳一個值;誠如前面所說,function component所回傳的JSX標籤語法其實也是回傳一個物件的函式:

<h1 class="redTitle">Hello!</h1>

// i.e., React.createElement("h1",{className: "redTitle", "Hello!"})
// return {"h1", "redTitle", "Hello!" }

若function component回傳兩個JSX標籤即代表會回傳兩個值:

return (<h1 class="redTitle">Hello!</h1><h3>World!</h3>)

/*
 *  return (
 *      React.createElement("h1",{className: "redTitle", "Hello!"});
 *      React.createElement("h3",{null, "World!"});
 *  );
 */

由此可知這並不符合JavaScript語法的規範,所以才必須再用一組JSX標籤包住回傳:

return (
<div>
    <h1 class="redTitle">Hello!</h1>
    <h3>World!</h3>
</div>
);


/*
 *  return (
 *      React.createElement("div",{null, {
 *          React.createElement("h1", {className: "redTitle", "Hello!"}),
 *          React.createElement("h3",{null, "World!"})
 *      });
 *  );
 */

但因為Component可以包住另一個component,若一個component內有另一個component,而這個component內又有另一個component;若為了回傳一個值而使用 <div></div>,解析成HTML很可能會變成如下面的狀況,導致重要資訊被多個不重要 <div></div> 包住:

<div>
    <div>
        <h1 class="redTitle">Hello!</h1>
        <h3>World!</h3>
        <div>
            <p>...</p>
        </div>
    </div>
    <div>
        <div>
            <p>...</p>
        </div>
    </div>
</div>

在網頁語意上, <h1> 代表重要標題,而 <div> 標籤無法傳達任何重要資訊,若 <h1> 的外圍被多個 <div> 包住,造成網頁搜尋引擎不能從網頁第一層看到重要訊息 <h1> ,就會降低網頁的SEO。

若將 <div></div> 改成 <Fragement></Fragement>標籤(或用簡寫 <></>):

return (<>
    <h1 class="redTitle">Hello!</h1>
    <h3>World!</h3>
    <>
        <p>...</p>
    </>
</>);

則會解析成:

<h1 class="redTitle">Hello!</h1>
<h3>World!</h3>
<p>...</p>

如此一來就可以避免重要的語意標籤外圍有好幾層的 <div></div> 標籤,同時增強網頁的SEO。


References


#frontend #React







Related Posts

[html] 暗藏玄機的跨域連結 - link rel

[html] 暗藏玄機的跨域連結 - link rel

Python - 隨機模組(random)

Python - 隨機模組(random)

 [day 05] getter & setter: 屬性描述器

[day 05] getter & setter: 屬性描述器


Comments