初入React
JSX語法
- 定義標(biāo)簽時,只允許被一個標(biāo)簽包裹
- 標(biāo)簽一定要閉合
- 注釋被{}包起來
React組件
- 無狀態(tài)組件創(chuàng)建時始終保持了一個實例
- 有狀態(tài)組件創(chuàng)建幾次組件就會創(chuàng)建幾次實例
React數(shù)據(jù)流
state
- setState是異步方法
props
- props本身是不可變的(readonly)
- defaultProps靜態(tài)變量可以定義props默認(rèn)配置(默認(rèn)類型)
static defaultProps = {
classPrefix: 'tabs',
onChange: () => {},
};
- React中有一個內(nèi)置的prop:children,代表組件的子組件集合
- JavaScript不是強(qiáng)類型的語言,React對此做了改進(jìn),propTypes用于規(guī)范props的類型與必要狀態(tài)(類型檢查)。
static propTypes = {
tab: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.node,
]).isRequired,
order: React.PropTypes.string.isRequired,
disble: React.PropTypes.bool,
};
React生命周期
分為掛載、渲染、卸載幾個階段
掛載或卸載
主要做組件狀態(tài)初始化
掛載

卸載

數(shù)據(jù)更新
指父組件向下傳遞props或組件自身執(zhí)行setState方法時發(fā)生的一系列更新動作
import React, {Component, PropTypes} from 'react';
class App extends Component {
componentWillReceiveProps(nextProps){
//this.setState({})
}
shouldComponentUpdate(nextProps, nextState) {
}
componentWillUpdate(nextProps, nextState) {
}
componentDidUpdate(prevProps, prevState) {
}
render() {
}
}
組件自身state更新

