生命周期
定制shouldComponentUpdate
shouldComponentUpdate(){
console.log(this.state.num) // 1 2 3 4 5....每次都正常打印
if(this.state.num%5== 0){
return true
}
return false
}
--------------------------------
shouldComponentUpdate(nextProps,nextState){
console.log(nextProps,nextState)
console.log(this.props,this.state)
if(nextState.num%5== 0){ // 如果下一個(gè)狀態(tài)%5=0
return true
}
return false
}
SetState
setState有隊(duì)列的概念饭耳,對(duì)狀態(tài)的更新是異步的膛腐,比如在一個(gè)事件函數(shù)中更新多次state,render只執(zhí)行一次恭朗。不要不通過setState直接修改state,每次調(diào)用setState都會(huì)塞到隊(duì)列中依疼。render中不要執(zhí)行setState,因?yàn)閟etState會(huì)執(zhí)行render,這樣操作會(huì)導(dǎo)致循環(huán)調(diào)用痰腮。
調(diào)用setState之后,它會(huì)把當(dāng)前要修改的狀態(tài)存儲(chǔ)在隊(duì)列中律罢,最終會(huì)調(diào)用隊(duì)列更新的方法膀值。
高階組件
- 高階組件就是一個(gè)函數(shù),傳給它一個(gè)組件误辑,它返回一個(gè)新的組件沧踏。
- 高階組件的作用:就是為了組件之間的代碼復(fù)用。組件可能有著某些相同的邏輯巾钉,把這些邏輯抽離出來翘狱,放到高階組件中進(jìn)行復(fù)用。
- 高階組件包裝的新組件和原來組件之間通過 props 傳遞信息砰苍,減少代碼的重復(fù)程度潦匈。
function heighCom(Com,name){
class NewComponent extends React.Component(){
constructor(props){
super(props)
this.state = {
data:null
}
}
componentDidMount(){
this.setState({
data : name
})
}
render(){
<Com data={this.state.data}></Com>
}
}
return NewComponent
}
context
- 一個(gè)組件的 context 只有它的子組件能夠訪問,它的父組件是不能訪問到的;
- 要給父組件設(shè)置 context赚导,childContextTypes 是必寫的茬缩;
- 子組件要獲取context內(nèi)容,必須寫contextTypes聲明和驗(yàn)證你需要獲取的狀態(tài)的類型吼旧。
class Index extends Component {
//要給組件設(shè)置 context凰锡,childContextTypes 是必寫的
static childContextTypes = {
themeColor: PropTypes.string
}
// 初始化themeColor狀態(tài)
constructor () {
super()
this.state = { themeColor: 'red' }
}
// 設(shè)置 context 的過程
getChildContext () {
return { themeColor: this.state.themeColor }
}
}
----------------------------------------------------------
//子組件要獲取context內(nèi)容,必須寫contextTypes聲明和驗(yàn)證你需要獲取的狀態(tài)的類型
class Title extends Component {
static contextTypes = {
themeColor: PropTypes.string
}
render () {
return (
<h1 style={{ color: this.context.themeColor }}>React.js 小書標(biāo)題</h1>
)
}
}
context的好處和壞處 : 如果一個(gè)組件設(shè)置了 context,那么它的子組件都可以直接訪問到里面的內(nèi)容掂为,它就像這個(gè)組件為根的子樹的全局變量裕膀。任意深度的子組件都可以通過 contextTypes 來聲明你想要的 context 里面的哪些狀態(tài),然后可以通過 this.context 訪問到那些狀態(tài)勇哗。
context 打破了組件和組件之間通過 props 傳遞數(shù)據(jù)的規(guī)范昼扛,極大地增強(qiáng)了組件之間的耦合性。而且智绸,就如全局變量一樣野揪,context 里面的數(shù)據(jù)能被隨意接觸就能被隨意修改,每個(gè)組件都能夠改 context 里面的內(nèi)容會(huì)導(dǎo)致程序的運(yùn)行不可預(yù)料瞧栗。
純函數(shù)(Pure Function)簡(jiǎn)介&&為什么reducer必須是純函數(shù)
一個(gè)函數(shù)的返回結(jié)果只依賴于它的參數(shù)斯稳,并且在執(zhí)行過程里面沒有副作用,我們就把這個(gè)函數(shù)叫做純函數(shù)迹恐。
- 函數(shù)的返回結(jié)果只依賴于它的參數(shù)挣惰。
- 函數(shù)執(zhí)行過程里面沒有副作用:一個(gè)函數(shù)執(zhí)行過程對(duì)產(chǎn)生了外部可觀察的變化那么就說這個(gè)函數(shù)是有副作用的。
純函數(shù)很嚴(yán)格殴边,也就是說你幾乎除了計(jì)算數(shù)據(jù)以外什么都不能干憎茂,計(jì)算的時(shí)候還不能依賴除了函數(shù)參數(shù)以外的數(shù)據(jù)。
總結(jié):
一個(gè)函數(shù)的返回結(jié)果只依賴于它的參數(shù)锤岸,并且在執(zhí)行過程里面沒有副作用竖幔,我們就把這個(gè)函數(shù)叫做純函數(shù)。
為什么要煞費(fèi)苦心地構(gòu)建純函數(shù)是偷?因?yàn)榧兒瘮?shù)非橙猓“靠譜”,執(zhí)行一個(gè)純函數(shù)你不用擔(dān)心它會(huì)干什么壞事蛋铆,它不會(huì)產(chǎn)生不可預(yù)料的行為馋评,也不會(huì)對(duì)外部產(chǎn)生影響。不管何時(shí)何地刺啦,你給它什么它就會(huì)乖乖地吐出什么留特。如果你的應(yīng)用程序大多數(shù)函數(shù)都是由純函數(shù)組成,那么你的程序測(cè)試玛瘸、調(diào)試起來會(huì)非常方便蜕青。
為什么reducer必須是純函數(shù)
Redux接收一個(gè)給定的state(對(duì)象),然后通過循環(huán)將state的每一部分傳遞給每個(gè)對(duì)應(yīng)的reducer捧韵。如果有發(fā)生任何改變市咆,reducer將返回一個(gè)新的對(duì)象。如果不發(fā)生任何變化再来,reducer將返回舊的state。
Redux只通過比較新舊兩個(gè)對(duì)象的存儲(chǔ)位置來比較新舊兩個(gè)對(duì)象是否相同(譯者注:也就是Javascript對(duì)象淺比較)。如果你在reducer內(nèi)部直接修改舊的state對(duì)象的屬性值芒篷,那么新的state和舊的state將都指向同一個(gè)對(duì)象搜变。因此Redux認(rèn)為沒有任何改變,返回的state將為舊的state针炉。
因?yàn)楸容^兩個(gè)Javascript對(duì)象所有的屬性是否相同的的唯一方法是對(duì)它們進(jìn)行深比較挠他。但是深比較在真實(shí)的應(yīng)用當(dāng)中代價(jià)昂貴,因?yàn)橥ǔs的對(duì)象都很大篡帕,同時(shí)需要比較的次數(shù)很多殖侵。
因此一個(gè)有效的解決方法是作出一個(gè)規(guī)定:無論何時(shí)發(fā)生變化時(shí),開發(fā)者都要?jiǎng)?chuàng)建一個(gè)新的對(duì)象镰烧,然后將新對(duì)象傳遞出去拢军。同時(shí),當(dāng)沒有任何變化發(fā)生時(shí)怔鳖,開發(fā)者發(fā)送回舊的對(duì)象茉唉。也就是說,新的對(duì)象代表新的state结执。
必須注意到你只能使用slice(譯者注:此處slice類似數(shù)組的slice方法度陆,具體可以使用本文例子中解構(gòu)賦值等方法進(jìn)行slice)或者類似的機(jī)制去復(fù)制舊的值到新的對(duì)象里。
現(xiàn)在使用了新的策略之后献幔,你能夠比較兩個(gè)對(duì)象通過使用!==比較兩個(gè)對(duì)象的存儲(chǔ)位置而不是比較兩個(gè)對(duì)象的所有屬性懂傀。同時(shí)當(dāng)兩個(gè)對(duì)象不同的時(shí)候,你就能知道新的對(duì)象已經(jīng)改變了舊的state(也就是說蜡感,JavaScript對(duì)象當(dāng)中的某些屬性的值發(fā)生了變化)蹬蚁。這正是Redux所采取的策略。
這就是為什么Redux需要reducers是純函數(shù)的原因铸敏!
為什么Redux需要reducers是純函數(shù)
redux
- 專注于狀態(tài)管理的庫
-
單一狀態(tài)缚忧,單向數(shù)據(jù)流
Redux 是一種架構(gòu)模式(Flux 架構(gòu)的一種變種),它不關(guān)注你到底用什么庫杈笔,你可以把它應(yīng)用到 React 和 Vue闪水,甚至跟 jQuery 結(jié)合都沒有問題。而 React-redux 就是把 Redux 這種架構(gòu)模式和 React.js 結(jié)合起來的一個(gè)庫蒙具,就是 Redux 架構(gòu)在 React.js 中的體現(xiàn)球榆。
1、抽離出 store
function createStore(state,reducer){
const getState = ()=> state
const dispatch = (action) => reducer(state,action)
return {getState,dispatch}
}
//es5便于理解
function dispatch(action) {
return reducer(state, action);
};
- createStore: 會(huì)返回一個(gè)對(duì)象禁筏,這個(gè)對(duì)象包含兩個(gè)方法 getState 和 dispatch持钉。getState 用于獲取 state 數(shù)據(jù),其實(shí)就是簡(jiǎn)單地把 state 參數(shù)返回篱昔。
- dispatch: 用于修改數(shù)據(jù)每强,和以前一樣會(huì)接受action 始腾,然后它會(huì)把 state 和action 一并傳給 reducer ,那么reducer 就可以根據(jù) action 來修改 state 了空执。
監(jiān)控?cái)?shù)據(jù)變化
上面的代碼有一個(gè)問題浪箭,我們每次通過 dispatch 修改數(shù)據(jù)的時(shí)候,其實(shí)只是數(shù)據(jù)發(fā)生了變化辨绊,如果我們不手動(dòng)調(diào)用 renderApp奶栖,頁面上的內(nèi)容是不會(huì)發(fā)生變化的。但是我們總不能每次 dispatch 的時(shí)候都手動(dòng)調(diào)用一下 renderApp门坷,我們肯定希望數(shù)據(jù)變化的時(shí)候程序能夠智能一點(diǎn)地自動(dòng)重新渲染數(shù)據(jù)宣鄙,而不是手動(dòng)調(diào)用。
你說這好辦默蚌,往 dispatch里面加 renderApp 就好了冻晤,但是這樣 createStore 就不夠通用了。我們希望用一種通用的方式“監(jiān)聽”數(shù)據(jù)變化敏簿,然后重新渲染頁面明也,這里要用到觀察者模式。
function createStore(state,reducer){
let listeners = []
let subscribe = (listener)=>{listeners.push(listener)}
let getState = ()=>state
let dispatch = (action)=>{
reducer(action,state)
listeners.forEach( listener => {listener()} )
}
return {getState ,subscribe ,dispatch}
}
我們修改了 dispatch惯裕,每次當(dāng)它被調(diào)用的時(shí)候温数,除了會(huì)調(diào)用 reducer 進(jìn)行數(shù)據(jù)的修改,還會(huì)遍歷 listeners 數(shù)組里面的函數(shù)蜻势,然后一個(gè)個(gè)地去調(diào)用撑刺。相當(dāng)于我們可以通過 subscribe 傳入數(shù)據(jù)變化的監(jiān)聽函數(shù),每當(dāng) dispatch 的時(shí)候握玛,監(jiān)聽函數(shù)就會(huì)被調(diào)用够傍,這樣我們就可以在每當(dāng)數(shù)據(jù)變化時(shí)候進(jìn)行重新渲染:
const store = createStore(appState, reducer)
store.subscribe(() => renderApp(store.getState()))
renderApp(store.getState()) // 首次渲染頁面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小書》' }) // 修改標(biāo)題文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改標(biāo)題顏色
// ...后面不管如何 store.dispatch,都不需要重新調(diào)用 renderApp
我的小結(jié):
- subscribe : 訂閱挠铲,將參數(shù)push到處理數(shù)組
- getState : 返回state
- dispatch : 發(fā)布冕屯,遍歷事件處理數(shù)組,并執(zhí)行拂苹;調(diào)用reducer安聘,傳入state和action
共享結(jié)構(gòu)的對(duì)象提高性能
const obj = { a: 1, b: 2}
const obj2 = { ...obj } // => { a: 1, b: 2 }
const obj2 = { ...obj } 其實(shí)就是新建一個(gè)對(duì)象 obj2,然后把 obj 所有的屬性都復(fù)制到 obj2 里面瓢棒,相當(dāng)于對(duì)象的淺復(fù)制浴韭。上面的 obj 里面的內(nèi)容和 obj2 是完全一樣的,但是卻是兩個(gè)不同的對(duì)象脯宿。
const obj = { a: 1, b: 2}
// { a: 1, b: 3, c: 4 }念颈,覆蓋了 b,新增了 c
const obj2 = { ...obj, b: 3, c: 4}
我們可以把這種特性應(yīng)用在 state 的更新上连霉,我們禁止直接修改原來的對(duì)象榴芳,一旦你要修改某些東西嗡靡,你就得把修改路徑上的所有對(duì)象復(fù)制一遍,例如翠语,我們不寫下面的修改代碼:
appState.title.text = '《React.js 小書》'
取而代之的是叽躯,我們新建一個(gè) appState财边,新建 appState.title肌括,新建 appState.title.text:
let newAppState = { // 新建一個(gè) newAppState
...appState, // 復(fù)制 appState 里面的內(nèi)容
title: { // 用一個(gè)新的對(duì)象覆蓋原來的 title 屬性
...appState.title, // 復(fù)制原來 title 對(duì)象里面的內(nèi)容
text: '《React.js 小書》' // 覆蓋 text 屬性
}
}
修改數(shù)據(jù)的時(shí)候就把修改路徑都復(fù)制一遍,但是保持其他內(nèi)容不變酣难,最后的所有對(duì)象具有某些不變共享的結(jié)構(gòu)(例如上面三個(gè)對(duì)象都共享 content 對(duì)象)谍夭。大多數(shù)情況下我們可以保持 50% 以上的內(nèi)容具有共享結(jié)構(gòu),這種操作具有非常優(yōu)良的特性憨募,我們可以用它來優(yōu)化上面的渲染性能紧索。
優(yōu)化性能
我們修改 reducer,讓它修改數(shù)據(jù)的時(shí)候菜谣,并不會(huì)直接修改原來的數(shù)據(jù) state珠漂,而是產(chǎn)生上述的共享結(jié)構(gòu)的對(duì)象:
function reducer (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
return { // 構(gòu)建新的對(duì)象并且返回
...state,
title: {
...state.title,
text: action.text
}
}
case 'UPDATE_TITLE_COLOR':
return { // 構(gòu)建新的對(duì)象并且返回
...state,
title: {
...state.title,
color: action.color
}
}
default:
return state // 沒有修改,返回原來的對(duì)象
}
}
代碼稍微比原來長(zhǎng)了一點(diǎn)尾膊,但是是值得的媳危。每次需要修改的時(shí)候都會(huì)產(chǎn)生新的對(duì)象,并且返回冈敛。而如果沒有修改(在 default 語句中)則返回原來的 state 對(duì)象待笑。
因?yàn)?reducer 不會(huì)修改原來對(duì)象了,而是返回對(duì)象抓谴,所以我們需要修改一下 createStore暮蹂。讓它用每次 reducer(state, action) 的調(diào)用結(jié)果覆蓋原來的 state:
function createStore (reducer) {
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
//reducer只是返回對(duì)象,所以需要用每次 reducer(state, action) 的調(diào)用結(jié)果覆蓋原來的 state
state = reducer(state, action)
listeners.forEach((listener) => listener())
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
我們成功地把不必要的頁面渲染優(yōu)化掉了癌压,問題解決仰泻。另外,并不需要擔(dān)心每次修改都新建共享結(jié)構(gòu)對(duì)象會(huì)有性能滩届、內(nèi)存問題集侯,因?yàn)闃?gòu)建對(duì)象的成本非常低,而且我們最多保存兩個(gè)對(duì)象引用(oldState 和 newState)丐吓,其余舊的對(duì)象都會(huì)被垃圾回收掉浅悉。
我的小結(jié):
修改數(shù)據(jù)時(shí),禁止直接修改原來的對(duì)象券犁,一旦你要修改某些東西术健,你就得把修改路徑上的所有對(duì)象復(fù)制一遍,但是保持其他內(nèi)容不變粘衬,最后的所有對(duì)象具有某些不變共享的結(jié)構(gòu)荞估,多數(shù)情況下我們可以保持 50% 以上的內(nèi)容具有共享結(jié)構(gòu)咳促,這種操作具有非常優(yōu)良的特性。
Redux只通過比較新舊兩個(gè)對(duì)象的存儲(chǔ)位置來比較新舊兩個(gè)對(duì)象是否相同(譯者注:也就是Javascript對(duì)象淺比較)勘伺。如果你在reducer內(nèi)部直接修改舊的state對(duì)象的屬性值跪腹,那么新的state和舊的state將都指向同一個(gè)對(duì)象。因此Redux認(rèn)為沒有任何改變飞醉,返回的state將為舊的state冲茸。
state = reducer(state, action) // 覆蓋原對(duì)象
reducer總結(jié):
createStore 接受一個(gè)叫 reducer 的函數(shù)作為參數(shù),這個(gè)函數(shù)規(guī)定是一個(gè)純函數(shù)缅帘,它接受兩個(gè)參數(shù)轴术,一個(gè)是 state,一個(gè)是 action钦无。
如果沒有傳入 state 或者 state 是 null逗栽,那么它就會(huì)返回一個(gè)初始化的數(shù)據(jù)。如果有傳入 state 的話失暂,就會(huì)根據(jù) action 來“修改“數(shù)據(jù)彼宠,但其實(shí)它沒有、也規(guī)定不能修改 state弟塞,而是要通過上節(jié)所說的把修改路徑的對(duì)象都復(fù)制一遍凭峡,然后產(chǎn)生一個(gè)新的對(duì)象返回。如果它不能識(shí)別你的 action宣肚,它就不會(huì)產(chǎn)生新的數(shù)據(jù)想罕,而是(在 default 內(nèi)部)把 state 原封不動(dòng)地返回。
!!!reducer 是不允許有副作用的霉涨。你不能在里面操作 DOM按价,也不能發(fā) Ajax 請(qǐng)求,更不能直接修改 state笙瑟,它要做的僅僅是 —— 初始化和計(jì)算新的 state楼镐。
還要注意:為什么reducer必須是純函數(shù)?在純函數(shù)章節(jié)已經(jīng)做了總結(jié)往枷。
redux總結(jié)
不知不覺地框产,到這里大家不僅僅已經(jīng)掌握了 Redux,而且還自己動(dòng)手寫了一個(gè) Redux错洁。我們從一個(gè)非常原始的代碼開始秉宿,不停地在發(fā)現(xiàn)問題、解決問題屯碴、優(yōu)化代碼的過程中進(jìn)行推演描睦,最后把 Redux 模式自己總結(jié)出來了。這就是所謂的 Redux 模式导而,我們?cè)賮砘仡櫼幌逻@幾節(jié)我們到底干了什么事情忱叭。
我們從一個(gè)簡(jiǎn)單的例子的代碼中發(fā)現(xiàn)了共享的狀態(tài)如果可以被任意修改的話隔崎,那么程序的行為將非常不可預(yù)料,所以我們提高了修改數(shù)據(jù)的門檻:你必須通過 dispatch 執(zhí)行某些允許的修改操作韵丑,而且必須大張旗鼓的在 action 里面聲明爵卒。
這種模式挺好用的,我們就把它抽象出來一個(gè) createStore撵彻,它可以產(chǎn)生 store钓株,里面包含 getState 和 dispatch 函數(shù),方便我們使用千康。
后來發(fā)現(xiàn)每次修改數(shù)據(jù)都需要手動(dòng)重新渲染非常麻煩享幽,我們希望自動(dòng)重新渲染視圖。所以后來加入了訂閱者模式拾弃,可以通過 store.subscribe 訂閱數(shù)據(jù)修改事件,每次數(shù)據(jù)更新的時(shí)候自動(dòng)重新渲染視圖摆霉。
接下來我們發(fā)現(xiàn)了原來的“重新渲染視圖”有比較嚴(yán)重的性能問題豪椿,我們引入了“共享結(jié)構(gòu)的對(duì)象”來幫我們解決問題,這樣就可以在每個(gè)渲染函數(shù)的開頭進(jìn)行簡(jiǎn)單的判斷避免沒有被修改過的數(shù)據(jù)重新渲染携栋。
我們優(yōu)化了 stateChanger 為 reducer搭盾,定義了 reducer 只能是純函數(shù),功能就是負(fù)責(zé)初始 state婉支,和根據(jù) state 和 action 計(jì)算具有共享結(jié)構(gòu)的新的 state鸯隅。
createStore 現(xiàn)在可以直接拿來用了,套路就是:
// 定一個(gè) reducer
function reducer (state, action) {
/* 初始化 state 和 switch case */
}
// 生成 store
const store = createStore(reducer)
// 監(jiān)聽數(shù)據(jù)變化重新渲染頁面
store.subscribe(() => renderApp(store.getState()))
// 首次渲染頁面
renderApp(store.getState())
// 后面可以隨意 dispatch 了向挖,頁面自動(dòng)更新
store.dispatch(...)
Redux和 React.js 一點(diǎn)關(guān)系都沒有r蛞浴!何之!接下來我們要把 React.js 和 Redux 結(jié)合起來跟畅,用 Redux 模式幫助管理 React.js 的應(yīng)用狀態(tài)。
react-redux
前端中應(yīng)用的狀態(tài)存在的問題:一個(gè)狀態(tài)可能被多個(gè)組件依賴或者影響溶推,而 React.js 并沒有提供好的解決方案徊件,我們只能把狀態(tài)提升到依賴或者影響這個(gè)狀態(tài)的所有組件的公共父組件上,我們把這種行為叫做狀態(tài)提升蒜危。但是需求不停變化虱痕,共享狀態(tài)沒完沒了地提升也不是辦法。
后來我們?cè)?React.js 的 context 中提出辐赞,我們可用把共享狀態(tài)放到父組件的 context 上部翘,這個(gè)父組件下所有的組件都可以從 context 中直接獲取到狀態(tài)而不需要一層層地進(jìn)行傳遞了。但是直接從 context 里面存放占拍、獲取數(shù)據(jù)增強(qiáng)了組件的耦合性略就;并且所有組件都可以修改 context 里面的狀態(tài)就像誰都可以修改共享狀態(tài)一樣捎迫,導(dǎo)致程序運(yùn)行的不可預(yù)料。
解決思路:把 context 和 store 結(jié)合起來表牢。畢竟 store 的數(shù)據(jù)不是誰都能修改窄绒,而是約定只能通過 dispatch 來進(jìn)行修改,這樣的話每個(gè)組件既可以去 context 里面獲取 store 從而獲取狀態(tài)崔兴,又不用擔(dān)心它們亂改數(shù)據(jù)了瓶佳。
到底什么樣的組件才叫復(fù)用性強(qiáng)的組件翩迈。如果一個(gè)組件對(duì)外界的依賴過于強(qiáng),那么這個(gè)組件的移植性會(huì)很差,就像這些嚴(yán)重依賴 context 的組件一樣帆疟。
如果一個(gè)組件的渲染只依賴于外界傳進(jìn)去的 props 和自己的 state,而并不依賴于其他的外界的任何數(shù)據(jù)楣责,也就是說像純函數(shù)一樣淆储,給它什么,它就吐出(渲染)什么出來秆剪。這種組件的復(fù)用性是最強(qiáng)的赊淑,別人使用的時(shí)候根本不用擔(dān)心任何事情,只要看看 PropTypes 它能接受什么參數(shù)仅讽,然后把參數(shù)傳進(jìn)去控制它就行了陶缺。
我們需要高階組件幫助我們從 context 取數(shù)據(jù),我們也需要寫 Dumb 組件幫助我們提高組件的復(fù)用性洁灵。所以我們盡量多地寫 Dumb 組件饱岸,然后用高階組件把它們包裝一層,高階組件和 context 打交道徽千,把里面數(shù)據(jù)取出來通過 props 傳給 Dumb 組件苫费。
實(shí)現(xiàn)過程:
1、connect 現(xiàn)在是接受一個(gè)參數(shù) mapStateToProps罐栈,然后返回一個(gè)函數(shù)黍衙,這個(gè)返回的函數(shù)才是高階組件。它會(huì)接受一個(gè)組件作為參數(shù)荠诬,然后用 Connect 把組件包裝以后再返回琅翻。
//connect高階組件,從context取出store柑贞,將state傳給mapStateToProps
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {} // 防止 mapStateToProps 沒有傳入
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 防止 mapDispatchToProps 沒有傳入
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render () {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
}
2方椎、高階組件用法
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor,
themeName: state.themeName,
fullName: `${state.firstName} ${state.lastName}`
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSwitchColor: (color) => {
dispatch({ type: 'CHANGE_COLOR', themeColor: color })
}
}
}
Header = connect(mapStateToProps)(Header)
我們?cè)?Connect 組件的 constructor 里面初始化了 state.allProps,它是一個(gè)對(duì)象钧嘶,用來保存需要傳給被包裝組件的所有的參數(shù)棠众。生命周期 componentWillMount 會(huì)調(diào)用調(diào)用 _updateProps 進(jìn)行初始化,然后通過 store.subscribe 監(jiān)聽數(shù)據(jù)變化重新調(diào)用 _updateProps。
為了讓 connect 返回新組件和被包裝的組件使用參數(shù)保持一致闸拿,我們會(huì)把所有傳給 Connect 的 props 原封不動(dòng)地傳給 WrappedComponent空盼。所以在 _updateProps 里面會(huì)把 stateProps 和 this.props 合并到 this.state.allProps 里面,再通過 render 方法把所有參數(shù)都傳給 WrappedComponent新荤。
mapStateToProps 也發(fā)生點(diǎn)變化揽趾,它現(xiàn)在可以接受兩個(gè)參數(shù)了,我們會(huì)把傳給 Connect 組件的 props 參數(shù)也傳給它苛骨,那么它生成的對(duì)象配置性就更強(qiáng)了篱瞎,我們可以根據(jù) store 里面的 state 和外界傳入的 props 生成我們想傳給被包裝組件的參數(shù)。
現(xiàn)在已經(jīng)很不錯(cuò)了痒芝,Header.js 和 Content.js 的代碼都大大減少了俐筋,并且這兩個(gè)組件 connect 之前都是 Dumb 組件。接下來會(huì)繼續(xù)重構(gòu) ThemeSwitch严衬。
Provider
我們要把 context 相關(guān)的代碼從所有業(yè)務(wù)組件中清除出去澄者,額外構(gòu)建一個(gè)組件來做這種臟活,然后讓這個(gè)組件成為組件樹的根節(jié)點(diǎn)瞳步,那么它的子組件都可以獲取到 context 了闷哆。
Provider 做的事情也很簡(jiǎn)單,它就是一個(gè)容器組件单起,會(huì)把嵌套的內(nèi)容原封不動(dòng)作為自己的子組件渲染出來。它還會(huì)把外界傳給它的 props.store 放到 context劣坊,這樣子組件 connect 的時(shí)候都可以獲取到嘀倒。
export class Provider extends Component {
static propTypes = {
store: PropTypes.object,
children: PropTypes.any
}
static childContextTypes = {
store: PropTypes.object
}
getChildContext () {
return {
store: this.props.store
}
}
render () {
return (
<div>{this.props.children}</div>
)
}
}
總結(jié):
- 到這里大家已經(jīng)掌握了 React-redux 的基本用法和概念,并且自己動(dòng)手實(shí)現(xiàn)了一個(gè) React-redux局冰,我們回顧一下這幾節(jié)都干了什么事情测蘑。
- React.js 除了狀態(tài)提升以外并沒有更好的辦法幫我們解決組件之間共享狀態(tài)的問題,而使用 context 全局變量讓程序不可預(yù)測(cè)康二。通過 Redux 的章節(jié)碳胳,我們知道 store 里面的內(nèi)容是不可以隨意修改的,而是通過 dispatch 才能變更里面的 state沫勿。所以我們嘗試把 store 和 context 結(jié)合起來使用挨约,可以兼顧組件之間共享狀態(tài)問題和共享狀態(tài)可能被任意修改的問題。
- 第一個(gè)版本的 store 和 context 結(jié)合有諸多缺陷产雹,有大量的重復(fù)邏輯和對(duì) context 的依賴性過強(qiáng)诫惭。我們嘗試通過構(gòu)建一個(gè)高階組件 connect 函數(shù)的方式,把所有的重復(fù)邏輯和對(duì) context 的依賴放在里面 connect 函數(shù)里面蔓挖,而其他組件保持 Pure(Dumb) 的狀態(tài)夕土,讓 connect 跟 context 打交道,然后通過 props 把參數(shù)傳給普通的組件瘟判。
- 而每個(gè)組件需要的數(shù)據(jù)和需要觸發(fā)的 action 都不一樣怨绣,所以調(diào)整 connect角溃,讓它可以接受兩個(gè)參數(shù) mapStateToProps 和 mapDispatchToProps,分別用于告訴 connect 這個(gè)組件需要什么數(shù)據(jù)和需要觸發(fā)什么 action篮撑。
- 最后為了把所有關(guān)于 context 的代碼完全從我們業(yè)務(wù)邏輯里面清除掉减细,我們構(gòu)建了一個(gè) Provider 組件。Provider 作為所有組件樹的根節(jié)點(diǎn)咽扇,外界可以通過 props 給它提供 store邪财,它會(huì)把 store 放到自己的 context 里面,好讓子組件 connect 的時(shí)候都能夠獲取到质欲。
- 這幾節(jié)的成果就是 react-redux.js 這個(gè)文件里面的兩個(gè)內(nèi)容:connect 函數(shù)和 Provider 容器組件树埠。這就是 React-redux 的基本內(nèi)容,當(dāng)然它是一個(gè)殘疾版本的 React-redux嘶伟,很多地方需要完善怎憋。例如上幾節(jié)提到的性能問題,現(xiàn)在不相關(guān)的數(shù)據(jù)變化的時(shí)候其實(shí)所有組件都會(huì)重新渲染的九昧,這個(gè)性能優(yōu)化留給讀者做練習(xí)绊袋。
- 通過這種方式大家不僅僅知道了 React-redux 的基礎(chǔ)概念和用法,而且還知道這些概念到底是解決什么問題铸鹰,為什么 React-redux 這么奇怪癌别,為什么要 connect,為什么要 mapStateToProps 和 mapDispatchToProps蹋笼,什么是 Provider展姐,我們通過解決一個(gè)個(gè)問題就知道它們到底為什么要這么設(shè)計(jì)的了。
使用真正的 Redux 和 React-redux
現(xiàn)在 make-react-redux
工程代碼中的 Redux 和 React-redux 都是我們自己寫的剖毯,現(xiàn)在讓我們來使用真正的官方版本的 Redux 和 React-redux圾笨。
在工程目錄下使用 npm 安裝 Redux 和 React-redux 模塊:
npm install redux react-redux --save
把 src/
目錄下 Header.js
、ThemeSwitch.js
逊谋、Content.js
的模塊導(dǎo)入中的:
import { connect } from './react-redux'
改成:
import { connect } from 'react-redux'
也就是本來從本地 ./react-redux
導(dǎo)入的 connect
改成從第三方 react-redux
模塊中導(dǎo)入擂达。
修改 src/index.js
,把前面部分的代碼調(diào)整為:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import Header from './Header'
import Content from './Content'
import './index.css'
const themeReducer = (state, action) => {
if (!state) return {
themeColor: 'red'
}
switch (action.type) {
case 'CHANGE_COLOR':
return { ...state, themeColor: action.themeColor }
default:
return state
}
}
const store = createStore(themeReducer)
...
我們刪除了自己寫的 createStore
胶滋,改成使用第三方模塊 redux
的 createStore
板鬓;Provider
本來從本地的 ./react-redux
引入,改成從第三方 react-redux
模塊中引入镀钓。其余代碼保持不變穗熬。
接著刪除 src/react-redux.js
,它的已經(jīng)用處不大了丁溅。最后啟動(dòng)工程 npm start
:
可以看到我們?cè)瓉淼臉I(yè)務(wù)代碼其實(shí)都沒有太多的改動(dòng)唤蔗,實(shí)際上我們實(shí)現(xiàn)的 redux
和 react-redux
和官方版本在該場(chǎng)景的用法上是兼容的。接下來的章節(jié)我們都會(huì)使用官方版本的 redux
和 react-redux
。