React Step by Step

pardon110發表於2019-08-05

react官方指引反覆強調 props與 state 區別。基本上 props 類似於其他語言中的形參,state則相當於內部臨時變數。一個是對外溝通的橋樑,而另一個則為互動私有變數。state 基於與 Dom 元素互動考量,這是因為透過setState 方法對 state 的變更,會加入非同步佇列,在合適的時機進行頁面渲染。

ES6

  1. 不使用ES6
    • class關鍵字繼承 React.Component 類
    • 使用 create-react-class 模組來建立
  2. 宣告預設屬性
    • 自定義屬性物件寫到類的 defaultProps 屬性
    • createReactClass 方法建立元件,那就需要在引數物件中定義 getDefaultProps 方法,並且在這個方法中返回包含自定義屬性的物件
  3. 設定初始狀態
    • class 關鍵字建立元件,在 constructor 中給 this.state 賦值
    • 使用 createReactClass 方法建立元件,要寫一個 getInitialState 方法,並讓這個方法返回你要定義的初始屬性
  4. 自動繫結
    • 透過 ES6 class 生成的例項,例項上的方法不會繫結 this
    • 使用 class 關鍵字建立的 React 元件,元件中的方法是不會自動繫結 this
      • 因此,需要在 constructor 中為方法手動新增 .bind(this) [建議]
    • 使用 createReactClass 方法建立元件,元件中的方法會自動繫結至例項
    • 目前還處於實驗性階段的 Babel 外掛 Class Properties(注通常意義上的箭頭函式屬性)
  5. Mixin 混入
    • ES6本身是不包含混入支援
    • 相似功能-> 橫切關注點
    • createReactClass 建立React元件時,可引入混入
      • 配置類似mixins:[SetIntervalMixin]選項
    • 多個混入定義相同生命週期方法,執行順序與定義順序一致

此項織入同vue的功能一致,所不同的是在最新版react,官方推薦使用高階元件來解決資料共享問題

JSX

  1. 不使用jsx
    • 每一個JSX元素都只是 React.createElement(component, props, ...children) 的語法糖
    • react元件型別
      • 可以是一個字串
      • 亦可以是React.Component的子類
      • 當元件是無關的時候,它可以是一個普通的函式
  2. React.createElement
  3. jsx語法

        class Hello extends React.Component {
            render() {
                return <div>Hello {this.props.toWhat}</div>;
            }
        }
    
        ReactDOM.render(
            <Hello toWhat="World" />,
            document.getElementById('root')
        );
    

協調(Reconciliation)

  1. 目的
    • 在單一時間點你可以考慮render()函式作為建立React元素的樹
    • React需要算出如何高效更新UI以匹配最新的樹,React基於兩點假設
      • 兩個不同型別的元素將產生不同的樹
      • 透過過渲染器附帶key屬性,開發者可以示意哪些子元素可能是穩定的
  2. 對比演算法
    • 不同型別的元素
      • 每當根元素有不同型別,React將解除安裝舊樹並重新構建新樹
      • 元件例項會呼叫componentWillUnmount() -> componentWillMount() -> componentDidMount()
      • 任何與舊樹有關的狀態都將丟棄
    • 相同型別的DOM元素
      • React則會觀察二者的屬性[即樹的狀態],保持相同的底層DOM節點,並僅更新變化的屬性。
      • 在處理完DOM元素後,React遞迴其子元素。
    • 相同型別的元件元素
      • 當元件更新時,例項仍保持一致,以讓狀態能夠在渲染之間保留
      • React透過更新底層元件例項的props來產生新元素
      • 並在底層例項上依次呼叫componentWillReceiveProps() -> componentWillUpdate() -> render()
    • 遞迴子節點
      • 預設時,React僅在同一時間點遞迴兩個子節點列表,並在有不同時產生一個變更
    • Keys
      • React使用key來匹配原本樹的子節點和新樹的子節點
      • key必須在其兄弟節點中是唯一,但當索引用作key時,元件狀態在重新排序時會有問題。
      • 萬不得已,你可以傳遞他們在陣列中的索引作為key,建議元素在沒有重排情況下使用
  3. 協調
    • 當前實現,子樹在其兄弟節點中移動,是無法告知其移動位置
    • React依賴於該啟發式演算法(即合理假設),若其背後的假設沒得到滿足,則其效能將會受到影響
      • 演算法無法嘗試匹配不同元件型別的子元素。
      • Keys應該是穩定的,可預測的,且唯一的。
      • 非穩定的key會使得大量元件例項和DOM節點進行不必要的重建,且丟失子元件的狀態,效能下降。

