Web框架概述——React.js

  目前,在前端Web開發中,三大熱門框架爲React.jsVue.jsAngular.js 。固然,三大框架各有各的優缺點,這裏就很少說了,下面我就針對前段時間所學的React框架作一下總體知識點的概述。html

 

React是什麼?前端

   咱們在用js腳本操做DOM元素時若是存在大量的dom元素操做時就很消耗瀏覽器性能。有個貼切的比喻:把 DOMJavascript各自想象成一個島嶼,它們之間用一個橋樑鏈接,每過一次橋就須要交納 」過橋費「,當 js 頻繁操做 DOM 時,」過橋費「 就越高昂,有沒有一種途徑來減小費用呢?推薦方法就是儘可能少過橋(即非必須狀況下),都呆在各自的島嶼上。因此 React 就創造了一個叫 虛擬DOM 的方法,它創造了虛擬dom並把它們儲存起來,每當狀態變化的時候就會創造新的虛擬節點和之前的進行對比,讓變化的部分進行渲染。整個過程沒有對dom進行獲取和操做,只有一個渲染過程。因此說 React 是一種 ui框架react

 

React的組件化webpack

   React 的一個組件很明顯的由 dom視圖state數據組成,兩個部分涇渭分明。state是數據中心,它的狀態決定着視圖的狀態。這一點和 MVC 開發模式有點相似,沒了controller 控制器,那用戶交互怎麼處理,數據變化誰來管理?這些並不都是 React 要關心的事情,它只負責 ui 的渲染。與其餘框架監聽數據動態變化改變dom不一樣,react採用 setState 來控制視圖的更新。setState會自動調用render函數,觸發視圖的從新渲染,若是僅僅只是state數據的變化而沒有調用setState,並不會觸發更新。組件就是擁有獨立能力的視圖模塊,許多小的組件組成一個大的組件(組件模塊化),整個頁面就是由一個個組件嵌套結合而成,使用組件化可以提升代碼的可複用性和維護性(性能優化)。es6

 

React中的Diff算法web

    React 的 diff 算法用在了什麼地方呢?當組件更新的時候,react會建立一個新的虛擬dom樹 而且會和以前存儲的 dom樹進比較,這個比較的過程就用到了 diff 算法,因此組件初始化的時候是用不到的。react提出了一種假設,相同的節點具備相似的結構,而不一樣的節點具備不一樣的結構。在這種假設上進行逐層的比較,若是發現對應的節點是不一樣的,那就直接刪除舊的節點以及它所包含的全部子節點而後替換成新的節點。若是是相同的節點,則只是進行屬性的更改。算法

 

對於列表的 diff算法稍有不一樣,由於列表同程具備相同的結構,在對列表節點進行刪除、插入、排序的時候,單個節點的總體操做遠比一個個對比一個個替換要好不少,因此在建立列表的時候須要 設置key值,這樣 react 才能分清楚具體哪個節點。固然不寫 key值也沒有錯誤(會有警告提示),會提示加上key值以提升 react 的性能。redux

diff算法圖示:瀏覽器

 

 

React中的組件性能優化

  組件的創造方法爲 React.createClss() —創造一個類,react內部系統設計了一套類系統,利用它來創造react組件。但這並非必須的,咱們還可使用 es6 的 class 類來新建組件(react框架中推薦用法)

1 class Index extends React.Component{
2     constructor(props){
3         super(props)
4         this.state = {            // 初始狀態
5             productList:[]        // 商品列表
6             params:'',            // 傳入的參數
7         }
8     }
9 }

  這種 es6 的 class 類能夠當作是構造函數的一個語法糖,能夠把它當成構造函數來看,extends 實現了類之間的繼承 — 定義一個類 Index 繼承 React.Component 全部的屬性和方法,組件的生命週期函數就是從這來的。constructor 是構造器,在實例化對象時調用,super 調用了父類的 constructor 創造了父類的實例對象 this,而後用子類的構造函數進行修改。這和 es5 的原型繼承是不一樣的,原型繼承是先創造一個實例對象 this,而後再繼承父級的原型方法。瞭解了這些以後咱們在看組件的時候就清楚不少。

  當咱們再使用 <Index /> 組件時,實際上是對 Index 類的實例化 — new Index,只不過 react 對這個過程進行了封裝,讓它看起來更像是一個標籤。

  :1.定義的類名的首字母必須大寫;2.由於 class 變成了關鍵字,因此在添加類名的時候不能用 class,須要使用 className 代替;3.類和模塊內部默認使用 嚴格模式,因此不須要使用 use strict 指定運行模式。

 

