一、redux是什么
-
redux是专门用于管理状态的
JS
库,可以集中式管理react
应用中的多个组件共享的状态; -
redux
不是react
插件库,它可以用在react、angular、vue
中,但基本与react
配合使用; - 使用:在多个组件需要共享状态时,或者一个组件需要改变另一个组件的状态时使用。
二、redux工作流程
-
action
:动作对象,包含两个属性:
-
type
: 动作标识,string
,唯一且必填,初始化时默认为@@redux/INI*
; -
data
:数据,any
, 可选属性,初始化不传;
eg:{type: 'ADD', data: {name: 'tom'}}
-
reducer
:用于初始化状态和加工状态的 纯函数;
纯函数,是指对与同样的参数,返回值始终一样的函数。
纯函数必须遵守以下约束:
(1)函数内部不得改写参数;
(2)不得存在网络请求等操作;
(3)不得调用Date.now()
或者Math.round
等不纯的方法;
-
store
: 将state、action、reducer
连在一起的对象;
三、使用
实现下述操作:点击运算按钮,显示下拉框的选中值与当前求和值的运算结果。
1. 不用redux
实现
Count.js
import React, { Component } from "react";
export default class Count extends Component {
state = {count: 0}
increment = () => {
const {value} = this.selectNumber;
// const {count} = this.state;
// this.setState({
// count: count + value * 1
// })
this.setState((state) => ({
count: state.count + value * 1
}))
}
decrement = () => {
const {value} = this.selectNumber;
this.setState((state) => ({
count: state.count - value * 1
}))
}
incrementIfOdd = () => {
const {value} = this.selectNumber;
const {count} = this.state;
if(count % 2 !== 0) {
this.setState({
count: count + value * 1
})
}
}
incrementAsync = () => {
const {value} = this.selectNumber;
setTimeout(() => {
this.setState((state) => ({
count: state.count + value * 1
}))
}, 500)
}
render() {
return (
<div>
<p>当前求和为: {this.state.count}</p>
<select ref={dom => this.selectNumber = dom}>
{
[1,2,3].map((data, i) => <option value={data} key={i}>{data}</option>)
}
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加和</button>
</div>
)
}
}
2. 使用redux
实现
第一步:准备好store
yarn add redux
- 创建
src/redux/store.js
:
import {createStore} from 'redux';
import countReducer from './count-reducer';
export default createStore(countReducer);
- 引入api
createStore
, 用于创建store
,第一个参数传入需要的reducers
;
第二步:准备 reducer
- 在
redux
文件夹下创建reducer/count.js
; -
reducer
是一个函数,接收previousState
和action
,返回newState
;
import {INCREMENT, DECREMENT} from "./constants";
const initState = 0;
export default function countReducer(preState = initState, action) {
console.log({preState, action});
const { type, data } = action;
switch (type) {
case INCREMENT:
return preState + data;
case DECREMENT:
return preState - data;
default:
return preState;
}
}
- 初次展示状态时,
store
会自动调用一次reducer
,之后为dispatch
通知触发; -
preState
默认为undefined
,可以自定义默认值。之后该值自动传为上次的数据。action
中的type
初始值为@@redux/INI*
; -
action
中的type
最好用switch...case...
来分类,最好用常量表示,定义在固定的文件中; -
reducer
的返回值会展示在store.getState()
中; -
reducer
可以在组件中直接封装参数对象通过dispatch
触发,也可以通过action
得到参数对象再触发;
// 组件中直接触发reducer
increment = () => {
const {value} = this.selectNumber;
store.dispatch({type: 'increment', data: value * 1});
}
-
reducer
中处理的state
的类型是引用类型,修改时必须修改引用地址值,如下方法,在数组前面推送一个对象,不能对原数组使用unshift
方法,需要返回一个新的数组:
const initState = [
{
name: 'ad',
age: 11,
id: '123esd'
},
]
export default function addPerson(preState = initState, action) {
const { type, data} = action;
switch (type) {
case ADD_PERSON:
return [data, ...preState];
default:
return preState;
}
}
- 如果有多个子组件的
reducer
,需要借助redux
中的combineReducers
合并传入store:
src/redux/reducers/index
import {combineReducers} from "redux";
import count from './count';
import person from './person';
// 使用combineReducers把所有reducer合并返回
export default combineReducers({count, person})
第三步: 准备action
- 创建
redux/actions/count.js
文件;
import { INCREMENT, DECREMENT } from "./constants";
// 同步action,返回值为一般对象
export const increment = (data) => ({type: INCREMENT, data});
export const decrement = (data) => ({type: DECREMENT, data})
// 异步action,返回值为函数。异步action中一般都会调用同步action,所以有默认参数dispatch。
// 但是直接使用redux不认,需要引入中间件`redux-thunk`;
// 异步action不是必须要用,可以在组件内部处理异步函数。
export const incrementAsync = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(increment(data));
}, time)
}
}
-
action
分为同步和异步。同步action
返回值为一般对象, 异步action
,返回值为函数; - 异步
action
中一般都会调用同步action
,所以有默认参数dispatch
; -
reducer
不认返回值为函数的action
,需要在store
中安装并引入中间件redux-thunk
,使用applyMiddleware
应用到store
中:
store.js
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import countReducer from './count-reducer';
export default createStore(countReducer, applyMiddleware(thunk));
- 异步
action
不是必须要用,可以在组件内部处理完异步操作,再调用同步action
替代;
第四步:组件中的使用
import React, {Component} from "react";
import store from "../../redux/store";
import {
increment,
incrementAsync,
decrement
} from "../../redux/actions/count";
export default class Count extends Component{
state = {
// count: 0
}
/**
* 通过在componentDidMount中调用store.subscribe,监听store的变化。
* 并且在每次变化时,通过this.setState的合并方法来触发页面重新渲染
* */
// componentDidMount() {
// store.subscribe(() => {
// this.setState({})
// })
// }
increment = () => {
const {value} = this.selectDom;
/**
* 组件中直接生成对象,使用dispatch触发reducer
* */
store.dispatch({
type: 'increment',
data: value*1,
})
}
decrement = () => {
const {value} = this.selectDom;
/**
* 通过action触发reducer
* */
store.dispatch(decrement(value*1))
}
incrementIfOdd = () => {
const {value} = this.selectDom;
if(store.getState().count %2 !== 0) {
store.dispatch(increment(value*1))
}
}
/**
* reducer只接受一个普通函数作为action参数;
* 如果action要异步返回一个方法,需要下载依赖 redux-thunk,作为中间件在store中引入
* */
incrementAsync = () => {
const {value} = this.selectDom;
store.dispatch(incrementAsync(value*1, 500))
}
render() {
return (
<div>
{/**
通过store.getState()获取状态数据
*/}
<h2>当前求和为{store.getState().count}</h2>
<h2>下面人数为{store.getState().person.length}</h2>
<select ref={c=>this.selectDom = c}>
{[1,2,3].map((number, i) => <option value={number} key={i}>{number}</option>)}
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>求和为奇数则加</button>
<button onClick={this.incrementAsync}>异步求和</button>
</div>
)
}
}
第五步:渲染
通过上述步骤,点击按钮,可以触发reducer
,修改store中
的值,但是页面并没有改变。这是因为改变store
后,需要自己监听store
变化,调用渲染的方法。
- 方法一: 在每个组件内部的
componentDidMount
生命周期钩子中,通过store.subscribe
监听store
中状态数据的变化,并调用this.setState({})
,通过调用setState
合并空对象,触发react渲染机制;
Count.js
export default class Count extends Component {
state = {}
componentDidMount() {
// 检测store中状态的变化,只要变化就触发
store.subscribe(() => {
// this.render(); // 无法触发渲染
this.setState({}); // 通过调用`setState`合并空对象,触发react渲染机制
})
}
increment = () => {
const {value} = this.selectNumber;
store.dispatch({type: 'increment', data: value * 1});
}
- 方法二: 在 入口文件中引入
store
,通过store.subscribe
监听并重新渲染App
:
import React from "react";
import ReactDom from 'react-dom';
import store from "./redux/store";
import App from "./App";
ReactDom.render(<App/>, document.getElementById('root'));
store.subscribe(() => {
ReactDom.render(<App/>, document.getElementById('root'));
})