Skip to content

六、Redux

1.Redux

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

1.操作步骤

1.下载安装

shell
yarn add redux

2.创建仓库

reducer:仓库管理员,函数形式,有两个参数state、action

  • state:用来存放数据
  • action:是个对象,有两个参数,type、payload
    • type:一般是字符串,根据传过来的字符串触发不同方法
    • payload:调用action里的方法时传过来的参数,可以是任意类型

src/store/index.js

jsx
const initData={
    num:1
}
const reducer = (state=initData,action)=>{
    //...
    return state
}

const store = Redux.createStore(reducer)

3.获取数据

jsx
import store from '路径'

store.getState().键

4、更新数据/触发动作

  • type 选择触发的方法
  • payload 传递的参数 可选
js
store.dispatch({
    type: 'ADD/DEL', 
    payload: 参数任意类型
})

5.监控仓库数据变化

js
store.subscribe(() => {
    组件state中的键=store.getState().键
})

2.模块化

语法

注意:模块中的方法名必须唯一

js
// A模块
const initData1={
    num:1
}
const modeA=(state=initData1,action)=>{
    //...
	return state
}

// B模块
const initData2={
    num:2
}
const modeB=(state=initData1,action)=>{
    //...
	return state
}

const store = Redux.createStore(
    Redux.combineReducers({
        modeA,
        modeB
    })
);

// 获取数据
console.log(store.getState().modeA.键)
console.log(store.getState().modeB.键)


// 触发更新
store.dispatch({
    type:'XXX',
    payload:xxx
})

// 监控仓库数据变化
store.subscribe(() => {
    组件state中的键=store.getState().键
})

2.高阶组件

1.高阶函数

参数为函数或者返回函数函数,例如forEach、map、filter

2.高阶组件

概念本身是一个函数形参是组件,必须返回新组件

好处:减少代码冗余,方便后期维护

场景:网站公共footer

语法

higerFooter.jsx

jsx
import React from "react";

const higherFooter = (OldComponent) => {

    return () => {
        return (
            // 空标签不会占空间
            <>
                <OldComponent />
                <footer style={{ background: "gray" }} >
                    网站footer:xxxxxxx
                </footer>
            </>
        );
    };
};

export default higherFooter;

调用

home.jsx

jsx
import higherFooter from './higherFooter'

const Home=()=>{
	return (
        <h1>主页</h1>
	)
}

export default higherFooter(Home)

3.immutable

1.概括

浅拷贝:减少内存占用,但是容易相对出问题

深拷贝:解决浅拷贝问题,但是增加内存的占用

解决:使用Facebook的immutable技术实现深拷贝

首先有一个immutable对象
然后只要修改了就会返回一个新的immutable对象,它由两部分组成
1、没有改的数据  旧内存地址
2、修改后的数据  新内存地址

2.语法

1.创建immutable对象

  • Immutable.fromJS()方法传入一个普通对象创建一个新immutable对象
js
let obj={
    a:1,
    b:[2,3]
}
let immutable对象 = Immutable.fromJS(obj)

2.修改

js
let 新的immutable对象 = list.setIn([键1,....,键n],数据)

3.修改多个

js
let 新的immutable对象 = list.updateIn([键1,....,键n],回调 当前数据=>当前数据+1)

4.获取

js
let 新的immutable对象 = list.getIn([键1,....,键n])

5.immutable对象转换为普通js对象

js
新的immutable对象.toJS()

4.react-redux

基于redux、context封装

1.相比较redux

redux问题1:层层传 或 每个组件中都需要些../../store

redux问题2:每个组件都要监控

react-redux通过:context解决层层传 或 每个组件都需要导入问题

react-redux通过:高阶组件将store里面的数据注入props中

2.语法

1.安装redux、react-redux

shell
yarn add redux react-redux

2.修改入口文件

src/index.js

jsx
import { createRoot } from "react-dom/client";
// 1.导入 Provider
import { Provider } from "react-redux";
// 2.导入store
import store from './store'

import App from "./App";
const root = createRoot(document.querySelector("#root"));

// 3.Provider包裹根组件
root.render(
    <Provider store={store}>
        <App />
    </Provider>
);

3.定义store

src/store/index.ts

ts
import {legacy_createStore as createStore,combineReducers} from 'redux'
import test from '../pages/test/store'

export default createStore(
    combineReducers({
        test,
    })
)

4.定义reducer

src/pages/test/store/index.ts

ts
const initData={
    num:11
}

const reducer=(state=initData,action)=>{
    // 深拷贝
    state=JSON.parse(JSON.stringify(state))
    switch (action.type) {
        case 'ADD':
            state.num++
            break;
        default:
            break;
    }
    return state
}

export default reducer