組件的生命週期函數

  在 react 中常常會使用到生命週期函數,下面就列舉了一些可能會用到的生命週期函數:

組件在初始化時會觸發的5個鉤子函數:

  1. getDefaultProps()

    設置默認的 props,也能夠用 defaultProps 設置組件的默認屬性

  2. getInitalState()

    在使用 es6 的 class 語法時是沒有這個鉤子函數的,能夠直接在 constructor 中定義 this.state。此時能夠訪問 this.props

  3. componentWillMount()

    組件初始化時調用,之後組件更新不調用,整個生命週期只調用一次,此時能夠修改 state

  4. render()

    react 中最重要的步驟,建立虛擬dom,進行diff算法,更新(渲染)dom樹都在該函數中進行,此時就不能更改 state了

  5. componentDidMount()

    組件渲染以後調用,能夠經過 this.getDOMNode()獲取和操做 dom 節點,只調用一次

在更新時也會觸發的5個鉤子函數:

  1. componentWillReceiveProps(nextProps)

    組件初始化時不調用,在接收新的 props 時調用

  2. shouldComponentUpdate(nextProps,nextState)

    react 性能優化很是重要的一環,在組件接收新的 state 或者 props 時調用,咱們能夠設置在此對比先後兩個 props 和 state 是否相同,若是相同則返回 false 阻止更新,由於相同的屬性狀態必定會生成相同的 dom樹,這樣就不須要創造新的dom樹和舊的dom樹進行 diff算法對比,從而節省大量性能,尤爲是在 dom結構複雜的時候。(該函數存在返回值)

  3. componentWillUpdate(nextProps,nextState)

    組件初始化時不調用,只有在組件將要更新時才調用,此時能夠修改 state

  4. render()

    和第一階段 render 函數相同

  5. componentDidUpdate()

    組件初始化時不調用,組件更新完成後調用,此時能夠獲取 dom節點

在組件卸載時會觸發的1個鉤子函數:

  1. componentWillUnmount()

    組件將要被卸載時調用,一些事件監聽和定時器須要在此清除

  以上能夠看出來 react 在整個生命週期中存在 10個周期函數(render重複一次),這10個函數能夠知足咱們對全部組件操做的需求,利用的好能夠提升開發效率和組件性能

 

React-Router路由

  Router 就是 React 的一個組件,它並不會渲染,只是一個建立內部路由規則的配置對象,根據匹配的路由地址展示相應的組件。Route 則對路由地址和組件進行綁定,Route 具備嵌套功能,表示路由地址的包含關係(多級路由),這和組件之間的嵌套並無直接聯繫。Route能夠綁定的組件傳遞七個屬性:children,history,location,params,route,routeParams,routes,每一個屬性都包含路由的相關信息。比較經常使用的有children(以路由的包含關係來區分的組件),location(包括地址,參數,地址切換方式,key值,hash值)。react-router 提供 link 標籤,這只是對 a 標籤的封裝,注:點擊連接跳轉的並非默認的方式,react-router阻止了a標籤的默認行爲並用pushState進行hash值的轉變。切換頁面的過程是在點擊Link標籤或者後退前進按鈕時,會先發生url地址的轉變,Route監聽到地址的改變根據Route的path屬性匹配到對應的組件,將state值改爲對應的組件並調用setState觸發render函數從新渲染dom。

 

  當頁面比較多時,項目就會變得愈來愈大,尤爲是對於單頁面應用來講,初次渲染的速度就會很慢(須要加載大量資源),這時候就須要按需加載,只有切換頁面的時候纔去加載對應的js文件。react配合webpack(react-cli)進行按需加載的方法很簡單,Route的Component改成getComponent,組件用require.ensure的方式獲取,並在webpack中配置chunkFilename。

 1 const chooseProducts = ( loaction,cb )=>{
 2     require.ensure( [],require =>{
 3         cb(null,require('../Component/chooseProducts').default)
 4     },'chooseProducts' )
 5 }
 6 const helpCenter = ( loaction,cb )=>{
 7     require.ensure( [],require =>{
 8         cb(null,require('../Component/helpCenter').default)
 9     },'helpCenter' )
10 }
11 const saleRecord = ( loaction,cb )=>{
12     require.ensure( [],require =>{
13         cb(null,require('../Component/saleRecord').default)
14     },'saleRecord' )
15 }
16 const RouteConfig = {
17     <Router history = {history}>
18         <Route path = '/' component = {Roots}>
19             <Route path = 'index' component = {index} />
20             <Route path = 'helpCenter' component = {helpCenter} />
21             <Route path ='saleRecord' component = {saleRecord} />
22             <Redirect from ='*' to = '/' />        // 路由重定向
23         </Route>
24     </Router>
25 }

 