Context

  1. 應用場景(資料共享)
    • React.createContext
    • Context 提供了一種在元件之間共享props值的方式,而不必透過元件樹的每個層級顯式地傳遞 props
    • Context 設計目的是為共享那些被認為對於一個元件樹而言是“全域性”的資料,當前認證的使用者、主題或首選語言
    • React應用資料是透過 props 屬性由上向下(由父及子)的進行傳遞,使用context可以避免透過中間元素傳遞
    • 應用於多個層級的多個元件需要訪問相同資料的情景。
  2. 相關API
    • const {Provider, Consumer} = React.createContext(defaultValue);
    • <Provider value={/* some value */}>
    • Comsumer 一個可以訂閱 context 變化的 React 元件。
  3. 父子耦合
    • 可以透過 context 向下傳遞一個函式,以允許 Consumer 更新 contex

Fragment

  1. 存在的意義
    • html元素在某些情況下,是不允許新增無意義標籤,比如 trtd 之間,但 React 元件又必須存在一個根節點,Fragment 的出現解決了這一問題
    • Fragments 聚合一個子元素列表,並且不在DOM中增加額外節點
    • 常見模式是為一個元件返回一個子元素列表
  2. React.Fragment 元件
    • <></> 是 的語法糖
    • <></>語法不接受鍵值或屬性

Portals(彈窗對話方塊)

  1. 目的
    • Portals 提供一種將子節點渲染到父元件以外的DOM節點的方式
    • ReactDOM.createPortal(child, container)
      • 第一個引數(child)是任何可渲染的 React 子元素, 如 一個元素,字串或碎片
      • 數(container)則是一個 DOM 元素。
  2. 應用
    • 通常從元件的 render 方法返回一個元素,該元素僅能裝配 DOM 節點中離其最近的父元素
    • 典型用例
      • 當父元件有 overflow: hidden 或 z-index 樣式
      • 需要子元件能夠在視覺上“跳出(break out)”其容器,如 對話方塊、hovercards以及提示框
  3. 透過 Portals 進行事件冒泡
    • portal 仍存在於 React 樹中,而不用考慮其在 DOM 樹中的位置
    • 一個從 portal 內部會觸發的事件會一直冒泡至包含 React 樹 的祖先
  4. 錯誤邊界僅可以捕獲其子元件的錯誤

Web Components

  1. React vs web
    • Web元件為可重用元件提供了強大的封裝能力
    • React則是提供了保持DOM和資料同步的宣告式庫
    • React元件與web元件內都可使用對方
    • 由web元件觸發的事件可能無法透過React渲染樹來正確冒泡,可能需要手動捕獲

高階元件(hoc)

  1. 定義
    • const EnhancedComponent = higherOrderComponent(WrappedComponent)
    • 高階元件就是一個沒有副作用的純函式,且該函式接受一個元件作為引數,並返回一個新的元件
    • 對比元件將props屬性轉變成UI,高階元件則是將一個元件轉換成另一個新元件
  2. 應用場景
    • 使用高階元件(HOC)解決交叉問題
    • 移除混入,mixins)技術產生的問題要比帶來的價值大
    • 與混入向元件內部注入,高階元件更像是變更元件外部生存環境
    • 比如Redux的connect方法和Relay的createContainer.
  3. 注意點
    • 不要改變原始元件,使用組合技術
    • 高階元件就是引數化的容器元件定義
    • 容器元件是專注於在高層次和低層次關注點之間進行責任劃分的策略的一部分
    • 容器元件處理諸如資料訂閱和狀態管理等事情,並傳遞props屬性給展示元件
    • 展示元件負責處理渲染UI等事情
    • 將不相關的props屬性傳遞給包裹元件
    • 高階元件給元件新增新特性,不大幅修改原元件的介面(props屬性)
    • 最大化使用組合,高階元件會接收額外的引數
  4. 高階元件簽名
    • 函式簽名如下
    • const ConnectedComment = connect(commentSelector, commentActions)(Comment)
    • connect是一個返回函式的函式(高階函式)
    • React中元件可以是函式
  5. 約定俗成
    • 包裝顯示名字以便於除錯
    • 不要在render函式中使用高階元件
    • 必須將靜態方法做複製
      • 理由:當使用高階元件包裝元件,原始元件被容器元件包裹,也就意味著新元件會丟失原始元件的所有靜態方法
    • Refs屬性不能傳遞(理由refs是一個偽屬性,React對它進行了特殊處理)

