React×Redux——react-redux庫connect()方法與Provider組件

【本文源址:http://blog.csdn.net/q1056843325/article/details/54880804 轉載請添加該地址】

在寫Redux的時候我們就瞭解了
如果使用Redux的話配合React是最好的
Dan Abramov爲此還特意封裝了一個react-redux庫來提供便利

概念

一旦我們選擇使用了這個react-redux庫
那麼我們的組件概念就要加以區分了
從現在起我們的組件分爲展示組件和容器組件兩種
(參考了通俗易懂的阮大神博客)

展示組件

展示組件(presentational component)
也叫UI組件、純組件
特點如下:

  • 負責UI顯示
  • 無狀態不使用this.state
  • 數據來自this.props
  • 不使用任何redux的API

展示組件其實就是把我們的普通組件的數據與邏輯抽離出來

容器組件

容器組件(container component)
特點如下:

  • 負責管理數據和業務邏輯
  • 帶有內部狀態
  • 使用redux的API

容器組件是由我們react-redux庫的API通過展示組件生成的

關係

從它們的名字也可以猜到,它們是內外關係
容器組件包裹着展示組件

優勢

說的再通俗一些
我們原來是將結構和邏輯都裝在一個組件中
現在將這個組件繼續拆成負責視圖的組件和負責邏輯數據的組件
這樣做有如下優點:

  • 理解
    • 數據與邏輯分開,更便於我們理解
  • 分離
    • 必須將標籤拆分,可用性更強
  • 重用
    • 一個展示組件可以搭配不同容器組件
  • 視圖
    • 展示組件可以放到單獨頁面中調整UI

下面我們來看一下react-redux庫的核心
connect()方法與Provider組件

connect

上面也說到了
我麼的容器組件是由庫API得到的
而這個函數就是connect
connect的意思就是連接展示組件與容器組件的意思
爲了加以區分,我用Container表示容器組件,用Component表示展示組件
用法如下

import {connect} from 'react-redux';
const Container = connect()(Component);

結構就是這個樣子

<Container>
    <Component/>
</Container>

不過現在我們僅僅是通過展示組件生成了一個容器組件
並且將它們連接了起來
但是容器組件中並沒有數據和邏輯
只是一具空殼,毫無意義
所以我們還需要向這個connect函數中傳入兩個參數
它接收兩個值作爲參數:(實際是四個,另外兩個不常用暫時不講)

  • mapStateToProps(輸入邏輯)
    • 負責將通過state獲得的數據映射到展示組件的this.props
  • mapDispatchToProps(輸出邏輯)
    • 負責將用戶操作轉化爲Action的功能函數映射到展示組件的this.props

名字就和reducer一樣,只是官方的概念性叫法(不過還是蠻形象的)
使用的時候可以自定義名字(不過一定要語義化)

所以完整的用法應該是這樣的

const Container = connect(
    mapStateToProps,
    mapDispatchToProps
)(Component);

但是此時mapStateToProps與mapDispatchToProps我們還沒有定義

mapStateToProps

mapStateToProps負責將state的數據映射到展示組件的this.props
它是一個函數,接收參數state對象
如果有必要的話,還可以使用第二個參數:容器組件的props屬性
返回一個對象表示state到展示組件props的映射關係

const mapStateToProps = (state) => {
    return {
        list: state.list
    }
}

此時你會發現這個函數名有多合適

  • 返回對象中的“值”—— state.list
    • 表示我們要將state的list數組傳遞給內部的展示組件
  • 返回對象中的“鍵”—— list
    • 表示我們在展示組件中可以通過this.props.list來獲取這個數組

但有時,我們不能這麼輕鬆的就通過state的某個屬性值獲得要傳遞的數據
這時我們可以自定義一個處理函數返回要傳遞的數據

const mapStateToProps = (state) => {
    return {
        list: handler(state.list, state.option);
    }
}

比如說這裏handler就是我們的處理函數
拿我上一篇文章的toDoList待辦事項列表爲例
這個handler大概是這樣的

const handler = (list, option) => {
    switch(option){
        case "SHOW_ACTION":
            return list.filter(...);
        case "SHOW_CROSSED":
            return list.filter(...);
        ...
        default:
            return list;
    }
}

