一、Redux是什么
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
javascript开发的应用程序需要管理的状态(state)越来越多,这些state管理起来十分复杂,不易追踪,所以一般开发时候需要合适的数据管理工具,不如vue用到的有vuex,pinia等,react则经常使用redux来管理数据。(当然redux不止可以用到react,还能用到vue等其他框架)。
二、Redux三大核心理念(store,action,reducer)
1.Store
一个redux应用只有单一的store 。Store 有以下职责:
- 维持应用的 state;
- 提供
getState()方法获取 state; - 提供
dispatch(action)方法更新 state; - 通过
subscribe(listener)注册监听器; - 通过
subscribe(listener)返回的函数注销监听器。
2.action
Redux要求我们使用dispatch(action)方式来修改store数据,action是一个普通的JavaScript对象,用来描述这次更新的type以及text。
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
3. reducer
reducer是个纯函数。将传入的state和action结合起来生成一个新的state。
三、Redux三大原则
1.单一数据源
整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中。
Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护;
单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改;
2.state是只读的
唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State。
这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state。
这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;
3.使用纯函数来进行修改
通过reducer将 旧state和 actions联系在一起,并且返回一个新的State, 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分。 但是所有的reducer都应该是纯函数,不能产生任何的副作用;
四、Redux的基本使用
下面介绍下redux基本使用,如果用在开发有些操作可能多余重复,因此一般都使用一些redux工具用来更好的搭建redux,比如ReduxToolKit。但是还是得熟悉Redux的基本使用方式。一般有这几步:
1.先创建actionCreators.js
生成action对象。
const addCounterAction = (num)=>({
type:change_counter,
//num:num 对象增强
num
})
2.通过action来修改state
触发事件需要修改store的state,通过dispatch来派发action;
btnClick(num){
store.dispatch(addCounterAction(num))
}
3.创建一个对象,作为我们要保存的状态:
const initialValues = {
counter:10
}
4.修改reducer中的处理代码
通过dispatch派发事件,reducer函数会执行一次返回新的state,根据传入的action的type和值,编写对应的逻辑语句,以达到直接修改store的state的目的。
function reducer(state===initialValues,action){
switch(action.type){
case change_counter:
return {...state,state.counter:state.counter+action.num}
case xxxx: return {}
......//各类情况
default:
return state
}
}
5.创建Store来存储这个state
import {createStore} from 'redux'
const store = createStore(reducer)
export default store
创建store时必须创建reducer;
我们可以通过 store.getState 来获取当前的state;
通常action中都会有type属性,也可以携带其他的数据;
这里一定要记住,reducer是一个纯函数,不需要直接修改state;
6.可以在派发action之前,监听store的变化:
componentDidMount(){
const unsubscribe = store.subscribe(() => {
console.log("订阅数据的变化:", store.getState())
})
}
、、、、、、
componentWillUnmount(){
const unsubscribe = store.subscribe(() => {
})
unsubscribe() //取消订阅
}
这样使用redux的话,当组件想要用store的数据就得import store对象,在组件constructor定义state来保存store数据。而且都得用subscribe订阅store数据是否改变,并更新state。这样使用还是有点麻烦。接下来使用react-redux里Provider,connect进行优化。
五、使用react-redux的provider和connect
导出react-redux里的Provider,在根index.js包裹根组件并传入store。再配合connect就可以直接全局使用store。
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>
);
如果想要在Home组件使用store数据。 使用connect()
export default connect(mapStateToProps, mapDispatchToProps )(Home)
connect(fn1,fn2)
fn1:
const mapStateToProps = (state) => ({
counter: state.counter
})
将counter通过props传给Home,Home就可以通过this.props.counter获取到store的counter值。
fn2:
const mapDispatchToProps = (dispatch) => ({
addNumber(num) {
dispatch(addNumberAction(num))
},
subNumber(num) {
dispatch(subNumberAction(num))
}
})
将函数通过props传给Home,Home可以直接通过this.props.addNumber()调用函数,以达到dispatch的目的。 connect()返回值也是个函数,将类组件传入就可以直接在该类组件获取store,或者dispath修改store数据。
connect原理。
但是通过上面的写法只是简化了import store对象、subscrible订阅一些步骤,还是需要多个reducer、actionCreators.js、constants等,文件过于繁多复杂。因此继续使用Redux Toolkit包进行Redux编写。
六、Redux Toolkit
核心API:
1.configureStore:包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "./features/counter"
import homeReducer from "./features/home"
const store = configureStore({
reducer: {
counter: counterReducer,
home: homeReducer
}
})
export default store
2.createSlice:接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。
//counter.js
import { createSlice } from "@reduxjs/toolkit"
const counterSlice = createSlice({
name: "counter",
initialState: {
counter: 888
},
reducers: {
addNumber(state, { payload }) {
state.counter = state.counter + payload
},
subNumber(state, { payload }) {
state.counter = state.counter - payload
}
}
})
export const { addNumber, subNumber } = counterSlice.actions
export default counterSlice.reducer
//home.js
const homeSlice = createSlice({
name: "home",
initialState: {
banners: [],
recommends: []
},
reducers: {
changeBanners(state, { payload }) {
state.banners = payload
},
changeRecommends(state, { payload }) {
state.recommends = payload
}
}
3.createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk
第一种方法: createAsyncThunk第二个函数一个返回值:需要用extraReducers处理。
// store/home.js
export const fetchHomeMultidataAction = createAsyncThunk("fetch/homemutidata", async () => {
const res= await axios.get(url)
return res.data
})
const homeslice=createSlice({
name:''
initialValue:{}
reducers:{}
extraReducers: {
[fetchHomeMultidataAction.pending]() {
},
[fetchHomeMultidataAction.fulfilled](state, { payload}) {
state.banners = payload.data.banner.list
state.recommends=payload.data.recommend.list
},
[fetchHomeMultidataAction.rejected]() {
}
}
})
第二种方法:createAsyncThunk第二个函数无需返回值和extraReducers,但需要发送多发送dispatch.
import axios from 'axios'
export const fetchHomeMultidataAction = createAsyncThunk(
"fetch/homemutidata",
async (extraInfo, { dispatch,getState }) => {
const res = await axios.get(url)
const recommends = res.data.data.recommend.list
const banners = res.data.data.banner.list
dispatch(changeBanners(banners))
dispatch(changeRecommends(recommends))
})
const homeSlice = createSlice({
name: 'home',
initialState: {
banners: [],
recommends:[]
},
reducers: {
changeBanners(state, {payload}){
state.banners=payload
},
changeRecommends(state, { payload }){
state.recommends=payload
}
},
// extraReducers: {
// [fetchHomeMultidataAction.pending]() {
// },
// [fetchHomeMultidataAction.fulfilled](state, { payload}) {
// state.banners = payload.data.banner.list
// state.recommends=payload.data.recommend.list
// },
// [fetchHomeMultidataAction.rejected]() {
// }
// }
})
export default homeSlice.reducer
export const {changeBanners,changeRecommends }=homeSlice.actions