5.组件中

  • react-redux导入connect

  • 导出组件时调用connect方法,并传入mapStateToPropsmapDispatchToProps两个参数

  • 定义mapStateToPropsstate作为参数

    • mapStateToProps用来定义数据
    • mapStateToPropsreturn的数据会被注入到当前组件的props中
    • 可以通过state.模块名.键获取store中state里的数据
  • 定义mapDispatchToPropsdispatch作为参数

    • mapDispatchToProps用来定义方法
    • mapDispatchToPropsreturn的方法同样会被注入到当前组件的props中

src/pages/test//index.jsx

jsx
import {connect} from 'react-redux'

const 组件名=()=>{
    const {a,data,handleAdd}=this.props
    return(
    	<ul>
        	<li>{a}</li>
            <li>
                {num}
                <button onClick={()=>handleAdd()}>触发store中事件</button>
            </li>
            
        </ul>
    )
}

// 将state放到props属性中
const mapStateToProps = state => {
    return {
        a:11,
        num:state.test.num
    } 
}
// 将dispatch封装成方法放到props属性中
const mapDispatchToProps = dispatch => {
    return {
		handleAdd:()=>{
            dispatch({
                type:'ADD'
            })
        }
    }
}

// 1-从react-redux导出connect
// 2-调用connect传递两个实参都是函数类型 这两个函数的返回值  会注入到当前组件的props上  
// 3-组件中通过属性使用模型数据
export default connect(mapStateToProps, mapDispatchToProps)(组件名)

5.redux-immutable

明确:redux-immutable是基于immutable封装让你在redux中使用immutable

需求:将之前写的购物车手动深拷贝改为immutable深拷贝提升性能

1.配置(仅一次)

1.下载安装

bash
yarn add immutable redux-immutable

2.配置store

注意:combineReducers换成从redux-immutable导入

src/store/index.js

js
// 导入模块
import { createStore } from "redux";
import { combineReducers } from "redux-immutable"; // 改
import { composeWithDevTools } from "redux-devtools-extension";

// 导入管理员reducer
import carts from "../pages/carts/store";
import orders from "../pages/orders/store";

// 导出仓库
export default createStore(
  combineReducers({
    carts,
    orders,
  }),
  composeWithDevTools()
);

3.配置reducer

  • immutable导入fromJS
  • 数据对象用fromJS()包裹
  • 修改数据用updateIn()
js
// 导入模块
import { fromJS } from "immutable";
// 定义reducer
let initData = fromJS({
  a: 1,
  carts: [
    { id: 1, title: "商品1", num: 1, price: 1.11 },
    { id: 2, title: "商品2", num: 2, price: 2.22 },
    { id: 3, title: "商品3", num: 3, price: 3.33 },
  ],
});
// 纯函数 就是里面不要写任何副作用代码(副作用:异步请求、H5存储等)
const reducer = (state = initData, action) => {
  // 深拷贝:
  // let temp = JSON.parse(JSON.stringify(state));

  switch (action.type) {
    case "ADD":
      // temp.carts[action.payload].num++;
      return state.updateIn(["carts", action.payload, "num"], (val) => val + 1);
      break;
    case "DEL":
      // temp.carts[action.payload].num--;
      return state.updateIn(["carts", action.payload, "num"], (val) => val - 1);
    // break;
    default:
      break;
  }
  // return temp;
  return state;
};

export default reducer;

4.组件中使用

  • toJS()immutable对象转换成普通对象使用
