Redux是什么:Redux是一個專門用來做狀態(tài)管理的JS庫(不是React插件庫)。它可以用在React,Angular,Vue應用中,但基本和React配合使用。用于集中式管理React應用中多個組件共享的狀態(tài)。
首先我們先介紹一個概念集中式管理,集中式管理要求狀態(tài)在哪里,修改狀態(tài)的行為就定義在那個組件。
但是集中式管理有一個問題。一個組件下面有很多路由組件。如果這些組件的行為全部放在一個文件下會造成很大的冗余。
然而Redux使用集中式管理來管理狀態(tài)。將狀態(tài)以及狀態(tài)管理的方法放在一處,需要的時候調(diào)用狀態(tài)管理的方法。此時狀態(tài)不在組件的內(nèi)部,而是在Redux中。

組件的兩大功能:
- 展現(xiàn)數(shù)據(jù)
- 與用戶交互更新數(shù)據(jù)
Action Creators,Store,Reducers都是Redux代碼。因此整個過程可以看做是React組件和Redux進行交互。
展現(xiàn)數(shù)據(jù):Store是Redux中的核心對象。從圖中可以看出React組件是從Store中讀取狀態(tài)。
更新新的狀態(tài)顯示:使用分發(fā)功能時會使用到action,其中有兩個屬性type,data。Action Creators是一個工廠函數(shù),用于修改action對象。我們需要傳遞一個type和data。type代表的是事件的類型。Action Creators通過type決定生成什么類型的Action。data是相關的數(shù)據(jù)。
Reducers相當于一個函數(shù),該函數(shù)的形參是previousState和action,返回值為newState。返回的newState交給store進行狀態(tài)的更新。
React中狀態(tài)無法直接更新,必須調(diào)用this.setState()。
總結(jié):一個組件重要的功能是兩方面:顯示狀態(tài)和更新狀態(tài)。顯示狀態(tài)可以通過Store來修改狀態(tài)。更新狀態(tài)時我們主要做兩件事,首先是發(fā)送通知,通過dispatch。之后是實現(xiàn)Reducers函數(shù)產(chǎn)生新的狀態(tài)。
什么情況下需要使用Redux
- 總體原則:能不用就不用,如果不用比較吃力才考慮使用。
- 某個組件的狀態(tài),需要共享。
- 某個狀態(tài)需要在任何地方都可以拿到。
- 一個組件需要改變?nèi)譅顟B(tài)。
- 一個組件需要改變另一個組件的狀態(tài)。
使用Redux
首先先下載依賴包
npm install --save redux
Store對象
作用:Redux庫最核心的管理對象。
內(nèi)部維護:state,reducer
核心方法:
- getState()
- dispatch(action)
- subscribe(listener)
編碼:
- store.getState()
- store.dispatch({type:'INCREMENT', number})
- store.subscribe(render)
Redux三個核心概念
action
標識要執(zhí)行行為的對象
包含兩個方面的屬性:
- type:標識屬性,值為字符串,唯一,必要屬性。
- xxx:數(shù)據(jù)屬性,值為任意類型,可選屬性。
例子:
const action = {
type: "INCREMENT",
data: 2
}
Action.Creator(Action的工廠函數(shù))的創(chuàng)建
const increment = (number) => ({type: "INCREMENT", data: number})
reducer
根據(jù)老的state和action產(chǎn)生新的state的純函數(shù)。
例子:
export default function counter(state=0, action){
switch(action.type){
case: "INCREMENT":
return state+action.data
case: "DECREMENT":
return state-action.data
default:
return state
}
}
注意:
- 每一次都返回一個新的狀態(tài)數(shù)據(jù)。
- 不要改變以前的狀態(tài)數(shù)據(jù)。
store
將state, action與reducer聯(lián)系到一起的對象。
如何得到該對象:
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
此對象的功能:
getStete():得到state
dispatch(action):分發(fā)action,觸發(fā)reducer的調(diào)用,產(chǎn)生新的state。
subscribe(listener):注冊監(jiān)聽,當產(chǎn)生了新的state對象時,自動調(diào)用。
Redux實例
我們需要實現(xiàn)以下的效果:
點擊“加號”,“減號”按鈕,上面的數(shù)字分別增加或減少顯示的數(shù)字。點擊“increment if odd”如果顯示的數(shù)字為奇數(shù)則增加顯示的數(shù)據(jù)。點擊“increment async”隔一段時間后增加顯示的數(shù)據(jù)。
首先在index.js中引入Store對象
import {createStore} from 'redux'
之后是創(chuàng)建Store對象
const store = createStore()
之后是編寫Action Creators(reducers.js)并將其傳給Store對象。和前面說的一樣Action Creators相當于一個工廠方法根據(jù)傳遞的type決定產(chǎn)生什么樣的Action。
export function counter(state = 0, action) {
switch (action.type){
case "INCREMENT":
return state + action.data
case "DECREMENT":
return state - action.data
default:
return state
}
}
這里做一些改進。由于我們在傳遞type的時候可能會將type的類型寫錯。所以這里我們最好寫一個類(action-types.js)來統(tǒng)一管理這些字符。
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
之后在reducers.js中引入之前寫的action-types.js
import {INCREMENT, DECREMENT} from './action-types'
export function counter(state = 0, action) {
switch (action.type){
case INCREMENT:
return state + action.data
case DECREMENT:
return state - action.data
default:
return state
}
}
這個時候我們就不需要擔心type寫錯的問題了。
之后回到index.js中,引入之前寫的reducers.js。并將Action Creators傳遞給Store對象。并將Store對象傳遞給組件類。
import {counter} from './redux/reducers'
const store = createStore(counter)
function new_render(){
render(<App store={store}/>, document.getElementById('root'))
}
new_render()
store.subscribe(new_render)
讓我們來到組件類app.jsx。由于狀態(tài)是被Redux管理的。因此在組件類中我們無需定義state了。狀態(tài)值的獲取與更新也要經(jīng)過Redux。不需要自己寫方法了。例如:
//獲取狀態(tài)值
const count = this.props.store.getState()
//更新狀態(tài)值
this.props.store.dispatch({type: INCREMENT, data: number})
整個app.jsx的代碼如下:
import React, {Component} from 'react'
import {INCREMENT, DECREMENT} from '../redux/action-types'
class App extends Component{
increment = () => {
const number = this.select.value*1
this.props.store.dispatch({type: INCREMENT, data: number})
}
decrement = () => {
const number = this.select.value*1
this.props.store.dispatch({type: DECREMENT, data: number})
}
incrementIfOdd = () => {
const number = this.select.value*1
const count = this.props.store.getState()
if(count %2 === 1)
// this.setState({count: count + number})
this.props.store.dispatch({type: INCREMENT, data: number})
}
incrementAsync = () => {
const number = this.select.value*1
setTimeout(() => {
this.props.store.dispatch({type: INCREMENT, data: number})
}, 1000)
}
render(){
const count = this.props.store.getState()
return (
<div>
<p>Click {count} times</p>
<div>
<select ref={select => this.select = select}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>increment if odd</button>
<button onClick={this.incrementAsync}>increment async</button>
</div>
</div>
)
}
}
export default App
使用react-redux進行簡化
react-redux是一個react插件庫。專門用于簡化在react應用中使用redux。
export default connect(
state => ({count: state}),
{increment, decrement}
)(App)
在此示例中。state和{increment, decrement}會被結(jié)構(gòu)傳遞給App組件。狀態(tài)放到了Redux中,我們需要這些狀態(tài)就需要屬性來接收。connect起到了傳遞屬性的作用。注意:connect中的屬性名必須和組件中的屬性名一致。
React-Redux將所有的組件分為兩大類:
-
UI組件
不使用Redux的API
一般放在components文件夾下 -
容器組件
使用Redux的API
一般保存在containers文件夾下
action-types.js:包含所有action類型的名稱常量。
actions.js:包含了所有的action,工廠函數(shù)。
reducers.js:包含多個reducer函數(shù)。根據(jù)老的state和action,返回一個新的state。
store.js:redux最核心的管理對象。
components下存放著UI組件(counter.jsx)
import React, {Component} from 'react'
import PropTypes from 'prop-types'
export default class Counter extends Component{
static propTypes = {
count: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired
}
increment = () => {
const number = this.select.value*1
this.props.increment(number)
}
decrement = () => {
const number = this.select.value*1
this.props.decrement(number)
}
incrementIfOdd = () => {
const number = this.select.value*1
const {count} = this.props
if(count %2 === 1)
// this.setState({count: count + number})
this.props.increment(number)
}
incrementAsync = () => {
const number = this.select.value*1
setTimeout(() => {
this.props.increment(number)
}, 1000)
}
render(){
const {count} = this.props
return (
<div>
<p>Click {count} times</p>
<div>
<select ref={select => this.select = select}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>increment if odd</button>
<button onClick={this.incrementAsync}>increment async</button>
</div>
</div>
)
}
}
containers下放容器組件(app.jsx)
import {decrement, increment} from "../redux/actions";
import {connect} from "react-redux";
import Counter from "../components/counter";
export default connect(
state => ({count: state}),
{increment, decrement}
)(Counter)
Provider組件:
讓所有組件都可以得到state數(shù)據(jù)
<Provider store={store}>
<App />
</Provider>
connect組件:
用于包裝UI組件形成容器組件。將組件和Redux關聯(lián)起來。
import {connect} from 'react-redux'
connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
mapStateToProps():
將外部數(shù)據(jù)(即state對象)轉(zhuǎn)換為UI組件的標簽屬性。
const mapStateToProps = function(state){
return (
{value: state}
)
}
mapDispatchToProps():
將分發(fā)action的函數(shù)轉(zhuǎn)換為UI組件的標簽屬性。
簡潔語法可以直接指定為actions對象或包含多個action方法的對象。
管理多個Reducer
import {combineReducers} from 'react'
合并多個reducer放在一起管理。
假設此時有兩個reducer。我們不應將它們分別暴露。而是統(tǒng)一放到combineReducers方法中進行統(tǒng)一暴露。同時之前暴露的方法也要統(tǒng)一改成統(tǒng)一管理的方法。