本系列文以製作專案為主軸,紀錄小弟學習React以及GrahQL的過程。主要是記下重點步驟以及我覺得需要記憶的部分,有覺得不明確的地方還請留言多多指教。
React component 的主要目的就是回傳一個 React element,看是作為children傳遞給親代element,或是給ReactDOM.reneder掛到html。
製作component有兩個方法: Class component 與 Function component。
這邊會大略談談class 跟 function的用法與差異,不過因為專案主要使用function component + hooks,class component的部分就不會太深入。
定義一個繼承 React.Component 的class就做成了class component,簡單範例:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
render這個function負責回傳element,是component中唯一必須被定義的,通常主要的JSX都寫在這。
當然只有render的話作為component不太有用,一般還要制定state,定義event handler,不過這些屬於component的共通觀念,待function compnent介紹完一併討論。
Function component相較class更簡單:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
只要定義一個回傳element的function就算。
那為什麼有兩種component呢?
兩者間最主要的差異在於class能夠保有自己的state,紀錄並更新部件的狀態,繼而更新畫面,而function原本純粹作為產生element的工具,不帶有自己的state。
看看帶有state的class component:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 為了讓 `this` 能在 callback 中被使用,這裡的綁定是必要的:
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
在constructor中用 this.state 宣告初始的state,就能在JSX代入並反映在畫面上。
而要更新state的話,用的是 this.setState()這個功能,像上面handleClick裡用的那樣。
React裡要更新state只能用這種方式,不能直接賦值給this.state,這麼做的話並不會觸發React更新畫面的機制。
另外class裡的function要調用this的話,都需要在constructor進行綁定,
像是這行:
this.handleClick = this.handleClick.bind(this);
如果沒麼做,就在function裡呼叫this.setState的話會報錯:
而除了state以外,class比function多了的功能還有生命週期相關的函式,像是componentDidMount(),componentWillUnmount()等,能在生成畫面的步驟間加入更多細節,詳細在這邊不做介紹,請參考官方文件。
雖然上面提到class跟function component間的功能差異,不過在Hooks的推出後這些差異被一口氣填平了許多,Hooks就是一系列能讓function component達成原先只有class component能做到的功能的函式,這裡挑幾個作簡介。
const [state, setState] = useState(initialState);
在function component作如上宣告的話就能在function component裡像class component一樣保存狀態並反映在畫面上,跟class component一樣的是要更新狀態的話必須經由setState,不能直接賦值。
state跟setState的名稱是可以改的,像是
const [count, setCount] = useState(initialCount);
所以多次呼叫useState設定不同的state是可以的,不像class component要把全部的state包在一起:
const [count, setCount] = useState(initialCount);
const [amount, setAmount] = useState(initialAmount);
使用上要注意的就是更新的時候,是會把整個狀態給覆蓋掉的,所以如果state是個包含多個key的object的話,當你想只更新部分key的話不能只代入那幾個key,這樣的話其他key會消失。
像是這樣:
const [state,setState] = useState({foo:"foo",bar:"bar"});
setState({bar:"foobar"}) //state會變成{bar:"foobar"},foo消失了
所以可利用ES6的展開語法:
setState(prevState => {
// Object.assign would also work
return {...prevState, bar:"foobar"};
});
prevState是更新前的state,用展開語法把整個前state代入後再用一樣的key把目標值覆蓋更新。
useEffect會在每一次render後被執行,它可以用來代替一些class component的生命週期功能,像是componentDidMount()。
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
useEffect有兩個參數,第一個就是render後被執行的function,而第二個則是可選的,以陣列代入的參數。
第二個參數用於管理useEffect呼叫的時機,主要有三個狀況:
省略
省略第二個參數的話就會在每回的render後執行這個useEffect。
包含依賴值們的陣列
所謂的"依賴值"指的時當這些值被更新的時候,才會在render後觸發useEffect,不然若在render時這些值跟前回一樣保持不變的話,這個useEffect就會被跳過。
空陣列
如果陣列裡沒有任何依賴值,這個useEffect在第一次render後就只會執行一次,因為沒有可以觸發更新的值。
在有用依賴值管理useEffect觸發的時候,React不建議在useEffect呼叫外部的function,因為這樣只看useEffect不會知道該function用了哪些參數,該把那些參數設為依賴值,詳細說明見此。
因為有了Hooks,使用function component變的便利許多,撰寫上也較class component輕便,接下來的實作用的都是function component 加 hooks。