一.什么是redux
官方解释:Redux is a predictable state container for JavaScript apps. 意思就是Redux是js应用的 一种可预测的状态容器。
二.为什么使用redux?
没有使用Redux的情况,如果两个组件(非父子关系)之间需要通信的话,可能需要多个中间组件为他们进行消息传递,这样既浪费了资源,代码也会比较复杂。
Redux中提出了单一数据源Store 用来存储状态数据,所有的组件都可以通过Action修改Store,也可以从Store中获取最新状态。使用了redux就可以完美解决组建之间的通信问题
三.怎么使用redux?
redux官方图片
文字解释
React Component: 借书的人
Action Creator: 大喇叭 (用来通知图书管理员)
Store: 图书管理员
Reducer: 小本本 (图书管理员大爷记性不好,需要用小本本记录图书信息
借书的人(ReactComponent)说了一句话(Action Creator),向图书馆管理员(Store)借一本书,可是图书馆管理员年纪大了啊记不住啊,便掏出了自己的小本本(Reducers)。看了看知道了那本书有没有,在哪,怎么样。这样一来管理员就拿到了这本书,再把这本书交给了借书人。
翻译过来就是:
组件想要获取State, 用ActionCreator创建了一个请求交给Store,Store借助Reducer确认了该State的状态,Reducer返回给Store一个结果,Store再把这个State转给组件。
四.通过案例TodoList学习redux
安装redux:
npm install redux --save
# 或
yarn add redux
复制代码
搭建项目结构
项目目录如图所示
src/index.js 入口文件
import React from "react";
import ReactDOM from "react-dom";
import App from "./TodoList";
ReactDOM.render(<App></App>, document.getElementById("root"));
复制代码
src/TodoList.js
import React, { Component } from "react";
import "antd/dist/antd.css";
import { Input, Button, List } from "antd";
import store from "./store";
import {
changeValue,
addListItem,
removeListItem,
getList,
} from "./store/actionCreators";
class App extends Component {
constructor(props) {
super(props);
this.state = store.getState();
store.subscribe(() => this.storeChange());
}
storeChange() {
this.setState(store.getState());
}
componentDidMount() {
const action = getList();
store.dispatch(action);
}
render() {
return (
<div style={{ margin: "10px" }}>
<div>
<Input
placeholder={this.state.inputValue}
style={{ width: "250px", marginRight: "10px" }}
onChange={(e) => this.changeValue(e)}
/>
<Button type="primary" onClick={() => this.addListItem()}>
增加
</Button>
</div>
<div style={{ margin: "10px", width: "300px" }}>
<List
bordered
dataSource={this.state.list}
renderItem={(item, index) => (
<List.Item onClick={(item) => this.removeListItem(item, index)}>
{item}
</List.Item>
)}
/>
</div>
</div>
);
}
changeValue(e) {
const action = changeValue(e.target.value);
store.dispatch(action);
}
addListItem() {
const action = addListItem();
store.dispatch(action);
}
removeListItem(item, index) {
const action = removeListItem(index);
store.dispatch(action);
}
}
export default App;
复制代码
src/store/index.js redux 入口文件
import { createStore, applyMiddleware, compose } from "redux";
import reducer from "./reducer";
import thunk from "redux-thunk";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));
const store = createStore(reducer, enhancer);
export default store;
复制代码
src/store/actionCreators action存放
import {
CHANGE_VALUE,
ADD_LIST_ITEM,
REMOVE_LIST_ITEM,
GET_ACTION,
} from "./actionTypes";
import axios from "axios";
export const changeValue = (value) => {
return {
type: CHANGE_VALUE,
value,
};
};
export const addListItem = (value) => {
return {
type: ADD_LIST_ITEM,
value,
};
};
export const removeListItem = (value) => {
return {
type: REMOVE_LIST_ITEM,
value,
};
};
export const getAction = (data) => {
return {
type: GET_ACTION,
value: data,
};
};
export const getList = () => {
return (dispatch) => {
axios
.get(
"https://www.fastmock.site/mock/6f4fec629859811f980f52a147ea3236/xbb_pl/xbb"
)
.then((res) => {
const data = res.data;
const action = getAction(data);
dispatch(action);
});
};
};
复制代码
src/store/actionTypes 变量储存文件
export const CHANGE_VALUE = "changeValue";
export const ADD_LIST_ITEM = "addListItem";
export const REMOVE_LIST_ITEM = "removeListItem";
export const GET_ACTION = "getAction";
复制代码
src/store/reducer.js
import {
ADD_LIST_ITEM,
REMOVE_LIST_ITEM,
CHANGE_VALUE,
GET_ACTION,
} from "./actionTypes";
const defaultState = {
inputValue: "Write Something",
list: [
"早8点开晨会,分配今天的开发工作",
"早9点和项目经理作开发需求讨论会",
"晚5:30对今日代码进行review",
],
};
export default (state = defaultState, action) => {
if (action.type === CHANGE_VALUE) {
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState;
}
if (action.type === ADD_LIST_ITEM) {
console.log(state);
let newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue); //push新的内容到列表中去
return newState;
}
if (action.type === REMOVE_LIST_ITEM) {
let newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.value, 1); //push新的内容到列表中去
return newState;
}
if (action.type === GET_ACTION) {
let newState = JSON.parse(JSON.stringify(state));
newState.list = action.value.list;
return newState;
}
return state;
};
复制代码
五. 使用redux会遇到的坑
store必须是惟一的
store必须是唯一的,多个store是坚决不允许,只能有一个store空间
只有store能改变自己的内容,Reducer不能
Reudcer只是返回了更改的数据,但是并没有更改store中的数据,store拿到了Reducer的数据,自己对自己进行了更新。
Reducer必须是纯函数
如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
六.redux实际开发中的小技巧
将Action Types提取成常量
写Redux Action的时候,我们写了很多Action的派发,产生了很多Action Types,如果需要Action的地方我们就自己命名一个Type,会出现两个基本问题:
- 这些Types如果不统一管理,不利于大型项目的服用,设置会长生冗余代码。
- 因为Action里的Type,一定要和Reducer里的type一一对应在,所以这部分代码或字母写错后,浏览器里并没有明确的报错,这给调试带来了极大的困难。
那我会把Action Type单独拆分出一个文件。在src/store文件夹下面,新建立一个actionTypes.js文件,然后把Type集中放到文件中进行管理。
export const CHANGE_INPUT = 'changeInput'
export const ADD_ITEM = 'addItem'
export const DELETE_ITEM = 'deleteItem'
复制代码
好处
1.可以避免冗余代码
2.如果我们写错了常量名称,程序会直接在浏览器和控制台报错,可以加快开发效率,减少找错时间。
将所有的Redux Action放到一个文件里进行管理
七.redux中间件redux-thunk
Redux-thunk这个Redux最常用的插件。什么时候会用到这个插件那?比如在Dispatch一个Action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware(中间件)。在实际工作中你可以使用中间件来进行日志记录、创建崩溃报告,调用异步接口或者路由。 这个中间件可以使用是Redux-thunk来进行增强(当然你也可以使用其它的),它就是对Redux中dispatch的加强,
1.安装Redux-thunk组件
npm install --save redux-thunk
复制代码
2.配置Redux-thunk组件
引入applyMiddleware,如果你要使用中间件,就必须在redux中引入applyMiddleware
import { createStore , applyMiddleware } from 'redux'
复制代码
引入redux-thunk库
import thunk from 'redux-thunk'
复制代码
官方文档推荐
const store = createStore(
reducer,
applyMiddleware(thunk)
) // 创建数据存储仓库
复制代码
最后配置
但是这样我们便无法继续使用redux-devtools这个插件,所以我们应该利用增强函数这样配置
import { createStore , applyMiddleware ,compose } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose
const enhancer = composeEnhancers(applyMiddleware(thunk))
const store = createStore( reducer, enhancer) // 创建数据存储仓库
export default store //暴露出去
复制代码
八.redux-thunk的使用
没有引入redux-thunk中间件前,我们请求数据只能在TodeList类组件中,有了redux-thunk,我们就可以在store将state传入reducer时对action做一些事情。
在actionCreators.js里编写业务逻辑
以前actionCreators.js都是定义好的action,根本没办法写业务逻辑,有了Redux-thunk之后,可以把TodoList.js中的componentDidMount业务逻辑放到这里来编写。也就是把向后台请求数据的代码放到actionCreators.js文件里。那我们需要引入axios,并写一个新的函数方法。(以前的action是对象,现在的action可以是函数了,这就是redux-thunk带来的好处)
import axios from 'axios'
...something...
export const getTodoList = () =>{
return ()=>{
axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList').then((res)=>{
const data = res.data
console.log(data)
})
}
}
复制代码
现在我们需要执行这个方法,并在控制台查看结果,这时候可以修改TodoList.js文件中的componentDidMount代码。
//先引入getTodoList
import {getTodoList , changeInputAction , addItemAction ,deleteItemAction,getListAction} from './store/actionCreatores'
---something---
componentDidMount(){
const action = getTodoList()
store.dispatch(action)
}
复制代码
然后我们到浏览器的控制台中查看一下,看看是不是已经得到了后端传给我们的数据,如果一切正常,应该是可以得到。得到之后,我们继续走以前的Redux流程就可以了。
export const getTodoList = () =>{
return (dispatch)=>{
axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList').then((res)=>{
const data = res.data
const action = getListAction(data)
dispatch(action)
})
}
}
复制代码