如何理解Redux
Redux = Reducer + Flux
Redux 是 React中的状态管理容器,可以理解为存放 公用数据的地方,提供一种更加规范和安全的方式去定义组件之间的公用数据(想象一下兄弟组件传值的过程)。对于初学者来说,它的使用并不简单。
Redux的理解.png
三大原则
- 单一数据源
- State 是只读的
- 使用纯函数来执行修改
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
State 是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这里的state是值 redux中的数据,不是组件的state
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
redux概念
Action
行为
Action 是把数据从应用。传到 store 的有效载荷。我们的数据只能通过 Action来触发,修改。
Reducer
Reducers 负责接收 action,然后根据action去处理store
不能直接修改原来的state。
Store
store其实是一个仓库,redux应用只有一个store,当需要拆分数据时,不能拆分store,但可以拆分reducer
工作流
Redux工作流.png体验步骤
- 安装依赖
- 创建store
- 创建reducer
- 将store数据映射到组件中
- 组件触发事件 创建action
- 将action派发到store
- store自己调用reducer
初体验
实现目标
实现一下以上效果.gif- 可以发请求加载数据
- 点击
+
-
组件 会修改数据
安装依赖
redux 是核心库 react-redux是负责将react组件连接redux
npm install redux react-redux --save
新建redux配套文件
在src/store/目录下新建 以下文件
- index.js store核心文件
- reducer/index.js 负责记录操作的reducer文件
reducer.js
// 1 定义默认数据,后期可以从接口中获取
const defaultState = {
num: -1
};
// 2 创建和对外暴露一个函数 返回state
export default (state = defaultState, action) => {
return state;
}
store/index.js
// 1 引入 store生成器
import { createStore } from "redux";
// 2 引入reducer
import reducer from "./reducer";
// 3 创建和对外暴露store
export default createStore(reducer);
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// 1 引入 react-redux 负责将store和组件连接起来
import { Provider } from "react-redux";
// 1 引入 store
import store from "./store";
// 2 将App用 Provider 标签包裹起来
// 2 将store通过属性的方式传递到App组件上
ReactDOM.render(<Provider store={store} ><App /></Provider>, document.getElementById('root'));
App.js
import React, { Component } from 'react';
// 1 引入 react-redux 中 链接 组件和store的对象 connect
import { connect } from "react-redux";
class App extends Component {
render() {
return (
// 4 使用store中的数据
<div className="App">
{this.props.num}
<hr />
<button> + </button>
<button> - </button>
</div>
);
}
}
// 2 将 store中的数据传递到 App的props上
const mapStateToProps = (state) => {
return {
num: state.num
}
}
// 3 用 connect 将store中的数据通过props的方式传递到App上
export default connect(mapStateToProps, null)(App)
思考
以上代码,“action去了哪里了呢? ”
答:没有操作数据的行为,当然么有action了
抽离组件,绑定事件
编辑App.js
文件
将 两个按钮 抽离出来变成两个组件,这才满足组件共享数据的设计理念
// 加
class AddBtn extends Component {
render() {
return <button onClick={this.props.numAdd} >+</button>
}
}
// 减
class SubstraBtn extends Component {
render() {
return <button onClick={this.props.numSubStra}>-</button>
}
}
class App extends Component {
render() {
return (
<div className="App">
{this.props.num}
<hr />
{/* 使用组件 传递props */}
<AddBtn {...this.props}></AddBtn>
{/* 使用组件 传递props*/}
<SubstraBtn {...this.props}></SubstraBtn>
</div>
);
}
}
此时,我们发现 两个组件上都绑定了点击事件
<button onClick={this.props.numAdd} >+</button>
<button onClick={this.props.numSubStra}>-</button>
所以,现在我们需要另外定义 两个事件,在redux中就叫做行为 action
和 mapStateToProps同层级,创建 行为合集 mapDispatchToProps,并且把它传入 connect的第二个参数内。
// 2 将行为action 链接到store和组件上
const mapDispatchToProps = (dispatch) => {
return {
// 点击事件中的加
numAdd: () => {
// 创建一个action,负责将行为类型和数据交给reducer
const action = {
// type是一个自定义的字符串
type: "NUM_ADD",
value: 1
};
// 派发行为- 会将action 派发到 reducer中
dispatch(action);
},
// 点击事件中的减
numSubStra: () => {
const action = {
type: "NUM_SUBSTRA",
value: 1
};
dispatch(action);
}
}
}
// 3 用 connect 将store中的数据通过props的方式传递到App上
export default connect(mapStateToProps, mapDispatchToProps)(App)
编辑 reducer逻辑
// 1 定义默认数据,后期可以从接口中获取
const defaultState = {
num: -1
};
// 2 创建和对外暴露一个函数 返回state
export default (state = defaultState, action) => {
// 当 action被派发时(dispatch),会触发
if (action.type === "NUM_ADD") {
// 复制一份旧的state
let newState = Object.assign({}, state);
newState.num += action.value;
// 将新的state返回,即可触发store的更新
return newState;
}
if (action.type === "NUM_SUBSTRA") {
// 复制一份旧的state
let newState = Object.assign({}, state);
newState.num -= action.value;
return newState;
}
return state;
}
优化手段
通过以上步骤,可以把redux的使用流程走完,但是在公司中,还会对以上的代码进行优化,存在以下优化的步骤
- 将state中的数据修改为对象形式,因为数据一般不会这么简单。
- 将action的type类型提取成常量的形式,避免手写字符串出错
- 将action的创建由字面量改为 action生成器来创建,方便后期代码的维护和测试
- 拆分和合并reducer,有时候,会根据不同的数据使用不同的reducer
- 添加异步action,因为有时候我们的数据是从异步中获取的不是同步的方式。
将state中的数据修改为对象形式
编辑 reducer/index.js
const defaultState = {
// 修改为对象形式
payload: {
num: -1
}
};
export default (state = defaultState, action) => {
if (action.type === "NUM_ADD") {
let newState = Object.assign({}, state);
// 修改为对象形式
newState.payload.num += action.value;
return newState;
}
if (action.type === "NUM_SUBSTRA") {
let newState = Object.assign({}, state);
// 修改为对象形式
newState.payload.num -= action.value;
return newState;
}
return state;
}
修改App.js中使用的state的代码
const mapStateToProps = (state) => {
return {
{/* 修改为对象的形式 */}
num: state.payload.num
}
}
将action的type类型提取成常量的形式
新建文件 src/store/actionType/index.js
export const NUM_ADD = "NUM_ADD";
export const NUM_SUBSTRA = "NUM_SUBSTRA";
修改 使用到了 NUM_ADD的文件
编辑 src/store/reducer/index.js
// 1 导入 type常量
import { NUM_ADD, NUM_SUBSTRA } from "../actionType";
export default (state = defaultState, action) => {
// 2 修改为常量的方式
if (action.type === NUM_ADD) {
......
}
return state;
}
编辑 src/App.js
// 1 导入 type 常量
import { NUM_ADD, NUM_SUBSTRA } from "./store/actionType";
const mapDispatchToProps = (dispatch) => {
return {
numAdd: () => {
const action = {
// 2 使用 type常量
type: NUM_ADD,
value: 1
};
dispatch(action);
}
}
使用action生成器来创建action
新建文件 src/store/actionCreator/index.js
import { NUM_ADD,NUM_SUBSTRA} from "../actionType";
export const numAdd = () => ({
type: NUM_ADD,
value: 1
})
export const numSubstra = () => ({
type: NUM_SUBSTRA,
value: 1
})
修改 App.js
// 1 导入action
import { numAdd, numSubstra } from "./store/actionCreator";
const mapDispatchToProps = (dispatch) => {
return {
numAdd: () => {
// 2 修改为 生成器生成的action
dispatch(numAdd());
}
}
}
拆分和合并reducer
当需要共享的数据足够多时,一般会拆分多个reducer方便管理
如 拆分成两个 reducer
一个是操作 nums的,一个是操作水果的。
- 编辑
actionType/index.js
export const NUM_ADD = "NUM_ADD";
export const NUM_SUBSTRA = "NUM_SUBSTRA";
// 新增 增加 苹果action type
export const APPLE_NUM_ADD = "APPLE_NUM_ADD";
// 新增 减少 苹果action type
export const APPLE_NUM_SUBSTRA = "APPLE_NUM_SUBSTRA";
- 编辑
actionCreator/index.js
// 新增 添加苹果 action
export const appleNumAdd = () => ({
type: APPLE_NUM_ADD,
value: 1
})
// 新增 减少苹果 action
export const appleNumSubstra = () => ({
type: APPLE_NUM_SUBSTRA,
value: 1
})
- 新建文件
reducer/numReducer.js
将 以前 reducer/index.js 全部复制过去即可
import { NUM_ADD, NUM_SUBSTRA } from "../actionType";
const defaultState = {
payload: {
num: -1
}
};
export default (state = defaultState, action) => {
if (action.type === NUM_ADD) {
let newState = Object.assign({}, state);
newState.payload.num += action.value;
return newState;
}
if (action.type === NUM_SUBSTRA) {
let newState = Object.assign({}, state);
newState.payload.num -= action.value;
return newState;
}
return state;
}
4.新建文件 reducer/fruitReducer.js
import { APPLE_NUM_ADD, APPLE_NUM_SUBSTRA } from "../actionType";
const defaultState = {
payload: {
appleNum: 110
}
};
export default (state = defaultState, action) => {
if (action.type === APPLE_NUM_ADD) {
let newState = Object.assign({}, state);
newState.payload.appleNum += action.value;
return newState;
}
if (action.type === APPLE_NUM_SUBSTRA) {
let newState = Object.assign({}, state);
newState.payload.appleNum -= action.value;
return newState;
}
return state;
}
- 编辑
reducer/index.js
用来合并 两个reducer
fruitReducer
和numReducer
// 1 引入 合并reducer的对象
import { combineReducers } from "redux";
import fruitReducer from "./fruitReducer";
import numReducer from "./numReducer";
// 2 对象的形式传入 要合并的reducer
const rootReducer = combineReducers({ numReducer, fruitReducer });
export default rootReducer;
- 修改
App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
// 1 多导入两个action appleNumAdd 和 appleNumSubstra
import { numAdd, numSubstra, appleNumAdd, appleNumSubstra } from "./store/actionCreator";
class AddNumBtn extends Component {
render() {
return <button onClick={this.props.numAdd} >+</button>
}
}
class SubstraNumBtn extends Component {
render() {
return <button onClick={this.props.numSubStra}>-</button>
}
}
// 2 新增的组件
class AddFruitBtn extends Component {
render() {
// 2.1 新绑定的事件
return <button onClick={this.props.appleNumAdd} >+</button>
}
}
// 2 新增的组件
class SubstraFruitBtn extends Component {
render() {
// 2.1 新绑定的事件
return <button onClick={this.props.appleNumSubStra}>-</button>
}
}
class App extends Component {
render() {
// 3 修改过的页面代码
return (
<div className="App">
数量 {this.props.num}
<hr />
<AddNumBtn {...this.props}></AddNumBtn>
<SubstraNumBtn {...this.props}></SubstraNumBtn>
<hr />
水果数量 {this.props.appleNum}
<br />
{/* 3.1 引入新组件 */}
<AddFruitBtn {...this.props}></AddFruitBtn>
{/* 3.1 引入新组件 */}
<SubstraFruitBtn {...this.props}></SubstraFruitBtn>
</div>
);
}
}
const mapStateToProps = (state) => {
// 4 修改数据的获取方法
return {
num: state.numReducer.payload.num,
appleNum: state.fruitReducer.payload.appleNum
}
}
const mapDispatchToProps = (dispatch) => {
return {
numAdd: () => {
dispatch(numAdd());
},
numSubStra: () => {
dispatch(numSubstra());
},
// 5 新增的action
appleNumAdd: () => {
dispatch(appleNumAdd());
},
// 5 新增的action
appleNumSubStra: () => {
dispatch(appleNumSubstra());
},
}
}
// 3 用 connect 将store中的数据通过props的方式传递到App上
export default connect(mapStateToProps, mapDispatchToProps)(App)
图示.png
添加异步action redux-thunk
想象一下,我们对数据库进行查询,编辑和删除,其实都是异步操作。现在,让我们的应用支持异步action操作。
- 安装依赖
redux-thunk
npm install redux-thunk --save
- 修改
store/index.js
// 1 引入 redux的中间件连接器
import { createStore, applyMiddleware } from "redux";
import reducer from "./reducer";
// 1 引入 redux-thunk
import reduxThunk from "redux-thunk";
// 2 使用中间件连接器将redux-thunk传入 store构造器
export default createStore(reducer, applyMiddleware(reduxThunk));
- 修改
actionCreator/index.js
// 1 修改 减少苹果的action 为异步的形式
export const appleNumSubstra = () => {
// 2 返回一个函数
return (dispatch) => {
// 3 开启异步 后期将 setTimeout 替换成异步的方式即可
setTimeout(() => {
const action = {
type: APPLE_NUM_SUBSTRA,
value: 1
};
// 4 开启派发
dispatch(action);
}, 2000);
}
}