React-redux深入理解

首先,一張 Redux 解釋圖鎮(zhèn)樓:

Redux explained(圖片來源:https://github.com/buckyroberts/React-Redux-Boilerplate)
【回顧】Redux 的核心: store 是什么?(createStore 函數(shù)的實現(xiàn))
const store = createStore(reducer);

store 是一個對象,包含3個方法:getStatedispatchsubscribe

// createStore 函數(shù)實現(xiàn)
const createStore = (reducer) => {
    let state;
    let listeners = [];

    const getState = () => state;
    const dispatch = (action) => {
        state = reducer(state, action);
        listeners.forEach(listener => listener());
    }
    const subscribe = (listener) => {    // listener 就是一個要執(zhí)行的函數(shù)
        listeners.push(listener);
        return () => {  // 采用柯里化方式注銷監(jiān)聽器,用法:store.subscribe(listener)();
            listeners = listeners.filter(l => l != listener);
        }
    }

    dispatch({});   // 初始化state

    return { getState, dispatch, subscribe }
}

由函數(shù)可知,當(dāng)用戶 dispatch 一個 action 時,會自動調(diào)用 reducer 從而得到最新的 state,該 state 可通過 getState 函數(shù)獲取,并且會執(zhí)行所有已注冊的函數(shù)。

所以,redux 的套路就是(參考 React小書 ):

// 定一個 reducer
function reducer (state, action) {
  /* 初始化 state 和 switch case */
}

// 生成 store
const store = createStore(reducer)

// 監(jiān)聽數(shù)據(jù)變化重新渲染頁面,即更新狀態(tài)的過程
store.subscribe(() => renderApp(store.getState()))

// 首次渲染頁面
renderApp(store.getState()) 

// 后面可以隨意 dispatch 了,頁面自動更新
store.dispatch(...)
【問題】:React 和 Redux 之間是如何連接?

從圖中可以看到,Store 通過 Provider 傳遞給了我們的 React 組件,因此,使得組件能夠獲取到 store。那么它是如何將做到的呢?

為了弄明白 React 和 Redux 之間是如何連接的,我們需要了解以下一些內(nèi)容(參考 React小書 ):

一、背景:React 中父組件 context 的作用,用以擺脫狀態(tài)提升

在 React 中,父組件使用 getChildContext(),可以將 store 放到它的 context 里面,相當(dāng)于給子組件設(shè)置了一個全局變量,這樣每個子組件就都可以獲取到 store。

// 父組件
class Index extends Component {
  // 提供 context 的組件必須提供 childContextTypes 作為 context 的聲明和驗證
  static childContextTypes = {
    store: PropTypes.object
  }

  // 一個組件可以通過 getChildContext 方法返回一個對象,這個對象就是子樹的 context
  getChildContext () {
    return { store }
  }

  render () {
    return (
      <div>
        <Header />
        <Content />
      </div>
    )
  }
}

// 子組件
class Header extends Component {
  // 聲明想要的 context 里面的哪些狀態(tài),以便通過 this.context 進(jìn)行訪問
  // 子組件要獲取 context 里面的內(nèi)容的話,就必須寫 contextTypes 來聲明和驗證你需要獲取的狀態(tài)的類型
  static contextTypes = {
    store: PropTypes.object
  }

  constructor () {
    super()
    this.state = { themeColor: '' }
  }

  componentWillMount () {
    this._updateThemeColor()
  }

  _updateThemeColor () {
    // 子組件可以訪問到父組件 context 里面的內(nèi)容
    const { store } = this.context
    const state = store.getState()
    this.setState({ themeColor: state.themeColor })
  }

  render () {
    return (
      <h1 style={{ color: this.state.themeColor }}>React.js 小書</h1>
    )
  }
}

如果一個組件設(shè)置了 context,那么它的子組件都可以直接訪問到里面的內(nèi)容,它就像這個組件為根的子樹的全局變量。任意深度的子組件都可以通過 contextTypes 來聲明你想要的 context 里面的哪些狀態(tài),然后可以通過 this.context 訪問到那些狀態(tài)。

context 存在的問題:首先,它是一個試驗性的API,不穩(wěn)定,可能會改變,雖然好多庫都用到了這個特性;其次它是脆弱的,如果在層級中的任何一個組件執(zhí)行了 shouldComponentUpdate 返回 false,context 則不會傳遞給其之后所有的子組件。

二、react-redux 的誕生

因為 context 是一個比較危險的特性,我們不想在自己寫組件的時候被其污染,我們需要將其剝離出來,因此,react-redux 誕生了,其中的 Provider 以及 connect 就幫助我們將 React 的組件和 Redux 的 store 進(jìn)行了連接。

1. Provider 的實現(xiàn)

作用:充當(dāng)父組件的作用,把 store 放到自己的 context 里面,讓子組件 connect 的時候獲取。

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>
    )
  }
}
2. 高階組件 connect(connect 實現(xiàn))

高階組件:高階組件是一個接受一個組件為參數(shù),并返回一個被包裝過的組件的函數(shù),即返回傳入props的原組件。
connect 的作用:和 React 的 context 打交道,將 context 中的數(shù)據(jù)取出來,并以 prop 的形式傳遞給 Dumb 組件。

const mapStateToProps = (state) => { themeColor: state.themeColor }
const mapDispatchToProps = (dispatch) => ({
    onSwitchColor(color) => {
      dispatch({ type: 'CHANGE_COLOR', themeColor: color })
    }
});

// connect 實現(xiàn)
// connect 接受 mapStateToProps 和 mapDispatchProps 參數(shù)后,返回的函數(shù)是高階組件,該高階組件接受一個組件作為參數(shù),然后用 Connect 包裝之后返回
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
}

connect 接口:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(Component)

三、react-redux 的性能優(yōu)化

react-redux性能優(yōu)化之reselect

三、文章參考

React小書
Redux使用小結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,480評論 2 379

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