不能在componentWillUpdate里執(zhí)行setState。
父組件更新props而更新
- 在shouldComponentUpdate之前先執(zhí)行componentWillReceiveProps。
- 該方法可作為React在props傳入后,渲染之前setState。
componentWillReceiveProps(nextProps) {
}
React與DOM
refs
refs是React組件中特殊的props,可以附加到任何一個組件上。組件被調(diào)用時會新建一個該組件的實例,refs則指向這個實例。
- refs放在原生DOM組件中可以得到其DOM節(jié)點
- refs放在React組件中則獲得組件的實例,可以調(diào)用該組件的實例方法
漫談React
事件系統(tǒng)
在React底層,主要對合成事件做了兩件事:事件委派和自動綁定。
在React中使用原生事件
React生命周期方法中,componentDidMount會在組件已經(jīng)完成安裝并且在瀏覽器中存在真實的DOM后調(diào)用,此時可以完成原生事件的綁定。
import React, {Component} from 'react';
class NativeEventDemo extends Component {
componentDidMound() {
this.refs.button.addEventListener('click', e => {
handleClick(e);
});
}
handerClick(e) {
console.log(e);
}
componentWillUnMount() {
this.refs.button.removeEventListener('click');
}
render() {
return <button ref="button">Test</button>
}
}
注:在React中使用DOM原生事件時,一定要在組件卸載時手動卸除,否則很可能會出現(xiàn)內(nèi)存泄漏的問題。
對比React合成事件與JavaScript原生事件
- 瀏覽器原生DOM事件的傳播可以分為三個階段:事件捕獲階段、事件處理以及事件冒泡。React的合成事件并沒有實現(xiàn)事件捕獲,僅僅支持了事件冒泡機(jī)制。阻止事件傳播e.preventDefault();
- React合成事件的時間類型是JavaScript原生事件類型的一個子集。
組件間通信
父組件向子組件通信
父組件通過props向子組件傳遞需要的信息
import React, {Component} from 'react';
function ListItem({value}) {
return (
<li>
<span>{value}</span>
</li>
);
}
function List({list, title}) {
return(
<div>
<ListTitle title={title} />
<ul>
{this.props.list.map((entry, index) => {
<ListItem key={`list-${index}`} value={entry.text}>
})}
</ul>
</div>
);
}
子組件向父組件通信
- 利用回調(diào)函數(shù)
- 利用自定義事件機(jī)制
this.props.function()
跨級組件通信
組件新能優(yōu)化
影響網(wǎng)頁性能最大的因素是瀏覽器的重繪和重排版(回流與重繪?)。
純函數(shù)
純函數(shù)由三大原則構(gòu)成:
- 給定相同的輸入,返回相同的輸出
- 過程沒有副作用(在純函數(shù)中不能改變外部狀態(tài))
- 沒有額外的狀態(tài)依賴(方法內(nèi)的狀態(tài)都只在方法的生命周期內(nèi)存活,即不能再方法內(nèi)使用共享變量)
PureRender
Pure指的是組件滿足純函數(shù)的條件,即組件的渲染是被相同的props和state渲染進(jìn)而得到相同的結(jié)果
Immutable
key
- 如果每一個子組件是一個數(shù)組或迭代器,必須有一個唯一的key prop
- key用來做Virtual DOM dif
- 當(dāng)key相同時,只渲染第一個相同key的項,且會報一個警告
解讀React源碼
Virtual DOM實際上是在瀏覽器端用JavaScript實現(xiàn)的一套DOM API,它之于React就好似一個虛擬空間,包括一套Virtual DOM模型、生命周期的維護(hù)和管理、性能高效的diff算法和將Virtual DOM展示為原生DOM的path方法。
Virtual DOM模型
一個DOM標(biāo)簽所需的基本元素:
- 標(biāo)簽名
- 節(jié)點屬性,包含樣式、屬性、事件等
- 子節(jié)點
- 標(biāo)識id
Virtual DOM中的節(jié)點稱為ReactNode,它分為三種類型ReactElement、ReactFragment和ReactText。其中,ReactElement又分為ReactComponentElement和ReactDOMElement。
創(chuàng)建React元素
//createElement只是做了簡單的參數(shù)修正,返回一個ReactElement實例對象,也就是虛擬元素的實例
ReactElement.createElement = function(type, config, children){
}
DOM標(biāo)簽組件
ReactDOMComponent針對Virtual DOM標(biāo)簽的處理主要分為:
- 屬性的更新,包括更新樣式、更新屬性、處理事件等。
- 子節(jié)點的更新,包括更新內(nèi)容、更新子節(jié)點,涉及diff算法。
更新屬性
- 如果存在事件,則針對當(dāng)前的節(jié)點添加事件代理
- 如果存在樣式,首先會對樣式進(jìn)行合并操作,然后創(chuàng)建樣式
- 創(chuàng)建屬性
- 創(chuàng)建唯一標(biāo)識
刪除不需要的舊屬性,更新新屬性。
更新子節(jié)點
- 刪除不需要的子節(jié)點和內(nèi)容
- 更新子節(jié)點和內(nèi)容
生命周期的管理藝術(shù)
生命周期在不同狀態(tài)下的執(zhí)行順序:
- 當(dāng)首次掛載時,按順序執(zhí)行getDefaultProps、getInitialState、componentWillMount、render、componentDidMount
- 當(dāng)卸載組件時,執(zhí)行componentWillUnmount
- 當(dāng)重新掛載組件時,按順序執(zhí)行getInitialState、componentWillMount、render和componentDidMount,但并不執(zhí)行g(shù)etDefaultProps
- 當(dāng)再次渲染組件時,組件接受到更新的狀態(tài),此時按順序執(zhí)行componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate。
first render

props change

state change

