React-Redux

118 阅读9分钟

Redux

  • react:全局状态管理

  • 安装

    npm install redux
    

核心

  • state:存放数据
  • reducer:修改仓库数据
  • actions:派发行为触发 reducer

初体验

  • 1、安装 npm install redux

  • 2、在 src > store > index.js 中创建仓库

    // 导入redux
    import { createStore } from "redux";
    // 定义reducer
    function reducer(state = { age: 12 }, actions) {
        switch (actions.type) {
            case 'add':
                console.log('add:', state);
                return { age: state.age + 1 };
            default:
                return state
        }
    }
    // 创建仓库
    let stort = createStore(reducer)
    export default stort
    
  • 组件中使用仓库

    import stort from "./store/index"; // 引入仓库
    import { useState } from "react"; // 引入react中的hooks
    function App() {
        // 1、获取仓库中的数据
        console.log('获取整个仓库的数据:', stort.getState());
        let [age, setAge] = useState(stort.getState().age)
        // 2、修改数据
        const btn = () => {
            // 2.1、触发dispatch
            stort.dispatch({ type: 'add' })
            // 2.2、仓库里的数据已经更新=>动态数据(和仓库关联)
            setAge(stort.getState().age)
        }
        return (
            <div>
                <h1>仓库数据:{age}</h1>
                <button onClick={() => btn()}>修改仓库数据</button>
            </div>
        );
    }
    export default App;
    

创建仓库

  • 使用 redux 中的 createStore 来创建仓库,引入:

    import {createStore} from 'redux'
    
  • 语法:

    let stort = createStore(reducer,中间件)
    
  • 什么是reducer?

    1.reducer是一个函数
    2.reducer函数有2个参数(形参)
    	参数1:默认数据
        参数2:触发reducer方法传入的实参(触发reducer方法`dispatch`传递的参数,一个对象)
    3.reducer返回值为最新的数据
    4.只能处理同步问题(相当于vuex中的mutations)
    
  • stort

    @@observable: ƒ observable() // 观察仓库中的数据
    dispatch: ƒ dispatch(action) // 触发reducer
    getState: ƒ getState() // 获取到仓库中的数据
    replaceReducer: ƒ replaceReducer(nextReducer) // 替换reducer
    subscribe: ƒ subscribe(listener) // 订阅
    

获取仓库数据

  • 语法

    stort.getState()
    stort.getState().age
    

模块化(combineReducers)

  • 作用:合并reducer,使用仓库数据模块化

  • 引入 react 中提供的 combineReducers

    import { combineReducers } from 'redux'
    
  • 使用:

    let reducers = combineReducers({属性:各自的reducer,属性:各自的reducer...}) // 使用
    
  • 模块化目录结构

    |src
    |	|store
    |	|	|-index.js(创建仓库)
    |	|	|reducers
    |	|	|	|-index.js(合并reducer)
    |	|	|	|-CartReducer.js(购物车模块reducer)
    |	|	|	|-MeReducer.js(我的模块reducer)
    |	|	|	|-PrdReducer.js(产品模块reducer)
    
// 合并reducer
import { combineReducers } from 'redux' // 合并reducer
// 引入reducers
import CartReducer from "./CartReducer";
import PrdReducers from "./PrdReducers";
import MeReducer from "./MeReducer";
// 合并reducer
let reducers = combineReducers({
    CartReducer,
    PrdReducers,
    MeReducer
})
export default reducers
// 创建仓库
import { createStore } from "redux"; // 导入redux
import reducers from './reducers' // 导入合并的reducers
let store = createStore(reducers) // 创建仓库
export default store // 暴露
//组件中使用
// 引入仓库
import stort from "./store/index";
// 引入react中的hooks
import { useState } from "react";
function App() {
    #// 获取仓库中的数据
    console.log('获取所有仓库的数据:', stort.getState());
    let [meAge, setAge] = useState(stort.getState().MeReducer.age) #// 获取数据
    let [cartList, setList] = useState(stort.getState().CartReducer.cartList) #//获取数据
    // 修改MeReducer数据
    const btn1 = () => {
        // 触发dispatch
        stort.dispatch({ type: 'meAdd' })
        // 仓库里的数据已经更新=>动态数据(和仓库关联)
        setAge(stort.getState().MeReducer.age)
    }
    // 修改CartReducer数据
    const btn2 = () => {
        // 触发dispatch
        stort.dispatch({ type: 'cartAdd' })
        // 2.2、仓库里的数据已经更新=>动态数据(和仓库关联)
        setList(stort.getState().CartReducer.cartList)
    }
    return (
        <div>
            <h1>Me数据:{meAge}</h1>
            <h3>Cart数据:{cartList}</h3>
            <button onClick={() => btn1()}>修改Me数据</button>
            <button onClick={() => btn2()}>修改Cart数据</button>
        </div>
    );
}

