redux 是 JavaScript 状态容器,提供可预测化的状态管理。 可以用在React、Vue等前端库中。
react-redux是react中的redux插件,简化了redux的用法,使开发者更方便地使用redux来管理状态。
为什么要有redux?我们知道,react中的数据流是单项传递的,即状态只能从父组件传递给子组件,虽然可以用ref在父调中调用子组件的方法从而返回子组件的状态给父组件使用、或者使用contex跨级传递属性等方法,但是当组件很多而且都需要共享一个属性的时候,那些方法管理属性就显得不规范且不方便,因此,需要使用redux这个专门的状态容器来统一管理状态。
下面分别在不用redux、使用redux、使用react-redux实现同一功能为例讲解redux的原理以及用法。(记得用npm或者yarn安装redux和react-rudex包)
如果要实现这样的一个功能:点击“加号”按钮,数字加上下拉框中所选中的数字大小;点击“减号”按钮,数字减去下拉框中所选中的数字大小;点击“increment if odd”按钮,只有当数字是奇数的时候才做加运算;点击“increment async”按钮的时候异步做加运算。
效果图如下:
用一般的onClick回调函数的写法如下:
import React, {Component} from 'react'
class App extends Component {
constructor(props) {
super(props);
this.numberRef = React.createRef();
}
state = {
count: 0
};
increment = () => {
const number = this.numberRef.current.value * 1;
this.setState({
count: this.state.count + number
})
};
decrement = () => {
const number = this.numberRef.current.value * 1;
this.setState({
count: this.state.count - number
})
};
incrementIfOdd = () => {
const count = this.state.count;
const number = this.numberRef.current.value * 1;
if(count % 2 === 1) {
this.setState({
count: count + number
})
}
};
incrementAsync = () => {
const number = this.numberRef.current.value * 1;
setTimeout(() => {
this.setState({
count: this.state.count + number
})
}, 1000);
};
render() {
const count = this.state.count;
return (
click {count} time
)
}
}
export default App;
用redux的写法如下:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';
ReactDOM.render(
,
document.getElementById('root')
);
store.subscribe(() => {
ReactDOM.render(
,
document.getElementById('root')
);
});
App.js
import React, {Component} from 'react'
import store from './redux/store';
import {increment, decrement} from './redux/actions';
class App extends Component {
constructor(props) {
super(props);
this.numberRef = React.createRef();
}
increment = () => {
const number = this.numberRef.current.value * 1;
store.dispatch(increment(number))
};
decrement = () => {
const number = this.numberRef.current.value * 1;
store.dispatch(decrement(number))
};
incrementIfOdd = () => {
const count = store.getState();
const number = this.numberRef.current.value * 1;
if(count % 2 === 1) {
store.dispatch(increment(number))
}
};
incrementAsync = () => {
const number = this.numberRef.current.value * 1;
setTimeout(() => {
store.dispatch(increment(number))
}, 1000);
};
render() {
const count = store.getState();
console.log("render--"+count);
return (
click {count} time
)
}
}
export default App;
src/redux/constants.js
/**
* 包含n个action的type常量名称的模块
* 这里只是避免reducers和action的type中的常量不一致
* */
export const DECREMENT = 'decrement';
export const INCREMENT = 'increment';
src/redux/actions.js
/**
* n个用于创建action对象的工厂函数
* */
import {INCREMENT, DECREMENT} from './constants';
export const increment = (number) => ({ type: INCREMENT, number});
export const decrement = (number) => ({ type: DECREMENT, number});
src/redux/reducer.js
/**
* 管理状态数据的函数
* 根据旧的state,删除新的state
* 必须是纯函数
* */
import {INCREMENT, DECREMENT} from './constants';
export default function count (state = 1, action) {
switch(action.type) {
case INCREMENT:
return state + action.number;
case DECREMENT:
return state - action.number;
default:
return state;
}
}
src/redux/store.js
/**
* redux最核心的管理对象:store
* */
import {createStore} from 'redux';
import reducer from './reducer';
//根据指定的reducer函数,产生一个store对象
//store对象内部管理数据的新状态,状态数据的初始值为reducer的返回值
const store = createStore(reducer);
export default store;
分析
redux流程图如下:
redux的三个关键函数:getState()、subscribe()、dispatch()
- getState() :用于获取当前最新的状态
- subscribe() :用于订阅监听当前状态的变化,然后促使页面重新渲染
- dispatch() :用于发布最新的状态
.
现在解释上面代码中src/redux目录下建立的文件夹:- constants.js :用于定义常量
- actions.js :创建action对象的工厂函数,创建出来的action对象用于传递给dispatch()
- reducers.js :用于管理状态数据的函数,当调用dispatch发布最新状态的时候,根据dispatch的回调函数中type的类型,触发reducers做出不同的状态改变。例如,点击“+”按钮的时候,dispatch中的回调函数为increment,这个函数在actions.js中定义,然后触发reducers中的函数(这个函数就是需要在store.js中传给createStore()的参数的函数)作出不同的状态改变,
store.js 根据指定的reducer函数,生成一个store对象,store对象中存储的状态初始值就是reducer函数中默认返回(case default)的值。
.
再来解释上面的流程图:- 当react组件发生某个事件调用dispatch发布消息的时候,由action去触发reducers,reducers的第一个参数为改变之前的状态,第二个参数就是action函数中的对象(必须包含一个type,其他参数任意),然后对状态做出修改后再放入store容器中。通过subscribe订阅了状态改变的组件就会接受状态改变的事件,通过getState获取最新的状态后重新渲染页面。
.
从执行npm start后渲染页面的角度解释:- 当页面第一次渲染的之前,store.js中的createStore()函数就根据参数reducers创建了store对象,并且reduceers中的默认返回值就是store对象需要管理的状态以及状态初始值。然后页面调用getState()获取到状态,值获取完毕后进行页面渲染。当用户点击按钮的时候,就会将不同的action传递给dispatch,一旦调用dispatch,就会触发reducer中的函数调用,改变状态之后,将新的状态传递给store,store接受到新的状态之后就会去调用subscribe中的回调函数。
直接使用redux的方式导致react组件和redux的耦合度很高而且通过dispatch,getState等函数调用很麻烦,这时候就应该学习react-redux的使用了,毕竟该插件包装了redux后专门用于react中。
react-redux将所有的组件分成两大类:
UI组件:
容器组件
react-redux的相关API:
ReactDOM.render(
,
document.getElementById('root')
);
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux文件夹下放的四个js文件代码和案例2中一样
src/components/counters.js
/**
* UI组件,主要负责页面显示
* */
import React, {Component} from 'react'
import PropTypes from 'prop-types';
class Counter extends Component {
constructor(props) {
super(props);
this.numberRef = React.createRef();
}
static propTypes = {
count: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired,
};
increment = () => {
const number = this.numberRef.current.value * 1;
this.props.increment(number);
};
decrement = () => {
const number = this.numberRef.current.value * 1;
this.props.decrement(number);
};
incrementIfOdd = () => {
const count = this.props.count;
const number = this.numberRef.current.value * 1;
if(count % 2 === 1) {
this.props.increment(number);
}
};
incrementAsync = () => {
const number = this.numberRef.current.value * 1;
setTimeout(() => {
this.props.increment(number);
}, 1000);
};
render() {
const count = this.props.count;
console.log("render--"+count);
return (
click {count} time
)
}
}
export default Counter;
src/containers/App.js
/**
* 将UI组件包装成容器组件,这样在产生的新组件中就可以使用react-redux中的API
* */
import React, {Component} from 'react'
import {connect} from 'react-redux';
import {increment, decrement} from '../redux/actions';
import Counter from '../components/counters';
/**
* 将特定的state数据映射成标签属性传递给UI组件Counter
* redux在调用此函数时,传入了store.getState()的值
* 在reducer中我们定义了一个状态名为count,这里的state就是传递的count
* */
const mapStateToProps = (state) => ({
count: state
});
/**
* 将包含dispatch函数调用语句的函数映射成函数属性传递给UI组件Counter
* redux在调用此函数时,传入了store.dispatch
* */
const mapDispatchToProps = (dispatch) => ({
increment: (number) => {dispatch(increment(number))},
decrement: (number) => {dispatch(decrement(number))}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './containers/App';
ReactDOM.render(
/*Provider会将接受到的store对象提供给所有的容器组件*/
,
document.getElementById('root')
);