React
@前端讀書會
上德, 2021/6/4
Outline
1
一、React 基本知識
2
Hello World
<script>
3
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
)
<body>
<div id="root">
</div>
JSX
JSX
JSX 是一個直接寫在 javascript中的擴充語法,會透過 Babel編譯成 React 用來建立 Element的函式。
React 的 Element並不是真的 DOM,他是一種「virtual DOM」。所有的真實 DOM的改變都是經由改變 virtual DOM之後,經由 diff演算法批次改變 DOM的內容。
4
const content = (
<TalkList>
{ talks.map(talk => <Talk talk={talk} />)}
</TalkList>
)
const h1 = <h1 className="title">Hello Adam</h1>
const input = <label htmlFor="test">Test</label>
大部份的 attribute和原來的 html都長得一樣,只是轉為小駝峰的格式而已。只有極少部份會因為 javascript保留字的考量會稍微不太一樣,比如:
class => className
for => htmlFor
單向資料流 (Unidirectional Data Flow)
React Component只有兩種資料型式:1. Props 2. State
React 中資料的流向只能從父層 Component 的 State/Props 傳入子 Component的 Props
每當 React偵測到 Props或 State有更新時,就會自動重繪整個 Component
5
React的更新機制
6
Props/State 改變
重新呼叫 render()
生成新的 virtual DOM
新舊 virtual DOM 進行 diff 演算法比對
把差異更新到真的的 DOM上
State
State是 Component內部維護的的資料。
不能直接對 State賦值,只能透過 setState() 來更新 state。
當 state被更新時 render function會被重新呼叫。
註:在介紹 hooks之前,用 Class Component比較好解釋 State,所以暫時還是先用 Class Component的程式來當範例。
7
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
// 啟動一個 timer 每秒鐘會自動更新時間
this.timerID = setInterval(() => this.tick(),1000);
}
// 當元件要從 DOM 中被移除 (remove) 前,要手動清理監聽事件
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
// 用 setState() 來更新 State ,React 會自動呼叫 render() 重繪畫面
this.setState({ date: new Date() });
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Stateful / Stateless Component
Stateful Component:內部有維護自身狀態 (State)的改變,通常會伴隨著事件處理或生命週期下觸發狀態的更新。早期只能用 Class Component來寫,現在可以使用 Function Component + hooks來寫。
8
const ImageList = props => {
const images = props.images.map({ description, id, url} => {
return <img alt={description} key={id} src={urls.regular} />
})
return <div>{images}</div>
}
Stateless Component: (又稱 Pure Component) 是一個 pure function,props傳入什麼他就 render什麼。
Controlled / Uncontrolled Element
9
state = { term: '' }
�render () {
return (
<input
type="text"
value={this.state.term}
onChange={e => this.setState({ term: e.target.value })}
/>
)
}
Controlled Element: value存放在 state
Uncontrolled Element: value存放在 DOM裡面,必須要從 DOM取出來後才能知道 value是什麼(古早時期 jQuery的慣用寫法)
* 我們應該將所有資訊存放在 Component內而非 DOM裡面
Class Component vs Function Component
10
| Class Component | Function Componnet | Function Component + Hooks |
Lifecycle | O | X | X |
State | O | X | O |
非同步操作 | O | X | O |
傳統上 Function Component 因為必須是 pure function – 透過 function傳入進去的 props,回傳 virtual DOM回去;既無法暫停 render也不能自動 render。
所以早期大多採用 Class Component以保存 Component的內部狀態。
在React v16.8 (2019) 推出 hooks功能之後官方已不再推薦採用 Class Component了。
Lifecycle
看一下就好… 改用 hooks之後就不用再管 Lifecycle了
11
class Title extends Component {
shouldComponentUpdate(nextProps, nextState) {
// 改變後的 props 跟原本一樣的話就不要呼叫 render()
if (nextProps.title !== this.props.title) {
return true
}
return false
}
� render() {
return <div>{this.props.title}</div>
}
}
效能優化
React需要特別注意減少 render次數和計算量
範例(每當state變更,render就一直被執行)
https://codepen.io/cdpqdnvr/pen/ZEeawKx?editors=1010
參考資料:
12
import React from 'react';
�const Child = () => {
console.log('觸發Child元件渲染');
return (
<h1>這是child元件的渲染內容!</h1>
)
}
�const areEqual = (prevProps, nextProps) => {
// 在這裡比較差異
}
�const MemoChild = React.memo(Child, areEqual);
�export default () => {
const [num, setNum] = useState(0);
return (
<>
{num}
<button onClick={() => setNum(num + 1)}>num加1</button>
<MemoChild />
</>
);
}
Hooks
13
Hooks - useState
14
const { useState } = React
// 寫在 render function內
const [input, setValue] = useState("")
Hooks - useEffect
15
import React, { useState, useEffect } from 'react';
�function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
� useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// 指定如何在這個 effect 之後執行清除:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
� if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
和 Vue的 watch有點像,但在 React裡面被認為是一種 state變更的附作用,可見背後思考邏輯的不同
Hooks - useContext
16
// App.js
export const ThemeContext = React.createContext()
const App = () => {
const [dark, setDark] = useState(true);
return (
<>
<ThemeContext.Provider value={dark}>
<FunctionComponent />
<ClassComponent />
</ThemeContext.Provider>
</>
);
}
�// FunctionComponent.js
const FunctionComponent = () => {
return (
<ButtonGroupComponent />
);
}
�// ButtonGroupComponent.js
import { ThemeContext } from './App.js';
const ButtonGroupComponent = () => {
// get dark value from ThemeContext
const darkTheme = useContext(ThemeContext)
const themeStyle = {
backgroundColor: darkTheme ? '#2c3e50': '#f1c40f',
color: darkTheme ? '#ecf0f1' : '#2c3e50'
}
return (
<button style={themeStyle}>useContext</button>
);
}
二、React vs Vue
17
functional
View = fn(state)
18
MVVM
vs
Pull更新
this.setState({value: 3})
State是不可變的,需用新的 State-tree全部取代舊的 State-tree,再經由React內部的演算法比較全部的 virtual DOM差異。
需額外手動優化效能,以避免不必要的 rerender。
19
Push更新
vs
this.value = 3
各別元素監聽 State的更新,以響應式的機制更新局部節點的 DOM。
Vue內部已完成所有的效能優化,不需手動優化效能。
手動優化
20
Do nothing
vs
Controlled Element
Vue有和「雙向綁定」的方式,可以簡單的達到和 React 的 Controlled Element一樣的效果。
是以直接監聽資料變更,以響應式的方式來綁定資料。
21
Two-way binding
vs
state = { term: '' }
�render () {
return (
<input
type="text"
value={this.state.term}
onChange={e => this.setState({ term: e.target.value })}
/>
)
}
React 的 State是不可變的,必須透過 setState函式變更(實際上是替換掉),所以是以單向資料流的方式來完成。
實作上需要手動寫監聽事件將元素的 Value更新到 State。
<input
type="text"
v-model="term"
/>
props.fn (Callback)
22
Emit
vs
React 的資料流向只能從父層 Component 透過 Props 流向子 Component,所以當子 Component需要將資料傳遞過父層時,得透過 Props傳入的 Callback Function回傳回去。
// parent
const App = () => {
� const handleClick = (evt) => {
console.log(evt)
}
� return (
<div>
<AppButton onClick={handleClick}></AppButton>
</div>
)
}
// child�const AppButton = ({ onClick = () => {} }) => {
return <button onClick={onClick}>Click Me</button>
}
props.children
巢狀Component中,子Component會作為props.children傳入父Component
23
<slot>
vs
const Child = ({ name }) => (
<h1>{`Hello ${name}`}</h1>
);
�const Parent = ({ children }) => (
<>
<Something />
{children}
<OtherThings />
</>
);
�const Page = () => (
<div>
<Parent>
{/* <Child/>會作為 props.chilren傳入<Parent/>中 */}
<Child name="world"></Child>
</Parent>
</div>
);
<template>
<!– Child.vue -->
<h1>Hello {{name}}</h1>
</template>
�<template>
<!– Parent.vue -->
<div>
<Something />
<slot></slot>
<OtherThings />
</div>
</template>
�<template>
<!– Page.vue -->
<div>
<Parent>
<Child name="world"></Child>
</Parent>
</div>
</template>
巢狀Component中,子Component會傳入父Component的 <slot>元素中
Hooks
24
Composition-api
vs
CSS-in-JS
25
.css file
vs
Vue原生對於 css module支援度就很好,不需額外搭配套件就能整合的很好。
Vue 也可以額外搭配套件用 css-in-js的方式編寫樣式,但較少人這樣做。
三、總結
26
其他
React的生態系相較於 Vue和 Angular感覺比較零散一點,雖然近年來已經沒那麼大變化了,但還是會明顯感覺沒有所謂的「公版」,需要花力氣整理出自己的寫法;覺得比較麻煩的有:
27
個人小小的見解
28
29
優/缺點分析
謝謝聆聽
30