工程化处理reducer合并(模块化)

  • 作用:自动引入reducer,自动合并

  • 使用 webpack 提供的 require.context 来自动获取文件中暴露的内容

    • 语法:

      let webf = require.context('文件路径',布尔值,正则)
      参数:
      	参数1:要导入模块的文件夹目录
          参数2:是(true)否(false)搜索当前目录的子目录
          参数3:要匹配的文件
      提供2个方法:
      	获取到文件路径:webf.keys() => ['文件路径','文件路径',...]
          获取到文件的内容:webpack(路径).default
      
//工程化处理,自动合并reducer
import { combineReducers } from 'redux' // 合并reducer
// 使用webpack提供的require.context自动获取文件中暴露的内容
let webf = require.context('./', true, /\.js/)
let list = webf.keys() // 获取到文件路径数组
// 删除自己(index.js)
let index = list.findIndex((item, index) => {
    return item == './index.js'
})
list.splice(index, 1)
// 组合成对象
let objs = {}
list.forEach((item, index) => {
    // 处理item >> ./MeReducer.js => MeReducer
    let items = item.replace('./', '').replace('.js', '')
    objs[items] = webf(item).default //添加键值对
})
// 合并rducer
let reducers = combineReducers(objs)
export default reducers
创建仓库和组件中使用 与 原生的模块化一致

自动派发reducer行为(bindActionCreators)

  • 使用 react 提供的 bindActionCreators 来实现自动派发行为

    import { bindActionCreators } from "redux";
    
  • 使用语法:

    let meActions = bindActionCreators({属性:reducer方法,...}, dispatch)
    let meActions = bindActionCreators({ meAdd, reduce }, store.dispatch)
    
  • 自动派发行为目录结构

    |src
    |	|store
    |	|	|-index.js(创建仓库)
    |	|	|reducers
    |	|	|	|-index.js(合并reducer)
    |	|	|	|-CartReducer.js(购物车模块reducer)
    |	|	|	|-MeReducer.js(我的模块reducer)
    |	|	|	|-PrdReducer.js(产品模块reducer)
    |	|	|actions
    |	|	|	|-cartActions(购物车行为)
    |	|	|	|-meActions(我的行为)
    |	|	|	|-prdActions(产品行为)
    
  • store > actions 中实现reducer和行为一一对应

    // Me模块行为
    import { bindActionCreators } from "redux"; // 引入自动触发
    import store from "../index"; // 引入store
    // 创建派发方法
    let meAdd = () => {
        return { //行为actions
            type: 'meAdd'
        }
    }
    let reduce = () => {
        return { //行为actions
            type: 'reduce'
        }
    }
    // 使用bindActionCreators
    let meActions = bindActionCreators({ meAdd, reduce }, store.dispatch)
    export default meActions // 暴露
    
    // 组件中使用
    import stort from "./store/index"; // 引入仓库
    import { useState } from "react"; // 引入react中的hooks
    // 使用自动派发
    import meActions from "./store/actions/meActions";
    import cartActions from "./store/actions/cartActions";
    import prdActions from "./store/actions/prdActions";
    // 组件
    function App() {
        // 获取仓库中的数据
        console.log('获取所有仓库的数据:', stort.getState());
        let [meAge, setAge] = useState(stort.getState().MeReducer.age)
        let [cartList, setList] = useState(stort.getState().CartReducer.cartList)
        // 修改MeReducer数据
        const btn1 = () => {
            #// 触发dispatch
            meActions.meAdd()
            // 仓库里的数据已经更新=>动态数据(和仓库关联)
            setAge(stort.getState().MeReducer.age)
        }
        // 修改CartReducer数据
        const btn2 = () => {
            #// 触发dispatch
            cartActions.cartAdd()
            // 2.2、仓库里的数据已经更新=>动态数据(和仓库关联)
            setList(stort.getState().CartReducer.cartList)
        }
        return (
            <div>
                <h1>Me数据:{meAge}</h1>
                <h3>Cart数据:{cartList}</h3>
                <button onClick={() => btn1()}>修改Me数据</button>
                <button onClick={() => btn2()}>修改Cart数据</button>
            </div>
        );
    }
    export default App;
    