組件間的通訊

  react 推崇的是單向數據流,自上而下進行數據的傳遞,可是由下而上或者不在一條數據流上的組件之間的通訊就會變的很複雜。解決通訊問題的方法不少,若是隻是父子級關係,父級能夠將一個須要傳遞的值當作一個屬性傳遞給子級,子級經過this.props獲取父級傳來的值。

 

組件層級嵌套到比較深,可使用上下文getChildContext來傳遞信息,這樣不須要將函數一層一層往下傳,任何一層均可以經過this.context直接訪問。

 

兄弟關係的組件之間沒法直接通訊,它們只能利用同一層的上級(父級)做爲中間件進行傳遞。而若是兄弟組件都是最高層的組件,爲了可以讓它們通訊,必須在它們外層再嵌套一層組件,這個外層的組件起着保存數據,傳遞信息的做用,這其實就是redux所作的事情。

 

  組件之間的信息還能夠經過全局事件來傳遞。不一樣頁面能夠經過參數傳遞數據,下個頁面能夠用location.param來獲取。這就是react中的核心知識點

 

 

Redux 

  首先,聲明一下,redux並非項目開發所必須的,它的做用至關於在頂層組件之上又加了一個組件,做用就是進行邏輯運算、儲存數據和實現組件尤爲是頂層組件的通訊。若是組件之間的通訊很少,邏輯不復雜,只是單純的進行視圖渲染,這時候用回調,context就足夠了,不必使用redux,用了反而影響開發效率。可是若是組件通訊特別多,邏輯也很複雜,那使用redux就方便多了。

  先來講一下react和redux是怎麼配合的。react-redux提供了connectProvider兩個方法,它們一個將組件與redux關聯起來,一個將store傳給組件。組件經過dispatch發出 action,store根據 action 的type屬性調用對應的reducer並傳入 state 和這個 action,reducer對 state 進行處理並返回一個新的 state 放入 store,connect監聽到 store 發生變化,調用 setState 更新組件,此時組件的props也就跟着變化。

  流程圖:

  注:connect,Provider,mapStateToProps,mapDispatchToProps是 react-redux提供的,redux自己和react沒有關係,它只是數據處理中心,沒有和react產生任何耦合,是react-redux讓它們聯繫在一塊兒的。

  再具體分析 react 和 react-redux 的實現過程:

先具體介紹Redux

redux主要由三部分組成:storereduceraction

