redux 的学习记录

redux 是个状态管理工具,提供了一种良好的结构来管理状态和数据流。不依赖于某个库,可以在vue、react等项目中使用

Redux 的核心概念包括:

  • Store(存储): 一个包含应用状态的对象。整个应用的状态被保存在单一的 store 中。

  • Action(动作): 描述发生了什么的对象。Action 是一个普通的 JavaScript 对象,其中必须包含一个 type 字段用于描述动作的类型。

  • Reducer(处理器): 一个纯函数,接收当前状态和一个 action,并返回一个新的状态。Reducer 定义了状态的变化逻辑。

  • Dispatch(分发): 一个函数,用于将 action 发送到 reducer,从而触发状态的变化。在 Redux 中,通过 store.dispatch(action) 来分发 action。

  • Middleware(中间件): 一个函数,它可以拦截并处理 dispatch 的过程。中间件可以用于执行异步操作、日志记录、错误处理等。(这里最常用的就是redux-thunk,使redux支持异步的dispatch)

一、在node环境中使用redux

0、安装redux

npm i redux

1、创建store(创建一个index.js文件,在这里初始化store)

const { createStore } = require("redux");
const reducer = require("./reducer");
// createStore 有两个参数:
// 1、传入一个reducer: 就是执行action描述的方法
// 2、传入额外的中间件:这个后边在处理异步action中会使用
const store = createStore(reducer);
module.exports=store;

2、创建reducer(reducer.js)

const initalState = { // 初始化对象
    counter: 0
};

function reducer(state = initalState, actions) { 
    switch (actions.type) {
        case "counter_changer": 
        	return { ...state, counter: state.counter + actions.num };
        default: return state;
    }
}
//  这个reducer 将会传给createStore函数。也就是当某个界面发出某个修改store的指令时,
// redux 帮我们执行这个reducer,并执行订阅的回调
module.exports = reducer;

3、创建测试文件(test.js)

const store = require("./index.js");
// store 提供了这个获取state函数,可以得到仓库中state当前的状态
console.log(store.getState()); // { counter: 0}
// store提供了一个订阅方法,当state中值被修改时就会执行回调
// 返回值为一个取消订阅的函数
const unSubscribe = store.subscribe(() => {
	console.log("值被修改了!")
})
// 当我们想修改store 中某个值的状态时,需要用到dispatch这个方法
// dispatch接受一个对象为参数,这个对象就是action,相当于指令的描述
store.dispatch({type: "counter_changer", num: 12});
console.log(store.getState()); // { counter: 12 }
unSubscribe() // 取消订阅

4、actionCreators的封装

在步骤3中的dispatch(action),action可能被多个地方使用,而类型是相同的,只有num不会变。
所以项目中常常有一个叫做actionCreator的函数,传入num,返回action。

const store = require("./index.js");
const number_changer_action = (num) => ({type: "counter_changer", num});
// 这样使用这个action时执行这个函数,返回一个action对象
store.dispatch(number_changer_action(12))

5、异步的处理

问题:我们如果想将一个接口回调拿到的数据加入到action的payload中,需要在actionCreator返回action之前异步进行。但是dispatch 不能立马拿到想要的action。这里使用最多的是redux-thunk中间件,它使我们可以给dispatch传入一个异步函数,帮我们执行回调后修改state的内容。

npm i redux-thunk

对index.js进行改造

const { createStore, applyMiddleware } = require("redux");
const thunk = require("redux-thunk").thunk;
const reducer = require("./reducer");
// createStore 有两个参数:
// 1、传入一个reducer: 就是执行action描述的方法
// 2、传入额外的中间件:这时候将我们redux-thunk中间件就行插入
const store = createStore(reducer, applyMiddleware(thunk));
module.exports=store;

来测试一下支持异步的action生成器

const store = require("./index.js");
const number_changer_action = (num) => ({type: "counter_changer", num});
const number_changer_action_async = () => {
	// 这里我们的action可以是一个函数,在thunk内部,他帮我们执行这个函数,
	// 他会给我们传出两个参数分别是dispatch, getState 
	// 我们通过dispatch调用同步的action,也可以通过getState获取仓库状态
    return (dispatch, getState) => {
        // 模拟一个请求
        new Promise((resolve) => {
            setTimeout(() => {
                resolve(100);
            }, 1000);
        }).then(res => {
            dispatch(number_changer_action(res));
        });
    };
};
// 这样使用这个action时执行这个函数,返回一个action对象
store.dispatch(number_changer_action_async())

redux-thunk 大概原理:

export const reduxThunk = function (store) {
    const _cache = store.dispatch;
    async function newDispatch(action) {
        if (action instanceof Function) {
            return action(newDispatch, store.getState);
        }
        _cache(action);
    };
    store.dispatch = newDispatch;
};

6、分模块

在项目中通常只有一个store,但是可能有不同类型的数据,比如用户界面样式风格、用户登录信息、以及业务方面的数据。这时候可以分成不同的模块,便于管理。
目录结构如下图:
模块化
这时候就会有很多的reducer,我们需要修改index.js文件来将他们合并。

