npm install redux --save
状态管理的js库
(不是react插件)(共享)
(通信)
type
:属性标识,值为字符串,唯一,必要属性data
:数据属性,值任意类型,可选属性{type:'CHANGE_NAME', data: {name: 'why'}}
纯函数
if (type === 'CHANGE_NAME') {return { ...state, name }}
store
state
、action
、reducer
联系在一起的对象state和action
,产生新的state的纯函数
│ └─ store
│ ├─ actions // actions,文件夹内以模块区分
│ │ ├─ count.js
│ │ └─ person.js
│ ├─ constants.js // action type唯一标识常量
│ ├─ index.js // 入口文件
│ └─ reducer // reducer,文件夹内以模块区分
│ ├─ conut.js
│ ├─ index.js // reducer统一暴露文件,合并reducers
│ └─ persons.js
createStore
,专门用于创建redux中最为核心的store对象,而redux-thunk
、applyMiddleware用于支持异步action,npm i redux-thunk
// src/store/index.js
import { createStore, applyMiddleware } from "redux";
// 用于支持异步action
import thunk from "redux-thunk";
import reducers from "./reducers";
export default createStore(reducers, applyMiddleware(thunk));
type类型
的常量值// src/store/constants.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
对象
,异步action返回函数
可用于发送网络请求,执行setTimeout
等,// src/store/actions/count.js
import { INCREMENT, DECREMENT } from "../constants";
// 普通action的值为object `{type: INCREMENT, data }`
export const increment = data => ({ type: INCREMENT, data });
export const decrement = data => ({ type: DECREMENT, data });
export const incrementAsync = (data) => {
return (dispatch) => {
setTimeout(() => {
dispatch(increment(data));
}, 500);
};
};
异步action
(state)
,动作对象(action)
type
,data
type
决定如何加工数据reducer
没有初始化值时为undefined
因此我们可以设置初始值 initialState
import {INCREMENT, DECREMENT} from '../constants'
// 初始化状态
const initialState= 0;
export default function count(state = initialState, action) {
const { type, data } = action;
switch (type) {
case INCREMENT:
return state + data;
case DECREMENT:
return state - data;
default:
return state;
}
}
注意
state只读
action
,不要试图在其他地方通过任何的方式来修改State:这样可以保证所有的修改都被集中化处理
,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;使用纯函数
来执行修改
reducer拆分成多个小的reducers
,分别操作;纯函数
,不能产生任何的副作用
;通过combineReducers
合并,接收的参数是一个对象,对象的key值与getState()得到的对象的key一致
// src/store/reducers/index.js
import { combineReducers } from "redux";
import count from "./conut";
import persons from "./persons";
export default combineReducers({
count,
persons,
});
getState()
拿store的数据dispatch
触发actionimport store from "../../store";
import { increment } from "../../store/action/count";
//redux内部不支持自动更新,需要通过subscribeAPI监听redux中状态变化,只有变化,就需要重新调用render
componentDidMount() {
store.subscribe(() => {
this.forceUpdate();
});
}
clickIncrement = () => {
store.dispatch(increment(+1));
};
render() {
return (
<div>
<h1>当前求和为: {store.getState()}</h1>
...
<button onClick={this.clickIncrement}>+</button>
</div>
)
}
store.subscribe(() => { this.forceUpdate(); })
;react-redux不需要监听UI组件
、容器组件
;redux的操作都在容器组件中,connect(mapStateToProps, mapDispatchToProps)(UI)
连接容器组件与UI组件;redux没有区分UI的呈现
,容器组件负责管理数据和逻辑
,如果一个组件既有UI又有业务逻辑,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图React-Redux
提供connect
方法,用于从UI组件
生成容器组件
CountUI
是UI组件
,利用connect
最后导出的是容器组件
输入逻辑:外部的数据(即
state对象
)如何转换为 UI 组件的参数
输出逻辑:用户发出的动作如何变为 Action 对象,从 UI组件传出去
connect
方法接受两个参数:mapStateToProps
和mapDispatchToProps
。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state
映射到 UI 组件的参数(props
),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action
mapStateToProps
接收 state
参数,mapDispatchToProps
接收 dispatch
参数
// 容器组件
import { connect } from "react-redux";
import CountUI from "../../components/count";
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction,
} from "../../redux/count_action";
const mapStateToProps = (state) => ({ count: state });
const mapDispatchToProps = (dispatch) => ({
increment: (number) => {
dispatch(createIncrementAction(number));
},
incrementAsync: (number) => {
dispatch(createIncrementAsyncAction(number, 500));
},
decrement: (number) => {
dispatch(createDecrementAction(number));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
// UI组件
import React, { Component } from "react";
export default class CountUI extends Component {
// 加法
increment = () => {
const { value } = this.selectNumber;
this.props.increment(value * 1);
};
// 减法
decrement = () => {
const { value } = this.selectNumber;
this.props.decrement(value * 1);
};
// 奇数加
incrementIfOdd = () => {
if (this.props.count % 2 === 1) {
const { value } = this.selectNumber;
this.props.increment(value * 1);
}
};
// 异步加
incrementAsync = () => {
const { value } = this.selectNumber;
this.props.increment(value * 1);
};
render() {
return (
<div>
<h1>当前求和为: {this.props.count}</h1>
...
</div>
);
}
}
mapDispatchToProps
是connect
函数的第二个参数,用来建立 UI 组件的参数到store.dispatch
方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action
,传给 Store
它可以是一个函数,也可以是一个对象
/ 容器组件
const mapDispatchToProps = (dispatch) => ({
increment: (number) => {
dispatch(createIncrementAction(number));
},
incrementAsync: (number) => {
dispatch(createIncrementAsyncAction(number));
},
decrement: (number) => {
dispatch(createDecrementAction(number));
},
});
// mapDispatchToProps的一般写法,返回function
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
如果mapDispatchToProps是一个对象
键值是一个函数,Action creator
,返回的 Action
会由 Redux
自动发出
// mapDispatchToProps的简写,返回object
export default connect(mapStateToProps, {
increment: createIncrementAction,
incrementAsync: createIncrementAsyncAction,
decrement: createDecrementAction,
})(CountUI);
connect
方法生成容器组件以后,需要让容器组件拿到state
对象,才能生成 UI 组件的参数。
一种解决方法是将state
对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。
// src/App.js
import React, { Component } from "react";
import Count from "./container/count";
import store from "./redux/store";
export default class App extends Component {
render() {
return <Count store={store} />;
}
}
Provider
组件,可以让容器组件拿到stateProvider
在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了
- 它的原理是
React
组件的context
属性- 使原来整个应用成为
Provider
的子组件 接收Redux的store作为props
,通过context
对象传递给子孙组件上的connect
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from "react-redux"
import store from "./store"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
//
<Provider store={store}>
<App />
</Provider>
//
);
中间件
dispatch
的action
和最终达到的reducer
之间,扩展一些自己的代码;日志记录
、调用异步接口
、添加代码调试功能
等等;redux-thunk
、applyMiddleware
用于支持异步action,combineReducers
函数
redux-devtools
状态是如何被修改的
,修改前后的状态变化
import { applyMiddleware, compose, createStore,combineReducers } from 'redux';
import thunk from 'redux-thunk';
import count from "./conut";
import persons from "./persons";
const reducer= combineReducers({
count,
persons,
});
//创建store 传递reducer
// redux-devtools
// trace 开启
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
RTK
npm install @reduxjs/toolkit react-redux
configureStore
:包装createStore
以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer
,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。 createSlice
:接受reducer函数的对象
、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。 createAsyncThunk
: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected
基于该承诺分派动作类型的 thunk通过createSlice
创建一个slice
,createSlice主要包含如下几个参数:
name
:用户标记slice的名词, 在之后的redux-devtool中会显示对应的名词;initialState
:初始化值,第一次初始化时的值;reducers
:相当于之前的reducer函数
state和action
调用这个action时
,传递的action参数
;extraReducers
监听异步结果createSlice返回值是一个对象
,包含所有的actions;
import { createSlice } from '@reduxjs/toolkit';
const homeReducer = createSlice({
name: 'home',
initialState: {
banners: [],
recommends: []
},
reducers: {
changeRecommendActios(state, { paylaod }) {
state.recommends=paylaod
},
changeBannerActions(state, { payload }) {
state.banners=payload
}
}
})
export const { changeBannerActions, changeRecommendActios } = homeReducer.actions
export default homeReducer.reducer
configureStore
用于创建store对象
,常见参数如下:
reducer
,将slice中的reducer可以组成一个对象传入此处;middleware
:可以使用参数,传入其他的中间件devTools
:是否配置devTools工具,默认为true;import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './modules/Counter';
import homeReducer from './modules/Home';
const store = configureStore({
reducer: {
// 这里做分包
counter: counterReducer,
home: homeReducer
}
})
export default store
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from 'react-redux';
import App from "./App";
import store from './store';
const root = ReactDOM.createRoot(document.getElementById("root"));
// 1. react-redux使用 第一步提供全局store
root.render(
// 严格模式 render执行两次
<Provider store={store} >
<App />
</Provider>
);
Redux Toolkit默认已经给我们继承了Thunk相关的功能:createAsyncThunk
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
export const fetachHomeActions = createAsyncThunk('home/multidata', async (payload, extraInfo) => {
const res = await axios.get("http://123.207.32.32:8000/home/multidata")
return res.data
})
pending
:action被发出,但是还没有最终的结果;fulfilled
:获取到最终的结果(有返回值的结果);rejected
:执行过程中有错误或者抛出了异常;import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
export const fetachHomeActions = createAsyncThunk('home/multidata', async (payload, extraInfo) => {
const res = await axios.get("http://123.207.32.32:8000/home/multidata")
return res.data
})
const homeReducer = createSlice({
name: 'home',
initialState: {
banners: [],
recommends: []
},
reducers: {
changeRecommendActios(state, { paylaod }) {
state.recommends = paylaod
},
changeBannerActions(
state, { payload }
) {
state.banners = payload
}
},
// 异步操作(三种状态)
extraReducers: {
[fetachHomeActions.pending](state, action) {
console.log(action);
},
[fetachHomeActions.fulfilled](state, { payload }) {
state.banners=payload.data.banner.list
},
[fetachHomeActions.rejected](sate, action) {
}
}
})
export const { changeBannerActions, changeRecommendActios } = homeReducer.actions
export default homeReducer.reducer
链式调用
// 异步操作(三种状态)(链式调用的形式)
extraReducers: (builder) => {
builder.addCase(fetachHomeActions.pending, (state, action) => {
console.log("fetachHomeActions pending")
}).addCase(fetachHomeActions.fulfilled, (state, { payload }) => {
state.banners = payload.data.banner.list
state.recommends = payload.data.recommend.list
})
}
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
})
useSelector
访问/使用stateconst {counter} = useSelector((state) => state.counter.counter);
// 优化
// 1. memo包裹只有只有props改变才会重新渲染
// 2.第二个参数shallowEqual 进行浅层比较 (就是reducer中返回完全相同的对象 才不进行重新渲染)
// shallowEqual 解决使用相同的参数调用时,useSelector返回不同的结果。这可能导致不必要的重新渲染。
const App = memo((props) => {
const { counter } = useSelector((state) => ({
counte: state.counter.counter
}),shallowEqual)
return (
<div>
<h1>{counter}</h1>
</div>
)
})
useDispatch
变更stateimport {useSelector, useDispatch } from 'react-redux';
import {
decrement,
increment,
} from './counterSlice';
export function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<div>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span>{count}</span>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
</div>
);
}
参考文章:jjjona0215大神