store是一個對象,它有四個主要的方法:

  1. dispatch

      用於action的分支—在createStore中能夠用 middleware 中間件對 dispatch 進行改造,好比當 action 傳入 dispatch會當即觸發 reducer,有些時候咱們不但願它當即觸發,而是等異步操做完成以後再觸發,這時候用 redux-thunk 對 dispatch進行改造,之前只能傳入一個對象,改造完成以後能夠傳入一個函數,在這個函數裏能夠手動 dispatch 一個 action 對象,這個過程是可控的,就實現了異步。

  2. subscribe

      監聽 state 的變化—這個函數在store調用dispatch時會註冊一個listener監聽state變化,當咱們須要知道state是否變化時能夠調用,它返回一個函數,調用這個返回的函數能夠註銷監聽。

     let unsubsrible = store.subscrible(()=>{
         console.log('state發生了變化')
     })
  3. getState

      獲取store中的state—當咱們用action觸發reducer改變了state時,須要再拿到新的state數據,畢竟數據纔是咱們想要的。getState主要在兩個地方須要用到,一是在dispatch拿到action後store須要用它來獲取state裏的數據,並把這個數據傳給reducer,這個過程是自動執行的,二是在咱們利用subscribe監聽state發生變化後調用它來獲取新的state數據,作到這一步,說明咱們已經成功了。

  4. replaceReducer

      替換reducer,改變state修改的邏輯。

  store能夠經過createStore()方法建立,接收三個參數,通過combineReducers合併的reducer和state的初始狀態一級改變dispatch的中間件,後兩個參數並非必須的。store的主要做用是將action和reducer聯繫起來並改變state。

action

  action是一個對象,其中type屬性是必須的,同時能夠傳入一些數據。action能夠用actionCreator進行創造。dispatch就是把action對象發送出去。

reducer

  reducer是一個函數,它接收一個state和一個action,根據action的type返回一個新的state。根據業務邏輯能夠分爲不少個reducer,而後經過combineReducers將它們合併,state樹中有不少對象,每隔state對象對應一個reducer,state對象的名字能夠在合併時定義。以下:

1 const reducer = combineReducers({
2      a:doSomethingWithA,
3      b:processB,
4      c:c
5  })

combineReducers

  其實它也是一個reducer,它接收整個state和一個action,而後將整個state拆分發送給對應的reducer進行處理,全部的reducer會接收到相同的action,不過它們會根據action的type進行判斷,有這type就進行處理而後返回新的state,沒有就返回默認值,而後這些分散的state又會整合在一塊兒返回一個新的state樹。

  總結一下總體的流程:首先調用store.dispatch將action做爲參數傳入,同時使用getState獲取當前的狀態樹state並註冊subscribe的listener監聽state的變化,再調用combineReducers並將獲取的state和action傳入。combineReducers會將傳入的state和action傳給全部reducer,並根據action的type返回新的state,觸發state樹的更新,咱們調用subscribe監聽到state發生變化後用getState獲取新的state數據。

  注:redux的state 和 react的state徹底沒有關係,名字同樣而已。

再介紹React-Redux

  若是隻使用redux,那麼流程是這樣:

    component --> dispatch(action) --> reducer --> subscrilbe --> getState --> component

  用了reac-redux以後的流程爲:

    component --> actionCreator(data) --> reducer --> component

  store的三大功能:dispatch,subscribe和getState都不須要手動來寫了,react-redux幫咱們作了這些,同時它提供了兩個方法:Provider和connect。

  Provider是一個組件,它接收store做爲props,而後經過context往下傳,這樣react中任何組件均可以經過context獲取store。也就意味着咱們能夠在任何一個組件裏利用dispatch(action)來觸發reducr改變state,並用subscribe監聽state的變化,而後用getState獲取變化後的值。可是並不推薦這樣作,它會讓數據流變的婚戀,過分的耦合也會影響組件的複用,維護起來也更麻煩。

  connect是一個函數,connect(mapStateToProps,mapDispatchToProps,mergeProps,options)接收四個參數而且再返回一個函數wrapWithConnect(component),該函數接收一個組件做爲參數,其內部定義一個新組件Connect(容器組件)並將傳入的組件(ui組件)做爲Connect的子組件而後return出去。

  完整寫法:connect(mapStateToProps,mapDispatchToProps,mergeProps,options)(component)

mapStateToProps(state,[ownProps])

  mapStateToProps接收兩個參數,store的state和自定義的props,並返回一個對象,這個對象會做爲props的一部分傳入ui組件。咱們能夠根據組件所須要的數據自定義返回一個對象。ownProps的變化也會觸發mapStateToProps

1 function mapStateToProps(state){
2      return { todos:state.todos }
3  }