项目中使用(react-redux)

  • react-redux 提供了1个 组件 和2个 hooks

    • 组件<Provider />
  • 作用:在父级组件中提供数据

  • hooks

    • useSelector:获取仓库数据
    • useDispatch:触发 reducer 行为
  • 安装

    npm install react-redux
    

react-redux提供的组件和hooks

  • 引入:

    // 引入组件
    import { Provider } from "react-redux";
    // 引入hooks
    import { useSelector, useDispatch } from "react-redux";
    
  • 组件:<Provider />

    • 作用:在父级组件中提供数据

    • 语法:

      <Provider store={store}>
          <!-- 子级组件 -->
      </Provider>
      
  • hooks:

    • useSelector:获取仓库数据

      • 语法:

        let stateData = useSelector(state=>{return 返回值})
        	state:所有仓库数据
            返回值:仓库中的数据
        
    • useDispatch:触发 reducer 行为

      • 使用:

        let dispatch = useDispatch()
        底层:
        	1.触发reducer
            2.获取到仓库的最新数据
            3.通过useState更新
        
      • 语法:

        dispatch(行为)
        dispatch({ type: 'meAdd' })
        

🌰案例:

// 父级组件提供数据(index.js)
...
import { Provider } from "react-redux"; // 引入react-redux中的Provider
import store from "./store"; // 引入store
...
root.render(
    <Provider store={store}>
        <App />
    </Provider>
);
// 自己组件中获取仓库中的数据(App.js)
import { useSelector, useDispatch } from "react-redux"; // 引入react-redux提供的2个hooks
function App() {
    // 获取仓库中的数据
    let meAge = useSelector(state => {
        return state.MeReducer.age
    })
    let cartList = useSelector(state => {
        return state.CartReducer.cartList
    })
    console.log('获取数据:', meAge, cartList);
    // 使用useDispatch
    let dispatch = useDispatch() // 1.触发reducer 2.获取到仓库的最新数据 3.通过useState更新
    console.log('组件重新创建');
    return (
        <div>
            <h1>Me数据:{meAge}</h1>
            <h3>Cart数据:{cartList}</h3>
            <!-- 触发reducer行为 -->
            <button onClick={() => dispatch({ type: 'meAdd' })}>修改Me数据</button>
            <button onClick={() => dispatch({ type: 'cartAdd' })}>修改Cart数据</button>
        </div>
    );
}
export default App;

异步actions

  • 本质:写一个函数劫持

  • 为何需要处理异步?

    因为reducer只能出合理同步问题
    在项目中可能需要在仓库中发送请求,获取到异步数据
    
  • 异步actions的作用?

    1.获取到异步数据
    2.触发reducer修改长裤中的数据
    
  • 实现异步actions

    1.在store文件夹下创建actions文件夹
    2.实现actions和各自的reducer一一对应
    
    // 我的模块中的异步处理
    import axios from 'axios'
    import store from '../index'
    // 定义异步处理方法
    export function getAdd(){
        // 1.获取异步数据方法
        function getData(){
            return  axios.get('').then(res=>{ //后端给的数据
                  let a = 100 // 假设这是后端数据
                  return a
              })
         }
        // 2.触发reducer
        return  async()=>{
            let data = await getData() // 获取异步数据
            store.dispatch({type:'add',data }) // 触发reducer
        }
    }
    
    // 组件中
    <button onClick={()=>dispatch( getAdd())}>触发异步actions</button>
    
  • 在组件中使用dispatch触发actions

    报错。
    需要使用 中间件 和 redux-thunk
    

redux-thunk

