摘要:閱讀深入淺出和本書值得記錄的地方源碼第二章設計高質量的組件檢查雖然能夠在開發階段發現代碼中的問題,但是放在產品環境中就不大合適現有的就具有這個功能,可以通過安裝,但是應該確保只在發布產品代碼時使用它。
閱讀深入淺出react和redux本書值得記錄的地方
github源碼:https://github.com/mocheng/react-and-redux
第二章 設計高質量的 React 組件 1 React prop propTypes 檢查import PropTypes from "prop-types";
Counter.propTypes = { caption: PropTypes.string.isRequired, initValue: PropTypes.number }
prop Types 雖然能夠在開發階段發現代碼中的問題,但是放在產品環境中就不大合
適,現有的 babel-react-optimize 就具有這個功能,可以通過 npm 安裝,但
是應該確保只在發布產品代碼時使用它。
props默認值 React的 defaultProps 功能,讓代碼更加容易讀懂
Counter 組件添加 defaultProps 的代碼如下:
Counter .defaultProps = { initValue: 0 }2 組件的生命周期 裝載過程 constructor
1 初始化 state ,因為組件生命周期中任何函數都可能要訪問 state ,那么整個生命
周期中第一個被調用的構造函數自然是初始化 state 最理想的地方;
2 綁定成員函數的 this 環境
ES5的 React. createClass 方法創造的組件類才會發生作用,已經被 Facebook 官方逐漸廢棄
renderrender 函數應該是一個純函數,完全根據 this.state this.props 來決定返
回的結果,而且不要產生任何副作用。在 render 函數中去調用 this.setState 毫無疑問是錯
誤的,因為一個純函數不應該引起狀態的改變
1 在裝載過程中, componentWil!Mount 會在調用 render 函數之前被調用, componentDidMount
會在調用 render 函數之后被調用,這兩個函數就像是 render 函數的前哨和后
衛,一前一后,把 render 函數夾住,正好分別做 render 前后必要的工作
2 componentWillMount 都是緊貼著自己組件的 render 函數之
前被調用, componentDidMount 可不是緊跟著 render 函數被調用,當所有三個組件的
render 函數都被調用之后, 個組件的 componentDidMount 才連在一起被調用
之所以會有上面的現象,是因為 render 函數本身并不往 DOM 樹上渲染或者裝載內
容,它只是返回 JSX 表示的對象,然后由 React 庫來根據返回對象決定如何渲染
React 庫肯定是要把所有組件返回的結果綜合起來,才能知道該如何產生對應的 DOM
修改 所以,只有 React 庫調用 Counter 組件的 render 函數之后,才有可能完成裝
載,這時候才會依次調用各個組件的 componentDidMount 函數作為裝載過程的收尾
3 componentWilIMount componentDidMount 這對兄弟函數還有一個區別,就是 componentWillMount
可以在服務器端被調用,也可以在瀏覽器端被調用;而 component-DidMount
只能在瀏覽器端被調用,在服務器端使用 React 的時候不會被調用
1.只要是父組件的 render 函數被調用,在 render 函數里面被誼染的子組件就會經歷更新過
程,不管父組件傳給子組件的 props 有沒有改變,都會觸發子組件的 componentWillReceiveProps
函數
2、注意,通過 this.setState 方法觸發的更新過程不會調用這個函數,這是因為這個函數
適合根據新的 props 值(也就是參數 nextProps )來計算出是不是要更新內部狀態 state
更新組件內部狀態的方法就是 this.setState ,如果 this.setState 的調用導致 componentWillReceiveProps再一次被調用,那就是一個死循環了
3、this.setState 不會引發這個函數 componentWillReceiveProps
被調用
4、在 React 的組件組合中,完全可以只渲染 個子組件,
而其他組件完全不需要渲染,這是提高 React 性能的重要方式
1、render 函數重要,是因為 render 函數決定了該渲染什么,而說 shouldComponentUpdate
函數重要,是因為它決定了一個組件什么時候不需要渲染
2、說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
2、當在服務器端使用 React 渲染時,這一對函數中的 Did 函數,
也就是 componentDidUpdate 函數,并不是只在瀏覽器端才執行的,無論更新過程發生在
服務器端還是瀏覽器端,該函數都會被調用
3.React 組件被更新時,原有的內容被重新繪
制,這時候就需要在 componentDidUpdate 函數再次調用 jQuery 代碼
4.讀者可能會問, componentDidUpdate 函數不是可能會在服務器端也被執行嗎?在
服務器端怎么能夠使用 jQuery 呢?實際上,使用 React 做服務器端渲染時,基本不會經
歷更新過程,因為服務器端只需要產出 HTML 字符串,一個裝載過程就足夠產出 HTML
了,所以正常情況下服務器端不會調用 componentDidUpdate 函數,如果調用了,說明我
們的程序有錯誤,需要改進
1、React 組件要從
DOM 樹上刪除掉之前,對應的 componentWillUnmount 函數會被調用,所以這個函數適
合做一些清理性的工作
2、不過, 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 刁飛 法的一部分,但是3.2.3 窯器組件和傻瓜組件
因為其語法簡單,已經被廣泛使用,因為 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;
3.2.5 React-Redux 1. connect :連接容器組件和傻瓜組件;,
以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 函數具體做了什么工作呢?
把 Store 上的狀態轉化為內層傻瓜組件的 prop;
把內層傻瓜組件中的用戶動作轉化為派送給 Store 的動作
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 ,這三個函數
分別是
subscribe
dispatch
getState
擁有上述 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. js4.2.2 接功能組織
actionTypes.js 定義 action 類型;
actions. js定義 action 構造函數,決定了這個功能模塊可以接受的動作;
reducer扣定義這個功能模塊如何相應 actions. 中定義的動作;
views 目錄,包含這個功能模塊中所有的 React 組件,包括傻瓜組件和容器組件;
index.js 這個文件把所有的角色導人,然后統一導出
4.3 模塊接口“在最理想的情況下,我們應該通過增加代碼就能增加系統的功能,而不是 通過對現有代碼的修改來增加功能"4.4 狀態樹的設計 4.4.1 一個狀態節點只屬于一個模塊一一-Robert C. Martin
比如,如果 模塊的 reducer 負責修改狀態樹上 字段下的數據,那么另 個模塊
reducer 就不可能有機會修改 字段下的數據
工欲善其事,必先利其器 一一《論語·衛靈公》第五章 React 組件的性能優化 5.2 多個 React 組件的性能優化 5.2.1 React 的調和(Reconciliation )過程 1.節點類型不同的情況
舉個例子 在更新之前,組件的結構是這樣:
我們想要更新成這樣:
這時候, componentWillUnmount 方法會被調用,取而代之的組件則會經歷裝載過程
的生命周期,組件的 componentWillMount render componentDidMount 方法依次被
調用,一看根節點原來是 div ,新的根節點是 span ,類型就不一樣,切推倒重
來。
雖然是浪費,但是為了避免 O(N3)的時間復雜度, React 必須要選擇 個更簡單更快
捷的算法,也就只能采用這種方式
**作為開發者,很顯然一定要避免上面這樣浪費的情景出現 所以, 一定要避免作為
包裹功能的節點類型被隨意改變**
比如原本的節點用 JSX 表示是這樣:
Hello World
改變之后的 JSX 表示是這樣:
Good Bye
React 能做的只是根據新節點的 props 去更新原來根節點的組件實例,
引發這個組件實例的更新過程,也就是按照順序引發下列函數:
shouldComponentUpdate
componentWillReceiveProps
componentWillUpdate
render
componentDidUpdate
如果 shouldComponentUpdate 函數返回 false 的話,那么更新過程
就此打住,不再繼續 所以為了保持最大的性能,每個 React 組件類必須要重視 shouldComponentUpdate
,如果發現根本沒有必要重新渲染,那就可以直接返回 false
在更新之后,用 JSX 表示是這樣:
那么 React 會發現多出了一個 Todoltem ,會創建一個新的 Todoltem 組件實例,這個
Todoltem 組件實例需要經歷裝載過程,對于前兩個 Todoltem 實例, React 會引發它們的
更新過程,但是只要 To do Item shouldComponentUpdate 函數實現恰當,檢查 props
后就返回 false 的話,就可以避免實質的更新操作
從直觀上看,內容是“ 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;5.3.2 范式化狀態樹
所謂范式化,就是遵照關系型數據庫的設計原則,減少冗余數據.
如果使用反范式化的設計,那么狀態樹上的數據最好是能夠不用計算拿來就能用,
在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.Martin6.1 高階組件
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} } } export default removeUserProp;
只是忽略名為 user的 prop 也就是說,如果 Wrapped Component 能夠處理名為 user的
prop ,這個高階組件返回的組件則完全無視這個 prop。
5.假如我們現在不希望某個組件接收到 user prop ,那么我們就不要直接使用這個組
件,而是把這個組件作為參數傳遞給 removeU serProp 函數,然后我們把這個函數的返回
結果當做組件來使用:
const NewComponent = removeUserProp(SampleComponent) ;
在上面的代碼中, NewComponent 擁有和 SampleComponent 完全一樣的行為,唯一
的區別就是即使傳遞 user 屬性給它,它也會當沒有 user 來處理。
6.定義高階組件的意義何在呢?
首先,重用代碼
其次,修改現有 React 組件的行為
6.1.1 代理方式的高階組件1.上面的 removeUserProp 例子就是一個代理方式的高階組件,特點是返回的新組件類
直接繼承自 React. Component 新組件扮演的角色是傳入參數組件的一個“代理”,在
新組建的 render 函數中,把被包裹組件渲染出來,除了高階組件自己要做的工作,其余
功能全都轉手給了被包裹的組件.
2.如果高階組件要做的功能不涉及除了 render 之外的生命周期函數,也不需要維護自
己的狀態,那也可以干脆返回一個純函數,像上面的 removeUserProp ,代碼可以簡寫成
下面這樣:
function removeUserProp(WrappedComponent) { return function newRender(props) { con st {user, ... otherProps) = props; return} }
3.代理方式的高階組件,可以應用在下列場景中:
操縱 prop;
訪問 ref;
抽取狀態;
包裝組件
6.1.2 繼承方式的高階組件 1. 操縱 Props 2. 操縱生命周期函數例如,我們可以定義一個高階組件,讓參數組件只有在用戶登錄時才顯示,代碼
如下:
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; } } }6.1.3 高階組件的顯示名 6.1.4 曾經的 React Mixin
在 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的例子來說明:
{{cont}}
控制臺結果:
- 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 的變量來存儲調用編號。
redux-saga
redux-effects
redux-side-effects
redux-loop
redux-observable
第八章 單元測試 第九章 擴展 Redux 9.1 中間件中間件的特點是:
中間件是獨立的函數;
中間件可以組合使用;
中間件有一個統一的接口
第十章 動畫 10.1.1 css方式運行效率要比腳本方式高,因為瀏覽器原生支持,省去了 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 要解決的問題.
.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; }10.2.2 ReactCSSTransitionGroup 規則
假設 transitionName sample ,那么定制相關 React 組件的類名就是:
sample-enter
sample-enter-active
sample-leave
sample-leave-active
裝載時機
讀者可能會有一個疑問,為什么用 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 真的很適合來
做同構
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108055.html
摘要:在這篇文章中,分享了他如何克服恐懼并開始使用源代碼來提高他的知識和技能。不久之后,你正在閱讀的源代碼將引導您進入規范。 通過閱讀源碼來提高js知識 原文傳送門:《Improve Your JavaScript Knowledge By Reading Source Code》 showImg(https://segmentfault.com/img/remote/14600000197...
摘要:總結本文分析了在采用架構下的數據設計結構,在一個復雜的場景下,希望引起讀者對能有一個更深入的認識。 前幾天刷Twitter,發現Nicolas(Engineering at @twitter. Technical Lead for Twitter Lite)發布了這么一條推文: showImg(https://segmentfault.com/img/remote/1460000009...
摘要:司徒正美的一款了不起的化方案,支持到。行代碼內實現一個胡子大哈實現的作品其實就是的了源碼學習個人文章源碼學習個人文章源碼學習個人文章源碼學習個人文章這幾片文章的作者都是司徒正美,全面的解析和官方的對比。 前言 在過去的一個多月中,為了能夠更深入的學習,使用React,了解React內部算法,數據結構,我自己,從零開始寫了一個玩具框架。 截止今日,終于可以發布第一個版本,因為就在昨天,我...
閱讀 701·2021-11-18 10:02
閱讀 2235·2021-11-15 18:13
閱讀 3139·2021-11-15 11:38
閱讀 2934·2021-09-22 15:55
閱讀 3666·2021-08-09 13:43
閱讀 2438·2021-07-25 14:19
閱讀 2449·2019-08-30 14:15
閱讀 3441·2019-08-30 14:15