文章目录
- 1. redux
- 1.1 概述与使用原则
- 1.2工作流程
- 1.2.1 三个核心
 
- 1.3 store
- 1.4 action
- 1.5 reducer
- 1.5.1 合并reducers
 
- 1.6 dispatch getState 及状态更新
 
- 2. react-redux
- 2.1 基本特征
- 2.2 connect()、mapStateToProps
- 2.3 mapDispatchToProps
- 2.4Provider
- 2.5. 中间件,combineReducers函数,redux-devtools
 
- 3. Redux Toolkit
- 3. 1 基本特征与API
- 3.2 createSlice
- 3.3 store 创建
- 3.4 Provide ,connect
- 3.5 Redux Toolkit的异步操作
 
- 4. hooks,useSelector、useDispatch
 
图解
 
 
 
1. redux
npm install redux --save
1.1 概述与使用原则
- 是一个专门用来做状态管理的js库(不是react插件)
- 作用:集中式管理react应用中的多个组件共享的状态
- 使用: 
  - 某个组件的状态,需要让其他组件可以随时拿到(共享)
- 一个组件需要改变另一个组件的状态(通信)
 
- 某个组件的状态,需要让其他组件可以随时拿到
1.2工作流程
redux工作流程图如下:
 
1.2.1 三个核心
- action 
  - 动作的对象
- 包含两个属性 
    - type:属性标识,值为字符串,唯一,必要属性
- data:数据属性,值任意类型,可选属性
- 例子 {type:'CHANGE_NAME', data: {name: 'why'}}
 
 
- reducer 
  - 用于初始化、加工状态
- 加工时,根据旧的state和action,产生新的state的纯函数
- 例子if (type === 'CHANGE_NAME') {return { ...state, name }}
 
store
- 将state、action、reducer联系在一起的对象
- 加工时,根据旧的state和action,产生新的state的纯函数
1.3 store
- 整个文件以modules划分
│  └─ 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));
1.4 action
- 定义action对象中type类型的常量值
// src/store/constants.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
- 创建action,action返回对象,异步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- 延迟的动作不想交给组件本身,想交给action
- 执行异步方法对状态进行操作,但是具体的数据需要靠异步任务返回
 
1.5 reducer
- reducer函数会接到两个参数,分别为:之前的状态(state),动作对象(action)
- 从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只读- 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State:这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;
 
- 唯一修改State的方法一定是触发
-  使用 纯函数来执行修改- 通过reducer将 旧state和 actions联系在一起,并且返回一个新的State:
- 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作;
- 但是所有的reducer都应该是纯函数,不能产生任何的副作用;
 
1.5.1 合并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,
});
1.6 dispatch getState 及状态更新
- 组件通过getState()拿store的数据
- dispatch触发action
- subscribe() 完成视图更新
import 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>  
      )
 }
2. react-redux
2.1 基本特征
- redux需要监听store变化更新视图,利用store.subscribe(() => { this.forceUpdate(); });react-redux不需要监听
- react-redux将组件分为UI组件、容器组件;redux的操作都在容器组件中,
- 单一职责原则;通过connect(mapStateToProps, mapDispatchToProps)(UI)连接容器组件与UI组件;redux没有区分
- UI组件负责UI的呈现,容器组件负责管理数据和逻辑,如果一个组件既有UI又有业务逻辑,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图
  
2.2 connect()、mapStateToProps
- 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>
    );
  }
}
2.3 mapDispatchToProps
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store
 它可以是一个函数,也可以是一个对象
- 如果mapDispatchToProps是一个函数
/ 容器组件
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);
2.4Provider
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} />;
  }
}
- React-Redux 提供Provider组件,可以让容器组件拿到state
- Provider在根组件外面包了一层,这样一来,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(
  // <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  // </React.StrictMode>
);
2.5. 中间件,combineReducers函数,redux-devtools
-  中间件 - 中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码;
- 比如日志记录、调用异步接口、添加代码调试功能等等;
- redux-thunk、- applyMiddleware用于支持异步action,
 
- 中间件的目的是在
-  combineReducers函数- 它也是将我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函数了);
- 在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state;
- 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;
 
-  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)))
3. Redux Toolkit
3. 1 基本特征与API
- Redux Toolkit 也被称为RTK
npm install @reduxjs/toolkit react-redux
- Redux Toolkit的核心API主要是如下几个: 
  - configureStore:包装- createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的- slice reducer,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。
- createSlice:接受- reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。
- createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个- pending/fulfilled/rejected基于该承诺分派动作类型的 thunk
 
3.2 createSlice
通过createSlice创建一个slice ,createSlice主要包含如下几个参数:
- name:用户标记slice的名词, 在之后的redux-devtool中会显示对应的名词;
- initialState:初始化值,第一次初始化时的值;
- reducers:相当于之前的reducer函数- 对象类型,并且可以添加很多的函数;
- 函数类似于redux原来reducer中的一个case语句;
- 函数的参数: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
3.3 store 创建
- 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
3.4 Provide ,connect
- Provide 还是需要提供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>
);
3.5 Redux Toolkit的异步操作
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
})
- 当createAsyncThunk创建出来的action被dispatch时,会存在三种状态 
  - 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
		})
	}
4. hooks,useSelector、useDispatch
- createSlice 创建reducer
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访问/使用state
const {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变更state
 useDispatch接收的是你在createSlice中定义的reducers里的action,比如把increment传给useDispatch即可给state加1。
import {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大神



