jsx
// 将state放到props属性中
const mapStateToProps = state => {
    // console.log(state) //就是仓库数据
    return {
        carts: state.toJS().carts.datas
    } 
}
// 将dispatch封装成方法放到props属性中
const mapDispatchToProps = dispatch => {
    return {
        add: (index) => dispatch({type:'INCRMENT', payload: index}),
        del: (index) => dispatch({type:'DECRMENT', payload: index}),
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(Index)

6.中间件

概念

用来处理中间逻辑的称为中间件

场景

判断用户是否登录

1.redux-logger

用来打印变化的数据

下载

yarn add redux-logger

配置

src/store/index.js

  • redux-logger导入logger
  • 导出createStore时,传入第二个参数applyMiddleware(thunk, logger)
js
// 导入模块
import { createStore, applyMiddleware } from "redux";
import { combineReducers } from "redux-immutable";
import thunk from "redux-thunk";
import logger from "redux-logger";

// 导入管理员reducer
import carts from "../pages/carts/store";

// 导出仓库
export default createStore(
  combineReducers({
    carts,
  }),
  applyMiddleware(thunk, logger)
);

2.redux-thunk

redux-thunk是redux一个中间件,用于解决异步问题

下载

yarn add redux-thunk

配置

js
// import {createStore, combineReducers} from 'redux' 
// import {createStore} from 'redux'                // 更新		
import {createStore, applyMiddleware} from 'redux'  // 更新2(本次1)
import thunk from 'redux-thunk'		                // 新增.(本次2)
// 原因:reducer里面的数据使用了immutable语法
// 以前:从redux导出的combineReducers能力不够
import {combineReducers} from 'redux-immutable';    // 新增,
import {composeWithDevTools} from "redux-devtools-extension"

// 2. 导入reducers(实战:1都放在src/store目录下 学习,2放到pages目录中 项目)
import carts from './carts/index'
import orders from './orders/index'

// 3. 合并导出
// export default createStore(cartReducer)
// export default createStore(cartReducer, composeWithDevTools())
export default createStore(
    combineReducers({
        carts,
        orders,
	}),
    composeWithDevTools(applyMiddleware(thunk))
) // 更新(本次3)

使用

js
export const postCartAction = index => {
    return dispatch => {
        // 异步请求代码
        setTimeout(() => { // 异步请求axios、fetch都是会发异步请求
            dispatch({ 
                 type:'INCRMENT', 
                 payload: index
            }) 
        }, 2000)
    }
}

3.redux-saga

安装

shell
yarn add redux-saga

redux-saga 是 redux 一个中间件,用于解决异步问题

store/index.js

js
import { createStore, applyMiddleware } from "redux";
import { combineReducers } from "redux-immutable";

import thunk from "redux-thunk";
import createSagaMiddleware from "redux-saga"; // 改1.1

import carts from "../pages/carts/store";

import rootSagas from "./sagas"; // 改2.1

const sagaMiddleware = createSagaMiddleware(); // 改1.2
export default createStore(
  combineReducers({
    carts,
  }),
  applyMiddleware(thunk, sagaMiddleware) // 改1.3
);

sagaMiddleware.run(rootSagas); // 改2.2

store/sagas.js 后期所有的子saga都得导入注册

js
import { takeEvery } from "redux-saga/effects";

// 注入
import cartsSaga from "../pages/carts/store/saga";

/* eslint require-yield:0 */
const example = {
  namespace: "example",
  // 副作用:写异步请求
  effects: {
    // 获取话题列表
    *helloAsync({ payload }) {
      console.log("hello saga");
    },
    // *helloAsync({ payload }) {
    //   console.log("hello saga");
    //   // const res = yield call(api目录下方法名切记不能加小括号, 接口参数);
    //   // yield put({ type: '动作',  payload: '参数' });
    //  咱们这:用来触发 redux模块化里面reducer
    //  后期阿里项目中:dva 用来namespace、effects同级reducer
    // },
  },
};

export default function* rootSagas() {
  // 后期所有的saga代码都必须在这边注册下
  yield takeEvery(
    `${example.namespace}/helloAsync`,
    example.effects.helloAsync
  );
  yield takeEvery(
    `${cartsSaga.namespace}/addAsync`,
    cartsSaga.effects.addAsync
  );
}

触发example

dispatch({ type: 'namespace名称/effects名称', payload: 参数 })

7.redux-toolkit

1.配置store

src/store/index.ts

ts
import { configureStore } from "@reduxjs/toolkit";
import { useDispatch } from "react-redux";

// 导入模块
import test1 from "../pages/test1/store";

const store = configureStore({
  reducer: {
    test1,
  },
});

// 额外
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;

export default store;

2.定义数据

  • name:模块名
  • initialState:定义数据
  • reducers:定义方法,里面的方法具有两个参数:state,action
    • state:通过state.键获取数据
    • action:可通过action.payload获取传过来的参数

src/pages/test1/store/index.ts

ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

const slice = createSlice({
  // 命名空间作用
  // 获取 =》 state.模块名.initialState键
  // 更新 =》 dispatch({type: '模块名/reducers中的方法名'}) 或 dispatch(incr(参数))
  name: "test1",
  initialState: {
    count: 1,
  } as Test1.State,
  reducers: {
    incr(state, action: PayloadAction<number>) {
      state.count += action.payload;
    },
  },
});

// 导出方法
export const { incr } = slice.actions; // actions结构reducers数据

export default slice.reducer;

3.入口文件注册store

src/index.tsx

tsx
import { createRoot } from "react-dom/client";
import App from "./App";

// 导入store
import { Provider } from "react-redux";
import store from "./store";

const root = createRoot(document.querySelector("#root") as HTMLElement);

// 渲染
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

4.同步数据

1.导入useSelectoruseDispatch

ts
import {useSelector, useDispatch} from 'react-redux'

2.获取数据

ts
const = useSelector(state=>state.模块名.键)

3.调用方法

ts
const dispatch = useDispatch()

dispatch({
	type: 'test1/incr'
	payload: 2
})

例子

tsx
import { FC } from "react";
import { RootState } from "../../store";
import { incr } from "./store";
import { useSelector, useDispatch } from "react-redux";

interface Props {}
const Test1: FC<Props> = () => {
    const count = useSelector((state: RootState) => state.test1.count);
    const dispatch = useDispatch();

    return (
        <fieldset>
            <legend>Test1</legend>
            <h1>count: {count}</h1>
            <button onClick={() => dispatch({ type: "test1/incr", payload: 1 })}>+1</button>
            <button onClick={() => dispatch({ type: "test1/incr", payload: 2 })}>+2</button>
            <button onClick={() => dispatch(incr(3))}>+3 直接调用</button>
        </fieldset>
    );
};

export default Test1;

5.异步任务

src/pages/test1/store/index.ts

ts
// # 异步请求
// 1-创建异步任务:createAsyncThunk方法
// 2-组件调用
// - 2.1 组件直接保存数据
// - 2.2 通过仓库 :配置额外的reducer 也就是 extraReducers 这键 来保存数据
import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit";

// 1-定义异步任务
// export const 功能名Async = createAsyncThunk("当前模块名/当前异步请求方法名", async () => {
export const countAsync = createAsyncThunk("test1/countAsync", async () => {
  const res = await fetch("https://www.baidu.com/sugrec?prod=pc&wd=htm").then(
    (res) => res.json()
  );
  return res;
});

const slice = createSlice({
  // 命名空间作用
  // 获取 =》 state.模块名.initialState键
  // 更新 =》 dispatch({type: '模块名/reducers中的方法名'}) 或 dispatch(incr(参数))
  name: "test1",
  initialState: {
    count: 1,
    baiduLoading: false,
    baidu: [],
  } as Test1.State,
  reducers: {
    incr(state, action: PayloadAction<number>) {
      state.count += action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(countAsync.pending, (state) => {
        state.baiduLoading = true;
      })
      .addCase(countAsync.fulfilled, (state, action) => {
        state.baiduLoading = false;
        state.baidu = action.payload.g;
        // console.log("监控countAsync成功了!!!", action.payload.g);
      })
      .addCase(countAsync.rejected, (state) => {
        state.baiduLoading = false;
      });
  },
});

export const { incr } = slice.actions; // actions结构reducers数据

export default slice.reducer;

types.d.ts

ts
declare namespace Test1 {
  interface BaiduItem {
    q: string;
    [key: stirng]: any;
  }
  interface State {
    count: number;
    baiduLoading: boolean;
    baidu: BaiduItem[];
  }
}

组件内调用

tsx
import { FC, useEffect } from "react";
import { RootState, useAppDispatch } from "../../store";
// import { useSelector, useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { incr, countAsync } from "./store";

interface Props {}
const Test1: FC<Props> = () => {
  // const count = useSelector((state: Test1.State) => {
  // const count = useSelector((state: { test1: Test1.State }) => {
  //   return state.test1.count;
  // });
  const count = useSelector((state: RootState) => state.test1.count);
  const baiduLoading = useSelector(
    (state: RootState) => state.test1.baiduLoading
  );
  const baidu = useSelector((state: RootState) => state.test1.baidu);
  // const dispatch = useDispatch();  // 默认dispatch不可以执行异步任务 除非你写jsx代码 现在是tsx代码
  const dispatch = useAppDispatch();

  // 触发异步任务
  useEffect(() => {
    // 请求已发送 =》分析f12发送请求了
    // dispatch(countAsync());
    // 拿数据方法1
    // dispatch(countAsync()).then((res) => {
    //   console.log("有接口数据:", res);
    //   console.log("有接口数据-payload:", res.payload.g); // 保存到useState中去展示
    // });
    // 拿数据方法2 - 还是得改仓库
    dispatch(countAsync());
  }, [dispatch]);

  return (
    <fieldset>
      <legend>Test1</legend>

      <h1>count: {count} </h1>
      <button
        onClick={() =>
          dispatch({
            type: "test1/incr",
            payload: 1,
          })
        }
      >
        count+1
      </button>
      <button
        onClick={() =>
          dispatch({
            type: "test1/incr",
            payload: 2,
          })
        }
      >
        count+2
      </button>
      <button onClick={() => dispatch(incr(3))}>count+3</button>

      <h1>度娘数据</h1>
      <ul>
        {baiduLoading ? (
          <li>
            <h1>loading....</h1>
          </li>
        ) : (
          baidu.map((item: Test1.BaiduItem, i: number) => {
            return <li key={i}>{item.q}</li>;
          })
        )}
      </ul>
    </fieldset>
  );
};

export default Test1;

MIT Licensed