Redux
[TOC]
知识点
- 状态管理器
- state 对象
- reducer 纯函数
- store 对象
- action 对象
- combineReducers 方法
- react-redux
- provider 组件
- connect 方法
- Redux DevTools extension 工具
- 中间件 - middleware
- redux-chunk
状态(数据)管理
前端应用越来越复杂,要管理和维护的数据也越来越多,为了有效的管理和维护应用中的各种数据,我们必须有一种强大而有效的数据管理机制,也称为状态管理机制,Redux 就是解决该问题的
Redux
Redux 是一个独立的 JavaScript 状态管理库,与非 React 内容之一
https://www.bootcdn.cn/redux/
核心概念
理解 Redux 核心几个概念与它们之间的关系
- state
- reducer
- store
- action
state 对象
通常我们会把应用中的数据存储到一个对象树(Object Tree) 中进行统一管理,我们把这个对象树称为:state
state 是只读的
这里需要注意的是,为了保证数据状态的可维护和测试,不推荐直接修改 state 中的原数据
通过纯函数修改 state
什么是纯函数?
纯函数
- 相同的输入永远返回相同的输出
- 不修改函数的输入值
- 不依赖外部环境状态
- 无任何副作用
使用纯函数的好处
- 便于测试
- 有利重构
Reducer 函数
function todo(state, action) {
switch(action.type) {
case 'ADD':
return [...state, action.payload];
break;
case 'REMOVE':
return state.filter(v=>v!==action.payload);
break;
default:
return state;
}
}
上面的 todo 函数就是 Reducer 函数
- 第一个参数是原 state 对象
- Reducer 函数不能修改原 state,而应该返回一个新的 state
- 第二参数是一个 action 对象,包含要执行的操作和数据
- 如果没有操作匹配,则返回原 state 对象
action 对象
我们对 state 的修改是通过 reducer 纯函数来进行的,同时通过传入的 action 来执行具体的操作,action 是一个对象
- type 属性 : 表示要进行操作的动作类型,增删改查……
- payload属性 : 操作 state 的同时传入的数据
但是这里需要注意的是,我们不直接去调用 Reducer 函数,而是通过 Store 对象提供的 dispatch 方法来调用
Store 对象
为了对 state,Reducer,action 进行统一管理和维护,我们需要创建一个 Store 对象
Redux.createStore 方法
let store = Redux.createStore((state, action) => {
// ...
}, []);
todo
用户操作数据的 reducer 函数
[]
初始化的 state
我们也可以使用 es6 的函数参数默认值来对 state 进行初始化
let store = Redux.createStore( (state = [], action) => {
// ...
} )
getState() 方法
通过 getState 方法,可以获取 Store 中的 state
store.getState();
dispatch() 方法
通过 dispatch 方法,可以提交更改
store.dispatch({
type: 'ADD',
payload: 'MT'
})
action 创建函数
action 是一个对象,用来在 dispatch 的时候传递动作和数据,我们在实际业务中可能会中许多不同的地方进行同样的操作,这个时候,我们可以创建一个函数用来生成(返回)action
function add(payload) {
return {
type: 'ADD',
payload
}
}
store.dispatch(add('MT'));
store.dispatch(add('Reci'));
...
subscribe() 方法
可以通过 subscribe 方法注册监听器(类似事件),每次 dispatch action 的时候都会执行监听函数,该方法返回一个函数,通过该函数可以取消当前监听器
let unsubscribe = sotre.subscribe(function() {
console.log(store.getState());
});
unsubscribe();
Redux 工作流
[图片上传失败...(image-bba1ee-1614260835998)]
Reducers 分拆与融合
当一个应用比较复杂的时候,状态数据也会比较多,如果所有状态都是通过一个 Reducer 来进行修改的话,那么这个 Reducer 就会变得特别复杂。这个时候,我们就会对这个 Reducer 进行必要的拆分
let datas = {
user: {},
items: []
cart: []
}
我们把上面的 users、items、cart 进行分拆
// user reducer
function user(state = {}, action) {
// ...
}
// items reducer
function items(state = [], action) {
// ...
}
// cart reducer
function cart(state = [], action) {
// ...
}
combineReducers 方法
该方法的作用是可以把多个 reducer 函数合并成一个 reducer
let reducers = Redux.combineReducers({
user,
items,
cart
});
let store = createStore(reducers);
react-redux
再次强调的是,redux 与 react 并没有直接关系,它是一个独立的 JavaScript 状态管理库,如果我们希望中 React 中使用 Redux,需要先安装 react-redux
react-redux
安装
npm i -S redux react-redux
// ./store/reducer/user.js
let user = {
id: 0,
username: ''
};
export default (state = user, action) => {
switch (action.type) {
default:
return state;
}
}
// ./store/reducer/users.js
let users = [{
id: 1,
username: 'baoge',
password: '123'
},
{
id: 2,
username: 'MT',
password: '123'
},
{
id: 3,
username: 'dahai',
password: '123'
},
{
id: 4,
username: 'zMouse',
password: '123'
}];
export default (state = users, action) => {
switch (action.type) {
default:
return state;
}
}
// ./store/reducer/items.js
let items = [
{
id: 1,
name: 'iPhone XR',
price: 542500
},
{
id: 2,
name: 'Apple iPad Air 3',
price: 377700
},
{
id: 3,
name: 'Macbook Pro 15.4',
price: 1949900
},
{
id: 4,
name: 'Apple iMac',
price: 1629900
},
{
id: 5,
name: 'Apple Magic Mouse',
price: 72900
},
{
id: 6,
name: 'Apple Watch Series 4',
price: 599900
}
];
export default (state = items, action) => {
switch (action.type) {
default:
return state;
}
}
// ./store/reducer/cart.js
export default (state = [], action) => {
switch (action.type) {
default:
return state;
}
}
// ./store/index.js
import {createStore, combineReducers} from 'redux';
import user from './reducer/user';
import users from './reducer/users';
import items from './reducer/items';
import cart from './reducer/cart';
let reducers = combineReducers({
user,
users,
items,
cart
});
const store = createStore(reducers);
export default store;
Provider 组件
想在 React 中使用 Redux ,还需要通过 react-redux 提供的 Provider 容器组件把 store 注入到应用中
// index.js
import {Provider} from 'react-redux';
import store from './store';
ReactDOM.render(
,
document.getElementById('root')
);
connect 方法
有了 connect 方法,我们不需要通过 props 一层层的进行传递, 类似路由中的 withRouter ,我们只需要在用到 store 的组件中,通过 react-redux 提供的 connect 方法,把 store 注入到组件的 props 中就可以使用了
import {connect} from 'react-redux';
class Main extends React.Component {
render() {
console.log(this.props);
}
}
export default connect()(Main);
默认情况下,connect 会自动注入 dispatch 方法
注入 state 到 props
export default connect( state => {
return {
items: state.items
}
} )(Main);
connect 方法的第一个参数是一个函数
- 该函数的第一个参数就是 store 中的 state :
store.getState()
- 该函数的返回值将被解构赋值给 props :
this.props.items
Redux DevTools extension
为了能够更加方便的对 redux 数据进行观测和管理,我们可以使用 Redux DevTools extension 这个浏览器扩展插件
https://github.com/zalmoxisus/redux-devtools-extension
const store = createStore(
reducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
打开浏览器开发者工具面板 -> redux
异步 action
许多时候,我们的数据是需要和后端进行通信的,而且中开发模式下,很容易就会出现跨域请求的问题,好在 create-react-app 中内置了一个基于 node 的后端代理服务,我们只需要少量的配置就可以实现跨域
package.json 配置
相对比较的简单的后端 URL 接口,我们可以直接中 package.json 文件中进行配置
// 后端接口
http://localhost:7777/api/items
// http://localhost:3000
{
...
//
"proxy": "http://localhost:7777"
}
axios({
url: '/api/items'
});
./src/setupProxy.js 配置
针对相对复杂的情况,可以有更多的配置
npm i -S http-proxy-middleware
// setupProxy.js
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
proxy('/api', {
target: 'http://localhost:7777/',
pathRewrite: {
'^/api': ''
}
})
);
};
其它代码同上
Middleware
默认情况下,dispatch 是同步的,我们需要用到一些中间件来处理
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd(action.type)
return result
}
redux.applyMiddleware
通过 applyMiddleware 方法,我们可以给 store 注册多个中间件
注意:devTools 的使用需要修改一下配置
npm i -D redux-devtools-extension
...
import { composeWithDevTools } from 'redux-devtools-extension';
...
const store = createStore(
reducres,
composeWithDevTools(
applyMiddleware( logger )
)
)
redux-thunk
这是一个把同步 dispatch 变成异步 dispatch 的中间件
安装
npm i -S redux-thunk
import {createStore, combineReducers, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import user from './reducer/user';
import items from './reducer/items';
import cart from './reducer/cart';
let reducers = combineReducers({
user,
items,
cart
});
const store = createStore(
reducers,
composeWithDevTools(applyMiddleware(
thunk
))
);