Redux

  1. 動機
    • 不斷變化的state,釐清變化和非同步
    • state 一個物件樹
      • 基本資料型別,陣列,物件
      • 或Imutable.js生成的資料結構
      • 當state變化時需要返回全新的物件,而不是修改傳入的引數
    • action 普通物件
      • action只是描述了有事情發生什麼的普通物件,必須擁有一個type域,指示器
    • reducer 函式
      • 描述 action 如何改變 state 樹, 是一個純函式(相同的輸入得到相同的輸出)
    • Redux 所有的state都以一個物件樹的形式儲存在一個單一的store中
    • 可以用action追溯應用的每一次修改,Redux試圖讓state的變化變得可預測
  2. 三大原則
    • 單一資料來源
      • 整個應用的 state 被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個 store 中
    • state 是隻讀的
      • 唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通物件
    • 使用純函式來執行修改
      • 描述 action 如何改變 state tree ,編寫 reducers(純函式)
      • reducer只是函式,可以控制它被呼叫的順序,傳入附加資料,或複用reducer處理通用任務
  3. 溯源其它
    • Redux規定將模型的更新邏輯集中於一個特定層(flux裡的store,Redux裡的reducer)
    • 不允許直接修改資料,而是用普通物件來對變更資料進行描述
    • Redux沒有dispatcher概念,並假定永不變動資料,通常在reducer中返回一個新物件來更新state
    • Elm 函數語言程式設計語言
    • Immutable 不可變資料
      • 可實現持久資料結構的 JavaScript 庫
      • 提供了許多永久不可變資料結構
        • List,Stack,Map,OrderedMap,Set,OrderedSet和Record
    • 可變資料(引用型別)
      • 可變的好處是節約記憶體
      • 在 JS 中,objects, arrays,functions, classes, sets, maps 都是可變資料。
    • 不可變資料
      • js中數字和字串皆屬於不可變資料,即不會影響之前的資料
      • 源於函數語言程式設計,不可變每次操作產生新的物件,新的資料結構(資料複製)
      • 由於可變在程式越大,程式碼的可讀性,複雜度越來越高(因為其每一變動,可能導致另一變動,會產生副作用)
  4. 資料複製
    • 賦值 與const定義資料,後者避免了賦值引用
    • Object.assign({},x) 淺複製
    • deconstruction 解構 淺複製(只複製一層)
    • 深複製方式
      • 原生
      • json 序列化再反序列化 const y = JSON.parse(JSON.stringify(x))
      • 第三方庫
        • lodash
        • $.extend(true,...)
        • immutable.js 庫
      • 不可變涉及到資料複製演算法問題
    • 資料持久化
      • 資料不可變的時候,當每次操作,都不會引起初始資料的改變
      • 對函式程式設計而言,在一定的時期內,資料是永久存在, 故可透過讀取實現類似"回退/切換快照"操作
      • 問題:
        • 每次對整個資料結構進行完整的深複製,效率會很低
        • immutable部分持久化(即共用沒變化的地方)
    • 函式程式設計最重要原則之一不可變資料
    • 在使用建構函式建立陣列的時候可以省略掉new運算子
  5. js陣列操作方法
    • concat() 建立當前陣列副本,將接受到的資料新增到個副本末尾
    • slice() 返回起止位置之間的陣列項(不含結束位置),此方法不會影響原陣列
    • splice() 對陣列進行刪除,插入,替換操作,會改變原始陣列
    • indexOf() lastIndex() 位置查詢
    • 迭代方法(陣列每一項,當前項的下標,陣列物件本身(影響this))
      • every() 皆true則返回true
      • filer() 返回為true的項
      • forEach() 無返回值
      • map() 返回函式每次呼叫結果組成的陣列
      • some() 有一項為真,則為真
    • 歸併方法(累計計算)
      • reduce()
      • reduceRight() 從右項開始累計

Reducer

  1. reducer(純函式,只執行計算)
    • 定義 (previousState, action) => newState
    • 永遠不要在 reducer 裡做以下操作
      • 修改傳入的引數
      • 執行有副作用的操作,如API請求和路由跳轉
      • 呼叫非純函式,如Date.now()或Math.random()
  2. reducer合成
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章