自定義組件的聲明周期主要通過3個階段進(jìn)行管理:MOUNTING、RECEIVE_PROPS、UNMOUNTING
MOUNTING
mountComponent負(fù)責(zé)管理生命周期中的getInitialState、componentWillMount、render和componentDidMount。
- getDefault是通過構(gòu)造函數(shù)進(jìn)行管理的,所以也是整個生命周期中最先開始執(zhí)行的,只執(zhí)行一次。
- 此時在componentWillMount中調(diào)用setState方法不會觸發(fā)re-render,而是會進(jìn)行state合并。
- mountComponent的本質(zhì)是通過遞歸渲染內(nèi)容,由于遞歸的特性,父組件的componentWillMount在其子組件的componentWillMount之前調(diào)用,父組件的componentDidMount在子組件的componentDidMount之后調(diào)用。
RECEIVE_PROPS
updateComponent負(fù)責(zé)管理生命周期中的componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate。
- 首先通過updateComponent更新組件,如果前后元素不一致,說明需要進(jìn)行組件更新
- 此時在componentWillReceiveProps中調(diào)用setState不會觸發(fā)re-render,而是會進(jìn)行state合并
- 只有在render和componentDidUpdate中才能獲取到更新后的this.state
- updateComponent本質(zhì)上也是通過遞歸渲染內(nèi)容,父組件的componentWillUpdate是在其子組件的componentWillUpdate之前調(diào)用,父組件的componentDidUpdate子組件的componentDidUpdate之后調(diào)用
UNMOUNTING
unmountComponent負(fù)責(zé)管理生命周期中的componentWilUnmount。
- 如果存在componentWillUnmount,則執(zhí)行并重置所有相關(guān)參數(shù)、更新隊列以及更新狀態(tài)
- 此時在componentWillUnmount中調(diào)用setState不會觸發(fā)re-render,因為所有更新隊列和更新狀態(tài)都被重置為null,并清除了公共類,完成了組件卸載操作。
解密setState機(jī)制
- setState通過一個隊列機(jī)制實現(xiàn)state更新
- 當(dāng)執(zhí)行setState時,會將需要更新的state合并后放入狀態(tài)機(jī),而不會立刻更新this.state
- 如果在shouldComponentUpdate或componentWillUpdate方法中調(diào)用setState,會造成循環(huán)調(diào)用,使得瀏覽器內(nèi)存占滿后崩潰
setState調(diào)用棧
import React, {component} from 'react';
class Example extends Component {
constructor() {
super();
this.state = {
val: 0;
}
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val);
this.setState({val: this.state.val + 1});
console.log(this.state.val);
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val);
this.setState({val: this.state.val + 1});
console.log(this.state.val);
}, 0);
}
render() {
return null;
}
}
//輸出 0 0 2 3
事務(wù)
- 事務(wù)就是將需要執(zhí)行的方法使用wrapper封裝起來,再通過事務(wù)提供的perform方法執(zhí)行
diff算法
diff幫助計算出Virtual DOM中真正變化的部分,并只針對該部分進(jìn)行原生DOM操作,而非重新渲染整個頁面
詳解diff
diff策略
- Web UI中DOM節(jié)點跨層級的移動操作特別少,可以忽略不計
- 擁有相同類的兩個組件將會生成相似的樹形結(jié)構(gòu),擁有不同類的兩個組件將會生成不同的樹形結(jié)構(gòu)
- 對于同一層級的一組子節(jié)點,可以通過唯一id進(jìn)行區(qū)分
React分別對tree diff、component diff以及element diff進(jìn)行算法優(yōu)化。
tree diff
- 對樹進(jìn)行分層比較,兩棵樹只會對同一層次的節(jié)點進(jìn)行比較
- 當(dāng)發(fā)現(xiàn)節(jié)點已經(jīng)不存在時,則改節(jié)點及其子節(jié)點會被完全刪除掉
component diff
- 如果是同一類型的組件,按照原策略繼續(xù)比較Virtual DOM樹即可
- 如果不是,則將改組件判斷為dirty component,從而替換整個組件下的所有子節(jié)點
- React允許用戶通過shouldComponentUpdate()來判斷該組件是否需要進(jìn)行diff算法分析
element diff
- INSERT_MARKUP: 新的組件類型不在舊集合里,需對新節(jié)點執(zhí)行插入操作
- MOVE_EXISTING: 舊集合中有新組件類型,且element是可更新類型,需要做移動操作,可以復(fù)用以前的DOM節(jié)點
- REMOVE_NODE: 舊組件類型,在新集合里也有,但對應(yīng)的element不同則不能直接復(fù)用和更新,需要執(zhí)行刪除操作,或者舊組件不在新集合里的,也需要執(zhí)行刪除操作
節(jié)點在新集合中的索引值大于在舊集合中的索引時,需移動
刪除操作是移動完成之后遍歷舊集合,若有新集合中未出現(xiàn)的節(jié)點則刪除
React Patch方法
將tree diff計算出來的DOM差異隊列更新到真實的DOM節(jié)點上,讓瀏覽器能夠渲染出更新的數(shù)據(jù)
- 主要通過遍歷差異隊列實現(xiàn)
認(rèn)識Flux架構(gòu)模式
React獨立架構(gòu)
- 含有抽象數(shù)據(jù)而沒有業(yè)務(wù)邏輯的組件為容器型組件
- 沒有數(shù)據(jù)請求邏輯只有業(yè)務(wù)邏輯的組件為展示型組件
MV*與Flux
MVC/MVVM
主要涉及三種角色:Model、View和Controller
- Model:負(fù)責(zé)保存應(yīng)用數(shù)據(jù),和后端交互同步應(yīng)用數(shù)據(jù),或校驗數(shù)據(jù)
- View:是Model的可視化表示,表示當(dāng)前狀態(tài)的視圖。前端View負(fù)責(zé)構(gòu)建和維護(hù)DOM元素。
- Controller:負(fù)責(zé)連接View和Model,Model的任何改變會應(yīng)用到View中,View的操作會通過Controller應(yīng)用到Model中
Flux
核心思想是<mark style="box-sizing: border-box;">數(shù)據(jù)和邏輯永遠(yuǎn)單向流動</mark>。

