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,775
 
   
   
  