接下來要認識 Components。
Component 在 React 裡面分為 Function Component 和 Class Component,我目前的規畫是先熟悉Function Component的寫法,再去瞭解Class Components,會這樣規劃是基於以下幾個原因:
- 雖然以前大家都是用Class Component,但React 在16.8版推出Hook以後,現在專案開發的趨勢是使用 Function Component 這種比較簡潔的寫法;
- 就我所知,學習 Class Component 需要認識跟生命週期(Lifetime)有關的方法、比較複雜,所以先從Function Components下手;
- 考慮到舊專案可能還在用Class Component,所以之後還是有必要學習!
不過在認識Component以前,還是先快速認識一下React語法裡面真正的最小組成單位,Element。
Element
React對於Element的解釋是這樣子的:
- Element是React App的最小組成單位;
- 任何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有以下幾個性質:
Components可以各自獨立更新:意思是若網頁裡有好幾個Components,若只有其中一個Component的值改變,可以只更新這個Component;
前面所提到的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
React官方建議太過複雜的JSX語法可以拆成多個Component再去組合;
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。
重點整理
- Function Component 的
return
只能回傳一個Element;要回傳多個Elements,必須用一組Element(常用<div></div>
、或<React.Fragement></React.Fragement>
或<></>
)包住要多個Elements再回傳,例如return (<div> <h1></h1><img/> </div>)
或是return (<> <h1></h1><img/> </>)
。 - Component 可以重複利用、相互組合,因此太過複雜的語法或Element建議分成多個Component再組合。
- Component 可以有自己的屬性-props,props 能夠動態傳遞屬性值到Component裡面作處理或顯示在畫面上。
- 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