Redux是什么:Redux是一个专门用来做状态管理的JS库(不是React插件库)。它可以用在React,Angular,Vue应用中,但基本和React配合使用。用于集中式管理React应用中多个组件共享的状态。
首先我们先介绍一个概念集中式管理,集中式管理要求状态在哪里,修改状态的行为就定义在那个组件。
但是集中式管理有一个问题。一个组件下面有很多路由组件。如果这些组件的行为全部放在一个文件下会造成很大的冗余。
然而Redux使用集中式管理来管理状态。将状态以及状态管理的方法放在一处,需要的时候调用状态管理的方法。此时状态不在组件的内部,而是在Redux中。
组件的两大功能:
- 展现数据
- 与用户交互更新数据
Action Creators,Store,Reducers都是Redux代码。因此整个过程可以看做是React组件和Redux进行交互。
展现数据:Store是Redux中的核心对象。从图中可以看出React组件是从Store中读取状态。
更新新的状态显示:使用分发功能时会使用到action,其中有两个属性type,data。Action Creators是一个工厂函数,用于修改action对象。我们需要传递一个type和data。type代表的是事件的类型。Action Creators通过type决定生成什么类型的Action。data是相关的数据。
Reducers相当于一个函数,该函数的形参是previousState和action,返回值为newState。返回的newState交给store进行状态的更新。
React中状态无法直接更新,必须调用this.setState()。
总结:一个组件重要的功能是两方面:显示状态和更新状态。显示状态可以通过Store来修改状态。更新状态时我们主要做两件事,首先是发送通知,通过dispatch。之后是实现Reducers函数产生新的状态。
什么情况下需要使用Redux
- 总体原则:能不用就不用,如果不用比较吃力才考虑使用。
- 某个组件的状态,需要共享。
- 某个状态需要在任何地方都可以拿到。
- 一个组件需要改变全局状态。
- 一个组件需要改变另一个组件的状态。
使用Redux
首先先下载依赖包
npm install --save redux
Store对象
作用:Redux库最核心的管理对象。
内部维护:state,reducer
核心方法:
- getState()
- dispatch(action)
- subscribe(listener)
编码:
- store.getState()
- store.dispatch({type:'INCREMENT', number})
- store.subscribe(render)
Redux三个核心概念
action
标识要执行行为的对象
包含两个方面的属性:
- type:标识属性,值为字符串,唯一,必要属性。
- xxx:数据属性,值为任意类型,可选属性。
例子:
const action = {
type: "INCREMENT",
data: 2
}
Action.Creator(Action的工厂函数)的创建
const increment = (number) => ({type: "INCREMENT", data: number})
reducer
根据老的state和action产生新的state的纯函数。
例子:
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
}
}
注意:
- 每一次都返回一个新的状态数据。
- 不要改变以前的状态数据。
store
将state, action与reducer联系到一起的对象。
如何得到该对象:
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
此对象的功能:
getStete():得到state
dispatch(action):分发action,触发reducer的调用,产生新的state。
subscribe(listener):注册监听,当产生了新的state对象时,自动调用。
Redux实例
我们需要实现以下的效果:
点击“加号”,“减号”按钮,上面的数字分别增加或减少显示的数字。点击“increment if odd”如果显示的数字为奇数则增加显示的数据。点击“increment async”隔一段时间后增加显示的数据。
首先在index.js中引入Store对象
import {createStore} from 'redux'
之后是创建Store对象
const store = createStore()
之后是编写Action Creators(reducers.js)并将其传给Store对象。和前面说的一样Action Creators相当于一个工厂方法根据传递的type决定产生什么样的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)来统一管理这些字符。
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(, document.getElementById('root'))
}
new_render()
store.subscribe(new_render)
让我们来到组件类app.jsx。由于状态是被Redux管理的。因此在组件类中我们无需定义state了。状态值的获取与更新也要经过Redux。不需要自己写方法了。例如:
//获取状态值
const count = this.props.store.getState()
//更新状态值
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 (
Click {count} times
)
}
}
export default App
使用react-redux进行简化
react-redux是一个react插件库。专门用于简化在react应用中使用redux。
export default connect(
state => ({count: state}),
{increment, decrement}
)(App)
在此示例中。state和{increment, decrement}会被结构传递给App组件。状态放到了Redux中,我们需要这些状态就需要属性来接收。connect起到了传递属性的作用。注意:connect中的属性名必须和组件中的属性名一致。
React-Redux将所有的组件分为两大类:
- UI组件
不使用Redux的API
一般放在components文件夹下 - 容器组件
使用Redux的API
一般保存在containers文件夹下
action-types.js:包含所有action类型的名称常量。
actions.js:包含了所有的action,工厂函数。
reducers.js:包含多个reducer函数。根据老的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 (
Click {count} times
)
}
}
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数据
connect组件:
用于包装UI组件形成容器组件。将组件和Redux关联起来。
import {connect} from 'react-redux'
connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
mapStateToProps():
将外部数据(即state对象)转换为UI组件的标签属性。
const mapStateToProps = function(state){
return (
{value: state}
)
}
mapDispatchToProps():
将分发action的函数转换为UI组件的标签属性。
简洁语法可以直接指定为actions对象或包含多个action方法的对象。
管理多个Reducer
import {combineReducers} from 'react'
合并多个reducer放在一起管理。
假设此时有两个reducer。我们不应将它们分别暴露。而是统一放到combineReducers方法中进行统一暴露。同时之前暴露的方法也要统一改成统一管理的方法。