Inferno能夠看作是React的另外一個精簡、高性能實現。它的使用方式跟React基本相同,不管是JSX語法、組件的創建、組件的生命週期,仍是與Redux或Mobx的配合、路由控制等,均可以基本按照React的方式來開發,只有微小的不一樣。不過Inferno是專門針對網頁開發的,不能像React Native那樣開發移動端本地APP。javascript
既然Inferno和React基本差很少,又沒有開發本地APP的能力,那爲何要用Inferno呢?簡單來講就是由於性能。css
首先Inferno自己的體積很是小,只有React的五分之一;在頁面性能上,它也有着很是明顯的優點。Inferno也使用了虛擬DOM技術,但即與React的不一樣,也沒有使用那個比較流行的開源virtual-dom項目,而是本身完整開發了一套虛擬DOM,它的實現相對輕量、高效,性能更好。至於Inferno的性能究竟有多好,能夠參考Inferno主頁(www.infernojs.org)上的跑分對比。java
所以,在一些很是重視性能的設備上試用Inferno就顯得頗有優點了,尤爲是移動端。雖然如今手機的更新換代很快,配置愈來愈高,可是網速和網絡流量的制約依然要求下載的文件越小越好,並且手機上內存和CPU永遠是很是寶貴的資源,對頁面性能的要求依然高於PC。react
此外,Inferno有一些小的改進讓它用起來比React更爽,尤爲是按照flux模式構建純函數組件時。程序員
總之,想享受React這樣高效的響應式開發體驗,又想得到接近於原生代碼的高性能,Inferno是一個很是好的選擇。npm
可是若是真要把開發從React甚至其它框架上遷移到Inferno上來,有些問題須要先考慮清楚。json
如今不少WEB軟件,尤爲是行業軟件的開發,很是依賴於某一集成化的UI組件庫。我甚至瞭解到一些開發人員開始學習React是由於想用Ant Design。VUE如今有如此好的發展也與其生態系統內愈來愈豐富的組件庫有關。而Inferno畢竟是一個小衆框架,起碼如今想找到一個針對Inferno創建的完整的組件庫是不可能的。儘管有inferno-compact這樣的工具能夠把react組件適配到Inferno中去,可是因爲不少組件都會用到ref,而ref在兩個框架中的用法是不同的(下文會詳述),致使在Inferno中引用React組件困難重重。所以若是你的項目須要大量統一的、現成的組件的話,直接就放棄Inferno,老老實實用React或VUE就好。redux
不過當你的項目須要高度定製化,或者自己比較簡單的話,就能夠考慮用Inferno了。我就遇到了需求定製化程度過高,連Ant Design都沒法知足的狀況。而基於Inferno這樣的框架來封裝組件其實是一件很是愜意的事情。對於很是複雜的組件,好比日期選擇、移動端滑動等,能夠直接將那些不依賴於特定框架的庫封裝爲Inferno組件,用起來也十分方便。api
Inferno只支持現代瀏覽器。若是你有很是重要的目標用戶還非用IE9如下的瀏覽器不可,那最好仍是去用jQuery、用EasyUI。這是上一個時代的開發。數組
選擇採用React的一個緣由多是React有一個衍生品是React Native,這就意味着一些大型應用可讓移動端WEB和APP共享一套代碼從而節約開發成本。但Inferno只能用於WEB開發,也正所以,它相對於React纔有了大量的精簡和性能優化。
儘管Inferno和React用起來感受很像,但畢竟仍是有不一樣之處。當出現問題時,react隨便一搜就有一堆結果,而Inferno可供參考的恐怕只有官方文檔(固然是英文,目前沒有中文翻譯),目前連stackoverflow上關於inferno的提問和回答也寥寥無幾。固然,還有源代碼。
並且Inferno比React年輕,又沒有像React那樣的facebook豪華團隊來維護,儘管它基本穩定,但仍是會出現一些小問題。不過得益於他是一個處於活躍期的開源軟件,這個版本發現的明顯bug通常在隨後的幾個版本內就會被修復。
例如我曾遇到過一個古怪的問題,Inferno渲染的一組CheckBox,當其序列發生變化後,點擊一個CheckBox會把另外一個勾選上。經過跟蹤源代碼發現Inferno爲了達到最優性能,當虛擬DOM發生變化時,對於同一位置上的先後相同標籤名元素不從新渲染,而是給實際DOM上原來的節點從新賦予屬性,可是onChange這個事件屬性是通過特殊處理的,並不是原生,在從新賦屬性的時候就沒有改變這個事件處理函數,因此經過不把CheckBox的綁定值放在閉包中而放到元素上現用現讀就能夠規避這個bug,並且下一個版本就修正了這個問題。再看前一個版本的代碼發現這個bug挺烏龍的,原本前面不存在這個問題,是開發者在精簡事件處理代碼的時候忽略了對onChange這樣加工過的事件處理的方式。趕巧就這一個版本有這個bug,被我遇上了。
對於這個小衆框架的穩定性的懷疑多是不少人不敢用它最重要的緣由,實際上國外已經有一些公司在生產中使用了Inferno,因此也不用過度擔憂。
若是你已經會使用React開發,那就基本上已經會使用Inferno了。至於Inferno項目的搭建也與React項目基本相同,只是要把一些依賴的包替換成Inferno相關的。若是手頭上有個React的項目,那就請打開package.json文件看看有哪些包的名稱帶有React字樣,基本上把全部「react」都替換成「inferno」就能夠了,下面列列舉了一些可替換成Inferno相關的依賴包:
以及一些可能用到的開發依賴包:
對於沒有列舉到的react相關的包名,能夠到npmjs.com上驗證一下是否存在相對應的Inferno相關的包。
若是要使用jsx語法,須要讓babel將jsx標籤譯爲inferno所接受的函數,這就要在babel配置文件.babelrc中的plugins節點中添加inferno,好比一個簡單、完整的支持inferno的.babelrc文件是這個樣子:
{ "presets": ["env", "stage-0"], "plugins": ["inferno"] }
若是要使用eslint,一樣須要在eslint配置文件.eslintrc中添加相應插件:
{ "parser": "babel-eslint", "plugins": [ "inferno" ], ... }
總之就是按照react的配置方式把「react」都替換成「inferno」就好了。
Inferno的開發和React大同小異,把這些關鍵的「小異」弄清楚了,開發也就沒有什麼障礙了。
Inferno建立元素可使用JSX語法或者createElement函數,這與React相同,不過createElement函數並不在Inferno包中,而是須要另外引入一個inferno-create-element包。
此外,Inferno還提供了一個Hyperscript方式來建立元素。它的使用方式與createElement類似,不過類名和id能夠用css語法和標籤名寫在一塊兒,且div能夠省略,這與pug(原名jade)很類似。另一個不一樣就是子節點放在數組裏。
下面列舉了Inferno建立元素的三種方式:
import Inferno from 'inferno' const demo = <div id="example1" className="example-div"> Hello, <a className="example-link" href="infernojs.org"> Inferno </a> </div>
import createElement from 'inferno-create-element' const demo = createElement('div', { id: 'example1', className: 'example-div' }, 'Hello, ', createElement('a', { className: 'example-link', href: 'infernojs.org' }, 'Inferno' ) )
import h from 'inferno-hyperscript' const demo = h('#example1.example-div', [ 'Hello, ', h('a.example-link', { href: 'infernojs.org' }, [ 'Inferno' ]) ])
當向頁面上渲染節點時,Inferno通React同樣是使用render函數,不過Inferno的render函數是屬於Inferno包的,而不像React那樣是一個單獨的react-dom包.
import Inferno from 'inferno' Inferno.render(<div>Hello, Inferno</div>)
Inferno聲明組件有三種方式:函數組件、ES6(2015)的類繼承Component類,以及使用createClass函數。這基本上都與React相同,不過Inferno的Component類來自於單獨的包「inferno-component」,createClass函數也來自於單獨的包「inferno-create-class」。
咱們知道在React中,若是組件使用createClass函數建立或者繼承Component類,就能夠經過實現生命週期方法——如componentDieMount等——在組件生命週期的各關鍵點上作一些事情,而函數組件就沒辦法了。Inferno則能夠經過給函數組件傳入生命週期屬性函數來實現生命週期管理。生命週期屬性名大可能是在那些生命週期名稱前面加on。Inferno支持的全部生命週期屬性以下:
這樣咱們就能夠在用inferno-redux的connect函數建立容器型組件時給函數組件注入生命週期屬性函數了,這就使得在更多狀況下可使用函數組件。
shouldComponentUpdate這個生命週期對應的屬性是onComponentShouldUpdate,咱們知道在這個生命週期中作一些是否須要渲染的判斷能夠提高性能。Inferno對此有一個更方便的辦法,就是Inferno.NO_OP。這是一個無需從新渲染的標記,當在渲染的函數中(函數組件自己或是render函數)返回這個標記時,就至關因而在shouldComponentUpdate中返回了false。NO_OP和函數組件搭配使用很是簡潔方便。遺憾的是函數組件的函數中沒法獲取組件上一次渲染的屬性,最經常使用的根據屬性來判斷是否須要從新渲染的方法沒法用在NO_OP上。
在react中,能夠給元素添加一個ref字符串屬性,在組件渲染以後就能夠經過this.refs.xxx來找到標記了ref屬性的元素了。Inferno元素也支持ref屬性,不過與react不一樣的是它接受的不是一個字符串,而是回調函數。在組件渲染完成後會調用這個回調函數,傳入的參數是元素的真實dom節點,你能夠自由地把這個節點存儲到任何地方以備之後使用,也能夠當即使用。這樣函數組件也可使用ref引用出來的元素了。
不過因爲Inferno與React的這點差別,致使不少基於React的組件難以適配到Inferno上。目前我還沒發現有好的解決辦法,反正我也沒有怎麼嘗試把react組件用到inferno上來,就像我前面說的,若是想用Ant Design這樣的基於react的組件庫,仍是老老實實用react吧。
另外有個值得注意的地方是ref屬性只對原生dom元素有效,即'div'、'input'這類元素,而Inferno組件(非原生dom標籤)在加載時會自動忽略掉ref屬性。即使咱們想在本身寫的組件中經過ref屬性來手動傳遞元素,也會發現根本接收不到ref屬性。真一點真是挺奇怪的。不過既然是本身寫的組件,就能夠根據須要隨便命名屬性了。好比能夠定一個「elRef」屬性,將其直接傳給組件最外層標籤(原生dom標籤,非Inferno組件)的ref屬性,這樣就能夠在使用這個組件的時候獲得最外層實際dom元素。若是要讓組件的ref跟React組件經過ref所拿到的內容一致,能夠定一個「cpnRef」屬性,在componentDidMount方法中調用並傳入組件實例對象,即this。不過要注意作好項目中的命名規範。
Inferno的事件處理方式與React也基本相同,不過在change事件上的處理與React不一樣,Inferno採用與Input原生的change事件相同的觸發方式,不會讓每一次鍵盤的輸入都觸發change事件。而要獲得React中那樣的onChange效果,可使用onInput。
inferno提供了一個很小的事件輔助函數:LinkEvent。咱們知道當用ES6的類來聲明React組件時,做爲類方法的事件處理函數需和「this」綁定才能訪問到this,並且應當在構造函數中而不是在事件屬性上進行綁定,以避免性能損失。Inferno經過LinkEvent給出了更簡潔的方法,看下面示例就明白怎麼用了:
import Inferno, { linkEvent } from 'inferno' import Component from 'inferno-component' class MyComponent extends Component { render () { return <div><input type="text" onChange={linkEvent(this, handleChange)} /><div>; } } function handleChange(instance, event) { instance.props.setValue(event.target.value) }
linkEvent其實作了一個很是簡單的事情,就是返回了一個這樣的對象:{data: this, event: handleChange}。把這個對象直接寫在onChange屬性裏面效果也是同樣的。而Inferno的事件封裝函數遇到了這樣格式的對象時就會進行相應的特殊處理。
我以爲linkEvent的意義不只在於略微簡化了事件處理函數的綁定,而是讓函數組件能夠容易地使用事件處理函數。以下面例子所示:
import Inferno, { linkEvent } from 'inferno' export default function (props) { return <div><input type="text" onChange={linkEvent(props, handleChange)} /><div>; } function handleChange(props, event) { props.setValue(event.target.value) }
React要求渲染數組時數組中的每個元素必須有不相同的key屬性,若沒有key則會在控制檯出現嚇人的紅色警告。
Inferno的元素也支持key屬性,但做用與React不太同樣。它的做用是指導元素渲染,對於同一級別的兄弟元素,在一次渲染中,跟上一此渲染具備相同key的元素認爲是同一元素,這個元素就不會被替換,而會保持原有狀態(好比Input元素的聚焦狀態和非受控的輸入值不變)。
注意,Inferno元素的key屬性是對同一父元素中同一級別的兄弟元素有效,也就是不只限於數組元素。對於被渲染的數組,Inferno也不要求數組中的元素必須有key屬性,並且對於具備重複key的元素不會進行排除,只會給出控制檯警告。
在通常狀況下Inferno不推薦使用key屬性,除非須要保持元素的原生dom狀態,或者須要在數組的中間添加、刪除元素。
Inferno不支持像React那樣用PropTypes來限定組件的屬性。實際上我之前在用React開發的時候不多使用PropTypes,即使用,頂多也是體現出一個文檔的功能,由於React不會對未匹配到PropTypes類型的屬性拋出異常,而只是在開發時在控制檯輸出警告。的確PropTypes校驗會在團隊開發中讓一些類型錯誤更容易被發現,不過對於用慣了動態類型語言的我來講沒有PropTypes並沒感到有什麼缺失,我甚至在開發一些組件的時候故意讓某些屬性能夠是多個類型,以實現更靈活的api。
前面提到過,對於一些比較複雜的組件,能夠直接找一些成熟的、不依賴特定框架的第三方組件進行封裝,用起來也很是方便。這個其實不是Inferno特有的能力,React一樣很是擅長於此。不過一般因爲React有完善的配套組件庫,不少人在開發中不太有機會涉及到封裝第三方組件。這裏我舉個例子以供參考。
就拿日期選擇組件來講。我不打算使用my97datepicker這樣的「老式控件」,由於它須要生硬地引入一堆東西,不符合如今模塊化開發的方式,我選擇了在npm上找到的flatpickr。用npm將它安裝後,就能夠用下面的代碼進行封裝了:
import Inferno from 'inferno' import Component from 'inferno-component' import moment from 'moment' import flatpickr from 'flatpickr' import flatpickrZh from 'flatpickr/dist/l10n/zh.js' import 'flatpickr/dist/flatpickr.css' flatpickr.localize(flatpickrZh.zh) export default class extends Component{ render(){ const {style, className, value} = this.props return ( <input ref={el=>this.el = el} style={style} className={className} value={value}/> ) } componentDidMount(){ const {onChange, options} = this.props this.pickr = flatpickr(this.el, { onChange(dates){ onChange(moment(dates[0]).format('YYYY-MM-DD')) }, ...options }) } componentWillUnmount(){ this.pickr.destroy() } }
封裝組件的通常方式就是在Inferno組件渲染完成時,取出已渲染的dom元素,用它和經過props傳入的屬性來構建第三方組件。flatpickr須要用一個dom元素做爲日曆顯示的觸發元素,這裏固定使用了一個input元素。flatpickr所需的屬性除了onChagne外都經過options屬性傳入,在onChange裏作了日期格式化,以在特定狀況下組件使用更方便。因爲有了onChange和value,這個組件能夠像其餘表單元素同樣進行受控操做,並且爲了和其它表單元素使用上一致,在實際項目中我會改變一下傳給onChange的參數,模擬一個event對象,把值放到event.target.value中,便於批量處理。
最後不要忘記卸載組件時銷燬第三方組件,不然可能會在頁面上留下一堆垃圾。
我相信經過閱讀這些文字後,已經掌握了React開發的程序員就能夠用Inferno進行開發了。我寫這些既是對前段時間用Inferno的開發進行一些總結,也是但願藉此來讓更多人瞭解並嘗試使用Inferno這樣小而美的框架。