# 十年回首:React 的過去、現在與未來發展 - Zet {%hackmd LeyMdnM3Q4ipfr57bkqpyA %} #### [📚 議程介紹](https://webconf.tw/agenda/day1-5-f) ###### ▼▼▼ 開始筆記 ▼▼▼ [十年回首:React 的過去、現在與未來發展簡報-Slides](https://slides.com/tz5514/react-webconf2024) 講者 200X 年初次接觸 React ## React 誕生的時空背景 2011-2012 * 對效能更高的需求 * 狀態管理,資料連動畫面更新 * 程式碼重用、管理的需求 * Facebook (Meta)面對FB與IG產品需求 2013 * Facebook 在 JSConf 發表並開源 * 設計理念: * component-based * 單向資料流 ## Componet base ### ProductItem.jsx ```javascript var ProductItem = React.createClass({ render: function() { return ( <div> <div>{this.props.name}</div> <div>價格: {this.props.price}</div> </div> ); } }); ``` ### ProductList.jsx ```javascript var ProductList = React.createClass({ getInitialState: function() { return { products: [] }; }, componentDidMount: function() { ProductAPI.fetch().then(function(data) { this.setState({ products: data }); }.bind(this)); }, render: function() { return ( <div> <div>商品列表</div> {this.state.products.map(product => ( <ProductItem name={product.name} price={product.price} /> ))} </div> ); } }); ``` ## React 的單向資料流與畫面渲染策略 ### 單向資料流:資料驅動畫面 * 畫面結果是原始資料透過模板與渲染邏輯所產生的延伸結果 * 資料更新時,畫面才會產生對應的更新,以資料去驅動畫面 不被允許逆向改資料也就是說,透過畫面逆向改資料是不行的情況,例如 input。(雙向資料流可以透過 input 改變資料) ### 限縮變因的價值 在單向資料流的設計模式下,資料變動只會來自開發者手動觸發資料更新,畫面也只會由**原始資料**與**模板邏輯**這兩種變因構成。 好處: * 可維護性提高 * 可讀性提升 * 減少資料出錯的風險 * 效能優化 ### 策略一:資料更新後,人工判斷並手動修改所有連動的 DOM element ```javascript= const counterValues = [0, 0, 0]; function getNumbersSum(numbers) { return numbers.reduce((x, y) => x + y); } function incrementCounterAndUpdateDOM(index) { counterValues[index] += 1; // 資料更新後,需要具體知道這次資料的更新會影響到的 DOM 範圍,並且手動一一去更新: // 修改某個 counter 的 value 資料後, // 該 counter 對應的 <li> 裡面的 <span> 的文字內容會需要更新 document .querySelectorAll('#counter-list > li > span') .item(index) .textContent = counterValues[index]; // 修改某個 counter value 資料後,也會需要重新計算並更新 counter sum 的文字內容 document .querySelector('#counter-sum > span') .textContent = getNumbersSum(counterValues); } function initialRender() { // 只有初始化 render 時才會遍歷整個 counterValues 來印出每個 counter item 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> `; // increment button 事件綁定 const incrementButton = document.getElementById('increment-btn'); incrementButton.addEventListener('click', () => { // 範例行為:increment counter 0 & counter 2 incrementCounterAndUpdateDOM(0); incrementCounterAndUpdateDOM(2); }); } initialRender(); ``` 優點:只要開發者 DOM 操作的夠精確,可以減少多餘DOM操作的效能浪費 缺點:完全依照人為判斷,難以長期維護。 ### 策略二:資料更新後,一律將畫面的 DOM element 清除,再以最新的原始資料全部重繪 ```javascript= const counterValues = [0, 0, 0]; function getNumbersSum(numbers) { return numbers.reduce((x, y) => x + y); } function handleIncrementButtonClick() { // 範例行為:increment counter 0 & counter 2 counterValues[0] += 1; counterValues[2] += 1; // 在更新資料後,不需要判斷這次資料更新具體會影響到的 DOM elements 有哪些, // 一率直接呼叫 renderScreen() 來將整個畫面全部的 DOM elements 都清除後再全部重繪 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> `; // 重新綁定 increment button 事件 document .getElementById('increment-btn') .addEventListener('click', handleIncrementButtonClick); } renderScreen(); ``` 優點:只需關注模板及資料更新,不需要手動維護連動的畫面操作,維護更直覺簡單。 缺點:隨應用變龐大複雜,一律重繪會因大量不必要的 DOM 操作而產生效能問題,影響UI體驗。 ## 前端框架的處理策略 * 不管是什麼策略都有缺點 * 前端框架可以透過特殊的架構設計,幫助解決資料連動畫面更新的需求,保留渲染策略的優點,同時解決缺點。 * 例如 Vue MVVM(策略一) ,藉著 proxy 監聽來源資料,自動更新資料綁定的畫面。 React 採用策略二:當資料更新後,一律將整個畫面的 DOM element,全部清除,再以最新的原始資料來全部重繪 ## DOM 與 Virtual DOM 解釋什麼是 DOM, Virtual DOM 定義。 Virtual DOM 是一種自創的資料結構 用虛擬的畫面結構來模擬真實的 DOM element React 讓你先產 Virtual DOM 再產生實體 DOM ## 建立 React Elememt ```javascript import React from 'react'; const buttonReactElement = React.createElement( 'button', // 元素類型 { id: 'foo-btn' }, // 屬性 'I am a button' // 子元素 ); ``` 由於create jsx 來自於 react.createElement 的語法糖。 react 在run time的時候才知道畫面長怎樣 比較沒辦法在build time 的時候處理優化,換句話說語法分析也比較困難。 vue 用 v-if, v-on 語法相對受限,使用template,但也因此更容易調整及優化。 ## React 的一律重繪渲染策略 若一律全部清掉全部重繪,會很浪費效能。 => 改成一律重繪虛擬的畫面結構資料。 把前一次印出來的跟後一次印出的DOM比較找到差異。盡量減少操作 DOM element。 效能瓶頸是在建立react element 的過程 *講完基本策略接下來會講重大的 feature 發展。* ## Class Component ```javascript= ```javascript class ProductList extends React.Component { constructor(props) { super(props); this.state = { products: [] }; } componentDidMount() { ProductAPI.fetch().then(function(data) { this.setState({ products: data }); }.bind(this)); } render() { return ( <div> <div>商品列表</div> {this.state.products.map(product => ( <ProductItem name={product.name} price={product.price} /> ))} </div> ); } } ``` 有一段時間是主流的寫法 ## Create React App(2016) 由於需要 babel 轉譯 剛開始需要複雜的設定,進入門檻較高 因此創建了CRA腳手架 ## React Fiber(2017) ### 為啥需要架構重寫? * 前端對效能需求增加 - 前端對於「多任務」處理能力的需求增加 - 任務的兩大分類 - CPU 密集處理 - I/O(data fetch / 使用者互動) - 任務之間的依賴關係 - 任務之間的優先級關係 ### 在 React Fiber 之前 - Render phase - 由父 component 層層往內部的子 component 呼叫來產生 UI 樹狀結構,形成一個**遞迴**的 stack - 這個 stack 的產生過程是同步且不可中斷的,因此當 render 某一塊樹狀結構過深或過於複雜的畫面時,就有可能佔用瀏覽器的 main thread 過久,導致其他瀏覽器工作產生延遲,影響使用者體驗 - Commit phase - 這部分會呼叫瀏覽器 API 來更新實際的 DOM,因此本來就必定是同步且不可中斷的 以前是遍例整棵樹狀結構,這個過程不可中斷。 ### 在使用 fiber 之後 透過link list 來處理,因為只需要記得當下的節點位置 ## concurrent mode 當時再2018 要用就要全部用,在社群有些爭議 ## streaming ssr 必須載入所有的js 才可以 ## cocurren feature 從current mode 改成cureent feature ## useTrisition ## useDeferredValue ## server component ## meta framework - Next.js - Remix ## use use讚讚 ## 聊天區