Preact -- React的輕量解決方案

李CHENGXI發表於2016-08-15

原文連結

本文使用starter-kit:steamer-react react分支。此分支已整合react與preact。

背景

最近接手了互動視訊的專案,做了一個月的運營活動。跟基礎功能不同,運營活動更為輕量。因此許多同事並不想用那麼“重”的React。但同時,大家由於之前度過React的上手痛苦期後,開始體會到React的許多好處,裸寫運營活動的時候,又開始對React的好處念念不忘記:良好的元件化、解放js能力的jsx等。
因此,尋找輕量化的類React解決方案便提上日程。

Preact的優點

選型的時候,首先有幾個考量:

  • 開源社群有較多star(認可)
  • 較好的效能和相容性
  • api跟React接近
  • 足夠的框架周邊,配置redux,router等使用
  • 團隊成員有能力維護的

基本上以上幾點,Preact都能夠很好的滿足,因此最終選定為團隊的類React輕量化框架進行使用和研究。

開源社群有較多star(認可)

相比起react-liteDeku, Virtual-DOM,Preact雖然不是最多的star,但也能排第2,也具備測試用例,且作者開通了gitter chat跟開發者保持聯絡,某天在上面留言,作者也是回覆得很迅速。

Preact -- React的輕量解決方案

較好的效能和相容性

效能方面,Preact也不俗。載入效能方面,由於本身的bundle在gzip後大概只有3kb,跟React相比小太多,自由就有優勢。渲染效能方面,參考了一篇JS WEB FRAMEWORKS BENCHMARK系列測評文章,發現Preact在建立、更新、刪除節點等方面,都有良好的表現。

第一次效能測試:

Preact -- React的輕量解決方案

第二次效能測試:


Preact -- React的輕量解決方案

包大小:

framework version minimized size
React 0.14.3 136.0kb
React-lite 0.15.6 25kb
Preact 5.6.0 10kb
Deku 2.0.0-rc16 51.2kb
Virtual Dom 2.1.1 50.5kb

除了效能的良好表現,此框架的瀏覽器相容性也不錯,能相容目前的主流瀏覽器,並且在新增polyfill的情況下,能夠相容在國內還有不少份額的IE8,確實是不少還需要相容IE8開發者的福音。

api跟React接近

Preact的常用api基本跟React一致,這使得對React熟悉的開發者,完全沒有上手的難度,Preact作者單獨開闢了一個文件Differences to React,介紹React與Preact的異同。Preact主要缺少的React Api有PropType,Children, 和 Synthetic Events(合成事件)。作者解釋道,PropType其實許多人都不使用,並不影響開發; Children其實是陣列,所以也並不是必須的;而合成事件,由於不需要過度考慮不同瀏覽器對事件處理的異同,所以也並沒有做過度封裝。如果真的想使用以上這些缺失的React Api,作者也提供了preact-compat,使用的時候,在Webpack上的external這樣替換便可:

{
    // ...
    resolve: {
        alias: {
            'react': 'preact-compat',
            'react-dom': 'preact-compat'
        }
    }
    // ...
}複製程式碼

足夠的框架周邊,配置redux,router等使用

對於React開發者來說,最常用的就是redux, router這些周邊的外掛。而Preact也有提供preact-reduxpreact-router,甚至還有幫助Preact做同構直出的preact-render-to-string

團隊成員有能力維護的

Preact專案的框架小而美,合併成的dist檔案也只有500行左右,比較容易學習和維護。若團隊選擇此框架作為React的輕量解決方案的話,我們最好能具備維護和開發此框架的能力,這能夠在遇到bug的時候第一時間修復,而且能夠很好地開發一些元件,提升框架的開發效率。

如何上手及如何和React在同一構建下使用

作者在Getting Started裡有比較好的介紹。其實不外乎就2點差異:

  • 引入preact與引入react的差異。
    引入preact的時候,大概是這樣的:

    import preact, { h, render, Component } from 'preact';複製程式碼

    而引入react的時候,大概是這樣的:

    import React, { Component, PropTypes } from 'react';
    import { render } from 'react-dom';複製程式碼
  • 編譯所需的外掛差異。
    preact的jsx編譯,主要藉助babel-plugin-transform-react-jsx,而react則是藉助babel-preset-react

如果你想在一個構建裡面同時使用React和Preact(有的頁面使用React,有的用Preact),你可以通過Webapck的loader include或者exclude,然後憑路徑區分。而我在steamer-react的react-preact分支裡的處理是直接用檔名字尾。如果是有React相關引入的,則用.js字尾,而有Preact相關引入的,則用.jsx字尾。

Preact的實現簡介

粗略看了一下Preact的實現,簡單介紹一下。

Virtual Dom

Virtual Dom算是類React框架的最大賣點。Preac作者寫了一篇WTF is JSX。主要就是藉助babel-plugin-transform-react-jsx的能力,裡面有個pragma引數,用於設定用什麼函式來做virtual dom的轉換。此處定義的是preact.h

 ["transform-react-jsx", { "pragma":"preact.h" }]複製程式碼

所以,你會看到編譯後,有類似的程式碼:

_preact2.default.h(
    'p',
    { className: 'info-content' },
    item.des
)複製程式碼

檢視原始碼,preac定義了h的函式,用於將傳入的值轉換成virtual dom:

function h(nodeName, attributes, firstChild) {
    // some code here
}複製程式碼

所以,如果傳入上面的p和對應屬性,則會轉換成下面的物件:

VNode {nodeName: "p", attributes: {class:"info-content"}, children: undefined, key: undefined}複製程式碼

但virtual dom需要轉換成真實的dom,還需要一個函式進行轉換。在Preact中,大體是通過這個流程,然後最終轉換成真實dom:

render (類似於react-dom裡的render,主入口,觸發渲染) => diff => idiff (看起來應該是做dom diff) => createNode (生成真實dom)複製程式碼

元件化與生命週期

元件化也是類React框架的一大特色。Preact的元件化,主要是通過Component這一方法來實現的。主要包括,setState,render以及一眾生命週期。主要的渲染,生命週期的觸發,也主要定義在renderComponentsetComponentProps方法內。使用者的自定義元件只需要繼承Component就可以自由使用Preact元件化的能力。

事件機制

Preact並沒有像React那樣自己實現了一套事件機制,主要還是用瀏覽器自帶的能力。因此,在給生成真實dom並通過setAccessor給dom插入屬性的時候,有這麼一段程式碼:

else if ('o' === name[0] && 'n' === name[1]) {
     var l = node._listeners || (node._listeners = {});
     name = toLowerCase(name.substring(2));
     if (value) {
         if (!l[name]) node.addEventListener(name, eventProxy);
     } else if (l[name]) node.removeEventListener(name, eventProxy);
     l[name] = value;
 }複製程式碼

判斷屬性中是否含有on,也就是在看,有沒有on開頭的屬性(一般就是事件)。然後就進行addEventListener或者removeEventListener。看起來跟我們寫原生js的事件繫結沒有什麼區別。

如有錯誤,懇請斧正。

相關文章