ReactJS
Concept
HTML? DOM?
-
HTML 是一種方便開發者撰寫的語言,只是純文字
-
DOM 是瀏覽器渲染引擎中真正存在的物件節點
-
頁面初始化時,渲染引擎會依照 HTML 程式碼的內容產生出 DOM
<!DOCTYPE html>
<html>
<body>
<div>
<button>按鈕</button>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<body></body>
<script>
const div = document.createElement('div');
document.body.appendChild(div);
const button = document.createElement('button');
button.innerText('按鈕');
div.appendChild(button);
</script>
</html>
Demo
UI 元件是依據當前資料來產生
y = f(x)
componentInstance = componentClass(data)
函數式運算
僅僅是 UI
-
React 本身並不是一個完整的前端框架,而是一個只處理 View 的函式庫,也就是負責 DOM 的產生與操作
-
React 是一個中間媒介,連結了 UI 純粹的定義層面與 DOM 的實際層面
-
基本上,React 本身只做以下兩件工作;
-
讓你定義 UI 的藍圖
-
幫你把這個 UI 渲染到到使用者的瀏覽器畫面上
-
React 捨棄了傳統的 HTML 開發方式,
改成完全由 JavaScript 來代管 DOM 的產生與操作,
實現 100% 純粹的 Client-Side Rendering。
聲明式的定義前端 UI
-
前端 UI 程式碼本身,應該要足以完整的自我表達其擁有的行為與可能的顯示變化
-
UI 渲染無法避免邏輯,將 UI 的定義直接在 JavaScript 中進行,有助於提高 UI 的自我表達能力
class ProductItem extends React.Component {
handleButtonClick = () => {
alert(this.props.price);
}
render() {
return (
<div className="item">
<div className="title">{this.props.title}</div>
<div className="price">{this.props.price}</div>
<button onClick={this.handleButtonClick}>購買</button>
</div>
);
}
}
JSX
-
JSX 是 React 在使用的一種特殊 JavaScript 語法糖
-
能夠讓你以可讀性較高的語法來定義想要產生的 DOM 結構
-
語法長得很像 HTML,但本質上完全不是 HTML
-
瀏覽器看不懂,需要翻譯成原生的 JS 程式碼才能正常的在瀏覽器上執行
<div className="item">
<div className="title">{this.props.title}</div>
<div className="price">{this.props.price}</div>
<button onClick={this.handleButtonClick}>購買</button>
</div>
React.createElement("div", {"className": "item"},
React.createElement("div", {"className": "title"}, this.props.title),
React.createElement("div", {"className": "price"}, this.props.price),
React.createElement("button", {onClick: this.handleButtonClick}, "購買")
);
Component
-
自定義元件藍圖 ( Component Class ),可以嵌套或拼裝
-
讓前端 UI 程式碼有更好的可組合性與可重用性
-
每個 Component 的第一層,只能有一個根節點元素
class ProductItem extends React.Component {
handleButtonClick = () => {
alert(this.props.price);
}
render() {
return (
<div className="item">
<div className="title">{this.props.title}</div>
<div className="price">{this.props.price}</div>
<button onClick={this.handleButtonClick}>購買</button>
</div>
);
}
}
class ProductList extends React.Component {
render() {
return (
<div>
{dataList.map(data => (
<ProductItem title={data.title} price={data.price}/>
))}
</div>
);
}
}
ReactDOM.render(<ProductList/>, document.getElementsById('root'));
Component
Virtual DOM
-
Virtual DOM 是一份純資料的 Tree 物件,對應到實際的 DOM
-
Virtual DOM 為自定義 Component 提供了中介的虛擬層,讓開發者能描述 UI 樣貌與邏輯
-
我們透過定義 Component 來表達「UI 什麼情況該如何呈現」。而「要用什麼手段來達到這個畫面改變(如何操作 DOM)」 ,React 則會自動幫你做,而且絕大多數情況下都比你自己來要做的更好
Always Redraw
-
Single Source of Truth
-
UI 元件要如何顯示,資料是唯一變因
-
只有因為資料改變才能導致 UI 元件跟著改變
-
-
把畫面全部洗掉,然後再依據最新資料重新畫,顯示結果通常一定是正確的
-
每次都重繪全部的實體 DOM 顯然是不可行,但是重繪 VDOM 則成本相對降低許多
Always Redraw
當畫面需要改變時,根據最新的資料重繪出新的 VDOM Tree,
並與改變前的舊 VDOM Tree 進行全面式的比較與計算,
其中新舊差異的地方,才真的會在實際的 DOM 上發生操作改變
Props
Props
-
Component 外部(父 Component)傳遞給 Component 內部的靜態參數
-
抽象化出跟問題有關的參數,方便 Component 進行重用
-
Props 傳遞到 Component 內部後,應是不可變更的固定值
-
當 Component 外部改變傳遞進來的 Props 時,Component 內部會自動發起重繪
class ProductItem extends React.Component {
handleButtonClick = () => {
alert(this.props.price);
}
render() {
return (
<div className="item">
<div className="title">{this.props.title}</div>
<div className="price">{this.props.price}</div>
<button onClick={this.handleButtonClick}>購買</button>
</div>
);
}
}
class ProductList extends React.Component {
render() {
return (
<div>
{dataList.map(data => (
<ProductItem title={data.title} price={data.price}/>
))}
</div>
);
}
}
ReactDOM.render(<ProductList/>, document.getElementsById('root'));
propTypes & defaultProps
-
propTypes
-
檢查傳入的 props 型別
-
-
defaultProps
-
該 props 外部沒有傳入時的預設值
-
class ProductItem extends React.Component {
static propTypes = {
title: React.PropTypes.string,
price: React.PropTypes.number
}
static defaultProps = {
title: '預設的商品標題',
price: 0
}
}
this.props.children
-
取得 component 的子節點內容
class SomeThing extends React.Component {
render() {
console.log(this.props.children);
return (
<div/>
);
}
}
<SomeThing>
<button id="btn1">按鈕1</button>
<button id="btn2">按鈕2</button>
</SomeThing>
State
State
-
Component 內部私有的動態狀態值
-
在內部使用 this.setState 方法進行修改
-
在內部調用修改後,Component 會自動發起畫面重繪
-
State 的預設值,對 this.state 物件來指定
class ProductList extends Component {
state = {
data: [{
title: '小熊軟糖',
price: 100
}, {
title: '盆栽',
price: 300
}, {
title: 'Macbook Pro',
price: 40000
}]
}
componentDidMount() {
setTimeout(() => {
this.setState({
data: [
...this.state.data, {
title: '新增商品',
price: 9999
}
]
});
}, 5000);
}
render() {
return (
<div>
{this.state.data.map((data, index) => (
<ProductItem
title={data.title}
price={data.price}
/>
))}
</div>
);
}
}
JSX
JSX 語法
-
嚴格標籤閉合
-
支援原生 HTML 有的標籤以及自訂的 Component 標籤
-
與 HTML 重要的語法差異:class → className
-
使用 { } 來填入 JavaScript 的表達式(一個值)
<input type="text"/>
<br/>
<img src=""/>
JSX 中的迭代輸出
- 使用陣列的 .map() 方法批量迭代產生 component
class ProductList extends React.Component {
render() {
return (
<div>
{dataList.map(data => (
<ProductItem title={data.title} price={data.price}/>
))}
</div>
);
}
}
JSX 中的條件判斷式
- JSX 中不可以直接寫 if / else,因為實際上是一個物件結構
- 使用 && 運算子來達到 if 判斷式的效果
- 使用三元運算子來達到 if / else 判斷式的效果
<div className="item">
<div className="title">{this.props.title}</div>
{(this.props.price > 500) && (
<div className="price">{this.props.price}</div>
)}
<button onClick={this.handleButtonClick}>購買</button>
</div>
<div className="item">
<div className="title">{this.props.title}</div>
{(this.props.price > 500) ? (
<div className="price">{this.props.price}</div>
) : (
<div>一個很便宜的商品</div>
)}
<button onClick={this.handleButtonClick}>購買</button>
</div>
物件解構填入 Props
-
使用「...object」來將物件解構並填入當作多個 Props
class SomeThing extends React.Component {
render() {
const propsObject = {
name: 'Zet',
gender: 'male'
}
return (
<div>
<Person {...propsObject}/>
</div>
);
}
}
Inline Style
-
使用 JavaScript Object 來撰寫,並填入 HTML 類型的 element 的 style props 當中
-
Property:名稱改用駝峰式命名
-
Value:數字的預設單位是 px,其他數字單位或非數字的值要使用字串來表示
const styles = {
fontSize: 20,
marginLeft: '20%'
}
<button style={styles}>按鈕</button>
Lifecycle
組件的生命週期
render()
- 每個 React Component 都必需定義的方法,負責決定要繪製的 UI 之構成(Virtual DOM 資料結構)
- 每次重繪的生命週期中都會被呼叫到並執行
- 通常有三種情況會觸發重繪:
- 從外部 ( 父 component ) 傳入的 props 改變時
- 在內部 ( component 自己裡面 ) 調用 this.setState() 方法時
- 手動呼叫 this.forceUpdate() 方法時
componentDidMount
- 在 Component 初始化並且首次繪製完成時發生,重繪時不會發生
- 在 Component 被從畫面中拆除之前,只會發生一次
Input Dataflow
Input
- 原生的HTML <input> 元素是自帶資料狀態的
- 在 React 中,有兩種處理方式
- Uncontrolled:依照原來的自帶資料狀態
- Controlled:使用單向資料流,獨立地點存放 input 中的資料並綁定 UI
class UncontrolledInputExample extends Component {
handleInputChange = (event) => {
console.log(event.target.value);
}
render() {
return (
<input
type="text"
defaultValue="hello"
onChange={this.handleInputChange}
/>
);
}
}
class ControlledInputExample extends Component {
state = {
inputText: 'hello'
}
handleInputChange = (event) => {
console.log(event.target.value);
this.setState({
inputText: event.target.value
});
}
render() {
return (
<input
type="text"
value={this.state.inputText}
onChange={this.handleInputChange}
/>
);
}
}
Uncontrolled Input
-
input 本身自己管理資料狀態,不與資料來源綁定
-
使用 defaultValue 或 defaultChecked 來設定預設值
class UncontrolledInputExample extends React.Component {
handleInputChange = (event) => {
console.log(event.target.value);
}
render() {
return (
<input
type="text"
defaultValue="hello"
onChange={this.handleInputChange}
/>
);
}
}
Controlled Input
-
input 自己本身不存放資料,也不能改變自己的值,與指定的資料來源綁定
-
使用 value 或 checked 來指定綁定的資料
-
使用 onChange 來指定接收資料的函數
class ControlledInputExample extends Component {
state = {
inputText: 'hello'
}
handleInputChange = (event) => {
console.log(event.target.value);
this.setState({
inputText: event.target.value
});
}
render() {
return (
<input type="text" value={this.state.inputText} onChange={this.handleInputChange}/>
);
}
}
Redux Basic
Flux
- Flux 是 Facebook 為了搭配 React 而提出的一套 Dataflow 設計模式,用來解決前端狀態資料的管理,社群有相當多基於此概念的實作品
- 採用單向資料流 ( One-way Dataflow) 的概念
- Facebook 官方有推出一套同名的 Flux 實作品,不過現在已被 Redux 取代其主流地位
如果不使用 Flux...
- Component 們之間沒有共用的狀態資料儲存地點
- 你的 React Component 所有的狀態都儲存在各自的 this.state 當中,這導致不同 Component 之間想互相通知 State 的改變的話,需要層層的傳遞 callback function,前端狀態管理異常複雜
Redux
- Redux 是 JavaScript 的狀態容器,提供可預測化的資料狀態管理
- 由 Flux 演變而來,但避開了 Flux 的複雜性,非常單純易用
- 由 React 社群大神 Dan Abramov 所開發,日前他被 Facebook 招募,Redux 也納入 Facebook 官方項目,成為前端狀態管理的主流解決方案
- 跟 React 沒有相依關係,可以單獨使用或搭配其他前端框架使用
- Redux DevTools
三大原則
- 單一資料來源
- 整個前端應用的 State 都被儲存在一顆 Object Tree 當中,成為唯一的 Store
- 在 React 中,所有 Component 都共用這個 Store
- State 是唯讀的
- 唯一改變 State 的方法就是呼叫 Action,Action 負責定義發生的事件的種類
- 使用純函數來執行 State 的修改
- 為了描述這個 Action 實際上要如何改變 State Tree,我們需要定義 Reducer
View
( React )
Store
Action
Reducer
pass
by
dispatch
畫面需要改變
產生 action
return
newState
資料變更
Server
One-Way Dataflow
Action
-
Action 負責定義發生的事件種類與想要傳遞的參數,必須是一個純 Object
{
type: 'increment',
value: 1
}
Reducer
-
Action 只是描述了有事情發生,並沒有指明要如何更新 State,而 Reducer 正是要負責做這件事
-
Reducer 是一個純函數,接收完整的舊的 State 和當前 Action,回傳完整的新的 State
-
你不應該直接修改傳入的舊 State,而是應該回傳一個更新資料後的新 State
-
若找不到符合的 Action Type,也一定要把舊的 State 照舊回傳
function reducer(state = 0, action) {
switch (action.type) {
case 'increment':
return state + action.value;
case 'decrement':
return state - action.value;
default:
return state;
}
}
Store
-
Redux 的 createStore() 方法:傳入 Reducer 以建立一個 Store
-
Store 有以下幾個重要功能
-
存取 State 資料:使用 store.getState() 方法,可以取得 Store 裡目前最新的 State
-
store.dispatch() 方法:來發起一個 Action 以更新 State
-
store.subscribe() 方法:當 State 發生改變時,呼叫 Callback
-
const store = createStore(reducer);
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch({
type: 'increment',
value: 1
});
combineReducers
-
用來建立 Store 的 Reducer Function 只能有一個,因此如果你因為程式碼可維護性而分開寫了很多個 Reducer 的話,可以使用 Redux 的 combindReducers 方法將其合併成一個大 Reducer
const rootReducer = combineReducers({
number: numberReducer,
todos: todosReducer
});
const store = createStore(rootReducer);
Redux with React
Smart / Dumb Component
-
Smart Component(State Container)
-
向 Redux 存取資料並傳遞進 Dumb Component
-
-
Dumb Component (Display Component)
-
不知道外界發生什麼,只負責依據 Props 印出資料或調用 Callback
-
Smart Component | Dumb Component | |
---|---|---|
位於 | 外層 | 內層 |
能直接與 Redux 溝通 | 是 | 否 |
讀取資料 | 從 Redux 獲取 State | 從傳入的 Props 獲取資料 |
修改資料 | 透過 dispatch 方法 向 Redux 發送 Action |
從傳入的 Props 調用 Callback |
react-redux
class CounterContainer extends Component {
dispatchIncrementNumber = (value) => {
this.props.dispatch(incrementNumber(value));
}
dispatchDecrementNumber = (value) => {
this.props.dispatch(decrementNumber(value));
}
render() {
return (
<Counter
value={this.props.number}
dispatchIncrementNumber={this.dispatchIncrementNumber}
dispatchDecrementNumber={this.dispatchDecrementNumber}
/>
);
}
}
export default connect(state => ({
number: state.number
}))(CounterContainer);
- 使用 connect() 方法來製造資料綁定器,並用資料綁定器綁定指定的 React Component
Async Action
Call AJAX
-
當你呼叫一個非同步 API,有兩個關鍵的時間點:你開始呼叫的的時候,以及當你收到回應 (或是失敗) 的時候。
-
所以一個請求的行為應該可以分為下列四種狀態:
-
尚未開始請求(status: null)
-
請求中,還沒得到結果(status: request)
-
得到結果並成功(status: success)
-
得到結果但失敗 (status: failure)
-
設計 Action
-
你可以將上述中會發生的三種情況(除掉預設本來就是還沒請請求的狀態),設計成對應的三種 Action
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
設計 State 結構與 Reducer
const initState = {
status: null,
data: [],
error: null
}
function postReducer(state = initState, action) {
switch (action.type) {
case FETCH_POSTS_REQUEST:
return {
...state,
status: 'request'
};
case FETCH_POSTS_SUCCESS:
return {
...state,
status: 'success',
data: action.reponse
}
case FETCH_POSTS_FAILURE:
return {
...state,
status: 'failure',
error: action.error
}
default:
return state;
}
}
Redux Thunk
-
使用Redux Thunk 來讓 dispatch 方法可以接收一個函數
function fetchPosts() {
return async (dispatch) => {
dispatch({
type: FETCH_POSTS_REQUEST
});
try {
const httpResponse = await fetch('/posts');
if (httpResponse.status != 200) {
throw new Error(`${httpResponse.status(httpResponse.statusText)}`);
}
dispatch({
type: FETCH_POSTS_SUCCESS,
response: await httpResponse.json()
});
} catch (error) {
console.error(error);
dispatch({
type: FETCH_POSTS_FAILURE,
error: error.message
});
}
}
}
Redux Thunk 運作原理
dispatch
Redux
Thunk
Middleware
Reducer
action is a function
=> pass dispath and call it
action is a object
EXMA-Training-React & Redux
By tz5514
EXMA-Training-React & Redux
- 1,557