前言
距離我進新公司也有一個多月,這一個月的事件使用react寫了一個項目,期間斷斷續續重構了兩三次,目前已經完成第一階段測試,也總結分享一些使用react的一些坑。
react
先啰嗦幾句講一下react原理,新人可以認真看下,老鳥可跳過。
react并沒有像其他如vue,ng一樣采用MVVM模式,所謂MVVM模式,狹義來說就是將模板與數據綁定在一起,當數據發生改變時,模板自動更新,這是中間的VM,最左邊的M可以理解為我們看到的頁面,而最右邊的M可以理解為原始數據(例如數據庫數據)。
其實要知道,這些框架模式歸根結底的目的希望使代碼更容易開發和維護。當你寫一個小頁面你不覺得什么,但是當你的頁面越來越龐大,越來越復雜,開發人員走了一批又一批的時候,你就會明白了。而如何解決,現在比較公認的理念一個是組件化,將頁面拆分成一個個組件,其實拆分成組件的目的并不全是為了復用,我覺得更多是為了維護;一個就是希望讓應用層的編程能更專注于業務邏輯,那么這些框架都去做了處理,減少了大量DOM操作,讓業務開發更加專注。
那么我們看看react采用的是什么原理。其實react采用的方法簡單粗暴:它并沒有對模板做數據綁定,而是每當數據變化時,就重新渲染模板。這有一個很大的好處就是每當數據變化時,對頁面來說只有一次IO操作,而單純的雙向綁定更新DOM會有很多次;但有一個問題,如果只改變了一個dom的數據,整個模板都會重新渲染。那react解決的方法是每當數據改變時,就進行對比。
那么該如何對比呢,最簡單的方法是每當數據改變,就去頁面獲取相應的DOM信息,然后與現在的DOM信息做比較。這個方法有個致命的缺點就是每次有DOM改變,就會有許多讀取操作,IO操作太多,很影響性能。那么可以通過空間換時間的方法,將DOM信息保存起來,就避免了去頁面獲取信息的IO操作。那么我們看看一個真實DOM有多少數據
虛擬DOM
如果我們把所有原生DOM緩存起來進行比較顯然內存會爆炸,而我們所需要的僅僅是幾個為數不多的狀態信息(例如style啊這些),這時虛擬DOM應運而生,如果說原生DOM是一塊豬肉,那么虛擬DOM就是這塊豬肉中多精肉,他剔除了那些對我們來說沒有太多意義相反還占內存的狀態信息,而只將我們所需要的內容留了下來。那虛擬DOM該如何比較呢,就涉及到了虛擬DOM的diff算法。
diff算法
簡單來說兩個模板就像兩棵樹一樣,傳統的樹對比的時間復雜度是O^3,也就是說要整棵樹遍歷三次,那react根據DOM的特點:跨層級的操作較少。什么叫跨層級,舉個例子一個組件有三個層級關系(嵌套三層),把第二層的div放到第三層就屬于跨層級操作。這種操作其實還是比較少的,react官方也不推薦這么寫。那么利用這個特點,react只diff同層級的DOM。這里涉及以下幾種情況
- tag變化(標簽變化)
- 屬性變化
第二種沒什么好說,就是進行同層級對比。這里詳細說一下第一種變化,react的方法是當前層級往下全部刪除替換,簡單暴力。在業務開發來看,你同層級類型都不同了,比如div變成了input,那么你的子組件相同的幾率也較小,因此不如整個替換簡單暴力。同時這也說明為什么列表需要key屬性,因為列表很多的刪除操作對于react來說是不知道的,它需要一個key來了解到底誰是誰。
踩過的一些坑
state
作為前端,拿到原型第一件事就是要與你的產品充分溝通,評估該項目是否需要引入redux,那么如何確定需要引入redux呢,這邊有幾點:
- 頁面之間組件之間通信較多,且是跨層級的;
- 頁面的交互邏輯較復雜,且經常引起多處組件變化。
再然后就是對state的設計。對于后臺的返回的數據,你并不知道到底哪些是有用的,哪些可能現在沒用以后有用,因此我的建議是對于每個api都將它存在一個object里。那么組件該如何去獲取,首先不能全部通過頂部container獲取依次往下傳,也不能粒度很細的去一個個connect,我的建議是對于一個object,可以用一個container去connect,粒度把握主要看你的業務需求。
還有一個要不要全局都使用redux。我的觀點是否定的,我認為對于局部一個組件內的狀態完全可以通過setState來滿足。
圖片處理
首先將圖片做一些劃分。例如以500k作為分界,小于500k為小圖片,否則是大圖片。對于小圖片,我們需要做如下判斷:
- 頁面是否重復使用?是就用雪碧圖,否則轉base64.
- 對于大圖片,可以進行壓縮后使用。
base64可以用webpack的url-loader替換。
舉個例子
require('url?limit=250!./xxx.png')
//這里就會使用url-loader,假如圖片小于250,就會轉為base64
移動端適配
對于適配,我所知比較好的方法是利用rem作為單位,將頁面寬度等分成10個rem,根據頁面動態的用js去改變font-size,達到適配不同瀏覽器的目的。例如愛瘋寬320px,那么font-size設置為32px。那么10rem就是等于整個屏幕的寬度。但是有一個特例就是高清屏,一般高清屏的物理像素是實際像素的2倍,那么當你想顯示一個寬度為1的邊框時,在普通屏幕是1px,在高清屏可以有0.5px(問題是很多瀏覽器不支持,為將0.5px認為是0)。雖然都使用1px在兩者屏幕上實際上是一樣的,但是高清屏里的1px在射雞師眼里是無法達到他對于1的要求的。于是就有一些解決方案,比較簡單是是使用transform:scale(0.5)。那么還有一種解決方案就是阿里的移動端解決方案,原理是將頁面整體scale縮小,然后放大font-size,來保證rem為單位的布局不變,但是px為單位的會被縮小。
性能提升
首先先確定需求,確實有這個需求的時候再談。
懶加載
webpack其實會幫我們做第三方依賴的懶加載處理,那么針對react,我們可以通過現成的庫來實現懶加載react-lazyload,或者使用webpack現成的
require.ensure([],()=>{
require('public.js')
})
來實現。
shouldComponentUpdate
其實這個鉤子可以極大的幫助我們去提升性能,由于它的存在,我們可以自己判斷哪些是我們組件需要的state,哪些是不需要的,那么這就可以阻止react進行不必要diff。但是有一個問題就是對于對象我們很難去判斷他們是否相等,那么可以通過immutableJs的fromJS和is方法來解決這個問題。其實immutableJs的好處遠不止于此,目前我也尚在填坑中。
使用不可變數據,可以更好的達到函數式編程,不僅利于單元測試,也更有利于后期維護整個大的state。因為他的不可變特點,我們不會在不經意見不小心改變了state,而引起不必要的問題。
創建組件的痛點
為了使組件中的css作用域相互獨立,一般采用Css Module
,那么為了使組件看上去更像組件,并且易于后期維護,一般我們會這樣結構化組件文件:
那么對于每一個初始的jsx,我們大多會這樣初始化
import s from './App.scss'
import{Component} from 'react';//得到組件方法
class App extends Component{
render(){
return (
<div className={s.container}>
</div>
)
}
}
export default App;
不難發現,對于每一個組件,我都需要去手動的創建這些文件和文件夾,而這些操作其實是重復且無意義的勞動,于是我造了一個小輪子,一個命令行小工具,來解決這個痛點。
react-component-maker,歡迎猛戳點star!
結語
困了,本寶寶要睡覺了,還有的內容下次再說吧,再見。