[react] 元件(component)
未閱讀:Writing Resilient Components @ OverReacted
建立元件(Component)
React 中的元件(component)是一個小而可重複使用的程式碼,每一個元件都必須從 Component
這個類別(class)而來,component class 就像是一個可以用來建立許多不同元件的工廠。
在 render()
方法中,記得要使用 return
回傳樣版:
import React from 'react';
import ReactDOM from 'react-dom';
class MyComponentClass extends React.Component {
render() {
return <h1>Hello world</h1>;
}
}
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<MyComponentClass />);
轉譯多行的樣版(template)
當在 React Component 中要轉譯多行的樣版時,要使用括號 ()
把多行的程式碼包起來:
import React from 'react';
import ReactDOM from 'react-dom';
class QuoteMaker extends React.Component {
render() {
return (
<blockquote>
<p>What is important now is to recover our senses.</p>
<cite>
<a target="_blank" href="https://en.wikipedia.org/wiki/Susan_Sontag">
Susan Sontag
</a>
</cite>
</blockquote>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<QuoteMaker />);
Stateful 或 Stateless
在 React 中,Stateful 指的是帶有 state
屬性的元件;而 stateless 則是指任何不帶有 state
屬性的元件。
在 React 元件中,props
只可以透過自己以外的其他元件來修改它,絕不應該更新它自己的 this.props
;而 state
則相反,只應該透過自己更新 this.state
,其他的元件都不應該更改自己以外其他的元件。
States
在 React 元件中有兩種方式可以存取動態的資料,分別是 props
和 state
。與 props
不同的地方在於,state
不是透過外部傳進來的,而是由一個元件決定它自己內部 state
。
定義 States
我們可以在 Class(類別)的建構式(constructor)中,透過 this.state
來定義 state 的內容:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = { mood: 'decent' };
}
render() {
return <h1>I'm feeling {this.state.mood}!</h1>;
}
}
<Example />;
使用 States
keywords: this.state
在元件中,只需要透過 this.state.name-of-property
就可以取得 state 的內容。
更新 State
keywords: this.setState
使用 this.setState
可以更新資料狀態(state),這個方法會將新的物件合併(merge)進原本的物件,因此資料狀態中沒有被更新到的部分,則會保留原有的狀態。
每當呼叫 this.setState()
時,this.setState()
會在資料狀態改變時,自動呼叫 .render()
,進而導致畫面重新轉譯。
簡單來說,在
this.setState()
之後,會自動呼叫.render()
,因此不能在 render 函式中使用this.setState()
,因為這將導致無窮迴圈。
需要留意的是,在 <button onClick={this.toggleMood}>
中,它會尚失其原本的 this
所指稱的對象,因此需要在 constructor 中先使用 this.toggleMood = this.toggleMood.bind(this);
把指稱到的 this
綁定好:
import React from 'react';
import ReactDOM from 'react-dom';
class Mood extends React.Component {
constructor(props) {
super(props);
this.state = { mood: 'good' };
// 這段是必要的,如果沒有這樣寫的話, toggleMood 在 onClick 的 callback 執行時,
// this 會變成 Window 物件,而不是原本的 Mood 這個類別。
this.toggleMood = this.toggleMood.bind(this);
}
toggleMood() {
const newMood = this.state.mood == 'good' ? 'bad' : 'good';
this.setState({ mood: newMood });
}
render() {
return (
<div>
<h1>I'm feeling {this.state.mood}!</h1>
<button onClick={this.toggleMood}>Click Me</button>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<Mood />);
當你透過
this
來定義事件處理器(event handler)時,記得要在建構式中加上this.methodName = this.methodName.bind(this)
。若想瞭解背後的理由,可參考 Handling Events @ React。
如果不想使用在 constructor 中去綁定 this
使用 public class fields 語法
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
};
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
在綁定事件的地方使用箭頭函式
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return <button onClick={(e) => this.handleClick(e)}>Click me</button>;
}
}
這種作法的問題在於,每次轉譯 <LoggingButton />
時,都會有新的回呼函式(callback)被建立,在大部分的情況下這麼做是沒問題的,然而如果回呼函式是透過 prop 的方式傳遞到子層元件時,可能會導致這些元件執行額外不必要的重新轉譯(re-rendering)。因此官方建議還是在建構式中透過 bind
綁定,或使用 class fields 語法來避免可能的效能問題。
❗ 官方並不建議這種作法。
Presentational Component 和 Display Component
在 React 當中,要時刻提醒自己不要讓單一個元件做太多不同的功能,而 Separating container components from presentational components 可以幫助我們思考該元件需不需要拆開,以及如何進行這個分割。
基本的原則是:「如果一個元件中有 state
,而且又會根據 props
進行計算,或者要管理複雜的邏輯,那麼這個元件就不應該同時轉譯 HTML-like JSX。」
不要轉譯 HTML-like JSX 時,這個元件只需要轉譯其他元件,而被它轉譯的主要任務就是轉譯 HTML-like JSX。這也是將商業邏輯從表現層中抽離出來(separates your business logic from your presentational logic)的設計模式。
在 presentational 元件中,通常只會看到
render
而不會看到其他的功能。
Stateless Functional Component
如果你有一個元件裡面只有 render
函式,那麼你可以不用使用 React.Component
而是可以完全使用 JavaScript 的函式,這樣的函式稱作 stateless function component,舉例來說:
// 原本的 presentational 元件(只有 render function 在內)
export class MyComponentClass extends React.Component {
render() {
return <h1>{this.props.title}</h1>;
}
}
改成 stateless function component 後:
// Stateless functional component way to display a prop:
export const MyComponentClass = (props) => {
return <h1>{props.title}</h1>;
};
在 stateless functional components 中,通常會代入 props
,要取用這些 props
只需要在 stateless functional component 中代入參數,這些參數即會自動等同於 props
。
Controlled vs Uncontrolled
Controlled and uncontrolled form inputs in React don't have to be complicated
在 React 的 uncontrolled component 中,資料狀態是保存在 DOM 元素上,而不是由 React 來控制和管理;相反地,如果是 controlled component,所有表單的資料狀態都會透過 React 所控制。
uncontrolled component
想想 <input type='text' />
這個元素,當你在對話框輸入內容時,可以透過 input 元素的 value
屬性來取得當前輸入框內的內容,也就是說 <input type='text' />
時刻都追蹤著它裡面的內容,並且可以回傳給你,這個 input
的 value 是保存在 DOM 元素內,而非透過 React 來管控,這時就屬於 uncontrolled component。
controlled component
在 React 中大部分的元件都屬於 controlled components。在 controlled component 中,表單的資料狀態都是透過 React 來管控(而非直接由 DOM 去取得),如果你想得知該 input 的 value,必須透過 state
得知。
當你對 <input />
給予 value
屬性時,這個 <input />
就變成了 controlled,它不再會使用 DOM 內部的資料狀態,而這也是在 React 中較常所用的方式。
由於一旦對 <input />
加上 value
屬性後,該表單元素即會變成 controlled component,因此若是想針對 uncontrolled component 設定預設值的話,可以使用 default values
這個屬性。