Redux

119 阅读5分钟

一、Redux是什么

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

javascript开发的应用程序需要管理的状态(state)越来越多,这些state管理起来十分复杂,不易追踪,所以一般开发时候需要合适的数据管理工具,不如vue用到的有vuex,pinia等,react则经常使用redux来管理数据。(当然redux不止可以用到react,还能用到vue等其他框架)。


二、Redux三大核心理念(store,action,reducer)

1.Store

一个redux应用只有单一的store 。Store 有以下职责:

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