react-redux
关键步骤
- 安装依赖
- 创建
store
- 创建
reducer
- 将
store
数据映射到组件中 - 组件触发事件 创建
action
- 将
action
派发到store
store
自己调用reducer
实现目标
- 可以发请求加载数据
- 点击
+
-
组件 会修改数据
安装依赖
redux 是核心库 react-redux是负责将react组件连接redux
yarn add redux react-redux --dev
新建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; }
-
新建文件
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)
-
最终结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eh5NXZgd-1621092162174)(./medias/1560268076127.png)]
添加异步action redux-thunk
想象一下,我们对数据库进行查询,编辑和删除,其实都是异步操作。现在,让我们的应用支持异步action
操作。
-
安装依赖
redux-thunk
yarn add redux-thunk --dev
-
修改
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); } }
小结
当对redux流程熟悉后,使用流程应该是这个样子的
- 编写action生成器
- 编写reducer
- 编写store
- 连接store和组件
- 。。。
react 插件
vs code插件
-
Simple React Snippets
react的快捷代码段[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I68TNQJo-1621092162183)(./medias/1560268972967.png)]
chrome插件
-
React Developer Tools
-
Redux DevTools
Redux DevTools
-
没使用异步
react-thunk
时,编辑文件store/index.js
import { createStore } from "redux"; import reducer from "./reducer"; export default createStore(reducer, // 给谷歌调试工具使用的 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() );
-
使用了异步
react-thunk
时,编辑文件store/index.js
import { createStore, applyMiddleware,compose } from 'redux'; import reducer from "./reducer"; import thunk from 'redux-thunk'; const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ }) : compose; const enhancer = composeEnhancers( applyMiddleware(thunk), ); const store = createStore(reducer, enhancer); export default store;