mapDispatchToProps(dispatch,[ownProps])

  mapDispatchToProps若是是對象,那麼會和store綁定做爲props的一部分傳入ui組件。若是是個函數,它接收兩個參數,bindActionCreators會將action和dispatch綁定並返回一個對象,這個對象會和ownProps的一部分傳入ui組件。全部不管是對象仍是函數,它最終都會返回一個對象,若是是函數,這個對象的key值時能夠自定義的

1 function mapDispatchToProps(dispatch){
2      return {
3          todoActions: bindActionCreators(todoActionCreators,dispatch),
4          counterActions: bindActionCreators(counterActionCreators,dispatch)
5      };
6  }

  mapDispatchToProps返回的對象其屬性其實就是一個個actionCreator,由於已經和dispatch綁定,因此當調用actionCreator時會當即發送action,而不一樣手動dispatch。ownProps的變化也會觸發mapDispatchToProps。

mergeProps(stateProps,dispatchProps,ownProps)

  將mapStateToProps() 與 mapDispatchToProps() 返回的對象和組件自身的props合併成心的props並傳入組件。默認返回 Object.assign( {},ownProps,stateProps,dispatchProps )的結果

options

  pure = true 表示 Connect容器組件將在shouldComponentUpdate中對store的state和ownProps進行淺對比,判斷是否發生變化,優化性能。爲false則不對比。

  其實connect函數並無作什麼,大部分的邏輯都是在它返回的wrapWithConnect函數內實現的,確切的說是在wrapWithConnect內定義的Connect組件裏實現的。

 

完整的React-Redux-React流程  

 

複雜版

 

  1. Provider組件接受redux的store做爲props,而後經過context往下傳

  2. connect函數在初始化的時候會將mapDispatchToProps對象綁定到store,若是mapDispatchToProps是函數則在Connect組件得到store後,根據傳入的store.dispatch和action經過bindActionCreators進行綁定,再將返回的對象綁定到store,connect函數會返回一個wrapWithConnect函數,同時wrapWithConnect會被調用且傳入一個ui組件,wrapWithConnect內部使用class Connect extends Component定義了一個Connect組件,傳入的ui組件就是Connect的子組件,而後Connect組件會經過context得到store,並經過store.getState得到完整的state對象,將state傳入mapStateToProps返回stateProps對象、mapDispatchToProps對象或mapDispatchToProps函數會返回一個dispatchProps對象,stateProps、dispatchProps以及Connect組件的props三者經過Object.assign(),或者mergeProps合併爲props傳入ui組件。而後在ComponentDidMount中調用store.subscribe,註冊了一個回調函數handleChange監聽state的變化;

  3. 此時ui組件就能夠在props中找到actionCreator,當咱們調用actionCreator時會自動調用dispatch,在dispatch中會調用getState獲取整個state,同時註冊一個listener監聽state的變化,store將得到的state和action傳給combineReducers,combineReducers會將state依據state的key值分別傳給子reducer,並將action傳給所有子reducer,reducer會被依次執行進行action.type的判斷,若是有則返回一個新的state,若是沒有則返回默認。combineReducers再次將子reducer返回的單個state進行合併成一個新的完整的state。此時state發生了變化。dispatch在state返回新的值以後會調用全部註冊的listener函數其中包括handleChange函數,handleChange函數內部首先調用getState獲取新的state值並對新舊兩個state進行淺對比,若是相同直接return,若是不一樣則調用mapStateToProps獲取stateProps並將新舊兩個stateProps進行淺對比,若是相同,直接return結束,不進行後續操做。若是不相同則調用this.setState()觸發Connect組件的更新,傳入ui組件,觸發ui組件的更新,此時ui組件得到新的props,react --> redux --> react 的一次流程結束。

 

簡化版

 

  1. Provider組件接受redux的store做爲props,而後經過context往下傳

  2. connect函數收到Provider傳出的store,而後接受三個參數mapStateToProps,mapDispatchToProps和組件,並將state和actionCreator以props傳入組件,這時組件就能夠調用actionCreator函數來觸發reducer函數返回新的state,connect監聽到state變化調用setState更新組件並將新的state傳入組件

1 connect(state => state,action)(Component)

 

  以上就是我對整個React框架的知識體系作出的簡單整理,但願對你們有所幫助(Tips:有錯誤望指出,謝謝!!!)

相關文章
相關標籤/搜索