const { createStore, applyMiddleware, combineReducers } = require("redux");
const thunk = require("redux-thunk").thunk;
const { reducer } = require("./counter/reducer");
const combinedReducer = combineReducers({
    counter: reducer
});
// createStore 有两个参数:
// 1、传入一个reducer: 就是执行action描述的方法
// 2、传入额外的中间件:这时候将我们redux-thunk中间件就行插入
const store = createStore(combinedReducer, applyMiddleware(thunk));
module.exports=store;

这样的当我们访问counter模块中的counter字段时,需要再加一层

// 第一个counter为执行combineReducers函数传入的对象中与counter模块对应的键名
// 第二个counter就是state中的counter
store.getState().counter.counter

以上就是redux使用的基本过程了。

使用redux 有三个原则:
  1. 单一数据源(Single Source of Truth):

    • Redux 应用的整个状态(state)被存储在单一的 JavaScript 对象中,称为 “state” 或 “store”。
    • 这个单一数据源使得整个应用的状态可追踪、可预测,并且便于调试。
  2. 状态是只读的(State is Read-Only):

    • Redux 中的状态是只读的,唯一改变状态的方式是通过触发一个 action。
    • 这确保了状态的变化是可控的,且只能通过特定的方式进行。
  3. 通过纯函数进行状态修改(Changes are Made with Pure Functions):

    • 通过编写纯函数的方式来描述状态的变化,这些纯函数被称为 “reducers”。
    • Reducer 接收旧的状态和一个 action 作为参数,返回一个新的状态。纯函数不会改变原始状态,而是创建并返回一个新的状态。
    • 这种通过纯函数进行状态修改的方式提供了可预测性、可测试性和可维护性。
redux 处理数据流程

操作触发dispatch -> dispatch发送action描述 -> redux帮我们执行reducer更新state -> 执行订阅的回调

二、在react中的使用

其实在react 中使用过程跟上述过程基本一致,只是添加了state数据与视图绑定过程。react-redux给我们提供了state与视图绑定的便利操作。

1、在react 项目中安装redux 与 react-redux

npm i redux react-redux

2、使用react-redux 提供的Provider 组件包裹需要共享state数据的组件

这个的Provider 正是用了react 为我们提供的Context特性。

import { PureComponent } from "react";
import Prefile from "./Profile";
import Home from "./Home";
import { Provider } from "react-redux";
import store from "./store";
class App extends PureComponent {
  render() {
    return (
      <div>
      // 这里传入的store 正是我们创建的store对象
        <Provider store={store}>
          <h1>profile:</h1> <Prefile></Prefile>
          <div>=======================</div>
          <h1>home:</h1>
          <Home></Home>
        </Provider>
      </div>
    );
  }
}

export default App;

3、在组件中使用state的数据或者修改数据(connect函数的使用)

import { PureComponent } from "react";
import {
  asyncChangerAction,
  counterChangeAction,
} from "./store/counter/actionCreators";
// react-redux 提供了connect 高阶函数
// 这个函数执行传入两个参数,返回一个高阶组件,我们再将需要使用state数据的组件传入这个高阶组件
// 具体调用在下面
import { connect } from "react-redux";

class Home extends PureComponent {
  render() {
    return (
      <div>
        <div>counter: {this.props.counter}</div>
        <button onClick={() => this.props.changeCounter()}>
          dispatch-async
        </button>
        <button onClick={() => this.props.changeCounter_sync()}>
          dispatch-sync
        </button>
      </div>
    );
  }
}
const mapStateToProps = (state) => {
  return {
  	// 在Home 中可以使用this.props.counter 来访问state中的counter数据
    counter: state.counter.counter,
  };
};
const mapDispatchToProps = (dispatch) => {
  return {
  	// 在Home 中可以使用this.props.changeCounter来执行dispatch
    changeCounter: () => {
      dispatch(asyncChangerAction());
    },
    changeCounter_sync: () => {
      dispatch(counterChangeAction(100));
    },
  };
};
// connect 传入两个参数
// 1、state中的数据与props的绑定
// 2、在组件中需要用到的dispatch与props绑定
// 这时候connect 对上面两个对应关系已经做了缓存,并返回一个高阶组件
// 我们执行高阶组件,将Home 传入,这时对应关系注入到了Home组件的props中。
export default connect(mapStateToProps, mapDispatchToProps)(Home);

三、尝试自己实现一个react-redux

connect.js文件

import { PureComponent } from "react";
import StoreContext from "./storeContext";

function connect(mapStateToProps, mapDispatchToProps) {
    return function (WrapperComponent) {
        class HComponent extends PureComponent {
            constructor(props, context) {
                super(props);
                this.context = context;
                this.state = mapStateToProps(context.getState());
            }
            componentDidMount() {
                this.unSubscribe = this.context.subscribe(() => {
                    this.setState(mapStateToProps(this.context.getState()));
                });
            }
            componentWillUnmount() {
                this.unSubscribe();
            }
            render() {
                return <WrapperComponent {...this.props} {...this.state} {...mapDispatchToProps(this.context.dispatch)}></WrapperComponent>;
            }
        }
        HComponent.contextType = StoreContext;
        return HComponent;
    };
}

export default connect;

StoreContext.js文件

import { createContext } from "react";
export default createContext();

以上就是对我 redux 和react-redux 的学习记录。

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值