在Flux應(yīng)用中,數(shù)據(jù)從action到dispatcher再到store,最終到view的路線是單項不可逆的
Flux基本概念
一個Flux應(yīng)用由3大部分組成:dispatcher、store和view,其中dispatcher負(fù)責(zé)分發(fā)事件;store負(fù)責(zé)保存數(shù)據(jù),同時響應(yīng)時間并更新數(shù)據(jù);view負(fù)責(zé)訂閱store中的數(shù)據(jù)。
dispatcher
dispatcher是Flux中最核心的方法,也是flux這個npm包中的核心方法。 只需關(guān)心.register(callback)和.dispatch(action)這兩個API。
- register方法用來注冊一個監(jiān)聽器
- dispatch方法用來分發(fā)一個action
action
action是一個普通的JavaScript對象,一般包含type、payload等字段,用于描述一個事件以及需要改變的相關(guān)數(shù)據(jù)。
store
- 在Flux中,store負(fù)責(zé)保存數(shù)據(jù),并定義修改數(shù)據(jù)的邏輯,同時調(diào)用dispatcher的register方法將自己注冊為一個監(jiān)聽器。
- 每次使用dispatcher的dispatch方法分發(fā)一個action時,store注冊的監(jiān)聽器會被調(diào)用,同時得到這個action作為參數(shù)。
- 在Flux中,store對外只暴露getter而不暴露setter,即只能讀取store中的數(shù)據(jù)而不能進(jìn)行任何修改。
controller-view
一般來說,controller-view是整個應(yīng)用最頂層的view,主要進(jìn)行store與React組件之間的綁定,定義數(shù)據(jù)更新以及傳遞的方式
view
如果界面操作需要修改數(shù)據(jù),必須使用dispatcher分發(fā)一個action。
actionCreator
深入Redux應(yīng)用架構(gòu)
Redux簡介
Redux三大原則
單一數(shù)據(jù)源
- 在Redux的思想里,一個應(yīng)用永遠(yuǎn)只有唯一的數(shù)據(jù)源。
- 整個應(yīng)用狀態(tài)都保存在一個對象中
狀態(tài)是只讀的
定義一個reducer,其功能是根據(jù)當(dāng)前觸發(fā)的action對當(dāng)前應(yīng)用的狀態(tài)(state)進(jìn)行迭代。沒有直接修改應(yīng)用的狀態(tài),而是返回了一份全新的狀態(tài)。
- Reducer提供的createStore方法會根據(jù)reducer生成store
- 用store.dispatch方法來修改狀態(tài)
狀態(tài)修改均由純函數(shù)完成 在Redux里,通過定義reducer來確定狀態(tài)的修改,而每一個reducer都是純函數(shù),即其沒有副作用,接受一定的輸入,必定會得到一定的輸出。
Redux核心API
Redux的核心是一個store,這個store由Redux提供的createStore(reducers[, initialState])方法生成。
- 在Redux里,負(fù)責(zé)響應(yīng)action并修改數(shù)據(jù)的角色就是reducer
- reducer在處理action的同時,還需接受一個previousState參數(shù)
- reducer的職責(zé)是根據(jù)previousState和action計算出新的newState
Redux中最核心的API是createStore,通過createStore方法創(chuàng)建的store是一個對象,包含四個方法:
- getState():獲取store當(dāng)前狀態(tài)
- dispatch(action):分發(fā)一個action,并返回這個action,這是<mark style="box-sizing: border-box;">唯一能改變store中數(shù)據(jù)的方式</mark>
- subscribe(listener):注冊一個監(jiān)聽者,在store發(fā)生變化時調(diào)用
- replaceReducer(nextReducer):更新當(dāng)前store里的reducer,一般只在開發(fā)模式中調(diào)用該方法
subscribe()和replaceReducer()方法一般會在Redux與某個系統(tǒng)做橋接的時候使用
與React綁定
react-redux提供了一個組件和一個API幫助Redux和React進(jìn)行綁定。
一個是React組件<Provider />,一個是connect()
- <Provider />接受一個store作為props,它是整個Redux應(yīng)用的頂層組件
- connect()提供了在整個React應(yīng)用的任意組件中獲取store中數(shù)據(jù)的功能
Redux middleware
middleware的由來
Redux同步數(shù)據(jù)流動

