摘要:閱讀深入淺出和本書值得記錄的地方源碼第二章設(shè)計(jì)高質(zhì)量的組件檢查雖然能夠在開發(fā)階段發(fā)現(xiàn)代碼中的問題,但是放在產(chǎn)品環(huán)境中就不大合適現(xiàn)有的就具有這個(gè)功能,可以通過安裝,但是應(yīng)該確保只在發(fā)布產(chǎn)品代碼時(shí)使用它。
閱讀深入淺出react和redux本書值得記錄的地方
github源碼:https://github.com/mocheng/react-and-redux
第二章 設(shè)計(jì)高質(zhì)量的 React 組件 1 React prop propTypes 檢查import PropTypes from "prop-types";
Counter.propTypes = { caption: PropTypes.string.isRequired, initValue: PropTypes.number }
prop Types 雖然能夠在開發(fā)階段發(fā)現(xiàn)代碼中的問題,但是放在產(chǎn)品環(huán)境中就不大合
適,現(xiàn)有的 babel-react-optimize 就具有這個(gè)功能,可以通過 npm 安裝,但
是應(yīng)該確保只在發(fā)布產(chǎn)品代碼時(shí)使用它。
props默認(rèn)值 React的 defaultProps 功能,讓代碼更加容易讀懂
Counter 組件添加 defaultProps 的代碼如下:
Counter .defaultProps = { initValue: 0 }2 組件的生命周期 裝載過程 constructor
1 初始化 state ,因?yàn)榻M件生命周期中任何函數(shù)都可能要訪問 state ,那么整個(gè)生命
周期中第一個(gè)被調(diào)用的構(gòu)造函數(shù)自然是初始化 state 最理想的地方;
2 綁定成員函數(shù)的 this 環(huán)境
ES5的 React. createClass 方法創(chuàng)造的組件類才會(huì)發(fā)生作用,已經(jīng)被 Facebook 官方逐漸廢棄
renderrender 函數(shù)應(yīng)該是一個(gè)純函數(shù),完全根據(jù) this.state this.props 來決定返
回的結(jié)果,而且不要產(chǎn)生任何副作用。在 render 函數(shù)中去調(diào)用 this.setState 毫無疑問是錯(cuò)
誤的,因?yàn)橐粋€(gè)純函數(shù)不應(yīng)該引起狀態(tài)的改變
1 在裝載過程中, componentWil!Mount 會(huì)在調(diào)用 render 函數(shù)之前被調(diào)用, componentDidMount
會(huì)在調(diào)用 render 函數(shù)之后被調(diào)用,這兩個(gè)函數(shù)就像是 render 函數(shù)的前哨和后
衛(wèi),一前一后,把 render 函數(shù)夾住,正好分別做 render 前后必要的工作
2 componentWillMount 都是緊貼著自己組件的 render 函數(shù)之
前被調(diào)用, componentDidMount 可不是緊跟著 render 函數(shù)被調(diào)用,當(dāng)所有三個(gè)組件的
render 函數(shù)都被調(diào)用之后, 個(gè)組件的 componentDidMount 才連在一起被調(diào)用
之所以會(huì)有上面的現(xiàn)象,是因?yàn)?render 函數(shù)本身并不往 DOM 樹上渲染或者裝載內(nèi)
容,它只是返回 JSX 表示的對(duì)象,然后由 React 庫來根據(jù)返回對(duì)象決定如何渲染
React 庫肯定是要把所有組件返回的結(jié)果綜合起來,才能知道該如何產(chǎn)生對(duì)應(yīng)的 DOM
修改 所以,只有 React 庫調(diào)用 Counter 組件的 render 函數(shù)之后,才有可能完成裝
載,這時(shí)候才會(huì)依次調(diào)用各個(gè)組件的 componentDidMount 函數(shù)作為裝載過程的收尾
3 componentWilIMount componentDidMount 這對(duì)兄弟函數(shù)還有一個(gè)區(qū)別,就是 componentWillMount
可以在服務(wù)器端被調(diào)用,也可以在瀏覽器端被調(diào)用;而 component-DidMount
只能在瀏覽器端被調(diào)用,在服務(wù)器端使用 React 的時(shí)候不會(huì)被調(diào)用
1.只要是父組件的 render 函數(shù)被調(diào)用,在 render 函數(shù)里面被誼染的子組件就會(huì)經(jīng)歷更新過
程,不管父組件傳給子組件的 props 有沒有改變,都會(huì)觸發(fā)子組件的 componentWillReceiveProps
函數(shù)
2、注意,通過 this.setState 方法觸發(fā)的更新過程不會(huì)調(diào)用這個(gè)函數(shù),這是因?yàn)檫@個(gè)函數(shù)
適合根據(jù)新的 props 值(也就是參數(shù) nextProps )來計(jì)算出是不是要更新內(nèi)部狀態(tài) state
更新組件內(nèi)部狀態(tài)的方法就是 this.setState ,如果 this.setState 的調(diào)用導(dǎo)致 componentWillReceiveProps再一次被調(diào)用,那就是一個(gè)死循環(huán)了
3、this.setState 不會(huì)引發(fā)這個(gè)函數(shù) componentWillReceiveProps
被調(diào)用
4、在 React 的組件組合中,完全可以只渲染 個(gè)子組件,
而其他組件完全不需要渲染,這是提高 React 性能的重要方式
1、render 函數(shù)重要,是因?yàn)?render 函數(shù)決定了該渲染什么,而說 shouldComponentUpdate
函數(shù)重要,是因?yàn)樗鼪Q定了一個(gè)組件什么時(shí)候不需要渲染
2、說shouldComponentUpdate 重要,就是因?yàn)橹灰褂们‘?dāng),他就能夠大大提高 React
組件的性能,雖然 React 的渲染性能已經(jīng)很不錯(cuò)了,但是,不管渲染有多快,如果發(fā)現(xiàn)
沒必要重新渲染,那就干脆不用渲染好了,速度會(huì)更快
shouldComponentUpdate(nextProps, nextState) { return (nextProps.caption !== this.props.caption) || (nextState.count !== this.state.count); }
現(xiàn)在,只有當(dāng) caption 改變,或者 state 中的 count 值改變, shouldComponent 才會(huì)返回
true
3.通過 this setState 函數(shù)引發(fā)更新過程,并不是立刻更新組件的 state
值,在執(zhí)行到到函數(shù) shouldComponentUpdate 的時(shí)候, this state 依然是 this.setState 函數(shù)
執(zhí)行之前的值,所以我們要做的實(shí)際上就是在 nextProps nextState this.props this.state 中互相比對(duì)
1.如果組件的 shouldComponentUpdate 函數(shù)返回 true
2、當(dāng)在服務(wù)器端使用 React 渲染時(shí),這一對(duì)函數(shù)中的 Did 函數(shù),
也就是 componentDidUpdate 函數(shù),并不是只在瀏覽器端才執(zhí)行的,無論更新過程發(fā)生在
服務(wù)器端還是瀏覽器端,該函數(shù)都會(huì)被調(diào)用
3.React 組件被更新時(shí),原有的內(nèi)容被重新繪
制,這時(shí)候就需要在 componentDidUpdate 函數(shù)再次調(diào)用 jQuery 代碼
4.讀者可能會(huì)問, componentDidUpdate 函數(shù)不是可能會(huì)在服務(wù)器端也被執(zhí)行嗎?在
服務(wù)器端怎么能夠使用 jQuery 呢?實(shí)際上,使用 React 做服務(wù)器端渲染時(shí),基本不會(huì)經(jīng)
歷更新過程,因?yàn)榉?wù)器端只需要產(chǎn)出 HTML 字符串,一個(gè)裝載過程就足夠產(chǎn)出 HTML
了,所以正常情況下服務(wù)器端不會(huì)調(diào)用 componentDidUpdate 函數(shù),如果調(diào)用了,說明我
們的程序有錯(cuò)誤,需要改進(jìn)
1、React 組件要從
DOM 樹上刪除掉之前,對(duì)應(yīng)的 componentWillUnmount 函數(shù)會(huì)被調(diào)用,所以這個(gè)函數(shù)適
合做一些清理性的工作
2、不過, componentWillUnmount 中的工作往往和 componentDidMount 有關(guān),比如,在
componentDidMount 中用非 React 的方法創(chuàng)造了一些 DOM 元素,如果撒手不管可能會(huì)造
成內(nèi)存泄露,那就需要在 componentWillUnmount 中把這些創(chuàng)造的 DOM 元素清理掉
Counter.propTypes = { caption: PropTypes.string.isRequired, initValue: PropTypes.number, onUpdate: PropTypes.func }; Counter.defaultProps = { initValue: 0, onUpdate: f => f //默認(rèn)這個(gè)函數(shù)什么也不做 };
新增加的 prop 叫做 onUpdate ,類型是一個(gè)函數(shù),當(dāng) Counter 的狀態(tài)改變的時(shí)候,就
會(huì)調(diào)用這個(gè)給定的函數(shù),從而達(dá)到通知父組件的作用
這樣, Counter的 onUpdate 就成了作為子組件的 Counter 向父組件 ControlPanel
遞數(shù)據(jù)的渠道,我們先約定這個(gè)函數(shù)的第一個(gè)參數(shù)是 Counter 更新之后的新值,第二個(gè)
參數(shù)是更新之前的值,至于如何使用這兩個(gè)參數(shù)的值,是父組件 ControlPanel 的邏輯,
Counter 不用操心,而且根據(jù)兩個(gè)參數(shù)的值足夠可以推導(dǎo)出數(shù)值是增加還是減少
1.Flux 的體系中,如果兩個(gè) Store 之間有邏輯依賴關(guān)系,就必須用上 Dispatcher的
waitFor 函數(shù) 在上面的例子中我們已經(jīng)使用過 waitFor 函數(shù), SummaryStore對(duì) action 類型的
處理,依賴于 CounterStore 已經(jīng)處理過了 所以,必須要通過 waitFor 函數(shù)告訴 Dispatcher,
先讓 CounterStore 處理這些 action 對(duì)象,只有 CounterStore 搞定之后 SummaryStore才
繼續(xù)
2.那么, SummaryStore 如何標(biāo)識(shí) CounterStore 呢?靠的是 register 函數(shù)的返回值 dispatchToken
,而 dispatchToken 的產(chǎn)生,當(dāng)然是 CounterStore 控制的,換句話說,要這樣設(shè)計(jì):
1)CounterStore 必須要把注冊(cè)回調(diào)函數(shù)時(shí)產(chǎn)生的 dispatchToken 公之于眾;
2)SummaryStore 必須要在代碼里建立對(duì) CounterStore的 dispatchToken 的依賴
雖然 Flux 這個(gè)設(shè)計(jì)的確解決了 Store 之間的依賴關(guān)系,但是,這樣明顯的模塊之間
的依賴,看著還是讓人感覺不大舒服,畢竟,最好的依賴管理是根本不讓依賴產(chǎn)生
1).如果狀態(tài)數(shù)據(jù)分散在多個(gè) Store 中,容易造成數(shù)據(jù)冗余,這樣數(shù)據(jù)一致性方面就會(huì)出
問題。 雖然利用 Dispatcher的 waitFor 方法可以保證多個(gè) Store 之間的更新順序,但是這
又產(chǎn)生了不同 Store 之間的顯示依賴關(guān)系,這種依賴關(guān)系的存在增加了應(yīng)用的復(fù)雜度,容
易帶來新的問題
Redux 對(duì)這個(gè)問題的解決方法就是,整個(gè)應(yīng)用只保持一個(gè) Store ,所有組件的數(shù)據(jù)源
就是這個(gè) Store 上的狀態(tài)
2)Redux阻并沒有阻止一個(gè)應(yīng)用擁有多個(gè)Store,只是,在Redux的框架下,讓一個(gè)應(yīng)
用擁有多個(gè) Store 不會(huì)帶來任何好處,最后還不如使用一個(gè) Store 更容易組織代碼
修改 Store 的狀態(tài),必須要通過派發(fā)一個(gè)
action 對(duì)象完成,這一點(diǎn) ,和 Flux 的要求并沒有什么區(qū)別
這里所說的純函數(shù)就是 Reducer
在 Redux 中,一個(gè)實(shí)現(xiàn)同樣功能的 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 函數(shù)不光接受 action 為參數(shù),還接受 state 為參數(shù) 也就是說, Redux的
reducer 只負(fù)責(zé)計(jì)算狀態(tài),卻并不負(fù)責(zé)存儲(chǔ)狀態(tài)
“如果你愿意限制做事方式的靈活度,你幾乎總會(huì)發(fā)現(xiàn)可以做得更好?!?/p>
一一-John earmark
作為制作出《 Doom >< Quake 》這樣游戲的杰出開發(fā)者, John earmark 這句話道出
了軟件開發(fā)中的一個(gè)真諦
在計(jì)算機(jī)編程的世界里,完成任何一件任務(wù),可能都有一百種以上的方法,但是無節(jié)制的靈活度反而讓軟件難以維護(hù)增加限制是提高軟件質(zhì)量的法門。
創(chuàng)造一個(gè) src/Store 文件,這個(gè)文件輸出全局唯一的那個(gè) 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 函數(shù),這個(gè)函數(shù)第一個(gè)參數(shù)代表更
新狀態(tài)的 reducer ,第二個(gè)參數(shù)是狀態(tài)的初始值,第三個(gè)參數(shù)可選,代表 Store Enhancer,
在這個(gè)簡(jiǎn)單例子中用不上,在后面的章節(jié)中會(huì)詳細(xì)介紹
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 } };
擴(kuò)展操作符 (spread operator) 并不是因樺的一部分,甚至都不是 ES Next 刁飛 法的一部分,但是3.2.3 窯器組件和傻瓜組件
因?yàn)槠湔Z法簡(jiǎn)單,已經(jīng)被廣泛使用,因?yàn)?babel 的存在,也不 會(huì)有兼容性問題,所以我們完全可以放心使用
1.傻瓜組件 Counter 代碼的邏輯前所未有的簡(jiǎn)單,只有一個(gè) render 函數(shù)
1.CounterContainer ,這是容器組件,組件承擔(dān)了所有的和 Store 關(guān)聯(lián)的工作,它的 render 函數(shù)所做的就是渲染傻瓜組件 Counter 而已,只負(fù)責(zé)傳遞必要的 prop
1.Provider 也是一個(gè) React 組件,不過它的 render 函數(shù)就是簡(jiǎn)單地把子組件渲染出來,
在渲染上, 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 這樣命名的容器組件,而是直接導(dǎo)出了一個(gè)這樣的語句
export default connect(mapStateToProps, mapDispatchToProps), Counter);
1)第一眼看去,會(huì)讓人覺得這不是正常的 JavaScript 語法 其實(shí), connect是 react-redux
提供的一個(gè)方法,這個(gè)方法接收兩個(gè)參數(shù) mapStateToProps和 mapDispatch-ToProps ,執(zhí)行
結(jié)果依然是一個(gè)函數(shù),所以才可以在后面又加一個(gè)圓括號(hào),把 connect 函數(shù)執(zhí)行的結(jié)果立
刻執(zhí)行,這一次參數(shù)是 Counter 這個(gè)傻瓜組件。
2)這里有兩次函數(shù)執(zhí)行,第一次是 connect 函數(shù)的執(zhí)行,第二次是把 connect 函數(shù)返回
的函數(shù)再次執(zhí)行,最后產(chǎn)生的就是容器組件,功能相當(dāng)于前面 redux_smart_dumb 中的
CounterContainer;
3)這個(gè) connect 函數(shù)具體做了什么工作呢?
把 Store 上的狀態(tài)轉(zhuǎn)化為內(nèi)層傻瓜組件的 prop;
把內(nèi)層傻瓜組件中的用戶動(dòng)作轉(zhuǎn)化為派送給 Store 的動(dòng)作
4)mapStateToProps函數(shù)
function mapStateToProps(state ,ownProps) { return { value: state[ownProps.caption] } }
5)mapDispatchToProps函數(shù)
function mapDispatchToProps(dispatch, ownProps) { return { nincrement () => { dispatch(Actions.increment(ownProps.caption)); } onDecrement : () => { dispatch(Actions.decrement(ownProps.caption)); } } }
6)mapStateToProps和 mapDispatchToProps 都可以包含第二個(gè)參數(shù),代表 ownProps,
也就是直接傳遞給外層容器組件的 props ,在 ControlPanel 的例子中沒有用到,我們?cè)诤?br>續(xù)章節(jié)中會(huì)有詳細(xì)介紹
react-redux 和我們例子中的 Provider 幾乎一樣,但是更加嚴(yán)謹(jǐn),比如我們只要求 store 屬性是一個(gè) object ,而react-redux 要求 store 不光是 object ,而且是必須包含三個(gè)函數(shù)的 object ,這三個(gè)函數(shù)
分別是
subscribe
dispatch
getState
擁有上述 3個(gè)函數(shù)的對(duì)象,才能稱之為一個(gè) Redux 的store;
另外, react-redux 定義了 Provider的 componentWillReceiveProps 函數(shù),在 React組
件的生命周期中, componentWillReceiveProps 函數(shù)在每次重新渲染時(shí)都會(huì)調(diào)用到, react-redux在
componentWillReceiveProps 函數(shù)中會(huì)檢查這一次渲染時(shí)代表 store的 prop 和上
一次的是否一樣。 如果不一樣,就會(huì)給出警告,這樣做是為了避免多次渲染用了不同的
Redux Store。 每個(gè) Redux 應(yīng)用只能有一個(gè) Redux Store ,在整個(gè) Redux 的生命周期中都
應(yīng)該保持 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 構(gòu)造函數(shù),決定了這個(gè)功能模塊可以接受的動(dòng)作;
reducer扣定義這個(gè)功能模塊如何相應(yīng) actions. 中定義的動(dòng)作;
views 目錄,包含這個(gè)功能模塊中所有的 React 組件,包括傻瓜組件和容器組件;
index.js 這個(gè)文件把所有的角色導(dǎo)人,然后統(tǒng)一導(dǎo)出
4.3 模塊接口“在最理想的情況下,我們應(yīng)該通過增加代碼就能增加系統(tǒng)的功能,而不是 通過對(duì)現(xiàn)有代碼的修改來增加功能"4.4 狀態(tài)樹的設(shè)計(jì) 4.4.1 一個(gè)狀態(tài)節(jié)點(diǎn)只屬于一個(gè)模塊一一-Robert C. Martin
比如,如果 模塊的 reducer 負(fù)責(zé)修改狀態(tài)樹上 字段下的數(shù)據(jù),那么另 個(gè)模塊
reducer 就不可能有機(jī)會(huì)修改 字段下的數(shù)據(jù)
工欲善其事,必先利其器 一一《論語·衛(wèi)靈公》第五章 React 組件的性能優(yōu)化 5.2 多個(gè) React 組件的性能優(yōu)化 5.2.1 React 的調(diào)和(Reconciliation )過程 1.節(jié)點(diǎn)類型不同的情況
舉個(gè)例子 在更新之前,組件的結(jié)構(gòu)是這樣:
我們想要更新成這樣:
這時(shí)候, componentWillUnmount 方法會(huì)被調(diào)用,取而代之的組件則會(huì)經(jīng)歷裝載過程
的生命周期,組件的 componentWillMount render componentDidMount 方法依次被
調(diào)用,一看根節(jié)點(diǎn)原來是 div ,新的根節(jié)點(diǎn)是 span ,類型就不一樣,切推倒重
來。
雖然是浪費(fèi),但是為了避免 O(N3)的時(shí)間復(fù)雜度, React 必須要選擇 個(gè)更簡(jiǎn)單更快
捷的算法,也就只能采用這種方式
**作為開發(fā)者,很顯然一定要避免上面這樣浪費(fèi)的情景出現(xiàn) 所以, 一定要避免作為
包裹功能的節(jié)點(diǎn)類型被隨意改變**
比如原本的節(jié)點(diǎn)用 JSX 表示是這樣:
Hello World
改變之后的 JSX 表示是這樣:
Good Bye
React 能做的只是根據(jù)新節(jié)點(diǎn)的 props 去更新原來根節(jié)點(diǎn)的組件實(shí)例,
引發(fā)這個(gè)組件實(shí)例的更新過程,也就是按照順序引發(fā)下列函數(shù):
shouldComponentUpdate
componentWillReceiveProps
componentWillUpdate
render
componentDidUpdate
如果 shouldComponentUpdate 函數(shù)返回 false 的話,那么更新過程
就此打住,不再繼續(xù) 所以為了保持最大的性能,每個(gè) React 組件類必須要重視 shouldComponentUpdate
,如果發(fā)現(xiàn)根本沒有必要重新渲染,那就可以直接返回 false
在更新之后,用 JSX 表示是這樣:
那么 React 會(huì)發(fā)現(xiàn)多出了一個(gè) Todoltem ,會(huì)創(chuàng)建一個(gè)新的 Todoltem 組件實(shí)例,這個(gè)
Todoltem 組件實(shí)例需要經(jīng)歷裝載過程,對(duì)于前兩個(gè) Todoltem 實(shí)例, React 會(huì)引發(fā)它們的
更新過程,但是只要 To do Item shouldComponentUpdate 函數(shù)實(shí)現(xiàn)恰當(dāng),檢查 props
后就返回 false 的話,就可以避免實(shí)質(zhì)的更新操作
從直觀上看,內(nèi)容是“ Zero ,,的新加待辦事項(xiàng)被插在了第一位,只需要?jiǎng)?chuàng)造一個(gè)新
的組件 Todoltem 實(shí)例放在第一位,剩下兩個(gè)內(nèi)容為“ First ”和“ Second ,,的 Todoltem
實(shí)例經(jīng)歷更新過程,但是因?yàn)?props 沒有改變,所以 shouldComponentUpdate 可以幫助
這兩個(gè)組件不做實(shí)質(zhì)的更新動(dòng)作。
可是實(shí)際情況并不是這樣 如果要讓 React 按照上面我們構(gòu)想的方式來做,就必須
要找出兩個(gè)子組件序列的不同之處,現(xiàn)有的計(jì)算出兩個(gè)序列差異的算法時(shí)間是 O(N2),雖
然沒有樹形結(jié)構(gòu)比較的 O(N3)時(shí)間復(fù)雜度那么夸張,但是也不適合一個(gè)對(duì)性能要求很高
的場(chǎng)景,所以 React 選擇看起來很傻的一個(gè)辦法,不是尋找兩個(gè)序列的精確差別,而是
直接挨個(gè)比較每個(gè)子組件。
, React 并不是沒有意識(shí)到這個(gè)問題,所以 React 提供了方法來克服這種浪費(fèi),
不過需要開發(fā)人員在寫代碼的時(shí)候提供一點(diǎn)小小的幫助,這就是 key 的作用
用數(shù)組下標(biāo)作為 key ,看起來 key 值是唯一的,但是卻不是穩(wěn)定不變的,隨著 todos
數(shù)組值的不同,同樣一個(gè) Todoltem 實(shí)例在不同的更新過程中在數(shù)組中的下標(biāo)完全可能不
同,把下標(biāo)當(dāng)做 key 就讓 React 徹底亂套了。
需要注意,雖然 key 是一個(gè) prop ,但是接受 key 的組件并不能讀取到 key 的值,因
key 和ref是 React 保留的兩個(gè)特殊 prop ,并沒有預(yù)期讓組件直接訪問。
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) }; }
既然這個(gè) selectVisibleTodos 函數(shù)的計(jì)算必不可少,那如何優(yōu)化呢?
如果上 次的計(jì)算結(jié)果被緩存起來的話,那就可以重用緩存的
數(shù)據(jù)。
這就是 reselect 庫的工作原理:只要相關(guān)狀態(tài)沒有改變,那就直接使用上一次的緩存
結(jié)果
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 提供了創(chuàng)造選擇器的 createSelector 函數(shù) ,這是一個(gè)高階函數(shù),也就是接受
函數(shù)為參數(shù)來產(chǎn)生一個(gè)新函數(shù)的函數(shù)
第一個(gè)參數(shù)是一個(gè)函數(shù)數(shù)組,每個(gè)元素代表了選擇器步驟一需要做的映射計(jì)算,這
里我們提供了兩個(gè)函數(shù) getFilte和 getTodos ,對(duì)應(yīng)代碼如下:
const getFilter = (state) => state.filter; const getTodos = (state) => state.todos;5.3.2 范式化狀態(tài)樹
所謂范式化,就是遵照關(guān)系型數(shù)據(jù)庫的設(shè)計(jì)原則,減少冗余數(shù)據(jù).
如果使用反范式化的設(shè)計(jì),那么狀態(tài)樹上的數(shù)據(jù)最好是能夠不用計(jì)算拿來就能用,
在Redux Store 狀態(tài)樹的 todos 字段保存的是所有待辦事項(xiàng)數(shù)據(jù)的數(shù)組,對(duì)于每個(gè)數(shù)組元
素,反范式化的設(shè)計(jì)會(huì)是類似下面的對(duì)象:
{ id: 1, //待辦事項(xiàng)id text :”待辦事項(xiàng) ”,//待辦事項(xiàng)文字內(nèi)容 completed : false,//是否已完成 type: { //種類 name :”緊急”,//種類的名稱 color:”red” //種類的顯示顏色 } }
但這也有缺點(diǎn),當(dāng)需要改變某種類型的名稱和顏色時(shí),
不得不遍歷所有 Todoltem 數(shù)據(jù)來完成改變.
反范式化數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)就是讀取容易,修改比較麻煩
如果使用范式化的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì),那么 Redux Store 上代表 Todoltem 的一條數(shù)據(jù)是
類似下面的對(duì)象:
{ id: 1 , text :”待辦事項(xiàng) l ”, completed : false , typeid: 1 //待辦事項(xiàng)所屬的種類id }
用一個(gè)typeId 代表類型,然后在 Redux Store 上和 to dos 平級(jí)的根節(jié)點(diǎn)位置創(chuàng)建一個(gè)
types 字段,內(nèi)容是一 個(gè)數(shù)組,每個(gè)數(shù)組元素代表 一個(gè)類型,一個(gè)種類的數(shù)據(jù)是類似下面
的對(duì)象:
{ id: 1 , //種類 name :” 緊急 ”, //種類的名稱 color :”red” //種類的顯示顏 }
當(dāng)Todoltem 組件要渲染內(nèi)容時(shí)從 Redux Store 狀態(tài)樹的 to dos 宇段下獲取的數(shù)據(jù)
是不夠的,因?yàn)橹挥?typeId。
這個(gè)過程當(dāng)然要花費(fèi)一 些時(shí)間,但是當(dāng)要改變某個(gè)種類的名稱或者顏色時(shí),就異常
地簡(jiǎn)單,只需要修改 types 中的一處數(shù)據(jù)就可以了
1.利用 react-redux 提供的 shouldComponentUpdate 實(shí)現(xiàn)來提高
組件渲染功能的方法, 一個(gè)要訣就是避免傳遞給其他組件的 prop 值是 一個(gè)不同的對(duì)象,
不然會(huì)造成元謂的重復(fù)渲染
2.不能隨意修改一個(gè)作為容器的 HTML 節(jié)點(diǎn)的類型 其次,對(duì)于動(dòng)態(tài)數(shù)
量的同類型子組件,一 定要使用 key 這個(gè) prop
3.利用 reselect 庫來實(shí)現(xiàn)高效的數(shù)據(jù)獲取。 因?yàn)?reselect 的緩存功
能,開發(fā)者不用顧忌范式化的狀態(tài)樹會(huì)存在性能問題, Redux Store 的狀態(tài)樹應(yīng)該按照范
式化原則來設(shè)計(jì),減少數(shù)據(jù)冗余,這樣利于保持?jǐn)?shù)據(jù)一致。
“重復(fù)是優(yōu)秀系統(tǒng)設(shè)計(jì)的大敵。 ”一-Robert C.Martin6.1 高階組件
1.高階組件( Higher Order Component, HOC )并不是 React 提供的某種 API ,而是使用
React 的一種模式,用于增強(qiáng)現(xiàn)有組件的功能。
2.簡(jiǎn)單來說,一個(gè)高階組件就是一個(gè)函數(shù),這個(gè)函數(shù)接受一個(gè)組件作為輸入,然后返回一個(gè)新的組件作為結(jié)果,而且,返回的新組件擁有了輸入組件所不具有的功能
3.這里提到的組件指的并不是組件實(shí)例,而是一個(gè)組件類,也可以是一個(gè)無狀態(tài)組件
的函數(shù)。
4.我們先看一個(gè)非常簡(jiǎn)單的高階組件的例子,感受一下高階組件是如何工作的,代碼
如下:
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 ,這個(gè)高階組件返回的組件則完全無視這個(gè) prop。
5.假如我們現(xiàn)在不希望某個(gè)組件接收到 user prop ,那么我們就不要直接使用這個(gè)組
件,而是把這個(gè)組件作為參數(shù)傳遞給 removeU serProp 函數(shù),然后我們把這個(gè)函數(shù)的返回
結(jié)果當(dāng)做組件來使用:
const NewComponent = removeUserProp(SampleComponent) ;
在上面的代碼中, NewComponent 擁有和 SampleComponent 完全一樣的行為,唯一
的區(qū)別就是即使傳遞 user 屬性給它,它也會(huì)當(dāng)沒有 user 來處理。
6.定義高階組件的意義何在呢?
首先,重用代碼
其次,修改現(xiàn)有 React 組件的行為
6.1.1 代理方式的高階組件1.上面的 removeUserProp 例子就是一個(gè)代理方式的高階組件,特點(diǎn)是返回的新組件類
直接繼承自 React. Component 新組件扮演的角色是傳入?yún)?shù)組件的一個(gè)“代理”,在
新組建的 render 函數(shù)中,把被包裹組件渲染出來,除了高階組件自己要做的工作,其余
功能全都轉(zhuǎn)手給了被包裹的組件.
2.如果高階組件要做的功能不涉及除了 render 之外的生命周期函數(shù),也不需要維護(hù)自
己的狀態(tài),那也可以干脆返回一個(gè)純函數(shù),像上面的 removeUserProp ,代碼可以簡(jiǎn)寫成
下面這樣:
function removeUserProp(WrappedComponent) { return function newRender(props) { con st {user, ... otherProps) = props; return} }
3.代理方式的高階組件,可以應(yīng)用在下列場(chǎng)景中:
操縱 prop;
訪問 ref;
抽取狀態(tài);
包裝組件
6.1.2 繼承方式的高階組件 1. 操縱 Props 2. 操縱生命周期函數(shù)例如,我們可以定義一個(gè)高階組件,讓參數(shù)組件只有在用戶登錄時(shí)才顯示,代碼
如下:
const onlyForLoggedinHOC = (WrappedComponent) => { return class NewComponent extends WrappedComponent { render () { if (this.props.loggedin) { return super.render(); } else { return null; } } } }
又例如,我們可以重新定義 shouldComponentUpdate 函數(shù),只要 prop 中的 useCache
不為邏輯 false 就不做重新渲染的動(dòng)作,代碼如下:
const cacheHOC = (WrappedComponent) => { return class NewComponent extends WrappedComponent { shouldComponentUpdate(nextProps, nextState) { return !nextProps.useCache; } } }6.1.3 高階組件的顯示名 6.1.4 曾經(jīng)的 React Mixin
在 ES6的 React組件類定義方法中不能使用 Mixin, React 官方也很明確聲明 Mixin 是應(yīng)該被廢棄的方法
所以我們只需要知道在 React 的歷史上,曾經(jīng)有這樣一個(gè)重用代碼的解決方法就足夠了
使用 Redux 訪問服務(wù)器,同樣要解決的是異步問題
Redux 的單向數(shù)據(jù)流是同步操作,驅(qū)動(dòng) Redux 流程的 ac tion 對(duì)象, 每一個(gè) action
對(duì)象被派發(fā)到 Store 上之后,同步地被分配給所有的 reducer 函數(shù),每個(gè) reducer 都是純
函數(shù),純函數(shù)不產(chǎn)生任何副作用,自然是完成數(shù)據(jù)操作之后立刻同步返回, reducer 返回
的結(jié)果又被同步地拿去更新 Store 上的狀態(tài)數(shù)據(jù),更新狀態(tài)數(shù)據(jù)的操作會(huì)立刻被同步給監(jiān)
Store 狀態(tài)改變的函數(shù),從而引發(fā)作為視圖的 React 組件更新過程。
實(shí)際上, re dux-thunk 的實(shí)現(xiàn)極其簡(jiǎn)單,只有幾行代碼。
假如有一個(gè) JavaScript 函數(shù)f 如下定義:
const f = (x) => { return x () + 5; }
f把輸入?yún)?shù)x 當(dāng)做一個(gè)子程序來執(zhí)行,結(jié)果加上5 就是f 的執(zhí)行結(jié)果,那么我們?cè)?br>著調(diào)用一次 f:
const g = () => { return 3 + 4 ; } f (g); 11 結(jié)果是( 3+4 )巧= 37
上面代碼中函數(shù)f 就是一個(gè) thunk ,這樣使用看起來有點(diǎn)奇怪,但有個(gè)好處就是g的
執(zhí)行只有在f 實(shí)際執(zhí)行時(shí)才執(zhí)行,可以起到延遲執(zhí)行的作用,我們繼續(xù)看 redux-thunk的
用法來理解其意義。
按照 redux-thunk 的想法,在 Redux 的單向數(shù)據(jù)流中,在 action 對(duì)象被 reducer 函數(shù)
處理之前,是插入異步功能的時(shí)機(jī)
在Redux 架構(gòu)下,一個(gè) action 對(duì)象在通過 store.dispatch派發(fā),在調(diào)用 reducer 函數(shù)
之前,會(huì)先經(jīng)過 個(gè)中間件的環(huán)節(jié),這就是產(chǎn)生異步操作的機(jī)會(huì),實(shí)際上 redux-thunk提
供的就是一個(gè)Redux 中間件,我們需要在創(chuàng)建 Store 時(shí)用上這個(gè)中間件。
redux-也unk 的工作是檢查 action 對(duì)象是不是函數(shù),如果不是函數(shù)就放行,完成普通
action 對(duì)象的生命周期,而如果發(fā)現(xiàn) action 對(duì)象是函數(shù),那就執(zhí)行這個(gè)函數(shù),并把 Store的
dispatch 函數(shù)和 getState 函數(shù)作為參數(shù)傳遞到函數(shù)中去,處理過程到此為止,不會(huì)讓
這個(gè)異步 action 對(duì)象繼續(xù)往前派發(fā)到 reducer 函數(shù)
舉一個(gè)并不涉及網(wǎng)絡(luò) API 訪問的異步操作例子 ,在 Co unter 組件中存在一個(gè)普通的
同步增加計(jì)數(shù)的 action 構(gòu)造函數(shù) increment ,代碼如下:
const increment= () => ({ type: ActionTypes.INCREMENT, });
派發(fā) increment 執(zhí)行返回的 action 對(duì)象, Redux 會(huì)同步更新 Store 狀態(tài)和視圖,但是
我們現(xiàn)在想要?jiǎng)?chuàng)造一個(gè)功能,能夠發(fā)出一個(gè)“讓 Counter 組件在 秒之后計(jì)數(shù)加一”的
指令,這就需要定義一個(gè)新的異步 action 構(gòu)造函數(shù),代碼如下:
const incrementAsync = () => { return (dispatch) => { set Timeout ( () => { dispatch (increment()); },1000); } }
異步 action 構(gòu)造函數(shù) incrementAsync 返回的是一個(gè)新的函數(shù),這樣一 個(gè)函數(shù)被
dispatch 函數(shù)派發(fā)之后,會(huì)被 redux-thunk 中間件執(zhí)行,于是 setTimeout 函數(shù)就會(huì)發(fā)生作
用,在 1秒之后利用參數(shù) dispatch 函數(shù)派發(fā)出同步 action 構(gòu)造函數(shù) increment 的結(jié)果。
這就是異步 action 的工作機(jī)理,這個(gè)例子雖然簡(jiǎn)單,但是可以看得出來,異步
action 最終還是要產(chǎn)生同步 action 派發(fā)才能對(duì) Redux 系統(tǒng)產(chǎn)生影響。
對(duì)于訪問服務(wù)器這樣的異步操作,從發(fā)起操作到操作結(jié)束,都會(huì)有段時(shí)間延遲,在
這段延遲時(shí)間中,用戶可能希望中止異步操作。
用戶也會(huì)進(jìn)行一些操作引發(fā)新的請(qǐng)求發(fā)往服務(wù)器,而這就是我們開發(fā)者需要考慮的問題。
從用戶角度出發(fā)希望是最后一次選擇結(jié)果。
在jQuery 中,可以通過 abort 方法取消掉一個(gè) AJAX 請(qǐng)求:
const xhr = $.ajax( ... ); xhr.abort {);//取消掉已經(jīng)發(fā)出的AJAX請(qǐng)求
但是,很不幸,對(duì)于 fetch 沒有對(duì)應(yīng) abort 函數(shù)的功能,因?yàn)?fetch 返回的是一個(gè)
Promise 對(duì)象,在 ES6 的標(biāo)準(zhǔn)中, Promise 對(duì)象是不存在“中斷”這樣的概念的.
既然 fetch 不能幫助我們中止一個(gè) API 請(qǐng)求,那就只能在應(yīng)用層實(shí)現(xiàn)“中斷”的效
果,有一個(gè)技巧可以解決這個(gè)問題,只需要修改 action 構(gòu)造函數(shù)。
let nextSeqid = 0; export const fetchWeather = (cityCode) => { return (dispatch) => { const apiUrl =、/ data/cityinfo/${cityCode).html const seqid = ++ nextSeqid; const dispatchifValid = (action) => { if (seqid === nextSeqid) { //**這里一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)請(qǐng)求`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 構(gòu)造函數(shù)文件中定義一個(gè)文件模塊級(jí)的 nextSeqld 變量,這是一個(gè)遞增的整
數(shù)數(shù)字,給每一個(gè)訪問 API 的請(qǐng)求做序列編號(hào)。
這里一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)請(qǐng)求id如果不相等,就拋棄。
如果還不明白,另外用vue的例子來說明:
{{cont}}
控制臺(tái)結(jié)果:
- 1 1 "請(qǐng)求" - (index):55 2 2 "請(qǐng)求" - (index):60 1 2 "請(qǐng)求完成" - (index):55 3 3 "請(qǐng)求" - (index):60 2 3 "請(qǐng)求完成" - (index):55 4 4 "請(qǐng)求" - (index):60 3 4 "請(qǐng)求完成" - (index):55 5 5 "請(qǐng)求" - (index):60 4 5 "請(qǐng)求完成" - (index):55 6 6 "請(qǐng)求" - (index):60 5 6 "請(qǐng)求完成" - (index):55 7 7 "請(qǐng)求" - (index):60 6 7 "請(qǐng)求完成" - (index):60 7 7 "請(qǐng)求完成"
你會(huì)發(fā)現(xiàn)在重復(fù)請(qǐng)求,請(qǐng)求id不對(duì)應(yīng),所以不渲染,只有當(dāng)相等菜渲染。
if (seqid === this.nextSeqid) { this.cont=city; }
雖然不能真正“中止”一個(gè) API 請(qǐng)求,但是我們可以用這種方法讓一個(gè) API 請(qǐng)求的
結(jié)果被忽略,達(dá)到了中止一個(gè) API 請(qǐng)求一樣的效果。
在這個(gè)例子中 Weather 模塊只有一種API 請(qǐng)求,所以一個(gè) API 調(diào)用編號(hào)序列就足夠,
如果需要多種 API 請(qǐng)求,則需要更多類似nextSeqld 的變量來存儲(chǔ)調(diào)用編號(hào)。
redux-saga
redux-effects
redux-side-effects
redux-loop
redux-observable
第八章 單元測(cè)試 第九章 擴(kuò)展 Redux 9.1 中間件中間件的特點(diǎn)是:
中間件是獨(dú)立的函數(shù);
中間件可以組合使用;
中間件有一個(gè)統(tǒng)一的接口
第十章 動(dòng)畫 10.1.1 css方式運(yùn)行效率要比腳本方式高,因?yàn)闉g覽器原生支持,省去了 Java
Script 的解釋執(zhí)行負(fù)擔(dān),有的瀏覽器(比如 Chrome 瀏覽器)甚至還可以充分利用 GPU加
速的優(yōu)勢(shì),進(jìn)一步增強(qiáng)了動(dòng)畫渲染的性能
時(shí)間和速度曲線的不合理是 CSS3 先天的屬性更讓開發(fā)者頭疼的就是開發(fā) CSS3
則的過程,尤其是對(duì) tra nsition-duration 時(shí)間很短的動(dòng)畫調(diào)試,因?yàn)?CSS3 transition
程總是一閃而過,捕捉不到中間狀態(tài),只能一遍一遍用肉眼去檢驗(yàn)動(dòng)畫效果,用 CSS3
做過復(fù)雜動(dòng)畫的開發(fā)者肯定都深有體會(huì)
雖然 CSS3 有這樣一些缺點(diǎn),但是因?yàn)槠錈o與倫比的性能,用來處理一些簡(jiǎn)單的動(dòng)
畫還是不錯(cuò)的選擇
React 提供的 ReactCSSTransitionGroup 功能,使用的就是 CSS3 的方式來實(shí)現(xiàn)動(dòng)畫,
在后面的章節(jié)會(huì)詳細(xì)介紹
腳本方式最大的好處就是更強(qiáng)的靈活度,最原始的腳本方式就是利用 setlnterval 或者 setTimeout 來實(shí)現(xiàn)。
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);
在上面的例子中,有一個(gè)常量 ANIMATION INTERVAL 定義為 16 , setlnterval 以這
個(gè)常盤為間隔,每 16 毫秒計(jì)算一次 sample 元素的 left 值,每次都根據(jù)時(shí)間推移按比例增加 left 的值,直到 left 大于 400.
為什么要選擇 16 毫秒呢?因?yàn)槊棵脘秩?60 幀(也叫 60fps, 60 Frame Per Second)
會(huì)給用戶帶來足夠流暢的視覺體驗(yàn),一秒鐘有 1000 毫秒, 1000/60約等于16 ,也就是說,如
果我們做到每 16 毫秒去渲染一次畫面,就能夠達(dá)到比較流暢的動(dòng)畫效果。
對(duì)于簡(jiǎn)單的動(dòng)畫, setlnterval 方式勉強(qiáng)能夠及格,但是對(duì)于稍微復(fù)雜一些的動(dòng)畫,腳
本方式就頂不住了,比如渲染一幀要花去超過 32 毫秒的時(shí)間,那么還用 16 毫秒一個(gè)間
隔的方式肯定不行 實(shí)際上,因?yàn)橐粠秩疽加镁W(wǎng)頁線程 32 毫秒,會(huì)導(dǎo)致 setlnterval
根本無法以 16 毫秒間隔調(diào)用渲染函數(shù),這就產(chǎn)生了明顯的動(dòng)畫滯后感,原本一秒鐘完
成的動(dòng)畫現(xiàn)在要花兩秒鐘完成,所以這種原始的 setlnterval 方式是肯定不適合復(fù)雜的動(dòng)
畫的。
出現(xiàn)上面問題的本質(zhì)原因是 setlnterval和setTimeout 并不能保證在指定時(shí)間間隔或
者延遲的情況下準(zhǔn)時(shí)調(diào)用指定函數(shù) 所以可以換 個(gè)思路,當(dāng)指定函數(shù)調(diào)用的時(shí)候,根
據(jù)逝去的時(shí)間計(jì)算當(dāng)前這一幀應(yīng)該顯示成什么樣子,這樣即使因?yàn)闉g覽器渲染主線程忙
碌導(dǎo)致一幀渲染時(shí)間超過 16 毫秒,在后續(xù)幀誼染時(shí)至少內(nèi)容不會(huì)因此滯后,即使達(dá)不倒
60fps 的效果,也能保證動(dòng)畫在指定時(shí)間內(nèi)完成。
下面是一個(gè)這種方法實(shí)現(xiàn)動(dòng)畫的例子,首先我們實(shí)現(xiàn)一個(gè) raf 函數(shù), 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 函數(shù)參數(shù)是真正的渲染過程, raf 只是協(xié)調(diào)渲染的節(jié)奏。
raf 盡量以每隔 16 毫秒的速度去調(diào)用傳染的fn參數(shù),如果發(fā)現(xiàn)上一次被調(diào)用時(shí)間和
這一次被調(diào)用時(shí)間相差不足 16 毫秒,就會(huì)保持 16 毫秒一次的渲染間隔繼續(xù),如果發(fā)現(xiàn)
兩次調(diào)用時(shí)間間隔已經(jīng)超出了 16 毫秒,就會(huì)在下 次時(shí)鐘周期立刻調(diào)用 fn。
還是讓 id 為sample 的元素向右移動(dòng)的例子,我們定義渲染每一幀的函數(shù) 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 函數(shù)中根據(jù)當(dāng)前時(shí)間和開始動(dòng)圓的時(shí)間差來計(jì)算 sample 元素的 left屬
性,這樣無論 render 函數(shù)何時(shí)被調(diào)用,總能夠渲染出正確的結(jié)果。
最后,我們將 render 作為參數(shù)傳遞給 raf ,啟動(dòng)了動(dòng)畫過程:
raf (render);
實(shí)際上, 現(xiàn)代瀏覽器提供了 一個(gè)新 的函數(shù) requestAnimationFrame ,采用的就是
上面描述的思路,不是以固定 16 毫秒間隔的時(shí)間去調(diào)用渲染過程,而是讓腳本通過
requestAnimationFrame 傳一 個(gè)回調(diào)函數(shù),表示想要渲染一幀畫面,瀏覽器會(huì)決定在合
適的時(shí)間來調(diào)用給定的回調(diào)函數(shù),而回調(diào)函數(shù)的工作是要根據(jù)逝去的時(shí)間來決定將界面
渲染成什么樣子。
這樣一來,渲染動(dòng)面的方式就改成按需要來渲染,而不是每隔 16 毫秒渲染固定的幀內(nèi)容。
不是所有瀏覽器都支持 requestAnimationFrame ,對(duì)于不支持這個(gè)函數(shù)的瀏覽器,可
以使用上面 raf 函數(shù)的方式模擬 requestAnimationFrame 的行為。
React 提供了一個(gè)叫做 ReactCSSTransitionGroup 的功能幫助實(shí)現(xiàn)動(dòng)畫,為了使用這
個(gè)功能 ,首先要通過 npm 安裝 react-addons-css-transition-group 這個(gè)庫,之后就可以導(dǎo)人
這個(gè)庫的內(nèi)容:
import TransitionGroup from ’ react-addons- css-transition-group ’;
Transition Group 的工作就是幫助組件實(shí)現(xiàn)裝載過程和卸載過程的動(dòng)畫,而對(duì)于更新
過程,并不是 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 規(guī)則
假設(shè) transitionName sample ,那么定制相關(guān) React 組件的類名就是:
sample-enter
sample-enter-active
sample-leave
sample-leave-active
裝載時(shí)機(jī)
讀者可能會(huì)有一個(gè)疑問,為什么用 TransitionGroup在 todoList.js 文件中包住所有
Todoltem 組件實(shí)例的數(shù)組,而不是讓 TransitionGroup在 todoltem.js 文件中包住單個(gè)
Todoltem 組件呢?
看起來應(yīng)該能實(shí)現(xiàn)同樣效果,但實(shí)際上這樣做不行 因?yàn)?TransitionGroup 要發(fā)揮作
用,必須自身已經(jīng)完成裝載了 這很好理解, Transition Group 也只是一個(gè) React 組件,
功能只有在被裝載之后才能發(fā)揮,它自己都沒有被裝載,怎么可能發(fā)揮效力呢?
react-motion 是很優(yōu)秀的動(dòng)畫庫,它采用的動(dòng)
畫方式和 TransitionGroup 不同,是用腳本的方式。
在這一章中,我們了解了網(wǎng)頁動(dòng)畫的兩種實(shí)現(xiàn)方式, CSS3 方式和腳本方式,在 React
的世界,也有對(duì)應(yīng)這兩種方式的動(dòng)畫解決方案。
React 官方的 ReactCSSTransitionGroup ,能夠幫助定制組件在裝載過程和卸載過程
中的動(dòng)畫,對(duì)于更新過程的動(dòng)畫,則不在 ReactCSSTransitionGroup 考慮之列,可以直接用
CSS3 來實(shí)現(xiàn)。
React-Motion 庫提供了更強(qiáng)大靈活的動(dòng)畫實(shí)現(xiàn)功能,利用“以函數(shù)為子組件”的模
式, React-Motion 只需要提供幾個(gè)組件,這些組件通過定時(shí)向子組件提供動(dòng)畫參數(shù),就
可以讓開發(fā)者自由定義動(dòng)畫的功能。
React Redux 都是完全在瀏覽器中運(yùn)行的,其實(shí), React作為一
個(gè)產(chǎn)生用戶界面的 JavaScript 庫, Redux 作為一個(gè)管理應(yīng)用數(shù)據(jù)的框架,兩者也可以
在服務(wù)器端運(yùn)行。
理想情況下, 一個(gè)React 組件或者說功能組件既能夠在瀏覽器端渲染也可以在服務(wù)
器端渲染產(chǎn)生 HTML ,這種方式叫做“同構(gòu)”( Isomorphic ),也就是同 份代碼可以在不
同環(huán)境下運(yùn)行。
傳統(tǒng)的模板庫就是生硬的字符串替換操作,無論如何優(yōu)化都會(huì)有它的極限,而且模
板的輸出依然是字符串,將 HTML 字符串插入網(wǎng)頁的過程,也就是 DOM 樹的操作,性
能也無法優(yōu)化 在前面的章節(jié)中我們介紹過 React的 Virtual DOM 工作原理,配合生命
周期函數(shù)的應(yīng)用,性能不是字符串替換的模板庫能夠比擬的。
雖然 Face book 聲稱 React 并不是給服務(wù)器端渲染設(shè)計(jì)的,但是 React 真的很適合來
做同構(gòu)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/108055.html
摘要:在這篇文章中,分享了他如何克服恐懼并開始使用源代碼來提高他的知識(shí)和技能。不久之后,你正在閱讀的源代碼將引導(dǎo)您進(jìn)入規(guī)范。 通過閱讀源碼來提高js知識(shí) 原文傳送門:《Improve Your JavaScript Knowledge By Reading Source Code》 showImg(https://segmentfault.com/img/remote/14600000197...
摘要:總結(jié)本文分析了在采用架構(gòu)下的數(shù)據(jù)設(shè)計(jì)結(jié)構(gòu),在一個(gè)復(fù)雜的場(chǎng)景下,希望引起讀者對(duì)能有一個(gè)更深入的認(rèn)識(shí)。 前幾天刷Twitter,發(fā)現(xiàn)Nicolas(Engineering at @twitter. Technical Lead for Twitter Lite)發(fā)布了這么一條推文: showImg(https://segmentfault.com/img/remote/1460000009...
摘要:司徒正美的一款了不起的化方案,支持到。行代碼內(nèi)實(shí)現(xiàn)一個(gè)胡子大哈實(shí)現(xiàn)的作品其實(shí)就是的了源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章這幾片文章的作者都是司徒正美,全面的解析和官方的對(duì)比。 前言 在過去的一個(gè)多月中,為了能夠更深入的學(xué)習(xí),使用React,了解React內(nèi)部算法,數(shù)據(jù)結(jié)構(gòu),我自己,從零開始寫了一個(gè)玩具框架。 截止今日,終于可以發(fā)布第一個(gè)版本,因?yàn)榫驮谧蛱欤?..
閱讀 727·2021-11-18 10:02
閱讀 2269·2021-11-15 18:13
閱讀 3230·2021-11-15 11:38
閱讀 2996·2021-09-22 15:55
閱讀 3708·2021-08-09 13:43
閱讀 2477·2021-07-25 14:19
閱讀 2482·2019-08-30 14:15
閱讀 3472·2019-08-30 14:15