這個函數我沒有寫完整,相信大家應該都能看明白
通過判斷option我來將list數組進行 “過濾”
函數返回後作爲數據返回給展示組件


mapStateToProps會訂閱store,state更新後,就會觸發展示組件重繪
不過在connect( )函數中,我們可以省略mapStateToProps
如果這麼做的話,store更新就不會觸發展示組件重繪了

上面也說道了,除了state我們還可以使用容器組件的屬性props

const mapStateToProps = (state, ownProps) => {
    return {
        ...
    }
}

如果容器組件的props發生改變的話,同樣會觸發展示組件重繪

mapDispatchToProps

mapDispatchToProps負責定義發送action的函數映射到展示組件的this.props
與它的兄弟不同,它既可以是函數也可以是對象
作爲函數,它會得到store.dispatch作爲參數
同樣還有一個容器組件的props屬性可以使用
返回值我不用說大家也能猜到
就是一個表示映射關係的對象
但是這裏表示的是用戶如何發出Action(比如觸發事件)

const mapDispatchToProps = (state, ownProps) => {
    return {
        onClick: () => {
            dispatch({
                type: 'SET_FILTER',
                filter: ownProps.filter
            })
        }
    }
}
  • 返回對象中的“值”—— () => {dispatch(...)}
    • 表示我們要傳遞給內部展示組件的函數(函數功能:dispatch一個action)
  • 返回對象中的“鍵”—— onClick
    • 表示我們在展示組件中可以通過this.props.onClick來獲取這個函數

如果是作爲對象的話,就更簡單了
上面的寫法和下面的等價

const mapDispatchToProps = {
    onClick: (filter) => {
        type: 'SET_FILTER',
        filter: filter
    }
}

這個對象的值是一個函數,它被認爲是一個Action Creator
函數的參數可以填入容器組件的props
返回的Action會由redux自動dispatch

Provider

在完成了Container與Componet的連接
實現了Container的管理數據與業務邏輯之後還沒完
還有問題
我們使用了mapStateToProps,它的參數是state
也就是說,他需要傳入state
如果我們手動將state對象一層一層的傳入容器組件
應用小還好說,大應用深層的組件簡直累死了,絕對讓你傳到懷疑人生

好在,react-redux提供了Provider組件讓我們省了不少功夫
它就相當於我們整體的容器組件(不過區別很大)
用法就是在我們根組件外部嵌套一層Provider,傳入store
(使用全局的store有風險)
這樣所以的子組件都可以開心地拿到state了
我們也省心了

render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.getElementById('root')
);

內部的原理是:
Provider接受store作爲其props,並聲明爲context的屬性之一
子組件在聲明瞭contextTypes之後可以通過this.context.store訪問到store

小實例

上一次介紹Redux的時候介紹了一個簡單的計數器
這次我把那個代碼拿過來改裝一下

import React from 'react';
import {Component} from 'react'
import ReactDom from 'react-dom';
import {createStore, combineReducers} from 'redux';
import {connect, Provider} from 'react-redux';

首先定義單純用來展示UI的展示組件

class Counter extends Component {
  render(){
    const {value, reduceHandler, addHandler} = this.props;
    return (
      <div>
        <p>{value}</p>
        <button onClick={reduceHandler}>-</button>
        <button onClick={addHandler}>+</button>
      </div>
    )
  }
};

然後定義映射函數,生成容器組件

const mapStateToProps = (state) => {
  return {
    value: state.cnt
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    reduceHandler: () => {
      dispatch({type: 'REDUCE'});
    },
    addHandler: () => {
      dispatch({type: 'ADD'});
    }
  }
}
const APP = connect(mapStateToProps, mapDispatchToProps)(Counter);

Reducer稍微修改一下

const reducer = (state = {cnt: 0}, action) => {
  switch (action.type) {
    case 'ADD':
      return {cnt: state.cnt + 1};
    case 'REDUCE': 
      return {cnt: state.cnt - 1};
    default: 
      return state;
  }
};
const store = createStore(reducer);

渲染函數中的結構外部嵌套Provider並添加store

ReactDom.render(
  <Provider store={store}>
    <APP/>
  </Provider>,
  document.getElementById('root')
);

有了Provider,我們也就不需要 store.dispatch()
它會幫我們處理渲染
最後的樣式依然是那個樣子

==主頁傳送門==

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章