應(yīng)用middleware后Redux處理事件的邏輯

每一個middleware處理一個相對獨立的業(yè)務(wù)需求,通過串聯(lián)不同的middleware實現(xiàn)變化多樣的功能
理解middleware機(jī)制
Redux提供了applyMiddleware方法來加載middleware。
import compose from './compose';
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) {
let store = next(reducer, initialState);
let dispatch = sotre.dispatch;
let chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action),
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
}
}
函數(shù)式編程思想設(shè)計
middleware是一個層層包裹的匿名函數(shù),即函數(shù)式編程中的currying,是一種使用匿名單參數(shù)函數(shù)來實現(xiàn)多參數(shù)的方法。
currying的middleware皆有的好處:
- 易串聯(lián):currying函數(shù)具有延遲執(zhí)行的特性,通過不斷currying形成的middleware可以累積參數(shù),再配合組合(compose)的方式,很容易形成pipeline來處理數(shù)據(jù)流
- 共享store:在applyMiddleware執(zhí)行的過程中,store還是舊的,但是因為閉包的存在,applyMiddleware完成后,所有的middleware內(nèi)部拿到的store是最新且相同的。
給middleware分發(fā)store
//創(chuàng)建普通的store
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);
組合串聯(lián)middleware
dispatch = compose(...chain)(store.dispatch);
//假設(shè)n=3,dispatch為
dispatch = f1(f2(f3(store.dispatch)));
不能在middleware中調(diào)用dispatch
Redux異步流
Redux與路由
在Redux應(yīng)用中,遇到了一些新的問題,其中最迫切的是,應(yīng)用程序的所有狀態(tài)都應(yīng)該保存在一個單一的store中,而當(dāng)前的路由狀態(tài)很明顯也屬于應(yīng)用狀態(tài)的一部分。如果直接使用React Router,就意味著所有路由相關(guān)的信息脫離了Redux store的控制,這樣就違背了Redux的設(shè)計思想。
React Router
路由的基本原理
理由的基本原理即是保證View和URL同步,而View可以看成是資源的一種表現(xiàn)。

