Zet @ TrunkStudio
2017.08.10
Slides:goo.gl/aSdwai
React 捨棄了傳統使用 HTML 的開發方式,
改成完全由 JavaScript 程式來代管 DOM 的產生與操作,
實現 100% 純粹的 Client-Side Rendering。
React 本身並不是一個完整的前端框架,而是一個只處理 View 的函式庫,也就是負責定義 UI 並自動管理 DOM 的產生與操作
React 是一個中間媒介,連結了 UI 的定義層面與 DOM 的實體層面
React 的工作分為兩個部分:
Reconciler(react):將你定義的 UI 組合出一個虛擬的畫面結構
Renderer(react-dom):以這個虛擬結構作為依據,產生出對應的實際 DOM
<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
</body>
<script src="/dist/bundle.js"></script>
</html>
import React from 'react';
import ReactDOM from 'react-dom'
const reactElement = (
React.createElement('div', { className: 'box' },
React.createElement('button', { onClick: () => alert('clicked') }, 'click me')
)
);
ReactDOM.render(reactElement, document.getElementById('root'));
JSX 是 React 在使用的一種特殊 JavaScript 語法糖
能夠讓你以可讀性較高的語法來定義 React UI 結構
語法長得很像 HTML,但本質上完全不是 HTML
瀏覽器看不懂,需要翻譯成原本的 React.createElement 語法才能正常的在瀏覽器上執行
<div className="box">
<button onClick={() => alert('clicked')}>click me</button>
</div>
React.createElement("div", {"className": "box"},
React.createElement("button", {"onClick": () => alert("clicked")}, "click me")
)
Babel Compile
React 允許自定義組件藍圖 ( Component Class ),並抽象化資料傳遞接口
可以嵌套或組合,讓前端 UI 程式有更好的可組合性與可重用性
Component 本身足以完整的自我表達其可能的顯示變化和擁有的行為
每個 Component 結構的第一層,只能有一個根節點元素
Component Class 名稱首字母必須大寫
import React from 'react'
class AlertButton extends React.Component {
handleClick = () => {
alert(this.props.text);
}
render() {
return (
<button onClick={this.handleClick}>{this.props.text}</button>
);
}
}
class App extends React.Component {
render() {
return (
<div>
<AlertButton text="HelloJS"/>
<AlertButton text="React"/>
<AlertButton text="Basic"/>
</div>
);
}
}
Virtual DOM 是一份純資料的 Tree 物件結構,映射對應到實際的 DOM
使用 React.createElement 函數來產生 Tree 節點(React Element)
Virtual DOM 為自定義 Component 提供了中介的虛擬層,讓開發者能以聲明式的方式定義 UI 的顯示邏輯與行為
我們透過定義 Component 來表達「UI 什麼情況該如何呈現」,而「要用什麼手段來達到這個畫面改變(如何產生和操作 DOM)」 ,React 則會自動幫你完成 (react-dom 這個 Renderer 的工作)
React.createElement('div', null,
React.createElement(AlertButton, { text: 'HelloJS' }),
React.createElement(AlertButton, { text: 'React' }),
React.createElement(AlertButton, { text: 'Basic' })
)
UI 開發最大的兩個問題與挑戰就是「可重用性」與「反應變化」
然而 Web 中建構 UI 的媒介 – DOM,並沒有直接滿足以下需求的能力:
自定義資料抽象化
複雜情形的組合與重用
綁定資料來源以自動反應顯示結果與變化
因此 React 建立了一個虛擬結構層,來間接實現這些對於 UI 開發來說相當重要的能力
我們對於 Virtual DOM 這個虛擬結構層以 Component 的形式定義想要的 UI 呈現結構,而 Renderer 則會幫我們將其自動轉換成對應的實際 DOM 結果
One-way Data Flow(單向資料流)
只有因為資料改變,才能導致 UI 的顯示結果自動跟著改變
這個因果關係永遠不能逆向
UI 只能被動的隨資料而反應變化
UI 不能反過來主動直接修改資料或是修改 UI 自己的顯示結果
React 如何實現單向資料流:當 UI 的來源資料有變化時
不需要關心整份資料中具體是變化了哪些部分
先把 UI 畫面全部洗掉,然後再依據完整的最新資料全部重新產生 UI 畫面,通常可以保證顯示結果一定是正確的
然而每次都重繪全部的實體 DOM 顯然在效能考量之下是不可行的,但是重繪 Virtual DOM 則成本相對降低許多,因此 React 實作了一套 Reconciliation 演算法來實現這個概念與流程
當畫面需要改變時,根據最新的資料重繪出新的 Virtual DOM Tree,
並與改變前的舊 Virtual DOM Tree 進行全面式的比較與計算,
其中被發現新舊差異的地方,才真的會在實際的 DOM 上發生操作改變
JSX 是 React.createElement 函數的語法糖,用來建立 Virtual DOM 節點結構
支援原生 HTML DOM 有的標籤以及自訂的 Component Class 標籤
嚴格標籤閉合
與 HTML 重要的語法差異
class → className
所有 property 名稱改以駝峰式命名,EX:onclick → onClick
<div>
<h2 className="title">Title</h2>
<NumberItem number={100}/>
<br/>
</div>
使用 { } 語法來填入 JavaScript 表達式(一個值),其中可直接當作顯示內容印出的型別有:
React Element:當作子節點插入
String:直接印出
Number:轉成字串後直接印出
Array:攤平成多個表達式後印出(如果 item 的值也是這些可印的型別)
Boolean、Null、Undefined:什麼都不印,直接忽略
可以在 Component 之間用 Props 傳遞,但不能當顯示內容印出的型別有:
Object
Function
<div>
{(a > 100) && (
<AlertButton text="HelloJS"/>
)}
<AlertButton text="React"/>
<AlertButton text="Basic"/>
</div>
<div>
{(a > 100) ? (
<AlertButton text="HelloJS"/>
): (
<AlertButton text="React"/>
)}
<AlertButton text="Basic"/>
</div>
使用 JavaScript Object 來撰寫,並填入 HTML 類型的 React Element 的 style props 當中
CSS Property:名稱改用駝峰式命名
CSS Value:數字的預設單位是 px,其他數字單位或非數字的值要使用字串來表示
const styles = {
padding: 15,
backgroundColor: 'yellow',
fontSize: 20
}
<button style={styles}>按鈕</button>
<div>
{numbers.map((number, key) => (
<button key={key}>{number}</button>
))}
</div>
Props 是將資料從 Component 外部傳遞給 Component 內部的媒介
在 Component 內部透過 this.props 取得傳遞進來的 Props 資料物件
當 Props 資料傳遞到 Component 內部後,應是不可再變更的固定值
設計 Component 時抽象化出跟問題相關的 Props,以方便進行重用
JSX 中,若想要傳遞的 Props 的值是字串的話,可以直接使用雙引號,其他型別的值則需要使用 {} 來包住
class TodoList extends Component {
render() {
return (
<div>
<TodoItem title="吃飯" isCompleted={true}/>
<TodoItem title="洗澡" isCompleted={false}/>
<TodoItem title="睡覺" isCompleted={false}/>
</div>
);
}
}
class TodoItem extends React.Component {
render() {
const { isCompleted, title } = this.props;
return (
<div>
<input type="checkbox" checked={isCompleted}/>
<span>{title}</span>
</div>
);
}
}
State 是 Component 內部的可變資料的存放載體
透過 this.state 取得目前 Component 個體的 State 資料物件
使用 this.setState 函數來修改 State 資料物件,並連帶髮起重繪
調用 setState 修改資料後,該 Component 以及其包含的所有子孫 Component 都會跟著自動發起重繪,以更新 Virtual DOM Tree
因此 setState 方法就是 Virtual DOM 重繪以及 Reconciliation 流程的發起者,從資料變更一直到最後反應出實際 DOM 的變更效果
class TodoApp extends React.Component {
state = {
todos: [
{ title: '吃飯', isCompleted: true },
{ title: '洗澡', isCompleted: false},
{ title: '睡覺', isCompleted: false}
]
}
handleAddTodo = () => {
this.setState({
todos: [...this.state.todos, {
title: '上課',
isCompleted: true
}]
});
}
render() {
return (
<div>
<h2>Todo App</h2>
<TodoList todos={this.state.todos}/>
<button onClick={this.handleAddTodo}>add todo</button>
</div>
);
}
}
Virtual DOM 對於 HTML 實作了幾乎所有的事件的對應接口
事件名稱因應 Virtual DOM 與 JSX 的慣例,全部變成駝峰式
會將 event 物件以第一個參數傳入你綁定的函數,其中 event.target 可以取得該 React Element 對應的實體 DOM
更詳細的 API 說明,可以參考官方文件
class Button extends React.Component {
handleClick(event) {
console.log(event);
console.log(event.target);
}
render() {
return (
<button onClick={this.handleClick}>{this.props.text}</button>
);
}
}
若函數有存取原本定義處的 Component 的 this,在函數被傳遞之後將不再能夠正確地找到原本的 this
class Button extends React.Component {
handleClick = () => {
alert(this.props.text);
}
render() {
return (
<button onClick={this.handleClick}>{this.props.text}</button>
);
}
}
input 本身自己管理資料狀態,不與資料來源綁定
使用 defaultValue 或 defaultChecked 來設定預設值
這種做法代表你不能夠透過修改資料來源來隨意控制現在 Input 的值,也沒有一個集中地點可以取得目前 Input 的值
class UncontrolledInputExample extends React.Component {
handleInputChange = (event) => {
console.log(event.target.value);
}
render() {
return (
<input
type="text"
defaultValue="hello"
onChange={this.handleInputChange}
/>
);
}
}
Input 自己本身不存放資料,也不能隨意改變自己顯示的值,與指定的資料來源綁定
使用 value 或 checked 來指定綁定的資料來源
使用 onChange 來指定接收資料的函數,並且在該函數中呼叫修改資料來源,以達到資料變更後新資料回流到 input 中
class ControlledInputExample extends Component {
state = {
inputText: 'hello'
}
handleInputChange = (event) => {
this.setState({
inputText: event.target.value
});
}
render() {
return (
<input type="text" value={this.state.inputText} onChange={this.handleInputChange}/>
);
}
}
View
( React )
Store
Action
Reducer
dispatch
資料需要改變
return
newState
資料完成變更
React 發起重繪
Server