# 2-6 & 2-7
單向資料流與一率重繪渲染策略 & 畫面組裝的藍圖 - component初探
---
## 2-6 單向資料流與一率重繪渲染策略
---
## 什麼是單向資料流?
---
`「資料的傳遞方向是單向的,不可以逆向」。`
---
## 延伸到前端領域也就是...

---
### 資料驅動畫面
「原始資料的更動,會造成畫面的更新,畫面不會在原始資料變動外的狀況下有所變動,也無法從畫面的變動來更改原始資料。」
---
### 資料與畫面分離管理
資料與畫面渲染的進行分開處理,且各自獨立
**=>*更好地實現資料驅動畫面更新***
---
### 說到「單向資料流」,在React常見的應用
`以下這兩個我們在React中常見的動作,也就是「單向資料流」的概念。`
* 資料的傳遞方向
透過props從父層往下傳遞,這些資料無法被定義在子層,再回傳回父層。
* 更新資料的方向
從子層發起修改的動作,只能透過事件去提醒父層進行資料的改動,無法直接從子層更改資料。
---
### 「資料⭢實際的DOM渲染」如何維持單向資料流?
---
### 單向資料流的DOM渲染策略
* 策略一、資料更新後,人工判斷並手動修改更新對應的DOM Element
* 策略二、資料更新後,將整個DOM Element全部清除,以最新的原始資料來重繪
---
實際的例子

---
#### 策略一、資料更新後,人工判斷並手動修改更新對應的DOM Element
```javascript!
function incrementCounterAndUpdateDOM(index) {
// 更新資料
counterValues[index] += 1;
// 因為明確知道這個原始資料變更後需要更新哪些DOM Element
// 所以在更新資料後,對相對應的地方進行更新
document
.querySelectorAll('#counter-list > li > span')
.item(index)
.textContent = counterValues[index];
document
.querySelector('#counter-sum > span')
.textContent = getNumbersSum(counterValues);
}
```
---
#### 策略二、資料更新後,將整個DOM Element全部清除,以最新的原始資料來重繪
```javascript!
function handleIncrementButtonClick() {
// 更新資料
counterValues[0] += 1;
counterValues[2] += 1;
// 執行進行清除再重繪DOM Element的函式
renderScreen();
}
function renderScreen() {
// 清除的動作
document.body.innerHTML = '';
// 依照當前資料,將整個畫面的 DOM elements 全部重繪
document.body.innerHTML = `
<div id="counters-wrapper">
<ul id="counter-list">
${counterValues.map((counterValue, index) => `
<li>counter ${index}: <span>${counterValue}</span></li>
`).join('')}
</ul>
<div id="counter-sum">
counters sum: <span>${getNumbersSum(counterValues)}</span>
</div>
</div>
<button id="increment-btn">
increment counter 0 & 2
</button>
`;
document
.getElementById('increment-btn')
.addEventListener('click', handleIncrementButtonClick);
}
```
---
### 兩種單向資料流的DOM渲染策略優缺點

---
### 前端框架中的應用
主要是結合Virtual DOM的概念去執行前面的兩個策略。
---
* React - 採用策略二

* Vue - 採用策略一

---
### 那雙向資料流呢?
原始資料的變化可以影響畫面的顯示,同時畫面的改動也可以影響資料的變化,以此保持原始資料和畫面的同步。
---
### 聽說Vue的v-model是實現雙向資料流的雙向綁定?
**但事實上,它只是一個雙向綁定語法糖,因為它實際的運作原理是v-bind+v-on的組合。**
---
### 為什麼要使用單向資料流?
---
### 當我們不用單向資料流的概念進行畫面更動

