Virtual Dom
在瀏覽器環(huán)境下,DOM操作遠比JS操作開銷大,且項目越復雜,DOM操作的開銷越大,為了提升DOM渲染的效率,需要遵循的原則是盡量減少DOM操作。于是Virtual Dom橫空出世!
Virtual Dom顧名思義,是使用JS模擬DOM結(jié)構(gòu);并把DOM變化的對比,放在JS層來做,以此來提高重繪性能(將開銷大的Dom操作轉(zhuǎn)化為高效的JS操作)
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
</ul>
//用JS模擬DOM如下:
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 1']
},
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 1']
}
]
}
snabbdom
snabbdom為virtual dom的一個實現(xiàn)庫,其核心API有2:
h():創(chuàng)建virtual dom
patch(vnode, mewnode):渲染前對比兩個虛擬節(jié)點后并完成真實渲染
diff
virtual dom中(React、Vue)的diff算法起源于Linux的diff命令,用于比對文本的不同之處,是一個古老的命令。在virtual dom操作中,找出需要重新渲染的節(jié)點的過程,就是React中的diff算法。
//簡略模擬diff:
function creatElement(vnode){
var tag = vnode.tag
var attrs = vnode.attrs || {}
var children = vnode.children || {}
if(!tag){
return null
}
var elem = document.creatElement(tag)
var attrName
for (attrName in attrs) {
if(attrs.hasOwnProperty(attrName)){
elem.setAttribute(attrName, attrs[attrName])
}
}
chlidren.forEach((childVnode) => {
elem.appendChild(creatElement(childVnode))//遞歸
})
return elem
}
function updateChildren(Vnode, newVnode){
var children = vnode.children || {}
var newChildren = newVnode.children || {}
children.forEach((childVnode, index) => {
var newChildVnode = newChildren[index]
if(childVnode.tag === newChildVnode.tag){
updateChildren(childVnode, newChildVnode)
} else {
replace(childVnode, newChildVnode)
}
})
}
JSX
在React中負責VIew的編寫,其本質(zhì)上是語法糖,由于瀏覽器無法識別JSX,實際運行時由React庫中的核心函數(shù)React.creatElement將JSX翻譯成JS,再由JS渲染為HTML,類似于virtual dom經(jīng)由h()轉(zhuǎn)為真實節(jié)點一樣。
React首次渲染時,調(diào)用patch(container, vnode)
通過setState()函數(shù)再次渲染時,調(diào)用patch(newVnode, vnode)
JSX自定義組件的解析過程
-
React.creatElement(App, null)
首先調(diào)用React的核心函數(shù),傳入自定義組件App -
var app = new App()
App為class,進行類的實例化 -
return app.render()
調(diào)用類實例的render()方法,并繼續(xù)步驟1,直至沒有自定義組件為止
setState()方法
- 異步
setState()方法是異步的,這是因為一次操作中,可能執(zhí)行多次setState,根據(jù)瀏覽器單線程的特性和性能上的考量,無需每次執(zhí)行setState的時候都重新渲染,只需進行最后一次渲染即可。
......
{
setState({a: 1})
setState({a: 2})
setState({a: 3})//只執(zhí)行最后一次,前兩次不執(zhí)行
}
......
- 過程
1 每個React組件實例,均有繼承自Component類的renderComponent方法。
2 執(zhí)行renderComponent方法會再次執(zhí)行組件的render方法。
3render方法返回newVnode,同時系統(tǒng)緩存有preVnode的信息。
4 執(zhí)行patch(preVnode, newVnode)。
React生命周期相關(guān)

shouldComponentUpdate
React開發(fā)時,一個很奇妙的事情就是當state或props未發(fā)生改變時,組件依然會重新渲染,所以當追求性能的時候,shouldComponentUpdate就派上了用場。shouldComponentUpdate生命周期函數(shù)是重渲染時render()函數(shù)調(diào)用前被調(diào)用的函數(shù),它接受兩個參數(shù):nextProps和nextState,分別表示下一個props和下一個state。并且,當函數(shù)返回false時候,阻止接下來的render()的調(diào)用,阻止組件重渲染,而返回true時,組件照常重渲染。
//通過對比屬性來確定組件是否更新
shouldComponentUpdate(nextProps,nextState){
if(nextProps.numberObject.number == this.props.numberObject.number){
return false
}
return true
}
一個警告
Warning: setState(...): Can only update a mounted or mounting
component. This usually means you called setState() on an
unmounted component. This is a no-op. Please check the code for
the OrderTableList component.
在組件生命周期中,設(shè)立標志位來檢驗組件是否掛載,可規(guī)避此警告
componentDidMount(){
this.mounted = true
//some code
}
if(this.mounted){
this.setState({})
}
componentWillUnmount(){
this.mounted = false
}
react數(shù)據(jù)流
在并不復雜的webapp中,其實是可以不用redux的!
- 父組件向子組件傳遞數(shù)據(jù),類似如下的思路
<Father>
<Son data={this.state.data}/>
</Father>
- 子組件向父組件傳遞數(shù)據(jù)
<Father>
<Son changeData={this.addData.bind(this)}/>
</Father>
//Father中有addData()操作數(shù)據(jù),并將此方法向下傳遞下去(到Son)
addData(){
//do sth
}
//Son中有方法負責傳遞數(shù)據(jù),調(diào)用this.props上傳遞下來的方法處理,并將自身數(shù)據(jù)傳入其中
do(){
this.props.changeData(mydata)
}
- 子組件與子組件間傳遞數(shù)據(jù)
這里說的是有公共父組件的應用場景,即將子組件一的數(shù)據(jù)上傳到父組件,經(jīng)由父組件中轉(zhuǎn)后將數(shù)據(jù)下發(fā)到子組件二 - ref
當父組件需要直接操作子組件時,即調(diào)用子組件方法,可以使用ref
<Father>
<Son ref='loginAlert'/>
</Father>
~~~~~~~~~
~~~~~~~~~
this.refs.loginAlert.someFuc(this為Father組件上下文)
關(guān)于父組件更新而子組件不更新的問題
react中,當父組件通過props向子組件傳遞數(shù)據(jù)時,當父組件更新時,子組件會重新render,注意是重新render,子組件并不會完成卸載——加載過程,因此,constructor構(gòu)造函數(shù)并不會再次執(zhí)行,通過this.state語句自然不會傳入props的值。這種情況下,子組件可以定義為“無狀態(tài)”組件,直接使用this.props.data即可。
Immutable.js
var a = 'a'
var b = a
var b = 'b'
console.log(a) //b
修改引用型對象是一樣不安全的行為
var a = 'a'
var b = Object.assign({}, a)
console.log(a === b) //false
可以使用ES6方法生成指向不同內(nèi)存地址的新對象(concat(),...均可),可以使操作對象的行為變得相對安全,但是這種不斷新開辟內(nèi)存的行為會增大系統(tǒng)的開銷,此時immutable.js隆重登場 ,其核心思想為,對象一經(jīng)創(chuàng)建,其不會被改變;無論對其進行什么操作,都返回一個新對象;老對象始終不變,而新對象通過樹形結(jié)構(gòu)共享原則,既保留原來對象不變,又保留變化的這個部分,而因為每次變化僅為一部分,避免了深拷貝帶來的性能損耗,同時可以進行任意時刻的數(shù)據(jù)回溯。
Redux
Redux數(shù)據(jù)流

