瀏覽深入淺出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 環境
getlnitialState 和getDefaultProps
ES5的 React. createClass 要領製造的組件類才會發作作用,已被 Facebook 官方逐步燒毀
render
render 函數應當是一個純函數,完整依據 this.state this.props 來決議返
回的結果,而且不要發生任何副作用。在 render 函數中去挪用 this.setState 毫無疑問是錯
誤的,由於一個純函數不應當激髮狀況的轉變
componentWillMount 和componentDidMount
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 的時刻不會被挪用
更新曆程
componentWillReceiveProps
1.只如果父組件的 render 函數被挪用,在 render 函數內里被誼染的子組件就會閱歷更新過
程,不論父組件傳給子組件的 props 有無轉變,都邑觸發子組件的 componentWillReceiveProps
函數
2、注重,經由歷程 this.setState 要領觸發的更新曆程不會挪用這個函數,這是由於這個函數
合適依據新的 props 值(也就是參數 nextProps )來盤算出是不是是要更新內部狀況 state
更新組件內部狀況的要領就是 this.setState ,如果 this.setState 的挪用致使 componentWillReceiveProps再一次被挪用,那就是一個死循環了
3、this.setState 不會激發這個函數 componentWillReceiveProps
被挪用
4、在 React 的組件組合中,完整能夠只襯着 個子組件,
而其他組件完整不需要襯着,這是進步 React 機能的主要體式格局
sholdComponentUpdate(nextProps, nextState)
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 中相互比對
componentWillUpdate和componentDidUpdate
1.如果組件的 shouldComponentUpdate 函數返回 true
2、當在效勞器端運用 React 襯着時,這一對函數中的 Did 函數,
也就是 componentDidUpdate 函數,並非只在瀏覽器端才實行的,不管更新曆程發作在
效勞器端照樣瀏覽器端,該函數都邑被挪用
3.React 組件被更新時,原有的內容被從新繪
制,這時刻就需要在 componentDidUpdate 函數再次挪用 jQuery 代碼
4.讀者能夠會問, componentDidUpdate 函數不是能夠會在效勞器端也被實行嗎?在
效勞器端怎樣能夠運用 jQuery 呢?現實上,運用 React 做效勞器端襯着時,基礎不會經
歷更新曆程,由於效勞器端只需要產出 HTML 字符串,一個裝載歷程就充足產出 HTML
了,所以一般狀況下效勞器端不會挪用 componentDidUpdate 函數,如果挪用了,申明我
們的遞次有毛病,需要革新
卸載歷程
componentWillU nmount
1、React 組件要從
DOM 樹上刪撤除之前,對應的 componentWillUnmount 函數會被挪用,所以這個函數適
合做一些清算性的事變
2、不過, componentWillUnmount 中的事變每每和 componentDidMount 有關,比方,在
componentDidMount 頂用非 React 的要領製造了一些 DOM 元素,如果撒手不論能夠會造
成內存泄漏,那就需要在 componentWillUnmount 中把這些製造的 DOM 元素清算掉
3 組件向外通報數據
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 不必費心,而且依據兩個參數的值充足能夠推導出數值是增添照樣削減
Flux的瑕玷
Store 之間依靠關聯
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 之間的依靠關聯,然則,如許顯著的模塊之間
的依靠,看着照樣讓人以為不大愜意,畢竟,最好的依靠治理是基礎不讓依靠發生
難以舉行效勞器端襯着
3.2 Redux
3.2.1 Redux 的基礎準繩
1. 唯一數據源
1).如果狀況數據疏散在多個 Store 中,輕易形成數據冗餘,如許數據一致性方面就會出
題目。 雖然運用 Dispatcher的 waitFor 要領能夠保證多個 Store 之間的更新遞次,然則這
又發生了差異 Store 之間的顯現依靠關聯,這類依靠關聯的存在增添了運用的龐雜度,容
易帶來新的題目
Redux 對這個題目的處理要領就是,全部運用只堅持一個 Store ,一切組件的數據源
就是這個 Store 上的狀況
2)Redux阻並沒有阻撓一個運用具有多個Store,只是,在Redux的框架下,讓一個應
用具有多個 Store 不會帶來任何優點,末了還不如運用一個 Store 更輕易構造代碼
2. 堅持狀況只讀( State is read-only);
修正 Store 的狀況,必需要經由歷程派發一個
action 對象完成,這一點 ,和 Flux 的要求並沒有什麼區分
3. 數據轉變只能經由歷程純函數完成(Changes are made with pure functions)
這裏所說的純函數就是 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 這句話道出
了軟件開闢中的一個真理
在盤算機編程的天下里,完成任何一件使命,能夠都有一百種以上的要領,然則無節制的天真度反而讓軟件難以保護增添限定是進步軟件質量的秘訣。
3.2.2 Redux 實例
製造一個 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 的存在,也不 會有兼容性題目,所以我們完整能夠放心運用
3.2.3 窯器組件和傻瓜組件
1.傻瓜組件 Counter 代碼的邏輯亘古未有的簡樸,只需一個 render 函數
1.CounterContainer ,這是容器組件,組件累贅了一切的和 Store 關聯的事變,它的 render 函數所做的就是襯着傻瓜組件 Counter 罷了,只擔任通報必要的 prop
3.2.4 組件 Context
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>,
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 的例子中沒有用到,我們在後
續章節中會有細緻引見
2.Provider :供應包含 store context
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 的唯一性
模塊化 React 末日 Redux 運用
4.1 模塊化運用要點
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
4.2.2 接功用構造
- actionTypes.js 定義 action 範例;
- actions. js定義 action 構造函數,決議了這個功用模塊能夠吸收的行動;
- reducer扣定義這個功用模塊怎樣響應 actions. 中定義的行動;
- views 目次,包含這個功用模塊中一切的 React 組件,包含傻瓜組件和容器組件;
- index.js 這個文件把一切的角色導人,然後一致導出
4.3 模塊接口
“在最抱負的狀況下,我們應當經由歷程增添代碼就可以增添體系的功用,而不是 經由歷程對現有代碼的修正來增添功用”
逐一-Robert C. Martin
4.4 狀況樹的設想
4.4.1 一個狀況節點只屬於一個模塊
比方,如果 模塊的 reducer 擔任修正狀況樹上 字段下的數據,那末另 個模塊
reducer 就不能夠有機遇修正 字段下的數據
4.4.2 防止冗餘數據
4.4.3 樹形構造扁平
4.5 Todo 運用實例
4.5.1 Todo 狀況設想
4.6 開闢輔助工具
工欲善其事,必先利其器 逐一《論語·衛靈公》
第五章 React 組件的機能優化
5.2 多個 React 組件的機能優化
5.2.1 React 的折衷(Reconciliation )歷程
1.節點範例差異的狀況
舉個例子 在更新之前,組件的構造是如許:
<div>
<Todos />
</div>
我們想要更新成如許:
<span>
<Todos />
</span>
這時刻, componentWillUnmount 要領會被挪用,取而代之的組件則會閱歷裝載歷程
的性命周期,組件的 componentWillMount render componentDidMount 要領順次被
挪用,一看根節點本來是 div ,新的根節點是 span ,範例就不一樣,切推倒重
來。
雖然是糟蹋,然則為了防止 O(N3)的時候龐雜度, React 必需要挑選 個更簡樸更快
捷的算法,也就只能採納這類體式格局
**作為開闢者,很顯然一定要防止上面如許糟蹋的情形湧現 所以, 一定要防止作為
包裹功用的節點範例被隨便轉變**
2.節點範例雷同的狀況
比方底本的節點用 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
- componentWillReceiveProps
- componentWillUpdate
- render
- componentDidUpdate
如果 shouldComponentUpdate 函數返回 false 的話,那末更新曆程
就此打住,不再繼續 所以為了堅持最大的機能,每一個 React 組件類必需要注重 shouldComponentUpdate
,如果發明基礎沒有必要從新襯着,那就可以夠直接返回 false
3 .多個子組件的狀況
<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 的作用
5.2.2 Key 的用法
用數組下標作為 key ,看起來 key 值是唯一的,然則卻不是穩固穩定的,跟着 todos
數組值的差異,一樣一個 Todoltem 實例在差異的更新曆程當中在數組中的下標完整能夠不
同,把下標當作 key 就讓 React 完全亂套了。
需要注重,雖然 key 是一個 prop ,然則吸收 key 的組件並不能讀取到 key 的值,因
key 和ref是 React 保留的兩個特別 prop ,並沒有預期讓組件直接接見。
5.3 用reselect 進步數據獵取機能
5.3.1 兩階段挑選歷程
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 中的一處數據就可以夠了
5.4 本章小結
1.運用 react-redux 供應的 shouldComponentUpdate 完成來進步
組件襯着功用的要領, 一個要訣就是防止通報給其他組件的 prop 值是 一個差異的對象,
不然會形成元謂的反覆襯着
2.不能隨便修正一個作為容器的 HTML 節點的範例 其次,關於動態數
量的同範例子組件,一 定要運用 key 這個 prop
3.運用 reselect 庫來完成高效的數據獵取。 由於 reselect 的緩存功
能,開闢者不必忌憚範式化的狀況樹會存在機能題目, Redux Store 的狀況樹應當根據范
式化準繩來設想,削減數據冗餘,如許利於堅持數據一致。
第六章 React 高等組件
“反覆是優異體系設想的大敵。 ”一-Robert C.Martin
6.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 <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.定義高階組件的意義安在呢?
- 起首,重用代碼
- 其次,修正現有 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 <WrappedComponent { ... otherProps} />
}
}
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 的歷史上,曾有如許一個重用代碼的處理要領就充足了
6.2 以函數為子組件
6.2.1 實例 CountDown
6.2.2 機能優化題目
第七章 Redux 和效勞器通訊
7.2.1 redux-thunk 中間件
運用 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 時用上這个中間件。
7.2.2 異步 action 對象
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 體系發生影響。
7.2.3 異步支配的形式
7.2.4 異步支配的中綴
關於接見效勞器如許的異步支配,從提議支配到支配完畢,都邑有段時候耽誤,在
這段耽誤時候中,用戶能夠願望中綴異步支配。
用戶也會舉行一些支配激發新的要求發往效勞器,而這就是我們開闢者需要斟酌的題目。
從用戶角度動身願望是末了一次挑選結果。
在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 的變量來存儲挪用編號。
7.3 Redux 異步支配的其他要領
- 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 的體式格局來完成動畫,
在後面的章節會細緻引見
10.1.2 劇本體式格局
劇本體式格局最大的優點就是更強的天真度,最原始的劇本體式格局就是運用 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 的行動。
10.2 ReactCSSTransitionGroup
React 供應了一個叫做 ReactCSSTransitionGroup 的功用協助完成動畫,為了運用這
個功用 ,起首要經由歷程 npm 裝置 react-addons-css-transition-group 這個庫,以後就可以夠導人
這個庫的內容:
import TransitionGroup from ’ react-addons- css-transition-group ’;
Transition Group 的事變就是協助組件完成裝載歷程和卸載歷程的動畫,而關於更新
歷程,並非 Transition Group 要處理的題目.
10.2.1 Todo 運用動畫
<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;
}
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 組件,
功用只需在被裝載以後才發揮,它本身都沒有被裝載,怎樣能夠發揮效能呢?
10.3 React-Motion 動畫庫
react-motion 是很優異的動畫庫,它採納的動
畫體式格局和 TransitionGroup 差異,是用劇本的體式格局。
10.4 本章小結
在這一章中,我們了解了網頁動畫的兩種完成體式格局, CSS3 體式格局和劇本體式格局,在 React
的天下,也有對應這兩種體式格局的動畫處理方案。
React 官方的 ReactCSSTransitionGroup ,能夠協助定製組件在裝載歷程和卸載歷程
中的動畫,關於更新曆程的動畫,則不在 ReactCSSTransitionGroup 斟酌之列,能夠直接用
CSS3 來完成。
React-Motion 庫供應了更壯大天真的動畫完成功用,運用“以函數為子組件”的模
式, React-Motion 只需要供應幾個組件,這些組件經由歷程定時向子組件供應動畫參數,就
能夠讓開闢者自在定義動畫的功用。
第十一章 多頁面運用
第十二章 同構
React Redux 都是完整在瀏覽器中運轉的,實在, React作為一
個發生用戶界面的 JavaScript 庫, Redux 作為一個治理運用數據的框架,二者也能夠
在效勞器端運轉。
抱負狀況下, 一個React 組件或許說功用組件既能夠在瀏覽器端襯着也能夠在效勞
器端襯着發生 HTML ,這類體式格局叫做“同構”( Isomorphic ),也就是同 份代碼能夠在不
同環境下運轉。
傳統的模板庫就是僵硬的字符串替代支配,不管怎樣優化都邑有它的極限,而且模
板的輸出依然是字符串,將 HTML 字符串插進去網頁的歷程,也就是 DOM 樹的支配,性
能也沒法優化 在前面的章節中我們引見過 React的 Virtual DOM 事變道理,合營性命
周期函數的運用,機能不是字符串替代的模板庫能夠對比的。
12.2 構建襯着動態內容效勞器
雖然 Face book 宣稱 React 並非給效勞器端襯着設想的,然則 React 真的很合適來
做同構