【本文源址: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()
了
它會幫我們處理渲染
最後的樣式依然是那個樣子