github源碼:https://github.com/mocheng/react-and-reduxjavascript
import PropTypes from 'prop-types';css
Counter.propTypes = { caption: PropTypes.string.isRequired, initValue: PropTypes.number }
prop Types 雖然可以在開發階段發現代碼中的問題,可是放在產品環境中就不大合
適,現有的 babel-react-optimize 就具備這個功能,能夠經過 npm 安裝,但
是應該確保只在發佈產品代碼時使用它。html
props默認值 React的 defaultProps 功能,讓代碼更加容易讀懂
Counter 組件添加 defaultProps 的代碼以下:vue
Counter .defaultProps = { initValue: 0 }
1 初始化 state ,由於組件生命週期中任何函數均可能要訪問 state ,那麼整個生命
週期中第一個被調用的構造函數天然是初始化 state 最理想的地方;
2 綁定成員函數的 this 環境java
ES5的 React. createClass 方法創造的組件類纔會發生做用,已經被 Facebook 官方逐漸廢棄react
render 函數應該是一個純函數,徹底根據 this.state this.props 來決定返
回的結果,並且不要產生任何反作用。在 render 函數中去調用 this.setState 毫無疑問是錯
誤的,由於一個純函數不該該引發狀態的改變ios
1 在裝載過程當中, componentWil!Mount 會在調用 render 函數以前被調用, componentDidMount
會在調用 render 函數以後被調用,這兩個函數就像是 render 函數的前哨和後
衛,一前一後,把 render 函數夾住,正好分別作 render 先後必要的工做git
2 componentWillMount 都是緊貼着本身組件的 render 函數之
前被調用, componentDidMount 可不是緊跟着 render 函數被調用,當全部三個組件的
render 函數都被調用以後, 個組件的 componentDidMount 才連在一塊兒被調用
之因此會有上面的現象,是由於 render 函數自己並不往 DOM 樹上渲染或者裝載內
容,它只是返回 JSX 表示的對象,而後由 React 庫來根據返回對象決定如何渲染
React 庫確定是要把全部組件返回的結果綜合起來,才能知道該如何產生對應的 DOM
修改 因此,只有 React 庫調用 Counter 組件的 render 函數以後,纔有可能完成裝
載,這時候纔會依次調用各個組件的 componentDidMount 函數做爲裝載過程的收尾github
3 componentWilIMount componentDidMount 這對兄弟函數還有一個區別,就是 componentWillMount
能夠在服務器端被調用,也能夠在瀏覽器端被調用;而 component-DidMount
只能在瀏覽器端被調用,在服務器端使用 React 的時候不會被調用ajax
1.只要是父組件的 render 函數被調用,在 render 函數裏面被誼染的子組件就會經歷更新過
程,無論父組件傳給子組件的 props 有沒有改變,都會觸發子組件的 componentWillReceiveProps
函數
二、注意,經過 this.setState 方法觸發的更新過程不會調用這個函數,這是由於這個函數
適合根據新的 props 值(也就是參數 nextProps )來計算出是否是要更新內部狀態 state
更新組件內部狀態的方法就是 this.setState ,若是 this.setState 的調用致使 componentWillReceiveProps再一次被調用,那就是一個死循環了
三、this.setState 不會引起這個函數 componentWillReceiveProps
被調用
四、在 React 的組件組合中,徹底能夠只渲染 個子組件,
而其餘組件徹底不須要渲染,這是提升 React 性能的重要方式
一、render 函數重要,是由於 render 函數決定了該渲染什麼,而說 shouldComponentUpdate
函數重要,是由於它決定了一個組件何時不須要渲染
二、說shouldComponentUpdate 重要,就是由於只要使用恰當,他就可以大大提升 React
組件的性能,雖然 React 的渲染性能已經很不錯了,可是,無論渲染有多快,若是發現
不必從新渲染,那就乾脆不用渲染好了,速度會更快
shouldComponentUpdate(nextProps, nextState) { return (nextProps.caption !== this.props.caption) || (nextState.count !== this.state.count); }
如今,只有當 caption 改變,或者 state 中的 count 值改變, shouldComponent 纔會返回
true
3.經過 this setState 函數引起更新過程,並非馬上更新組件的 state
值,在執行到到函數 shouldComponentUpdate 的時候, this state 依然是 this.setState 函數
執行以前的值,因此咱們要作的實際上就是在 nextProps nextState this.props this.state 中互相比對
1.若是組件的 shouldComponentUpdate 函數返回 true
二、當在服務器端使用 React 渲染時,這一對函數中的 Did 函數,
也就是 componentDidUpdate 函數,並非只在瀏覽器端才執行的,不管更新過程發生在
服務器端仍是瀏覽器端,該函數都會被調用
3.React 組件被更新時,原有的內容被從新繪
制,這時候就須要在 componentDidUpdate 函數再次調用 jQuery 代碼
4.讀者可能會問, componentDidUpdate 函數不是可能會在服務器端也被執行嗎?在
服務器端怎麼可以使用 jQuery 呢?實際上,使用 React 作服務器端渲染時,基本不會經
歷更新過程,由於服務器端只須要產出 HTML 字符串,一個裝載過程就足夠產出 HTML
了,因此正常狀況下服務器端不會調用 componentDidUpdate 函數,若是調用了,說明我
們的程序有錯誤,須要改進
一、React 組件要從
DOM 樹上刪除掉以前,對應的 componentWillUnmount 函數會被調用,因此這個函數適
合作一些清理性的工做
二、不過, componentWillUnmount 中的工做每每和 componentDidMount 有關,好比,在
componentDidMount 中用非 React 的方法創造了一些 DOM 元素,若是撒手無論可能會造
成內存泄露,那就須要在 componentWillUnmount 中把這些創造的 DOM 元素清理掉
Counter.propTypes = { caption: PropTypes.string.isRequired, initValue: PropTypes.number, onUpdate: PropTypes.func }; Counter.defaultProps = { initValue: 0, onUpdate: f => f //默認這個函數什麼也不作 };
新增長的 prop 叫作 onUpdate ,類型是一個函數,當 Counter 的狀態改變的時候,就
會調用這個給定的函數,從而達到通知父組件的做用
這樣, Counter的 onUpdate 就成了做爲子組件的 Counter 向父組件 ControlPanel
遞數據的渠道,咱們先約定這個函數的第一個參數是 Counter 更新以後的新值,第二個
參數是更新以前的值,至於如何使用這兩個參數的值,是父組件 ControlPanel 的邏輯,
Counter 不用操心,並且根據兩個參數的值足夠能夠推導出數值是增長仍是減小
1.Flux 的體系中,若是兩個 Store 之間有邏輯依賴關係,就必須用上 Dispatcher的
waitFor 函數 在上面的例子中咱們已經使用過 waitFor 函數, SummaryStore對 action 類型的
處理,依賴於 CounterStore 已經處理過了 因此,必需要經過 waitFor 函數告訴 Dispatcher,
先讓 CounterStore 處理這些 action 對象,只有 CounterStore 搞定以後 SummaryStore才
繼續
2.那麼, SummaryStore 如何標識 CounterStore 呢?靠的是 register 函數的返回值 dispatchToken
,而 dispatchToken 的產生,固然是 CounterStore 控制的,換句話說,要這樣設計:
1)CounterStore 必需要把註冊回調函數時產生的 dispatchToken 公之於衆;
2)SummaryStore 必需要在代碼裏創建對 CounterStore的 dispatchToken 的依賴
雖然 Flux 這個設計的確解決了 Store 之間的依賴關係,可是,這樣明顯的模塊之間
的依賴,看着仍是讓人感受不大舒服,畢竟,最好的依賴管理是根本不讓依賴產生
1).若是狀態數據分散在多個 Store 中,容易形成數據冗餘,這樣數據一致性方面就會出
問題。 雖然利用 Dispatcher的 waitFor 方法能夠保證多個 Store 之間的更新順序,可是這
又產生了不一樣 Store 之間的顯示依賴關係,這種依賴關係的存在增長了應用的複雜度,容
易帶來新的問題
Redux 對這個問題的解決方法就是,整個應用只保持一個 Store ,全部組件的數據源
就是這個 Store 上的狀態
2)Redux阻並無阻止一個應用擁有多個Store,只是,在Redux的框架下,讓一個應
用擁有多個 Store 不會帶來任何好處,最後還不如使用一個 Store 更容易組織代碼
修改 Store 的狀態,必需要經過派發一個
action 對象完成,這一點 ,和 Flux 的要求並無什麼區別
這裏所說的純函數就是 Reducer
在 Redux 中,一個實現一樣功能的 reducer 代碼
以下:
function reducer(state , action) => { const {counterCaption} = action; switch (act on.type) { case ActionTypes.INCREMENT : return { ... state , [ counterCaption] : state [ counterCaption ] + 1}; case ActionTypes . DECREMENT: return { ... state, [counterCaption] : state [ counterCaption) - 1}; default : return state } }
能夠看到 reducer 函數不光接受 action 爲參數,還接受 state 爲參數 也就是說, Redux的
reducer 只負責計算狀態,卻並不負責存儲狀態
「若是你願意限制作事方式的靈活度,你幾乎總會發現能夠作得更好。」
一一-John earmark
做爲製做出《 Doom >< Quake 》這樣遊戲的傑出開發者, John earmark 這句話道出
了軟件開發中的一個真諦
在計算機編程的世界裏,完成任何一件任務,可能都有一百種以上的方法,可是無節制的靈活度反而讓軟件難以維護增長限制是提升軟件質量的法門。
創造一個 src/Store 文件,這個文件輸出全局惟一的那個 Store
import {createStore} from 'redux'; import reducer from './Reducer.js'; const initValues = { 'First': 0, 'Second': 10, 'Third': 20 }; const store = createStore(reducer, initValues);
在這裏,咱們接觸到了 Redux 庫提供的 create Store 函數,這個函數第一個參數表明更
新狀態的 reducer ,第二個參數是狀態的初始值,第三個參數可選,表明 Store Enhancer,
在這個簡單例子中用不上,在後面的章節中會詳細介紹
reducer文件
import * as ActionTypes from './ActionTypes'; export default (state,action)=>{ const {counterCaption} = action; switch (action.type){ case ActionTypes.INCREMENT: return { ...state, [counterCaption]:state[counterCaption]+1 } case ActionTypes.DECREMENT: return { ...state, [counterCaption]:state[counterCaption]-1 } default: return state } };
擴展操做符 (spread operator) 並非因樺的一部分,甚至都不是 ES Next 刁飛 法的一部分,可是
由於其語法簡單,已經被普遍使用,由於 babel 的存在,也不 會有兼容性問題,因此咱們徹底能夠放心使用
1.傻瓜組件 Counter 代碼的邏輯史無前例的簡單,只有一個 render 函數
1.CounterContainer ,這是容器組件,組件承擔了全部的和 Store 關聯的工做,它的 render 函數所作的就是渲染傻瓜組件 Counter 而已,只負責傳遞必要的 prop
1.Provider 也是一個 React 組件,不過它的 render 函數就是簡單地把子組件渲染出來,
在渲染上, Provider 不作任何附加的事情
import {PropTypes, Component} from 'react'; class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.propTypes = { store: PropTypes.object.isRequired } Provider.childContextTypes = { store: PropTypes.object }; export default Provider;
<Provider store={store}> <ControlPanel /> </Provider>,
以Counter 組件爲例,react-redux 的例子中沒
有定義 CounterContainer 這樣命名的容器組件,而是直接導出了一個這樣的語句
export default connect(mapStateToProps, mapDispatchToProps), Counter);
1)第一眼看去,會讓人以爲這不是正常的 JavaScript 語法 其實, connect是 react-redux
提供的一個方法,這個方法接收兩個參數 mapStateToProps和 mapDispatch-ToProps ,執行
結果依然是一個函數,因此才能夠在後面又加一個圓括號,把 connect 函數執行的結果立
刻執行,這一次參數是 Counter 這個傻瓜組件。
2)這裏有兩次函數執行,第一次是 connect 函數的執行,第二次是把 connect 函數返回
的函數再次執行,最後產生的就是容器組件,功能至關於前面 redux_smart_dumb 中的
CounterContainer;
3)這個 connect 函數具體作了什麼工做呢?
4)mapStateToProps函數
function mapStateToProps(state ,ownProps) { return { value: state[ownProps.caption] } }
5)mapDispatchToProps函數
function mapDispatchToProps(dispatch, ownProps) { return { nincrement () => { dispatch(Actions.increment(ownProps.caption)); } onDecrement : () => { dispatch(Actions.decrement(ownProps.caption)); } } }
6)mapStateToProps和 mapDispatchToProps 均可以包含第二個參數,表明 ownProps,
也就是直接傳遞給外層容器組件的 props ,在 ControlPanel 的例子中沒有用到,咱們在後
續章節中會有詳細介紹
react-redux 和咱們例子中的 Provider 幾乎同樣,可是更加嚴謹,好比咱們只要求 store 屬性是一個 object ,而react-redux 要求 store 不光是 object ,並且是必須包含三個函數的 object ,這三個函數
分別是
擁有上述 3個函數的對象,才能稱之爲一個 Redux 的store;
另外, react-redux 定義了 Provider的 componentWillReceiveProps 函數,在 React組
件的生命週期中, componentWillReceiveProps 函數在每次從新渲染時都會調用到, react-redux在
componentWillReceiveProps 函數中會檢查這一次渲染時表明 store的 prop 和上
一次的是否同樣。 若是不同,就會給出警告,這樣作是爲了不屢次渲染用了不一樣的
Redux Store。 每一個 Redux 應用只能有一個 Redux Store ,在整個 Redux 的生命週期中都
應該保持 Store 的惟一性
reducers/ todoReducer. js filterReducer.js actions/ todoActions.js filterActions.js components/ doList js todoitern . js filter.js containers/ todoListContainer . js todoiternCont ainer . js filterContaine r. js
「在最理想的狀況下,咱們應該經過增長代碼就能增長系統的功能,而不是 經過對現有代碼的修改來增長功能"一一-Robert C. Martin
好比,若是 模塊的 reducer 負責修改狀態樹上 字段下的數據,那麼另 個模塊
reducer 就不可能有機會修改 字段下的數據
工欲善其事,必先利其器 一一《論語·衛靈公》
舉個例子 在更新以前,組件的結構是這樣:
<div> <Todos /> </div>
咱們想要更新成這樣:
<span> <Todos /> </span>
這時候, componentWillUnmount 方法會被調用,取而代之的組件則會經歷裝載過程
的生命週期,組件的 componentWillMount render componentDidMount 方法依次被
調用,一看根節點原來是 div ,新的根節點是 span ,類型就不同,切推倒重
來。
雖然是浪費,可是爲了不 O(N3)的時間複雜度, React 必需要選擇 個更簡單更快
捷的算法,也就只能採用這種方式
**做爲開發者,很顯然必定要避免上面這樣浪費的情景出現 因此, 必定要避免做爲
包裹功能的節點類型被隨意改變**
好比本來的節點用 JSX 表示是這樣:
<div style={{color :」red」, fontSize: 15}} className=」 welcome 」 > Hello World </div>
改變以後的 JSX 表示是這樣:
<div style={{color :」 gree 」, fontSize: 15}} className=」 farewell 」 > Good Bye </div>
React 能作的只是根據新節點的 props 去更新原來根節點的組件實例,
引起這個組件實例的更新過程,也就是按照順序引起下列函數:
若是 shouldComponentUpdate 函數返回 false 的話,那麼更新過程
就此打住,再也不繼續 因此爲了保持最大的性能,每一個 React 組件類必需要重視 shouldComponentUpdate
,若是發現根本沒有必要從新渲染,那就能夠直接返回 false
<ul> <Todoitem text=」 First」 completed={ false}> <To do tern text Second completed={false}> </ul>
在更新以後,用 JSX 表示是這樣:
<ul> <Todo item text=」 First 」 completed={ false}> <Todoitem text=」 Second" completed={false}> <Todo tem text=」Third」 completed={false}> </ul>
那麼 React 會發現多出了一個 Todoltem ,會建立一個新的 Todoltem 組件實例,這個
Todoltem 組件實例須要經歷裝載過程,對於前兩個 Todoltem 實例, React 會引起它們的
更新過程,可是隻要 To do Item shouldComponentUpdate 函數實現恰當,檢查 props
後就返回 false 的話,就能夠避免實質的更新操做
<ul> <Todoltem text=」 Zero」 completed={ false)> <Todoltem text=」First」 completed={ false)> <Todoltem text=」Second」 completed={ false)> </ul>
從直觀上看,內容是「 Zero ,,的新加待辦事項被插在了第一位,只須要創造一個新
的組件 Todoltem 實例放在第一位,剩下兩個內容爲「 First 」和「 Second ,,的 Todoltem
實例經歷更新過程,可是由於 props 沒有改變,因此 shouldComponentUpdate 能夠幫助
這兩個組件不作實質的更新動做。
但是實際狀況並非這樣 若是要讓 React 按照上面咱們構想的方式來作,就必須
要找出兩個子組件序列的不一樣之處,現有的計算出兩個序列差別的算法時間是 O(N2),雖
然沒有樹形結構比較的 O(N3)時間複雜度那麼誇張,可是也不適合一個對性能要求很高
的場景,因此 React 選擇看起來很傻的一個辦法,不是尋找兩個序列的精確差異,而是
直接挨個比較每一個子組件。
, React 並非沒有意識到這個問題,因此 React 提供了方法來克服這種浪費,
不過須要開發人員在寫代碼的時候提供一點小小的幫助,這就是 key 的做用
用數組下標做爲 key ,看起來 key 值是惟一的,可是卻不是穩定不變的,隨着 todos
數組值的不一樣,一樣一個 Todoltem 實例在不一樣的更新過程當中在數組中的下標徹底可能不
同,把下標當作 key 就讓 React 完全亂套了。
須要注意,雖然 key 是一個 prop ,可是接受 key 的組件並不能讀取到 key 的值,因
key 和ref是 React 保留的兩個特殊 prop ,並無預期讓組件直接訪問。
const selectVisibleTodos = (todos, filter) => { switch (filter) { case FilterTypes.ALL: return todos; case FilterTypes.COMPLETED: return todos.filter(item => item.completed); case FilterTypes.UNCOMPLETED: return todos.filter(item => !item.completed); default: throw new Error('unsupported filter'); } } const mapStateToProps = (state) => { return { todos: selectVisibleTodos(state.todos, state.filter) }; }
既然這個 selectVisibleTodos 函數的計算必不可少,那如何優化呢?
若是上 次的計算結果被緩存起來的話,那就能夠重用緩存的
數據。
這就是 reselect 庫的工做原理:只要相關狀態沒有改變,那就直接使用上一次的緩存
結果
npm install --save reselect import {createSelector} from ’ reselect ’; import {FilterTypes} from ’.. /constants. j s ’; export const selectVisibleTodos = createSelector( [getFilter, getTodos], (filter, todos) => { switch (filter) { case FilterTypes.ALL: return todos; case FilterTypes.COMPLETED: return todos.filter(item =>item.completed}; case FilterTypes.UNCOMPPLETED: return todos.filter(item => !item.completed); default: throw new Error (’ unsupported filter ’); } } )
reselect 提供了創造選擇器的 createSelector 函數 ,這是一個高階函數,也就是接受
函數爲參數來產生一個新函數的函數
第一個參數是一個函數數組,每一個元素表明了選擇器步驟一須要作的映射計算,這
裏咱們提供了兩個函數 getFilte和 getTodos ,對應代碼以下:
const getFilter = (state) => state.filter; const getTodos = (state) => state.todos;
所謂範式化,就是遵守關係型數據庫的設計原則,減小冗餘數據.
若是使用反範式化的設計,那麼狀態樹上的數據最好是可以不用計算拿來就能用,
在Redux Store 狀態樹的 todos 字段保存的是全部待辦事項數據的數組,對於每一個數組元
素,反範式化的設計會是相似下面的對象:
{ id: 1, //待辦事項id text :」待辦事項 」,//待辦事項文字內容 completed : false,//是否已完成 type: { //種類 name :」緊急」,//種類的名稱 color:」red」 //種類的顯示顏色 } }
但這也有缺點,當須要改變某種類型的名稱和顏色時,
不得不遍歷全部 Todoltem 數據來完成改變.
反範式化數據結構的特色就是讀取容易,修改比較麻煩
若是使用範式化的數據結構設計,那麼 Redux Store 上表明 Todoltem 的一條數據是
相似下面的對象:
{ id: 1 , text :」待辦事項 l 」, completed : false , typeid: 1 //待辦事項所屬的種類id }
用一個typeId 表明類型,而後在 Redux Store 上和 to dos 平級的根節點位置建立一個
types 字段,內容是一 個數組,每一個數組元素表明 一個類型,一個種類的數據是相似下面
的對象:
{ id: 1 , //種類 name :」 緊急 」, //種類的名稱 color :」red」 //種類的顯示顏 }
當Todoltem 組件要渲染內容時從 Redux Store 狀態樹的 to dos 宇段下獲取的數據
是不夠的,由於只有 typeId。
這個過程固然要花費一 些時間,可是當要改變某個種類的名稱或者顏色時,就異常
地簡單,只須要修改 types 中的一處數據就能夠了
1.利用 react-redux 提供的 shouldComponentUpdate 實現來提升
組件渲染功能的方法, 一個要訣就是避免傳遞給其餘組件的 prop 值是 一個不一樣的對象,
否則會形成元謂的重複渲染
2.不能隨意修改一個做爲容器的 HTML 節點的類型 其次,對於動態數
量的同類型子組件,一 定要使用 key 這個 prop
3.利用 reselect 庫來實現高效的數據獲取。 由於 reselect 的緩存功
能,開發者不用顧忌範式化的狀態樹會存在性能問題, Redux Store 的狀態樹應該按照範
式化原則來設計,減小數據冗餘,這樣利於保持數據一致。
「重複是優秀系統設計的大敵。 」一-Robert C.Martin
1.高階組件( Higher Order Component, HOC )並非 React 提供的某種 API ,而是使用
React 的一種模式,用於加強現有組件的功能。
2.簡單來講,一個高階組件就是一個函數,這個函數接受一個組件做爲輸入,而後返回一個新的組件做爲結果,並且,返回的新組件擁有了輸入組件所不具備的功能
3.這裏提到的組件指的並非組件實例,而是一個組件類,也能夠是一個無狀態組件
的函數。
4.咱們先看一個很是簡單的高階組件的例子,感覺一下高階組件是如何工做的,代碼
以下:
import React from ’ react ’; function removeUserProp(WrappedComponent) { return class WrappingComponent extends React.Component { render() { const {user, ... otherProps} = this.props; return <WrappedComponent { ... otherProps) /> } } } export default removeUserProp;
只是忽略名爲 user的 prop 也就是說,若是 Wrapped Component 可以處理名爲 user的
prop ,這個高階組件返回的組件則徹底無視這個 prop。
5.假如咱們如今不但願某個組件接收到 user prop ,那麼咱們就不要直接使用這個組
件,而是把這個組件做爲參數傳遞給 removeU serProp 函數,而後咱們把這個函數的返回
結果當作組件來使用:
const NewComponent = removeUserProp(SampleComponent) ;
在上面的代碼中, NewComponent 擁有和 SampleComponent 徹底同樣的行爲,惟一
的區別就是即便傳遞 user 屬性給它,它也會當沒有 user 來處理。
6.定義高階組件的意義何在呢?
1.上面的 removeUserProp 例子就是一個代理方式的高階組件,特色是返回的新組件類
直接繼承自 React. Component 新組件扮演的角色是傳入參數組件的一個「代理」,在
新組建的 render 函數中,把被包裹組件渲染出來,除了高階組件本身要作的工做,其他
功能全都轉手給了被包裹的組件.
2.若是高階組件要作的功能不涉及除了 render 以外的生命週期函數,也不須要維護自
己的狀態,那也能夠乾脆返回一個純函數,像上面的 removeUserProp ,代碼能夠簡寫成
下面這樣:
function removeUserProp(WrappedComponent) { return function newRender(props) { con st {user, ... otherProps) = props; return <WrappedComponent { ... otherProps} /> } }
3.代理方式的高階組件,能夠應用在下列場景中:
例如,咱們能夠定義一個高階組件,讓參數組件只有在用戶登陸時才顯示,代碼
以下:
const onlyForLoggedinHOC = (WrappedComponent) => { return class NewComponent extends WrappedComponent { render () { if (this.props.loggedin) { return super.render(); } else { return null; } } } }
又例如,咱們能夠從新定義 shouldComponentUpdate 函數,只要 prop 中的 useCache
不爲邏輯 false 就不作從新渲染的動做,代碼以下:
const cacheHOC = (WrappedComponent) => { return class NewComponent extends WrappedComponent { shouldComponentUpdate(nextProps, nextState) { return !nextProps.useCache; } } }
在 ES6的 React組件類定義方法中不能使用 Mixin, React 官方也很明確聲明 Mixin 是應該被廢棄的方法
因此咱們只須要知道在 React 的歷史上,曾經有這樣一個重用代碼的解決方法就足夠了
使用 Redux 訪問服務器,一樣要解決的是異步問題
Redux 的單向數據流是同步操做,驅動 Redux 流程的 ac tion 對象, 每個 action
對象被派發到 Store 上以後,同步地被分配給全部的 reducer 函數,每一個 reducer 都是純
函數,純函數不產生任何反作用,天然是完成數據操做以後馬上同步返回, reducer 返回
的結果又被同步地拿去更新 Store 上的狀態數據,更新狀態數據的操做會馬上被同步給監
Store 狀態改變的函數,從而引起做爲視圖的 React 組件更新過程。
實際上, re dux-thunk 的實現極其簡單,只有幾行代碼。
假若有一個 JavaScript 函數f 以下定義:
const f = (x) => { return x () + 5; }
f把輸入參數x 當作一個子程序來執行,結果加上5 就是f 的執行結果,那麼咱們試
着調用一次 f:
const g = () => { return 3 + 4 ; } f (g); 11 結果是( 3+4 )巧= 37
上面代碼中函數f 就是一個 thunk ,這樣使用看起來有點奇怪,但有個好處就是g的
執行只有在f 實際執行時才執行,能夠起到延遲執行的做用,咱們繼續看 redux-thunk的
用法來理解其意義。
按照 redux-thunk 的想法,在 Redux 的單向數據流中,在 action 對象被 reducer 函數
處理以前,是插入異步功能的時機
在Redux 架構下,一個 action 對象在經過 store.dispatch派發,在調用 reducer 函數
以前,會先通過 箇中間件的環節,這就是產生異步操做的機會,實際上 redux-thunk提
供的就是一個Redux 中間件,咱們須要在建立 Store 時用上這個中間件。
redux-也unk 的工做是檢查 action 對象是否是函數,若是不是函數就放行,完成普通
action 對象的生命週期,而若是發現 action 對象是函數,那就執行這個函數,並把 Store的
dispatch 函數和 getState 函數做爲參數傳遞到函數中去,處理過程到此爲止,不會讓
這個異步 action 對象繼續往前派發到 reducer 函數
舉一個並不涉及網絡 API 訪問的異步操做例子 ,在 Co unter 組件中存在一個普通的
同步增長計數的 action 構造函數 increment ,代碼以下:
const increment= () => ({ type: ActionTypes.INCREMENT, });
派發 increment 執行返回的 action 對象, Redux 會同步更新 Store 狀態和視圖,可是
咱們如今想要創造一個功能,可以發出一個「讓 Counter 組件在 秒以後計數加一」的
指令,這就須要定義一個新的異步 action 構造函數,代碼以下:
const incrementAsync = () => { return (dispatch) => { set Timeout ( () => { dispatch (increment()); },1000); } }
異步 action 構造函數 incrementAsync 返回的是一個新的函數,這樣一 個函數被
dispatch 函數派發以後,會被 redux-thunk 中間件執行,因而 setTimeout 函數就會發生做
用,在 1秒以後利用參數 dispatch 函數派發出同步 action 構造函數 increment 的結果。
這就是異步 action 的工做機理,這個例子雖然簡單,可是能夠看得出來,異步
action 最終仍是要產生同步 action 派發才能對 Redux 系統產生影響。
對於訪問服務器這樣的異步操做,從發起操做到操做結束,都會有段時間延遲,在
這段延遲時間中,用戶可能但願停止異步操做。
用戶也會進行一些操做引起新的請求發往服務器,而這就是咱們開發者須要考慮的問題。
從用戶角度出發但願是最後一次選擇結果。
在jQuery 中,能夠經過 abort 方法取消掉一個 AJAX 請求:
const xhr = $.ajax( ... ); xhr.abort {);//取消掉已經發出的AJAX請求
可是,很不幸,對於 fetch 沒有對應 abort 函數的功能,由於 fetch 返回的是一個
Promise 對象,在 ES6 的標準中, Promise 對象是不存在「中斷」這樣的概念的.
既然 fetch 不能幫助咱們停止一個 API 請求,那就只能在應用層實現「中斷」的效
果,有一個技巧能夠解決這個問題,只須要修改 action 構造函數。
let nextSeqid = 0; export const fetchWeather = (cityCode) => { return (dispatch) => { const apiUrl =、/ data/cityinfo/${cityCode).html const seqid = ++ nextSeqid; const dispatchifValid = (action) => { if (seqid === nextSeqid) { //**這裏一個請求對應一個請求`id`若是不相等,就拋棄** return dispatch(action); } } dispatchifValid ( fetchWeatherStarted () ) fetch(apiUrl) .then((response) => { if (response.status !== 200) { throw new Erro r (’ Fail to get response with status ’+ response.status); } response.json() .then((responseJson) => { dispatchifValid(fetchWeatherSuccess(responseJson.weatherinfo)); )).catch((error) => { dispatchifVal (fetchWeatherFailure(error)); )); }).catch ((error) => { dispatchifValid(fetchWeatherFailure(error)); }) } }
在action 構造函數文件中定義一個文件模塊級的 nextSeqld 變量,這是一個遞增的整
數數字,給每個訪問 API 的請求作序列編號。
這裏一個請求對應一個請求id
若是不相等,就拋棄。
若是還不明白,另外用vue的例子來講明:
<div id="app"> <select @change="_getWeather"> <option v-for="(value, key) in city_code" >{{key}}</option> </select> <div>{{cont}}</div> </div>
<script> new Vue({ el:'#app', data:{ nextSeqid:0, baseUrl:'http://www.weather.com.cn', city_code: { '北京': 101010100, '上海': 101020100, '廣州': 101280101, '深圳': 101280601 }, cont:'正在請求。。。' }, methods:{ _getWeather(e){ const seqid = ++ this.nextSeqid; console.log(seqid,this.nextSeqid,'請求') let url=`/data/cityinfo/${this.city_code[e.target.value]}.html`; axios.get(url).then(res=>{ let {city}=res.data.weatherinfo; console.log(seqid,this.nextSeqid,'請求完成') if (seqid === this.nextSeqid) { this.cont=city; } }) } } }) </script>
控制檯結果:
- 1 1 "請求" - (index):55 2 2 "請求" - (index):60 1 2 "請求完成" - (index):55 3 3 "請求" - (index):60 2 3 "請求完成" - (index):55 4 4 "請求" - (index):60 3 4 "請求完成" - (index):55 5 5 "請求" - (index):60 4 5 "請求完成" - (index):55 6 6 "請求" - (index):60 5 6 "請求完成" - (index):55 7 7 "請求" - (index):60 6 7 "請求完成" - (index):60 7 7 "請求完成"
你會發如今重複請求,請求id不對應,因此不渲染,只有當相等菜渲染。
if (seqid === this.nextSeqid) { this.cont=city; }
雖然不能真正「停止」一個 API 請求,可是咱們能夠用這種方法讓一個 API 請求的
結果被忽略,達到了停止一個 API 請求同樣的效果。
在這個例子中 Weather 模塊只有一種API 請求,因此一個 API 調用編號序列就足夠,
若是須要多種 API 請求,則須要更多相似nextSeqld 的變量來存儲調用編號。
運行效率要比腳本方式高,由於瀏覽器原生支持,省去了 Java
Script 的解釋執行負擔,有的瀏覽器(好比 Chrome 瀏覽器)甚至還能夠充分利用 GPU加
速的優點,進一步加強了動畫渲染的性能
時間和速度曲線的不合理是 CSS3 先天的屬性更讓開發者頭疼的就是開發 CSS3
則的過程,尤爲是對 tra nsition-duration 時間很短的動畫調試,由於 CSS3 transition
程老是一閃而過,捕捉不到中間狀態,只能一遍一遍用肉眼去檢驗動畫效果,用 CSS3
作過複雜動畫的開發者確定都深有體會
雖然 CSS3 有這樣一些缺點,可是由於其無與倫比的性能,用來處理一些簡單的動
畫仍是不錯的選擇
React 提供的 ReactCSSTransitionGroup 功能,使用的就是 CSS3 的方式來實現動畫,
在後面的章節會詳細介紹
腳本方式最大的好處就是更強的靈活度,最原始的腳本方式就是利用 setlnterval 或者 setTimeout 來實現。
var animatedElement = document.getElementById ('sample'); var left = 0; var timer; var ANIMATIONINTERVAL = 16; timer = setInterval (function() { left += 10; animatedElement.style.left = left + 'px'; if ( left >= 400 ) { clearInterval(timer); } } , ANIMATIONINTERVAL);
在上面的例子中,有一個常量 ANIMATION INTERVAL 定義爲 16 , setlnterval 以這
個常盤爲間隔,每 16 毫秒計算一次 sample 元素的 left 值,每次都根據時間推移按比例增長 left 的值,直到 left 大於 400.
爲何要選擇 16 毫秒呢?由於每秒渲染 60 幀(也叫 60fps, 60 Frame Per Second)
會給用戶帶來足夠流暢的視覺體驗,一秒鐘有 1000 毫秒, 1000/60約等於16 ,也就是說,如
果咱們作到每 16 毫秒去渲染一次畫面,就可以達到比較流暢的動畫效果。
對於簡單的動畫, setlnterval 方式勉強可以及格,可是對於稍微複雜一些的動畫,腳
本方式就頂不住了,好比渲染一幀要花去超過 32 毫秒的時間,那麼還用 16 毫秒一個間
隔的方式確定不行 實際上,由於一幀渲染要佔用網頁線程 32 毫秒,會致使 setlnterval
根本沒法以 16 毫秒間隔調用渲染函數,這就產生了明顯的動畫滯後感,本來一秒鐘完
成的動畫如今要花兩秒鐘完成,因此這種原始的 setlnterval 方式是確定不適合複雜的動
畫的。
出現上面問題的本質緣由是 setlnterval和setTimeout 並不能保證在指定時間間隔或
者延遲的狀況下準時調用指定函數 因此能夠換 個思路,當指定函數調用的時候,根
據逝去的時間計算當前這一幀應該顯示成什麼樣子,這樣即便由於瀏覽器渲染主線程忙
碌致使一幀渲染時間超過 16 毫秒,在後續幀誼染時至少內容不會所以滯後,即便達不倒
60fps 的效果,也能保證動畫在指定時間內完成。
下面是一個這種方法實現動畫的例子,首先咱們實現一個 raf 函數, raf request
animation frame 的縮寫,代碼以下:
var lastTmeStamp = new Date().getTime(); function raf(fn) { var currTimeStamp = new Date().getTime(); var delay = Math.max(O, 16 - (currTimeStamp - lastTmeStamp)); var handle = setTimeout(function(){ fn(currTimeStamp) },delay); lastTmeStamp = currTimeStamp; return handle; }
在上面定義的 raf 中,接受的 fn 函數參數是真正的渲染過程, raf 只是協調渲染的節奏。
raf 儘可能以每隔 16 毫秒的速度去調用傳染的fn參數,若是發現上一次被調用時間和
這一次被調用時間相差不足 16 毫秒,就會保持 16 毫秒一次的渲染間隔繼續,若是發現
兩次調用時間間隔已經超出了 16 毫秒,就會在下 次時鐘週期馬上調用 fn。
仍是讓 id 爲sample 的元素向右移動的例子,咱們定義渲染每一幀的函數 render ,代
碼以下:
var left = 0; var animatedElement = document.getElementById("sample"); var startTimestamp = new Date().getTime(); function render(timestamp) { left += (timestamp - startTimestamp) / 16; animatedElement.style.left = left + 'px'; if (left < 400) { raf(render); } } raf(render);
上面的 render 函數中根據當前時間和開始動圓的時間差來計算 sample 元素的 left屬
性,這樣不管 render 函數什麼時候被調用,總可以渲染出正確的結果。
最後,咱們將 render 做爲參數傳遞給 raf ,啓動了動畫過程:
raf (render);
實際上, 現代瀏覽器提供了 一個新 的函數 requestAnimationFrame ,採用的就是
上面描述的思路,不是以固定 16 毫秒間隔的時間去調用渲染過程,而是讓腳本經過
requestAnimationFrame 傳一 個回調函數,表示想要渲染一幀畫面,瀏覽器會決定在合
適的時間來調用給定的回調函數,而回調函數的工做是要根據逝去的時間來決定將界面
渲染成什麼樣子。
這樣一來,渲染動面的方式就改爲按須要來渲染,而不是每隔 16 毫秒渲染固定的幀內容。
不是全部瀏覽器都支持 requestAnimationFrame ,對於不支持這個函數的瀏覽器,可
以使用上面 raf 函數的方式模擬 requestAnimationFrame 的行爲。
React 提供了一個叫作 ReactCSSTransitionGroup 的功能幫助實現動畫,爲了使用這
個功能 ,首先要經過 npm 安裝 react-addons-css-transition-group 這個庫,以後就能夠導人
這個庫的內容:
import TransitionGroup from ’ react-addons- css-transition-group ’;
Transition Group 的工做就是幫助組件實現裝載過程和卸載過程的動畫,而對於更新
過程,並非 Transition Group 要解決的問題.
<ul> <TransitionGroup transitionName="fade" transitionEnterTimeout={500} transitionLeaveTimeout={200}> { todos.map((item) => ( <TodoItem key={item.id} id={item.id} text={item.text} completed={item.completed} /> )) } </TransitionGroup> </ul>
.fade-enter{ opacity: 0.01; } .fade-enter.fade-enter-active { opacity: 1; transition: opacity 500ms ease-in; } .fade-leave { opacity: 1; } .fade-leave.fade-leave-active { opacity: 0.01; transition: opacity 200ms ease-in; }
假設 transitionName sample ,那麼定製相關 React 組件的類名就是:
裝載時機
讀者可能會有一個疑問,爲何用 TransitionGroup在 todoList.js 文件中包住全部
Todoltem 組件實例的數組,而不是讓 TransitionGroup在 todoltem.js 文件中包住單個
Todoltem 組件呢?
看起來應該能實現一樣效果,但實際上這樣作不行 由於 TransitionGroup 要發揮做
用,必須自身已經完成裝載了 這很好理解, Transition Group 也只是一個 React 組件,
功能只有在被裝載以後才能發揮,它本身都沒有被裝載,怎麼可能發揮效力呢?
react-motion 是很優秀的動畫庫,它採用的動
畫方式和 TransitionGroup 不一樣,是用腳本的方式。
在這一章中,咱們瞭解了網頁動畫的兩種實現方式, CSS3 方式和腳本方式,在 React
的世界,也有對應這兩種方式的動畫解決方案。
React 官方的 ReactCSSTransitionGroup ,可以幫助定製組件在裝載過程和卸載過程
中的動畫,對於更新過程的動畫,則不在 ReactCSSTransitionGroup 考慮之列,能夠直接用
CSS3 來實現。
React-Motion 庫提供了更強大靈活的動畫實現功能,利用「以函數爲子組件」的模
式, React-Motion 只須要提供幾個組件,這些組件經過定時向子組件提供動畫參數,就
可讓開發者自由定義動畫的功能。
React Redux 都是徹底在瀏覽器中運行的,其實, React做爲一
個產生用戶界面的 JavaScript 庫, Redux 做爲一個管理應用數據的框架,二者也能夠
在服務器端運行。
理想狀況下, 一個React 組件或者說功能組件既可以在瀏覽器端渲染也能夠在服務
器端渲染產生 HTML ,這種方式叫作「同構」( Isomorphic ),也就是同 份代碼能夠在不
同環境下運行。
傳統的模板庫就是生硬的字符串替換操做,不管如何優化都會有它的極限,並且模
板的輸出依然是字符串,將 HTML 字符串插入網頁的過程,也就是 DOM 樹的操做,性
能也沒法優化 在前面的章節中咱們介紹過 React的 Virtual DOM 工做原理,配合生命
周期函數的應用,性能不是字符串替換的模板庫可以比擬的。
雖然 Face book 聲稱 React 並非給服務器端渲染設計的,可是 React 真的很適合來作同構