Redux
:一個純粹的狀態管理系統,npm i redux -S
-
redux
是一個獨立的專門用于做狀態管理的JS庫,并不是react
的插件庫; -
redux
可以用在React、Angular、Vue
等項目中,但通常與React
配合使用; - 功能類似于
Vue
中的VueX
,集中式管理組件之間的狀態共享!
工作機制
-
createStore()
創建一個指定reducer
的store
對象; -
store
Redux
最核心的管理對象;- 內部維護著
store、reducer
- 核心方法
getState()、dispatch(action)、subscribe(listener)
- 內部維護著
核心概念
-
action
:標識要執行的行為對象,包括兩個屬性:type、xxx
const action = { type: 'INCREMENT', //標識屬性,值為字符串,唯一,必要屬性 data: 2 //數據屬性,任意名稱和類型,可選 } //創建action的工廠函數 const increment = (number) => ({type:'INCREMENT', data: number})
-
reducer
:是一個純函數,接收舊的state
和action
,返回新的state
之所以將這樣的函數稱為reducer
,是因為它與被傳入Array.prototype.reduce(reducer, ?initialValue)
里的回調函數屬于相同類型。
保持reducer
純凈非常重要,函數返回一個新的狀態,不要去直接修改原來的狀態!所以永遠不要在reducer
里做如下操作:- 修改傳入參數
- 執行有副作用的操作,如API請求和路由跳轉
- 調用非純函數,如
Date.now()、Math.random()
如果// reducers.js export default function counter(state=0, action) { //不要直接修改state,而是根據state的狀態,返回一個新的state switch(action.type) { case 'INCREMENT': return state + action.data case 'DECREMENT': return state - action.data default: return state } }
state
是一個對象,recuder
函數該如何處理呢?return { ...state, action.payload }
-
store
:將state
、action
與reducer
聯系在一起的對象;// store.js import { createStore } from 'redux' import reducer from './reducers' // 創建 `store` 對象 const store = createStore(reducer); export default store
-
getState()
獲取state
-
dispatch(action)
分發action
,觸發reducer
函數的調用,產生新的state
-
subscribe(listener)
注冊監聽,當產生新的state
時,回調listener
import React from 'react'; import store from './redux/store' export default class Home extends React.Component { constructor(props) { super(props) } componentDidMount() { store.subscribe(() => { // 監聽 store 的變化,手動強制更新組件視圖 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> ) } }
-
優雅封裝
創建目錄 src/redux
,用于存放 action-type.js、actions.js、reducers.js、store.js
-
reducers.js
,reducer
模塊,包含n
個reducer
函數export function counter(state=0, action) { //賦予state一個初始值,state是一個數值 switch(action.type) { case 'INCREMENT': return state + action.data case 'DECREMENT': return state - action.data default: //首次初始化調用時,返回的一個初始值 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
對象;import { createStore } from 'redux' import { counter } from './reducers' const store = createStore(counter); //如果創建了多個store,則使用 export 導出 export default store
-
App
組件中導入actions.js
store.dispatch(actions.increment(10)); // 0+10 store.dispatch(actions.decrement(2)); // 10-2
react-redux
react-redux
是 react
的一個插件,降低 redux
與 react
的代碼耦合度;
npm i react-redux --save
提供兩個API:
-
Provider -
為后代組件提供store
-
connect(mapStateToProps, mapDispatchToProps) -
連接組件與redux
,為組件提供狀態數據和變更方法。-
mapStateToProps
回調函數,回調的是當前狀態store.getState()
,返回值會傳給組件的props
-
mapDispatchToProps
對象,傳遞更新狀態的方法,映射store dispatch(action)
到組件的props
上; - 返回一個高階組件(函數),它會包裝傳入的組件,并把
mapStateToProps
和mapDispatchToProps
解構之后映射到組件的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 = { //聲明接收的屬性,類型校驗 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 }), // 回調參數state 也就是 store.getState() // mapDispatchToProps 把store dispatch(action)映射到props中的方法 { increment, decrement } )(App)
- 使用高階組件的裝飾器簡化
-
安裝支持裝飾器的插件
yarn add @babel/plugin-proposal-decorators --dev
- 彈出默認配置,并配置
package.json
yarn eject // package.json "babel": { "presets": [ "react-app" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ] }
- 注意:對于
VSCode -> 首選項 -> 設置
,勾選Experimental Decorators
@connect( state => ({ count: state }), { increment, decrement } ) class App extends React.Component { // ... } export default App
- 彈出默認配置,并配置
多個reducer函數
多個reducer
函數,通過redux
提供的 combineReducers()
函數進行合并。
-
reducers.js
import { combineReducers } from 'redux' function counter(state=0, action) { //賦予state一個初始值,state是一個數值 // ... } function comments(state=[], action) { //state是一個數組 switch(action.type) { case 'INCREMENT': // 不要直接修改state,而是根據state的狀態,返回一個新的state return [action.data, ...state] case 'DECREMENT': //filter() 不會修改原數組state return state.filter((item, index) => index!==action.data) default: return state } } export default combineReducers({ count: counter, comment: comments })
-
combineReducers()
讓redux
向外暴露的state
是一個對象結構:{ 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
是個純粹的狀態管理器,默認支持同步,不支持諸如延遲、網絡請求等異步處理,需要借助redux
插件(異步中間件)npm i redux-thunk -S npm i redux-logger -S //日志中間件
-
createStore()
支持第二個參數,應用中間件// store.js import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import logger from 'redux-logger' import { counter } from './reducers' // applyMiddleware() 應用中間件,根據中間件的傳入順序先后執行 // thunk, logger 的順序不能亂,自動打印日志 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(() => { // 異步代碼,會報錯 return { type: INCREMENT, data: number } }, 1000) } */ //異步的 action 返回一個函數,由中間件去回調這個函數 export const incrementAsync = (number) => { return dispatch => { // 異步代碼 setTimeout(() => { // dispatch({ type: INCREMENT, data: number }); dispatch(increment(number)); }, 1000) } }
redux的調試工具
- 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)));