Redux
:一個(gè)純粹的狀態(tài)管理系統(tǒng)己英,npm i redux -S
-
redux
是一個(gè)獨(dú)立的專門用于做狀態(tài)管理的JS庫(kù)迎瞧,并不是react
的插件庫(kù)夸溶; -
redux
可以用在React、Angular凶硅、Vue
等項(xiàng)目中缝裁,但通常與React
配合使用; - 功能類似于
Vue
中的VueX
,集中式管理組件之間的狀態(tài)共享捷绑!
-
createStore()
創(chuàng)建一個(gè)指定reducer
的store
對(duì)象韩脑; -
store
Redux
最核心的管理對(duì)象;- 內(nèi)部維護(hù)著
store粹污、reducer
- 核心方法
getState()段多、dispatch(action)、subscribe(listener)
- 內(nèi)部維護(hù)著
核心概念
-
action
:標(biāo)識(shí)要執(zhí)行的行為對(duì)象壮吩,包括兩個(gè)屬性:type进苍、xxx
const action = { type: 'INCREMENT', //標(biāo)識(shí)屬性,值為字符串鸭叙,唯一觉啊,必要屬性 data: 2 //數(shù)據(jù)屬性,任意名稱和類型沈贝,可選 } //創(chuàng)建action的工廠函數(shù) const increment = (number) => ({type:'INCREMENT', data: number})
-
reducer
:是一個(gè)純函數(shù)杠人,接收舊的state
和action
,返回新的state
之所以將這樣的函數(shù)稱為reducer
宋下,是因?yàn)樗c被傳入Array.prototype.reduce(reducer, ?initialValue)
里的回調(diào)函數(shù)屬于相同類型嗡善。
保持reducer
純凈非常重要,函數(shù)返回一個(gè)新的狀態(tài)杨凑,不要去直接修改原來(lái)的狀態(tài)滤奈!所以永遠(yuǎn)不要在reducer
里做如下操作:- 修改傳入?yún)?shù)
- 執(zhí)行有副作用的操作,如API請(qǐng)求和路由跳轉(zhuǎn)
- 調(diào)用非純函數(shù)撩满,如
Date.now()蜒程、Math.random()
如果// reducers.js export default function counter(state=0, action) { //不要直接修改state,而是根據(jù)state的狀態(tài)伺帘,返回一個(gè)新的state switch(action.type) { case 'INCREMENT': return state + action.data case 'DECREMENT': return state - action.data default: return state } }
state
是一個(gè)對(duì)象昭躺,recuder
函數(shù)該如何處理呢?return { ...state, action.payload }
-
store
:將state
伪嫁、action
與reducer
聯(lián)系在一起的對(duì)象领炫;// store.js import { createStore } from 'redux' import reducer from './reducers' // 創(chuàng)建 `store` 對(duì)象 const store = createStore(reducer); export default store
-
getState()
獲取state
-
dispatch(action)
分發(fā)action
,觸發(fā)reducer
函數(shù)的調(diào)用张咳,產(chǎn)生新的state
-
subscribe(listener)
注冊(cè)監(jiān)聽(tīng)帝洪,當(dāng)產(chǎn)生新的state
時(shí),回調(diào)listener
import React from 'react'; import store from './redux/store' export default class Home extends React.Component { constructor(props) { super(props) } componentDidMount() { store.subscribe(() => { // 監(jiān)聽(tīng) store 的變化脚猾,手動(dòng)強(qiáng)制更新組件視圖 this.forceUpdate() }) } render() { console.log('Home: render') return( <div> <h3>{store.getState()}</h3> <button onClick={() => store.dispatch({type:"INCREMENT", data: 3})}>遞增</button> <button onClick={() => store.dispatch({type:"DECREMENT", data: 1})}>遞減</button> </div> ) } }
-
優(yōu)雅封裝
創(chuàng)建目錄 src/redux
葱峡,用于存放 action-type.js、actions.js龙助、reducers.js砰奕、store.js
-
reducers.js
,reducer
模塊,包含n
個(gè)reducer
函數(shù)export function counter(state=0, action) { //賦予state一個(gè)初始值军援,state是一個(gè)數(shù)值 switch(action.type) { case 'INCREMENT': return state + action.data case 'DECREMENT': return state - action.data default: //首次初始化調(diào)用時(shí)仅淑,返回的一個(gè)初始值 return state } } // ...
-
action-type.js
存放常量字段export const INCREMENT = 'INCREMENT' export const DECREMENT = 'DECREMENT'
-
actions.js
包含所有action creator
import { INCREMENT, DECREMENT } from './action-type' export const increment = (number) => ({type: INCREMENT, data: number}) export const decrement = (number) => ({type: DECREMENT, data: number})
-
store.js
:包含所有生成的store
對(duì)象;import { createStore } from 'redux' import { counter } from './reducers' const store = createStore(counter); //如果創(chuàng)建了多個(gè)store胸哥,則使用 export 導(dǎo)出 export default store
-
App
組件中導(dǎo)入actions.js
store.dispatch(actions.increment(10)); // 0+10 store.dispatch(actions.decrement(2)); // 10-2
react-redux
react-redux
是 react
的一個(gè)插件涯竟,降低 redux
與 react
的代碼耦合度;
npm i react-redux --save
提供兩個(gè)API:
-
Provider -
為后代組件提供store
-
connect(mapStateToProps, mapDispatchToProps) -
連接組件與redux
烘嘱,為組件提供狀態(tài)數(shù)據(jù)和變更方法昆禽。-
mapStateToProps
回調(diào)函數(shù),回調(diào)的是當(dāng)前狀態(tài)store.getState()
蝇庭,返回值會(huì)傳給組件的props
-
mapDispatchToProps
對(duì)象醉鳖,傳遞更新?tīng)顟B(tài)的方法,映射store dispatch(action)
到組件的props
上哮内; - 返回一個(gè)高階組件(函數(shù))盗棵,它會(huì)包裝傳入的組件,并把
mapStateToProps
和mapDispatchToProps
解構(gòu)之后映射到組件的props
中北发。
-
基本使用
-
index.js
import { Provider } from 'react-redux' import store from './redux/store' ReactDOM.render(( <Provider store={store}> <App /> </Provider> ), document.getElementById('root'))
-
App
組件import { connect } from 'react-redux' import PropTypes from 'prop-types' import { increment, decrement } from './redux/actions' class App extends React.Component { constructor(props){ super(props) } static propTypes = { //聲明接收的屬性纹因,類型校驗(yàn) count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired, } render() { return (<div className="App"> <h2>I am App {this.props.count}</h2> <button onClick={() => this.props.increment(5)}>遞增5</button> <button onClick={() => this.props.decrement(2)}>遞減2</button> </div>); } } export default connect( // mapStateToProps 把store state映射到props中 state => ({ count: state }), // 回調(diào)參數(shù)state 也就是 store.getState() // mapDispatchToProps 把store dispatch(action)映射到props中的方法 { increment, decrement } )(App)
- 使用高階組件的裝飾器簡(jiǎn)化
-
安裝支持裝飾器的插件
yarn add @babel/plugin-proposal-decorators --dev
- 彈出默認(rèn)配置,并配置
package.json
yarn eject // package.json "babel": { "presets": [ "react-app" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ] }
- 注意:對(duì)于
VSCode -> 首選項(xiàng) -> 設(shè)置
琳拨,勾選Experimental Decorators
@connect( state => ({ count: state }), { increment, decrement } ) class App extends React.Component { // ... } export default App
- 彈出默認(rèn)配置,并配置
多個(gè)reducer函數(shù)
多個(gè)reducer
函數(shù)瞭恰,通過(guò)redux
提供的 combineReducers()
函數(shù)進(jìn)行合并。
-
reducers.js
import { combineReducers } from 'redux' function counter(state=0, action) { //賦予state一個(gè)初始值狱庇,state是一個(gè)數(shù)值 // ... } function comments(state=[], action) { //state是一個(gè)數(shù)組 switch(action.type) { case 'INCREMENT': // 不要直接修改state惊畏,而是根據(jù)state的狀態(tài),返回一個(gè)新的state return [action.data, ...state] case 'DECREMENT': //filter() 不會(huì)修改原數(shù)組state return state.filter((item, index) => index!==action.data) default: return state } } export default combineReducers({ count: counter, comment: comments })
-
combineReducers()
讓redux
向外暴露的state
是一個(gè)對(duì)象結(jié)構(gòu):{ count: 0, comment: [] }
// store.js import reducers from './reducers' const store = createStore(reducers, applyMiddleware(thunk)); store.getState() // { count: 0, comment: [] } export default store
-
App
組件@connect( state => { return { count: state.count, comment: state.comment, } }, { increment, decrement } ) class App extends React.Component { //... render() { console.log(this.props.comment) return <div className="App">{this.props.count}</div> } }
異步事件
-
redux
是個(gè)純粹的狀態(tài)管理器密任,默認(rèn)支持同步颜启,不支持諸如延遲、網(wǎng)絡(luò)請(qǐng)求等異步處理浪讳,需要借助redux
插件(異步中間件)npm i redux-thunk -S npm i redux-logger -S //日志中間件
-
createStore()
支持第二個(gè)參數(shù)缰盏,應(yīng)用中間件// store.js import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import logger from 'redux-logger' import { counter } from './reducers' // applyMiddleware() 應(yīng)用中間件,根據(jù)中間件的傳入順序先后執(zhí)行 // thunk, logger 的順序不能亂淹遵,自動(dòng)打印日志 const store = createStore(counter, applyMiddleware(thunk, logger)); export default store
- 異步處理在
action
中// actions.js export const increment = (number) => ({ type: INCREMENT, data: number }) /** * redux 本身不支持異步 export const incrementAsync = (number) => { setTimeout(() => { // 異步代碼口猜,會(huì)報(bào)錯(cuò) return { type: INCREMENT, data: number } }, 1000) } */ //異步的 action 返回一個(gè)函數(shù),由中間件去回調(diào)這個(gè)函數(shù) export const incrementAsync = (number) => { return dispatch => { // 異步代碼 setTimeout(() => { // dispatch({ type: INCREMENT, data: number }); dispatch(increment(number)); }, 1000) } }
redux的調(diào)試工具
- chrome瀏覽器:
redux-devtools
- 依賴包:
npm i redux-devtools-extension --save-dev
- 引入插件
// store.js import { composeWithDevTools } from 'redux-devtools-extension' const store = createStore(counter, composeWithDevTools(applyMiddleware(thunk)));