安装:npm i redux-thunk
作用:处理异步actions => 异步actions触发方法的第一个参数就是dispatch
  • 配置中间件=>redux-thunk

    // 在store/index.js中进行配置 
    
    // 1、引入redux中的applyMiddleware方法
    import {createStore,applyMiddleware} from 'redux'
    import reducers from './reducers'
    let store = createStore(reducers,applyMiddleware(ReduxThunk))
    // 2、引入redux-thunk
    import ReduxThunk from 'redux-thunk'
    
    • applyMiddleware(ReduxThunk)的作用

      • 让组件中的dispatch()中可以写方法

      • return返回函数的第一个参数就是dispatch

        // 我的模块中的异步处理
        import axios from 'axios'
        import store from '../index'
        // 定义异步处理方法
        export function getAdd(){
            // 1.获取异步数据方法
            function getData(){
                return  axios.get('').then(res=>{ //后端给的数据
                      let a = 100 // 假设这是后端数据
                      return a
                  })
             }
            // 2.触发reducer
            return  async(dispatch)=>{ // 第一个参数就是dispatch
                let data = await getData() // 获取异步数据
                dispatch({type:'add',data }) // 触发reducer
            }
        }
        
        <button onClick={()=>dispatch( getAdd())}>触发异步actions</button>
        
  • 总结redux-thunk

    • 组件中的dispatch()中可以写方法
    • 异步actions返回(return)的方法的第一个形参就是dispatch

Toolkit

  • Redux-Toolkit

    • 全局状态管理
    • 本质:redux + redux-thunk 进行封装
  • 安装

    # npm i @reduxjs/toolkit
    
  • 创建仓库

    // store/index.js
    
    // 1、下包 npm install @reduxjs/toolkit react-redux
    // 2、创建仓库
    import { configureStore,createSlice } from '@reduxjs/toolkit'
    const store = configureStore({
        reducer: { // 模块化对象
            me:meReducer,
            cart:cartReducer,
            limit:LimitReducer
        }
    })
    export default store
    
  • 各自的reducer:存放数据和定义仓库行为

    // store/modules/MeState.js
    
    import {createSlice} from '@reduxjs/toolkit'
    let MeState = createSlice({
        name:'me', // 作用域,唯一标识
        initialState:{ // 仓库存放的数据
            name:'乐乐',
            age:18
        },
        // 定义修改仓库数据的行为
        reducers:{ // 只能处理同步问题
            changeName(state,val){ // 参数1:该仓库存放的(模块)数据
                state.name = val.payload 
            },
            addAge(state){
                state.age +=1
            }
        }
    })
    //暴露行为,本质就是将方法暴露,"行为在actions中"
    export let { changeName,addAge}  = MeState.actions
    export default MeState.reducer
    
    • configureStore 语法

      作用:创建仓库
      configureStore的返回值:就是store仓库
      合并reducer:configureStore({各自的reducer})
      
    • createSlice 语法

      作用:创建各自的reducer
      返回值:一个对象
      createSlice({实例属性})
      
  • 仓库数据和react项目关联

    • 安装react-redux

      # npm i react-redux
      
    • 在入口文件中引入

      import store from './store/index'
      import {Provider} from 'react-redux'
      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(
          <Provider store={store}>
              <App />
          </Provider>
      );
      
  • 组件中使用仓库中的数据

    // 引入react-redux提供的两个方法
    import {useSelector,useDispatch} from 'react-redux'
    // 引入reducer行为
    // 注意:行为需要暴露处理,这些行为就是reducers中定义的方法
    import {changeName} from './store/modules/MeState'
    function App(){
        let {name} = useSelector(store=>store.meReducer)
        let dispatch = useDispatch()
        return(
            <div>
                <h2>获取到仓库中的数据{name}</h2>
                <button onClick={()=>dispatch(changeName('小乐'))}>修改数据</button>
            </div>
        )
    }
    export default App
    

异步actions

1.获取到异步数据
2.触发reducer修改仓库中的数据
  • 使用第三方插件redux-thunk
import {createSlice} from '@reduxjs/toolkit'
import axios from 'axios'
// 全局数据和行为
let LimitState = createSlice({
    name:'cart',
    initialState:{
        router:[]
    },
    reducers:{  //只能处理同步问题的问题
        addRouter(state,data){
            state.router= data.payload
        }
    }
})
// 暴露两个:行为=>组件使用直接引入;reducer=>模块划分
export let {addRouter} = LimitState.actions
export default LimitState.reducer
// 自己定义异步actions
export function getRouterData(){ //异步actions
    // 获取异步数据的方法
    function  getData(){
        return   axios.get('').then(res=>{
            let routerList = [1,2,3,4,5] // 假设后端数据
            return  routerList
        })
    }
    // 触发reducer行为
    return async (dispatch)=>{ // return第一个行参就是dispatch
        let data = await getData() // 获取后端数据
        dispatch(addRouter(data)) // 触发行为
    }
}