React Router特性
- 在React中,組件就是一個方法,props作為方法的參數(shù),當(dāng)它們發(fā)生變化時觸發(fā)方法執(zhí)行,重繪View
- 在React Router中,可以把Router組件看成一個方法,location作為參數(shù),返回的結(jié)果同樣是View
聲明式路由
- React是聲明式編程,所有的交互邏輯都在render返回的JSX標(biāo)簽中得到體現(xiàn)
- React Router允許使用JSX表現(xiàn)來書寫聲明式的路由
import {Router, Route, browserHistory} from 'react-router';
const routes = (
<Router history={browserHistory}>
<Route path="/" component={App} />
</Router>
);
嵌套路由及路徑匹配
import {Router, Route, IndexRoute, browserHistory} from 'react-router';
const routes = (
<Router history={browserHistory}>
<IndexRoute component={MailList} />
<Route path="/mail/:mailId" component={Mail}></Route>
</Route>
);
- 在聲明路由時,path屬性指明了當(dāng)前路由匹配的路徑形式
- 若某條路由需要參數(shù),只用加上 :參數(shù)名 即可
支持多種路由切換方式
路由切換可以使用hashChange或history.pushState。
- hashChange擁有良好的瀏覽器兼容性,但是url中多了/#/部分
- history.pushState能提供優(yōu)雅的url,但需要額外的服務(wù)端配置解決任意路徑刷新的問題
React Router提供了兩種解決方案
<mark style="box-sizing: border-box;">browserHistory即history.pushState的實現(xiàn)</mark>
React Router Redux
職責(zé)主要是將應(yīng)用的路由信息與Redux的store綁定在一起
采用Redux架構(gòu)時,所有的應(yīng)用狀態(tài)都必須放在一個單一的store中管理,路由狀態(tài)也不例外
將React Router與Redux store綁定
React Router Redux提供了簡單直白的API syncHistoryWithStore來完成與Redux store的綁定工作。只需傳入React Router中的history,以及Redux中的store,就可以獲得一個增強(qiáng)后的history對象。
import { browserHistory } from 'react-router';
import { syncHistoryWithState } from 'react-router-redux';
import reducers from '<project-path>/reducers'
const store = createStore(reducers);
const history = syncHistoryWithStore(browserHistory, store);
用Redux的方式改變路由
無論是Flux還是Redux,想要改變數(shù)據(jù),必須要分發(fā)一個action
- 在此之前,需要對Redux的store進(jìn)行一些增強(qiáng),以便分發(fā)的action能被正確識別
import { browserHistory } from 'react-router';
import { routerMiddleware} from 'react-router-redux';
const middleware = routerMiddleware(browserHistory);
const store = createStore(
reducers,
applyMiddleware(middleware)
);
- 用store.dispatch來分發(fā)一個路由變動的action
import {push} from 'react-router-redux';
store.dispatch(push('/home'));
Redux與組件
容器型組件
容器型組件,意為組件是怎么工作的,具體一些就是數(shù)據(jù)是怎么更新的。不包含任何Virtual DOM的修改或組合,也不會包含組件的樣式。
- 如果映射到Flux上,容器型組件就是與store綁定的組件
- 如果映射到Redux上,容器型組件就是使用connect的組件
展示型組件
展示型組件,意為組件是怎么渲染的。包含Virtual DOM的修改和組合,也可能包含組件的樣式。
Redux中的組件
Layouts
- 指的是頁面布局組件,描述了頁面的基本結(jié)構(gòu),目的是將主框架與頁面主題內(nèi)容分離
- 常常是無狀態(tài)函數(shù),傳入主題內(nèi)容的children屬性
//一般寫法為
const layout = ({ children }) => (
<div className='container'>
<Header />
<div className='content'>
{ children }
</div>
</div>
);
views
- 指的是子路由入口組件,描述子路由入口的基本結(jié)構(gòu),包含由此路由下所有的展示型組件
- 為了保持子組件的純凈,在這一層組件中定義了數(shù)據(jù)和action的入口,從這里開始將它們分發(fā)到子組件中去
Components
- 末級渲染組件,描述了從路由以下的子組件
- 包含具體的業(yè)務(wù)邏輯和交互
- 所有的數(shù)據(jù)和action都是由Views傳下來的,即其是可以完全脫離數(shù)據(jù)層而存在的展示型組件