react&redux&react-redux原理(react小書)

生命周期

組件的生命周期

定制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è)方法 getStatedispatch持钉。getState 用于獲取 state 數(shù)據(jù),其實(shí)就是簡(jiǎn)單地把 state 參數(shù)返回篱昔。
  • dispatch: 用于修改數(shù)據(jù)每强,和以前一樣會(huì)接受action 始腾,然后它會(huì)把 stateaction 一并傳給 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.jsThemeSwitch.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胶滋,改成使用第三方模塊 reduxcreateStore板鬓;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)的 reduxreact-redux 和官方版本在該場(chǎng)景的用法上是兼容的。接下來的章節(jié)我們都會(huì)使用官方版本的 reduxreact-redux

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妓柜,一起剝皮案震驚了整個(gè)濱河市箱季,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棍掐,老刑警劉巖藏雏,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異作煌,居然都是意外死亡掘殴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門粟誓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奏寨,“玉大人,你說我怎么就攤上這事鹰服〔⊥” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵悲酷,是天一觀的道長(zhǎng)套菜。 經(jīng)常有香客問我,道長(zhǎng)设易,這世上最難降的妖魔是什么逗柴? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮顿肺,結(jié)果婚禮上嚎于,老公的妹妹穿的比我還像新娘。我一直安慰自己挟冠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布袍睡。 她就那樣靜靜地躺著知染,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斑胜。 梳的紋絲不亂的頭發(fā)上控淡,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音止潘,去河邊找鬼掺炭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凭戴,可吹牛的內(nèi)容都是我干的涧狮。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼者冤!你這毒婦竟也來了肤视?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤涉枫,失蹤者是張志新(化名)和其女友劉穎邢滑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愿汰,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡困后,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衬廷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摇予。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泵督,靈堂內(nèi)的尸體忽然破棺而出趾盐,到底是詐尸還是另有隱情,我是刑警寧澤小腊,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布救鲤,位于F島的核電站,受9級(jí)特大地震影響秩冈,放射性物質(zhì)發(fā)生泄漏本缠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一入问、第九天 我趴在偏房一處隱蔽的房頂上張望丹锹。 院中可真熱鬧,春花似錦芬失、人聲如沸楣黍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽租漂。三九已至,卻和暖如春颊糜,著一層夾襖步出監(jiān)牢的瞬間哩治,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工衬鱼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留业筏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓鸟赫,卻偏偏與公主長(zhǎng)得像蒜胖,于是被迫代替她去往敵國和親消别。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容