-
store是Redux的核心,其集成了reducers和一些中間件,Action發(fā)出動作后,集成了reducers的store隨即發(fā)生狀態(tài)變化,然后數(shù)據(jù)便會從根容器(Provider)向下傳遞;由于數(shù)據(jù)在全局有且只有一份(Store),因此在React應用中頻繁修改state容易引起數(shù)據(jù)混亂,因此在Redux數(shù)據(jù)流中,需要使用Immutable Data。 -
Redux數(shù)據(jù)流中,一個Action有一個或者多個Reducer與之對應,多個Reducer用combineReducer包裹并傳入Store中 -
容器組件和視圖組件
容器和視圖組件
中間件
const logger = (store) => (next) => (action) => {
//do something
} //科里化函數(shù)
構(gòu)建store
import {creatStore, applyMiddleware} from 'redux'
import {fromJS} from 'immutable'
import thunk from 'redux-thunk'
import creatLogger from 'redux-logger'
import promiseMiddleware from 'redux-promise'
import reducers from './reducers'
const middlewares = [thunk , promiseMiddleware, creatLogger()]
function configerStore( initialState = fromJS({}){
const store = creatStore(reducers(), initialState, applyMiddleware(...middlewares))
if(module.hot){
module.hot.accept( () => {
store.replaceReducer(require('./reducers').default) //熱重載更新reducers
})
}
return store
}
export default configerStore
connect
用于建立store和actions的連接,使reducers可以接收到發(fā)出的actions,從而改變store中的數(shù)據(jù),即將action和reducer都綁定在一個實例上。connect()接受4個參數(shù):
mapStateToProps #把store中的數(shù)據(jù)作為屬性綁定到組件上,從而可以...this.props獲取
mapDispatchToProps #方法的綁定,從而可以this.props獲取action調(diào)用
mergeProps
options
Mobx
Mobx數(shù)據(jù)流相較Redux而言,概念更少。Mobx基于一份數(shù)據(jù)進行修改(Redux數(shù)據(jù)基于淺拷貝,每次更新都生成新的數(shù)據(jù)對象,但是由于淺拷貝和虛擬DOM的存在,對于整體性能的影響有限),因其對store數(shù)據(jù)的修改可以使用JS修改普通對象的方式,過程比較靈活(不嚴謹)。
const mobxStore = observable(
count: 0,
add: action(function(num){
this.count += num
})
)
mobxStore.add(1)
- Mobx應用中用Class組織
- Mobx重要概念
@observable
@action
@computed
autorun()//數(shù)據(jù)store更新時調(diào)用
- 通過@inject('storeName')向組件注入數(shù)據(jù)
- 通過@observer監(jiān)聽組件
React16新特性
- rollup打包,體積小
- 使用Fiber重寫
- 一些實用的功能:
- error boundary,捕獲React渲染過程中出現(xiàn)的錯誤,通過新的鉤子函數(shù):
componentIDidCatch - New render return types,可以在Render方法中返回Array和String,也可以使用
Fragment代替沒有實際意義的div - Portals,用于創(chuàng)建浮層或者Popup組件
- Better sever-side rendering
React中使用PropTypes進行類型檢查
從React15.5起,React內(nèi)置的類型檢測庫開始作為獨立的存在,開發(fā)者可以通過‘prop-types’類型檢測庫捕獲更多的bug,PropTypes 輸出了一系列的驗證器,可以用來確保接收到的參數(shù)是有效的。當給 props 傳遞了一個不正確的值時,JavaScript控制臺將會顯示一條警告。
import PropTypes from 'prop-types'
class A extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
A.PropTypes = {
name: PropTypes.string
}
同一組件無法重新渲染的解決方案
當使用路由切換時,不同的路由下對應的同一個組件 ,這時候React并不會重新渲染組件,此時只是傳入的props有了一定的變化,這時需要利用React的鉤子方法:componentWillReceiveProps,通過比對props,來完成新數(shù)據(jù)的獲取,進而重新渲染組件。
