# 2-6 & 2-7 單向資料流與一率重繪渲染策略 & 畫面組裝的藍圖 - component初探 --- ## 2-6 單向資料流與一率重繪渲染策略 --- ## 什麼是單向資料流? --- `「資料的傳遞方向是單向的,不可以逆向」。` --- ## 延伸到前端領域也就是... ![截圖 2024-03-03 下午6.24.20](https://hackmd.io/_uploads/r1olxCWaa.png) --- ### 資料驅動畫面 「原始資料的更動,會造成畫面的更新,畫面不會在原始資料變動外的狀況下有所變動,也無法從畫面的變動來更改原始資料。」 --- ### 資料與畫面分離管理 資料與畫面渲染的進行分開處理,且各自獨立 **=>*更好地實現資料驅動畫面更新*** --- ### 說到「單向資料流」,在React常見的應用 `以下這兩個我們在React中常見的動作,也就是「單向資料流」的概念。` * 資料的傳遞方向 透過props從父層往下傳遞,這些資料無法被定義在子層,再回傳回父層。 * 更新資料的方向 從子層發起修改的動作,只能透過事件去提醒父層進行資料的改動,無法直接從子層更改資料。 --- ### 「資料⭢實際的DOM渲染」如何維持單向資料流? --- ### 單向資料流的DOM渲染策略 * 策略一、資料更新後,人工判斷並手動修改更新對應的DOM Element * 策略二、資料更新後,將整個DOM Element全部清除,以最新的原始資料來重繪 --- 實際的例子 ![2IzlfZqFWT](https://hackmd.io/_uploads/SyJK03O6T.gif) --- #### 策略一、資料更新後,人工判斷並手動修改更新對應的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渲染策略優缺點 ![截圖 2024-03-09 上午9.25.06](https://hackmd.io/_uploads/r1TqqNKp6.png) --- ### 前端框架中的應用 主要是結合Virtual DOM的概念去執行前面的兩個策略。 --- * React - 採用策略二 ![截圖 2024-03-16 下午6.56.26](https://hackmd.io/_uploads/By0Bsg7Ra.png) * Vue - 採用策略一 ![截圖 2024-03-16 下午6.56.47](https://hackmd.io/_uploads/H1iNieQRa.png) --- ### 那雙向資料流呢? 原始資料的變化可以影響畫面的顯示,同時畫面的改動也可以影響資料的變化,以此保持原始資料和畫面的同步。 --- ### 聽說Vue的v-model是實現雙向資料流的雙向綁定? **但事實上,它只是一個雙向綁定語法糖,因為它實際的運作原理是v-bind+v-on的組合。** --- ### 為什麼要使用單向資料流? --- ### 當我們不用單向資料流的概念進行畫面更動 ![YtALsBjq9e](https://hackmd.io/_uploads/BJU5ou40T.gif) (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渲染策略是什麼? ![截圖 2024-03-16 下午10.22.22](https://hackmd.io/_uploads/rkoVsmQ0T.png)<!-- .element: class="fragment" data-fragment-index="1" --> --- ## 2-7 畫面組裝的藍圖 - component 初探 --- ![截圖 2024-03-05 下午10.53.07](https://hackmd.io/_uploads/SJPLzh46a.png) --- ### 什麼是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的差異? ![截圖 2024-03-17 上午9.15.03](https://hackmd.io/_uploads/BJkE46mCT.png) <!-- .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> ); } ``` --- ![截圖 2024-03-17 上午9.25.32](https://hackmd.io/_uploads/HJrTLaXAp.png) --- * 目的:讓資料以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的結果 ![截圖 2024-03-10 下午10.00.10](https://hackmd.io/_uploads/Bydku67Rp.png) --- ### 這種情境為什麼無法被發現有改動? --- ![images](https://hackmd.io/_uploads/BkU6Qrj66.gif) 雖然肉眼看到有改變,但記憶體位址並沒有被改動到,所以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`並不是只肉眼看到的畫面渲染` ![截圖 2024-03-17 上午9.47.39](https://hackmd.io/_uploads/r1vAo6XA6.png) --- ### 定義多層的component的component render呼叫流程 --- ![截圖 2024-03-11 下午11.06.42](https://hackmd.io/_uploads/HkIuONXAa.png) --- ```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> ); } ``` --- ![截圖 2024-03-16 上午12.52.28](https://hackmd.io/_uploads/HyryTlfCT.png) --- ### Q:當第一個H2Component有狀態的更新時,會發生什麼事? ![截圖 2024-03-17 下午10.06.15](https://hackmd.io/_uploads/rJsktOERT.png) <!-- .element: class="fragment" data-fragment-index="1" --> --- ### 從Component render例子歸納出的重點 - component的render流程是從上到下,由外至內。 - 當父層component render時,底下的子層也會render,即使子層的狀態沒有更動。 --- ### 2-6 & 2-7 Recap ![截圖 2024-03-17 下午9.52.36](https://hackmd.io/_uploads/H1OqhOERT.png) --- ### Q: 什麼是component? ![截圖 2024-03-16 下午11.44.17](https://hackmd.io/_uploads/HJZo04X06.png) <!-- .element: class="fragment" data-fragment-index="1" --> --- ### Q: props是什麼?props具有什麼樣的特性? ![截圖 2024-03-17 下午10.01.40](https://hackmd.io/_uploads/BytCP_ECp.png) <!-- .element: class="fragment" data-fragment-index="1" --> --- ### Q:試著講出一個內含子component的父component在資料更新後,會發生哪些事情? ![截圖 2024-03-17 下午10.22.55](https://hackmd.io/_uploads/H1R03_VA6.png) <!-- .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初探"}
    217 views