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)));