本篇文章主要的介紹了關於react的整體流程介紹,以下就讓我們一起來看這篇文章吧
# react的一個元件很明顯的由dom視圖和state資料組成,兩個部分涇渭分明。 state是資料中心,它的狀態決定視圖的狀態。這時候發現似乎跟我們一直推崇的MVC開發模式有點差別,沒了Controller控制器,那用戶互動怎麼處理,資料變化誰來管理?然而這並不是react所要關心的事情,它只負責ui的渲染。與其他框架監聽資料動態改變dom不同,react採用setState來控制視圖的更新。 setState會自動呼叫render函數,觸發視圖的重新渲染,如果只是state資料的變化而沒有呼叫setState,並不會觸發更新。 組件就是擁有獨立功能的視圖模組,許多小的元件組成一個大的元件,整個頁面就是由一個個元件組合而成。它的好處是利於重複利用和維護。
react的diff演算法用在什麼地方呢?當元件更新的時候,react會建立一個新的虛擬dom樹並且會和之前儲存的dom樹進行比較,這個比較多過程就用到了diff演算法,所以元件初始化的時候是用不到的。 react提出了一種假設,相同的組件具有類似的結構,而不同的組件具有不同的結構。在這種假設之上進行逐層的比較,如果發現對應的節點是不同的,那就直接刪除舊的節點以及它所包含的所有子節點然後替換成新的節點。如果是相同的節點,則只進行屬性的變更。
對於清單的diff演算法稍有不同,因為列表通常具有相同的結構,在對列表節點進行刪除,插入,排序的時候,單一節點的整體操作遠比一個個對比一個個替換要好得多,所以在創建列表的時候需要設定key值,這樣react才能分辨誰是誰。當然不寫key值也可以,但這樣通常會報出警告,通知我們加上key值以提高react的效能。
這兩種寫法實現的功能一樣但是原理卻是不同,es6的類類可以看作是建構函式的語法糖,可以把它當成建構子來看,extends實作了類別之間的繼承- 定義一個類別Main 繼承React.Component所有的屬性和方法,元件的生命週期函式就是從這來的。 constructor是建構器,在實例化物件時調用,super調用了父類別的constructor創造了父類別的實例物件this,然後用子類別的建構子進行修改。這和es5的原型繼承是不同的,原型繼承是先創造一個實例化物件this,然後再繼承父級的原型方法。了解了這些之後我們在看組件的時候就清楚很多。
當我們使用元件時,其實是對Main類別的實例化-new Main,只不過react對這個過程進行了封裝,讓它看起來更像是標籤。 (想看更多就到PHP中文網React參考手冊欄位中學習)
有三點值得注意:1、定義類別名字的首字母必須大寫2、因為class變成了關鍵字,類別選擇器需要用className來取代。 3、類別和模組內部預設使用嚴格模式,所以不需要用use strict指定運作模式。
元件在初始化時會觸發5個鉤子函數:
1、getDefaultProps()
設定預設的props,也可以用dufaultProps設定組件的預設屬性。
2、getInitialState()
#在使用es6的class語法時是沒有這個鉤子函數的,可以直接在constructor中定義this .state。此時可以存取this.props。
3、componentWillMount()
#元件初始化時只調用,以後元件更新不調用,整個生命週期只調用一次,此時可以修改state。
4、 render()
react最重要的步驟,建立虛擬dom,進行diff演算法,更新dom樹都在此進行。此時就不能更改state了。
5、componentDidMount()
元件渲染之後調用,可以透過this.getDOMNode()取得和操作dom節點,只調用一次。
在更新時也會觸發5個鉤子函數:
#6、componentWillReceivePorps(nextProps)
元件初始化時不調用,元件接受新的props時調用。
7、shouldComponentUpdate(nextProps, nextState)
react效能最佳化非常重要的一環。元件接受新的state或props時調用,我們可以設定在此對比前後兩個props和state是否相同,如果相同則返回false阻止更新,因為相同的屬性狀態一定會產生相同的dom樹,這樣就不需要創造新的dom樹和舊的dom樹進行diff演算法對比,節省大量效能,尤其是在dom結構複雜的時候。不過呼叫this.forceUpdate會跳過此步驟。
8、componentWillUpdata(nextProps, nextState)
元件初始化時不調用,只有在元件將要更新時才調用,此時可以修改state
9、render()
#不說
10、componentDidUpdate( )
元件初始化時不調用,元件更新完成後調用,此時可以取得dom節點。
還有一個卸載鉤子函數
11、componentWillUnmount()
元件會卸載時調用,一些事件監聽和定時器需要在此時清除。
以上可以看出來react總共有10個週期函數(render重複一次),這個10個函數可以滿足我們所有對元件操作的需求,利用的好可以提高開發效率和元件效能。
Router和Route就是React的一個元件,它不會被渲染,只是一個創造內部路由規則的設定對象,根據匹配的路由位址展現對應的組件。 Route則對路由位址和元件進行綁定,Route具有巢狀功能,表示路由位址的包涵關係,這和元件之間的巢狀並沒有直接聯繫。 Route可以傳遞7個屬性給綁定的元件:children,history,location,params,route,routeParams,routes,每個屬性都包涵路由的相關的資訊。比較常用的有children(以路由的包涵關係為區分的元件),location(包含位址,參數,位址切換方式,key值,hash值)。 react-router提供Link標籤,這只是a標籤的封裝,值得注意的是,點擊連結進行的跳轉並不是預設的方式,react-router阻止了a標籤的預設行為並用pushState進行hash值的轉變。切換頁面的過程是在點擊Link標籤或後退前進按鈕時,會先發生url位址的轉變,Router監聽到位址的改變根據Route的path屬性匹配到對應的元件,將state值改成對應的元件並調用setState觸發render函數重新渲染dom。
當頁面比較多時,專案就會變得越來越大,尤其對於單頁應用程式來說,初次渲染的速度就會很慢,這時候就需要按需加載,只有切換到頁面的時候才去載入對應的js檔。 react配合webpack進行按需載入的方法很簡單,Route的component改為getComponent,元件用require.ensure的方式獲取,並在webpack中配置chunkFilename。
const chooseProducts = (location, cb) => { require.ensure([], require => { cb(null, require('../Component/chooseProducts').default) },'chooseProducts') } const helpCenter = (location, cb) => { require.ensure([], require => { cb(null, require('../Component/helpCenter').default) },'helpCenter') } const saleRecord = (location, cb) => { require.ensure([], require => { cb(null, require('../Component/saleRecord').default) },'saleRecord') } const RouteConfig = ( <router> <route> <indexroute></indexroute>//首页 <route></route> <route></route>//帮助中心 <route></route>//销售记录 <redirect></redirect> </route> </router> );
react推崇的是單向資料流,自上而下進行資料的傳遞,但是由下而上或不在一條資料流上的元件之間的通訊就會變的複雜。解決通訊問題的方法很多,如果只是父子級關係,父級可以將一個回呼函數當作屬性傳遞給子級,子級可以直接呼叫函數從而和父級通訊。
元件層級嵌套到比較深,可以使用上下文Context來傳遞訊息,這樣在不需要將函數一層往下傳,任何一層的子級都可以透過this.context直接存取。
兄弟關係的元件之間無法直接通信,它們只能利用同一層的上級作為中轉站。而如果兄弟組件都是最高層的組件,為了能夠讓它們進行通信,必須在它們外層再套一層組件,這個外層的組件起著保存數據,傳遞信息的作用,這其實就是redux所做的事情。
元件之間的資訊也可以透過全域事件來傳遞。不同頁面可以透過參數傳遞數據,下個頁面可以用location.query來取得。
redux主要由三個部分組成:store,reducer,action。
store是一個對象,它有四個主要的方法:
1、dispatch:
#用于action的分发——在createStore中可以用middleware中间件对dispatch进行改造,比如当action传入dispatch会立即触发reducer,有些时候我们不希望它立即触发,而是等待异步操作完成之后再触发,这时候用redux-thunk对dispatch进行改造,以前只能传入一个对象,改造完成后可以传入一个函数,在这个函数里我们手动dispatch一个action对象,这个过程是可控的,就实现了异步。
2、subscribe:
监听state的变化——这个函数在store调用dispatch时会注册一个listener监听state变化,当我们需要知道state是否变化时可以调用,它返回一个函数,调用这个返回的函数可以注销监听。
let unsubscribe = store.subscribe(() => {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是一个对象,其中type属性是必须的,同时可以传入一些数据。action可以用actionCreactor进行创造。dispatch就是把action对象发送出去。
reducer是一个函数,它接受一个state和一个action,根据action的type返回一个新的state。根据业务逻辑可以分为很多个reducer,然后通过combineReducers将它们合并,state树中有很多对象,每个state对象对应一个reducer,state对象的名字可以在合并时定义。
像这个样子:
const reducer = combineReducers({ a: doSomethingWithA, b: processB, c: c })
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,reducer会根据state的key值获取与自己对应的state,并根据action的type返回新的state,触发state树的更新,我们调用subscribe监听到state发生变化后用getState获取新的state数据。
redux的state和react的state两者完全没有关系,除了名字一样。
上面分析了redux的主要功能,那么react-redux到底做了什么?
如果只使用redux,那么流程是这样的:
component --> dispatch(action) --> reducer --> subscribe --> getState --> component
用了react-redux之后流程是这样的:
component --> actionCreator(data) --> reducer --> component
store的三大功能:dispatch,subscribe,getState都不需要手动来写了。react-redux帮我们做了这些,同时它提供了两个好基友Provider和connect。
Provider是一个组件,它接受store作为props,然后通过context往下传,这样react中任何组件都可以通过contex获取store。也就意味着我们可以在任何一个组件里利用dispatch(action)来触发reducer改变state,并用subscribe监听state的变化,然后用getState获取变化后的值。但是并不推荐这样做,它会让数据流变的混乱,过度的耦合也会影响组件的复用,维护起来也更麻烦。
connect --connect(mapStateToProps, mapDispatchToProps, mergeProps, options)是一个函数,它接受四个参数并且再返回一个函数--wrapWithConnect,wrapWithConnect接受一个组件作为参数wrapWithConnect(component),它内部定义一个新组件Connect(容器组件)并将传入的组件(ui组件)作为Connect的子组件然后return出去。
所以它的完整写法是这样的:connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(component)
mapStateToProps(state, [ownProps]):
mapStateToProps 接受两个参数,store的state和自定义的props,并返回一个新的对象,这个对象会作为props的一部分传入ui组件。我们可以根据组件所需要的数据自定义返回一个对象。ownProps的变化也会触发mapStateToProps
function mapStateToProps(state) { return { todos: state.todos }; }
mapDispatchToProps(dispatch, [ownProps]):
mapDispatchToProps如果是对象,那么会和store绑定作为props的一部分传入ui组件。如果是个函数,它接受两个参数,bindActionCreators会将action和dispatch绑定并返回一个对象,这个对象会和ownProps一起作为props的一部分传入ui组件。所以不论mapDispatchToProps是对象还是函数,它最终都会返回一个对象,如果是函数,这个对象的key值是可以自定义的
function mapDispatchToProps(dispatch) { return { todoActions: bindActionCreators(todoActionCreators, dispatch), counterActions: bindActionCreators(counterActionCreators, dispatch) }; }
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元件裡實現的。
一、Provider元件接受redux的store當props,然後透過context往下傳。
二、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的變化。
三、此時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發生了變化。 Connect元件中呼叫的subscribe會監聽到state發生了變化,然後呼叫handleChange函數,handleChange函數內部首先呼叫getState取得新的state值並對新舊兩個state進行淺對比,如果相同直接return,如果不同則呼叫mapStateToProps取得stateProps並將新舊兩個stateProps進行淺對比,如果相同,直接return結束,不進行後續操作。如果不相同則呼叫this.setState()觸發Connect組件的更新,傳入ui組件,觸發ui組件的更新,此時ui組件獲得新的props,react --> redux --> react 的一次流程結束。
上面的有點複雜,簡化版的流程是:
一、Provider元件接受redux的store當props,然後透過context往下傳。
二、connect函數收到Provider傳出的store,然後接受三個參數mapStateToProps,mapDispatchToProps和元件,並將state和actionCreator以props傳入元件,這時元件就可以呼叫actionCreator函數來觸發reducer函數傳回新的state,connect監聽到state變更呼叫setState更新元件並將新的state傳入元件。
connect可以寫的非常簡潔,mapStateToProps,mapDispatchToProps只不過是傳入的回呼函數,connect函數在必要的時候會呼叫它們,名字不是固定的,甚至可以不寫名字。
簡化版本:connect(state => state, action)(Component);
上面說了react,react-router和redux的知識點。但是怎麼樣將它們整合起來,搭建一個完整的項目。
1、先引用 react.js,redux,react-router 等基本文件,建議用npm安裝,直接在文件中引用。
2、從 react.js,redux,react-router 中引入所需的物件和方法。
import React, {Component, PropTypes} from 'react';
import ReactDOM, {render} from 'react-dom';
import {Provider, connect} from 'react-redux' ;
import {createStore, combineReducers, applyMiddleware} from 'redux';
import { Router, Route, Redirect, IndexRoute, browserHistory, hashHistory } from 'react-router';
this.setState()會呼叫render方法,但不會立即改變state的值,state是在render方法中賦值的。所以執行this.setState()後立即取得state的值是不變的。同樣的直接賦值state不會出發更新,因為沒有呼叫render函數。
componentWillMount,componentDidMount 只有在初始化的時候才會呼叫。
componentWillReceivePorps,shouldComponentUpdate,componentWillUpdata,componentDidUpdate 只有元件在更新的時候才被調用,初始化時不調用。
因為在connect函數中會對新舊兩個state進行淺對比,如果state只是值改變但是引用位址沒有改變,connect會認為它們相同而不觸發更新。
require.ensure([], require => { cb(null, require('../Component/saleRecord').default) },'saleRecord')
8、react的路由有hashHistory和browserHistory,hashHistory由hash#控制跳转,一般用于正式线上部署,browserHistory就是普通的地址跳转,一般用于开发阶段。
9、标签里用到的,for 要写成htmlFor,因为for已经成了关键字。
10、componentWillUpdate中可以直接改变state的值,而不能用setState。
11、如果使用es6class类继承react的component组件,constructor中必须调用super,因为子类需要用super继承component的this,否则实例化的时候会报错。
本篇文章到这就结束了(想看更多就到PHP中文网React使用手册栏目中学习),有问题的可以在下方留言提问。
以上是React整體流程是什麼? react整體流程介紹(附實例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!