目錄
一. React-Redux是什么
?1. React-Redux的組件拆分規(guī)范
??1.1 UI組件
??1.2 容器組件
?2. React-Redux的API
??2.1
connect()方法??2.2 Provider組件
二. 為什么要使用React-Redux
三. 怎么使用React-Redux
需要導(dǎo)入的組件:
reduxreact-reduxredux-thunk-
redux-devtools(可選):Redux開(kāi)發(fā)者工具,支持熱加載、action重放、自定義UI等功能。 -
redux-persist(可選):支持store本地持久化。 -
redux-observable(可選):實(shí)現(xiàn)可取消的action。
yarn add redux
yarn add react-redux
yarn add redux-thunk
一. React-Redux是什么
React-Redux,是Redux的作者為了輔助我們使用Redux而編寫(xiě)的一個(gè)庫(kù),沒(méi)有提供什么新功能。
通常情況下,我們寫(xiě)的一個(gè)組件內(nèi)部既負(fù)責(zé)UI渲染,也負(fù)責(zé)管理數(shù)據(jù)和處理業(yè)務(wù)邏輯。而React-Redux秉承一個(gè)理念,就是要把一個(gè)組件拆分成UI組件和容器組件,UI組件只負(fù)責(zé)UI的渲染,容器組件只負(fù)責(zé)管理數(shù)據(jù)和處理業(yè)務(wù)邏輯,而容器組件的這些邏輯又完全是交給Redux來(lái)處理的,所以就可以使得我們編寫(xiě)的組件完全成了一個(gè)UI組件,組件內(nèi)部會(huì)更加清爽。
1. React-Redux的組件拆分規(guī)范
使用React-Redux時(shí)要對(duì)組件進(jìn)行拆分,React-Redux規(guī)定所有的UI組件都由用戶(hù)提供,容器組件則是由React-Redux自動(dòng)生成,生成后我們直接容器組件,而不再使用UI組件。容器組件其實(shí)是包在UI組件外面的,所以它即具備渲染能力,也具備數(shù)據(jù)管理和業(yè)務(wù)處理的能力,只不過(guò)我們看不到而已,我們只能看到我們編寫(xiě)的UI組件,那些東西都在容器組件內(nèi)部。
1.1 UI組件
UI 組件有如下特征。
- 只負(fù)責(zé)UI的呈現(xiàn),不帶有任何業(yè)務(wù)邏輯。
- 所有的數(shù)據(jù)都由
this.props屬性提供。 - 內(nèi)部沒(méi)有狀態(tài),即不使用
this.state這個(gè)屬性。 - 不使用任何Redux的API。
下面就是一個(gè)UI組件的例子。
class CustomNavigator extends Component {
render() {
return(
<View>
<Text>{this.props.headerTitle}<Text/>
<View/>
);
}
}
因?yàn)椴缓袪顟B(tài),所以UI組件又稱(chēng)為“純組件”,即它純函數(shù)一樣,純粹由參數(shù)決定它的值。
1.2 容器組件
容器組件的特征恰恰相反。
- 負(fù)責(zé)處理業(yè)務(wù)邏輯,不負(fù)責(zé)UI的呈現(xiàn)。
- 有負(fù)責(zé)管理數(shù)據(jù)。
- 內(nèi)部帶有狀態(tài)。
- 使用Redux的API。
2. React-Redux的API
2.1 connect()方法
React-Redux提供了connect()方法,用來(lái)生成一個(gè)UI組件對(duì)應(yīng)的容器組件,即把組件和應(yīng)用State建立映射關(guān)系。如果我們想讓一個(gè)組件響應(yīng)應(yīng)用State的變化(即想讓該組件的state由Redux來(lái)關(guān)系),就把搞成一個(gè)容器組件使用就可以了。
import {connect} from 'react-redux'
const VisibleTodoList = connect()(TodoList);
上面代碼中,TodoList是UI組件,VisibleTodoList就是由React-Redux通過(guò)connect()方法生成的容器組件。
但是,因?yàn)闆](méi)有定義業(yè)務(wù)邏輯,上面這個(gè)容器組件毫無(wú)意義,只是UI組件的一個(gè)單純的包裝層,實(shí)際開(kāi)發(fā)中我們需要為每個(gè)容器組件定義兩種業(yè)務(wù)邏輯,即:
- 輸入邏輯:UI組件的輸入邏輯類(lèi)
props要和應(yīng)用的state建立映射關(guān)系。 - 輸出邏輯:UI組件的輸出邏輯類(lèi)屬性要和dispatch(addAction)建立映射關(guān)系。
因此,使用connect()方法從UI組件生成容器組件的完整API如下。
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList);
上面代碼中,connect()方法接收了兩個(gè)參數(shù),其中mapStateToProps就負(fù)責(zé)輸入邏輯的映射,mapDispatchToProps就負(fù)責(zé)輸出邏輯的映射。
(1)mapStateToProps函數(shù)
mapStateToProps是一個(gè)函數(shù),它用來(lái)建立UI組件的輸入邏輯類(lèi)props和應(yīng)用state的映射關(guān)系,以此決定改組件想要使用應(yīng)用state里的哪一部分?jǐn)?shù)據(jù)。它可以得到應(yīng)用的state一個(gè)參數(shù)。
該函數(shù)執(zhí)行后立即返回一個(gè)JS對(duì)象,該JS對(duì)象里的每一個(gè)key都是該UI組件的某個(gè)props(必須同名),而每一個(gè)key對(duì)應(yīng)的value則來(lái)自于應(yīng)用state里數(shù)據(jù)的計(jì)算。這樣就建立起了映射關(guān)系,請(qǐng)看下面的例子。
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed);
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed);
default:
throw new Error('Unknown filter: ' + filter);
}
}
上面代碼中,mapStateToProps函數(shù)接受應(yīng)用的state作為參數(shù),返回一個(gè)JS對(duì)象。這個(gè)JS對(duì)象有一個(gè)todos屬性,是UI組件的同名參數(shù),后面的getVisibleTodos也是一個(gè)函數(shù),用來(lái)從state算出todos的值。
請(qǐng)注意:
mapStateToProps函數(shù)就像我們上一篇所說(shuō)到的store.subscribe(callback)的那個(gè)回調(diào)函數(shù)一樣,它會(huì)監(jiān)聽(tīng)?wèi)?yīng)用State的變化,所以每當(dāng)state更新的時(shí)候,它就會(huì)自動(dòng)執(zhí)行,重新賦值UI組件的props,從而觸發(fā)UI組件的重新渲染。
(2)mapDispatchToProps函數(shù)
mapDispatchToProps是connect()函數(shù)的第二個(gè)參數(shù),它用來(lái)建立UI組件的輸出邏輯類(lèi)props和應(yīng)用dispatch(addAction)的映射關(guān)系。它可以得到dispatch和ownProps(容器組件的props對(duì)象)兩個(gè)參數(shù)。
該函數(shù)執(zhí)行后立即返回一個(gè)JS對(duì)象,該JS對(duì)象里的每一個(gè)key都是該UI組件的某個(gè)事件類(lèi)props(必須同名,onPress、onClick等),而每一個(gè)key對(duì)應(yīng)的value就是我們要做的dispatch(addAction)。這樣就建立起了映射關(guān)系,請(qǐng)看下面的例子。
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
2.2 Provider組件
我們使用connect()方法生成容器組件以后,容器組件它自己也沒(méi)有應(yīng)用State啊,那它怎么完成應(yīng)用State和UI組件的映射呢?所以我們還需要做一步,就是讓讓容器組件拿到應(yīng)用State。
React-Redux提供Provider組件,可以讓容器組件拿到應(yīng)用State。做法很簡(jiǎn)單,只需要在應(yīng)用根組件外面包一層Provider組件,那App的所有子組件就都默認(rèn)拿到應(yīng)用State了,所以容器組件作為根容器的子組件,自然也就拿到了。
import {Provider} from 'react-redux';
export default class App extends Component<Props> {
render() {
return (
<Provider store={store}>
<TestPage/>
</Provider>
);
}
}
二. 為什么要使用React-Redux
首先說(shuō)明,React-Redux也不是必須使用的,它只是Redux的一個(gè)輔助庫(kù),可以幫助我們使得組件的內(nèi)部更簡(jiǎn)潔。
因?yàn)镽eact-Redux秉承一個(gè)理念,就是要把一個(gè)組件拆分成UI組件和容器組件,UI組件只負(fù)責(zé)UI的渲染,容器組件只負(fù)責(zé)管理數(shù)據(jù)和處理業(yè)務(wù)邏輯,而容器組件的這些邏輯又完全是交給Redux來(lái)處理的,所以就可以使得我們編寫(xiě)的組件完全成了一個(gè)UI組件,組件內(nèi)部會(huì)更加清爽。
三. 怎么使用React-Redux
我們依舊以一個(gè)最簡(jiǎn)單的計(jì)數(shù)器為例。
- 導(dǎo)入相關(guān)的東西
-----------TestPage.js-----------
// 導(dǎo)入Redux相關(guān)的東西
import {createStore, combineReducers} from 'redux';
// 導(dǎo)入React-Redux相關(guān)的東西
import {connect, Provider} from 'react-redux';
- 先做Redux部分
三個(gè)關(guān)鍵詞:store、reducer、action。
-----------TestPage.js-----------
// 第2步:
// 創(chuàng)建根reducer,合并所有子reducer
// 請(qǐng)注意,這個(gè)根reducer是個(gè)極其重要的東西,因?yàn)檎撬喜⒆觬educer的過(guò)程,決定了應(yīng)用state里到底存放著什么東西,即什么組件的state要被放在它里面
// 什么組件的state想要交由應(yīng)用的state來(lái)統(tǒng)一管理,我們就為該組件編寫(xiě)一個(gè)對(duì)應(yīng)的子reducer來(lái)描述它state具體變化的過(guò)程并返回一個(gè)state,然后把這個(gè)reducer作為value存放在應(yīng)用state里(即合并子reducer的時(shí)候)
// 剛創(chuàng)建根reducer時(shí),我們可能不知道將來(lái)會(huì)有那些組件的state會(huì)被放在應(yīng)用state里來(lái)統(tǒng)一管理,所以可以先空著,什么時(shí)候需要什么時(shí)候往這里添加就可以
// 這里我們假設(shè)計(jì)數(shù)器的state想要交由應(yīng)用的state統(tǒng)一管理,計(jì)數(shù)器可以改變數(shù)字和背景色
// 所以我們編寫(xiě)一個(gè)計(jì)數(shù)器子reducer,專(zhuān)門(mén)用來(lái)完成計(jì)數(shù)器state的變化,并返回新的state
// 計(jì)數(shù)器的初始state
const defaultState = {
number: 100,
backgroundColor: 'pink',
}
// 計(jì)數(shù)器的state具體變化的過(guò)程
const counterReducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD_NUMBER':
return {
...state,
number: state.number + 1,
};
case 'CHANGE_BACKGROUND_COLOR':
return {
...state,
backgroundColor: action.backgroundColor,
};
default:
return state;
}
}
// 接下來(lái)第3步,就是結(jié)合該組件reducer里action.type的規(guī)定,為該組件創(chuàng)建對(duì)應(yīng)的action,預(yù)備好action,到時(shí)候組件一被觸摸就dispatch一個(gè)action
const rootReducer = combineReducers({// 這個(gè)對(duì)象就是應(yīng)用的state
// 應(yīng)用state里的屬性名當(dāng)然可以隨便起,但是為了好理解,我們就起做xxxState,為什么這么起呢?
// 因?yàn)閼?yīng)用state的定義就是,它里面存放著項(xiàng)目中所有想被統(tǒng)一管理state的組件的state,所以我們起做xxxState,將來(lái)使用時(shí)很方便理解,比如state.counterState,就代表從應(yīng)用state里取出counterState
// 而且它的值就是對(duì)應(yīng)的該組件的那個(gè)子reducer嘛,而reducer函數(shù)又總是返回一個(gè)state,這樣xxxState = 某個(gè)state值,也很好理解
counterState: counterReducer,
});
// 第1步:
// 創(chuàng)建項(xiàng)目唯一的store,發(fā)現(xiàn)需要一個(gè)reducer
const store = createStore(rootReducer);
// 所以接下來(lái)第2步,我們?nèi)?chuàng)建一個(gè)reducer,回過(guò)頭來(lái)填在這里
// 第3步:
// 為該組件創(chuàng)建對(duì)應(yīng)的action,預(yù)備好action,到時(shí)候組件一被觸摸就dispatch一個(gè)action
// 負(fù)責(zé)描述state要做什么變化以及變化所需的原料,用來(lái)dispatch
const addNumberAction = {type: 'ADD_NUMBER'};
// Action Creator
function changeBackgroundColor(backgroundColor) {
// 返回一個(gè)action
return {
type: 'CHANGE_BACKGROUND_COLOR',
backgroundColor,
}
}
// 好,完成了這三步,Redux部分就算完成了,我們可以開(kāi)始React-Redux的部分
- 再做React-Redux部分
四個(gè)關(guān)鍵詞:UI組件、容器組件、connect方法、Provider組件。
-----------TestPage.js-----------
// 第1步:編寫(xiě)UI組件
// 僅負(fù)責(zé)UI的渲染工作,所需數(shù)據(jù)只能通過(guò)props獲得
// 不能處理業(yè)務(wù)邏輯,不能使用Redux的API
class Counter extends Component {
render() {
// 該UI組件有三個(gè)屬性,下面添加映射關(guān)系的時(shí)候會(huì)為該組件添加上這些屬性
// number:輸入邏輯類(lèi)屬性
// backgroundColor:輸入邏輯類(lèi)屬性
// addNumber:輸出邏輯類(lèi)屬性
// changeBackgroundColor:輸出邏輯類(lèi)屬性
const {number, backgroundColor, addNumber, changeBackgroundColor} = this.props;
return (
<View style={[styles.counterViewStyle, {backgroundColor: backgroundColor}]}>
<Button
title={'變色'}
// 第5步:在需要發(fā)出action的地方,通過(guò)調(diào)用props里方法的形式發(fā)出一個(gè)action就可以了
// 注意,onPress必須接收一個(gè)函數(shù),而不能接收一個(gè)函數(shù)的調(diào)用
// 所以這里不能寫(xiě)成:onPress={changeBackgroundColor('cyan')}
// 而應(yīng)該給它賦值一個(gè)函數(shù):
onPress={() => changeBackgroundColor('cyan')}
/>
<Text style={{fontSize: 24}}>{number}</Text>
<View>
<Text
style={{color: 'black', fontSize: 20}}
// 注意,onPress必須接收一個(gè)函數(shù),而不能接收一個(gè)函數(shù)的調(diào)用
// 因?yàn)閍ddNumber函數(shù)不需要參數(shù),所以可以直接賦值
onPress={addNumber}
>{'+'}</Text>
</View>
</View>
);
};
}
// 第2步:建立UI組件的props與外界的映射關(guān)系
// 你可能會(huì)問(wèn)“這個(gè)UI組件是什么添加上這些props的呢”?沒(méi)錯(cuò),正是這個(gè)添加映射關(guān)系的過(guò)程為該UI組件添加上了這些props
// 輸入邏輯類(lèi)屬性要和應(yīng)用state里該組件state里的屬性建立映射關(guān)系
function mapStateToProps(state) {
return {
// UI組件的屬性:應(yīng)用state.該組件的state.屬性
// 注意這里要保證,該組件props里的屬性,和該組件state里(這里只是姑且這么稱(chēng)呼,其實(shí)counterState并非真正該組件的state)的屬性同名
number: state.counterState.number,
backgroundColor: state.counterState.backgroundColor,
}
}
// 輸出邏輯類(lèi)屬性要和dispatch(action)建立映射關(guān)系
function mapDispatchToProps(dispatch) {
return {
// UI組件的屬性:dispatch(action)
addNumber: () => dispatch(addNumberAction),
changeBackgroundColor: (color) => dispatch(changeBackgroundColor(color)),
}
}
// 第3步:使用connect方法生成UI組件對(duì)應(yīng)的容器組件
// 將來(lái)我們就是使用UI組件的容器組件了,而不是使用UI組件
const CounterContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(Counter);
export default class TestPage extends Component {
render() {
return (
// 第4步:在整個(gè)項(xiàng)目的根組件外面包一層Provider組件(注意,這一步只針對(duì)整個(gè)項(xiàng)目的根組件,小組件不需要這一步,完成第3步之后直接使用就可以了),記得一定要把store={store}傳遞進(jìn)去
<Provider store={store}>
<View style={styles.container}>
<CounterContainer/>
</View>
</Provider>
);
}
}
- 樣式部分
-----------TestPage.js-----------
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
counterViewStyle: {
width: 200,
height: 60,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
},
});
參考博客:
阮一峰:Redux入門(mén)教程——React-Redux 的用法