(https://codepen.io/pinkymini/pen/mdgVoeE)
---
```javascript!
// 初始狀態
let data = {
name: 'Guest'
};
// 更新畫面
function updateView() {
const nameElement = document.getElementById('current-name');
const inputElement = document.getElementById('name-input')
// 第一個方向:畫面顯示以data為依據
nameElement.textContent = data.name;
inputElement.value = data.name
}
// 監聽 input 元素的輸入事件
document.getElementById('name-input').addEventListener('input', function (event) {
// 第二個方向:取得DOM元素的內容(畫面顯示),更新回data
data.name = document.getElementById('name-input').value;
// 接著更新畫面
updateView();
});
// 初始渲染
updateView();
```
---
### 透過「限縮變因」達到以下優點
* 提高可維護性及可讀性
* 減少資料意外出錯的風險
* 能進行有效的效能優化
---
### Q: 單向資料流與React的關係是什麼?React渲染策略是什麼?
<!-- .element: class="fragment" data-fragment-index="1" -->
---
## 2-7 畫面組裝的藍圖 - component 初探
---

---
### 什麼是component?
由開發者自定義的畫面元件藍圖,可以重複拿來使用,內含特定意義的畫面內容及邏輯。
---
也像一個「食譜或是室內設計圖」,能用同一份食譜,產生口味有所不同的同道料理,也能用一個室內設計圖,打造出大格局雖相同,但風格卻不同的室內設計。
---
`在「定義component」時,會將邏輯和實際用途明確地定義出來,也就是進行「抽象化」。`
---
### component的定義&呼叫
---
* 定義
透過JavaScript的函式來定義,把props當作參數傳入,回傳一個react element。
```javascript
function CustomButton (prop) {
return (
<button>客製化按鈕</button>
)
}
```
---
需要注意的是component function命名的第一個字母必須是大寫
```
// 標籤為小寫
const element 1 = <div />;
// 標籤為大寫
const element 2 = <CustomButton />;
```
`因為JSX在轉譯時,會以這個標籤的名稱首字母的大小寫來判斷是一個實際的DOM element名稱還是component function的名稱。`
---
* 呼叫
透過建立React element的形式被呼叫。
```
const reactElement = <CustomButtom />
```
jsx語法中會被轉譯成以下這樣的內容
```
const reactElement = React.createElement(CustomButtom)
```
---
### Q: component和React element的差異?

<!-- .element: class="fragment" data-fragment-index="1" -->
---
### 什麼是props?
是客製化component的屬性(properties),被當作參數傳遞的props會被打包成一個物件,這些props能讓component依照不同的情境,產出符合需求的React element。
---
### props的傳遞
當呼叫component來建立React element時,可將自定義的props當作參數傳遞,props可以傳遞基本型別、物件、陣列、函式的內容。
```javascript!
// 使用ProducItem component 建立了兩個內容不同的react element
<ProductItem
name="notebook"
price={60}
/>
<ProductItem
name="pencil"
price={20}
/>
```
---
### props的使用
可以在component內透過參數props取得傳遞進來的自定義props物件。
```javascript!
export default function ProductItem (props) {
return (
<div>
<h3>{props.name}</h3>
<p>價格:{props.price}</p>
</div>
);
}
```
---
也可以用解構的方式取得name和price
```javascript!
export default function ProductItem ({name, price}) {
return (
<div>
<h3>{name}</h3>
<p>價格:{price}</p>
</div>
);
}
```
---
### 特殊的prop - children
當我們把一些字串或是元件,透過元件內
```
// 把要傳遞的prop寫在標籤上
<CustomComponent name="名稱" />
```
```
// 透過children屬性傳遞
<CustomComponent>chirldren prop的值</CustomComponent>
// 等價於這樣的寫法
<CustomComponent children="chirldren prop的值" />
```
---
### props是唯讀且不可被修改的限制
```javascript!
export default function ProductItem (props) {
// 嘗試對price進行修改
props.price = props.price * 0.9;
return (
<div>
<h3>{props.name}</h3>
<p>價格:{props.price}</p>
</div>
);
}
```
---

---
* 目的:讓資料以props傳遞到component裡後,保證資料的源頭始終是唯一、不變且可追蹤的。
* 在開發環境有這個限制的原因:開發環境下的React會把props物件先以Object.freeze(props)做處理,避免不小心改動到。
---
如果真的需要拿props來做一些計算
```javascript!
export default function ProductItem (props) {
// 將計算後的price存成另一個變數,而不是改到props.price本身
const discountPrice = props.price * 0.9;
return (
<div>
<h3>{props.name}</h3>
<p>價格:{props.price}</p>
<p>折購價:{discountPrice}</p>
</div>
);
}
```
---
### 無法偵測到props被修改的情境
---
```javascript!
import List from './components/List';
function App() {
const data = [
{
id: 1,
name: 'notebook'
},
{
id: 2,
name: 'pencil'
}
]
return (
<div className="App">
<List data={data} />
</div>
);
}
export default App;
```
---
```javascript!
export default function List (props) {
// push進一個物件
props.data.push({ id: 3, name: 'push item' });
return (
<ul>
{
props.data.map((item) => <li key={item.id}>{item.name}</li>)
}
</ul>
);
}
```
---
實際渲染的畫面及console.log的結果

---
### 這種情境為什麼無法被發現有改動?
---

雖然肉眼看到有改變,但記憶體位址並沒有被改動到,所以Object.freeze()起不了作用。
---
### 父component與子component
component的使用除了直接呼叫外,也可以在component裡面呼叫另一個component來組裝畫面。
```javascript!
import childComponent from './ChildComponent';
export default function ParentComponent(props) {
return (
<div>
<ChildComponent />
</div>
)
}
```
---
定義商品的component
```javascript!
export default function ProductItem (props) {
return (
<div>
<h3>{props.name}</h3>
<p>價格:{props.price}</p>
</div>
);
}
```
---
被放在另一個component裡面使用
```javascript!
import ProductItem from "./ProductItem";
export default function ProductList () {
return (
<div>
<ProductItem
name="notebook"
price={60}
/>
<ProductItem
name="pencil"
price={20}
/>
</div>
);
}
```
---
### component的render和re-render
提醒!這裡的render和re-render`並不是只肉眼看到的畫面渲染`

---
### 定義多層的component的component render呼叫流程
---

---
```javascript!
function PComponent (props) {
return (
<p className="p的這一層">我的P的這一層</p>
);
}
function H2Component (props) {
return (
<div className="h2的這一層">
<h2>我是H2的這一層</h2>
<PComponent/>
<PComponent/>
</div>
);
}
export default function H1Component (props) {
return (
<div className="h1的這一層">
<h1>我是H1的這一層</h1>
<H2Component />
<H2Component />
</div>
);
}
```
---

---
### Q:當第一個H2Component有狀態的更新時,會發生什麼事?

<!-- .element: class="fragment" data-fragment-index="1" -->
---
### 從Component render例子歸納出的重點
- component的render流程是從上到下,由外至內。
- 當父層component render時,底下的子層也會render,即使子層的狀態沒有更動。
---
### 2-6 & 2-7 Recap

---
### Q: 什麼是component?

<!-- .element: class="fragment" data-fragment-index="1" -->
---
### Q: props是什麼?props具有什麼樣的特性?

<!-- .element: class="fragment" data-fragment-index="1" -->
---
### Q:試著講出一個內含子component的父component在資料更新後,會發生哪些事情?

<!-- .element: class="fragment" data-fragment-index="1" -->
---
### 兩章節的關鍵字
#單向資料流 #一律重繪的機制 #component #React Element #props #children #render #re-render
---
# Thank you
{"contributors":"[{\"id\":\"a3d2d9cc-28b2-4ed3-b705-29f539fd6f8a\",\"add\":27990,\"del\":18197}]","title":"2-6 & 2-7 導讀","slideOptions":"{\"transition\":\"slide\"}","description":"單向資料流與一率重繪渲染策略 & 畫面組裝的藍圖 - component初探"}