手写redux和applyMiddleware中间件react示例

目录

一 核心代码

1.reducer

2.store.js

二 关于context API的使用

1. MyContext

2.  createContext

3. ContextProvider

4. connect

三 组件验证效果

1. Todo

2. TodoList

3.TodoItem

4.TodoInput

5. App组件引入Todo组件


一 核心代码

1.reducer

// 新增列表数据和改变数组数据
// 将业务逻辑拆分到一个单独文件中,方便进行状态管理
import _ from 'lodash';

export interface StateProps {
    id: number;
    text: string;
    isFinished: boolean;
  }
  export interface ActionProps {
    type: string;
    [key: string]: any;
  }
  
  interface IStateObjectProps {
    pickerArr: StateProps[];
    filterTag: 'SHOW_ALL'|'SHOW_FINISHED'|'SHOW_NOT_FINISH';
    dispatch: any;
  }
const reducer = (state: IStateObjectProps, action: ActionProps) => {
    console.log(state, action, 'reducer11111');
    const pickerArr0 = _.get(state, 'pickerArr')||[];
    switch (_.get(action, 'type')) {
      case "ADD":
        return {
            ...state,
            pickerArr: [...pickerArr0, _.get(action, 'todo')]
        };
      case "CHANGESTATUS":
        const pickerArr = _.map(pickerArr0, (item) => {
          if (item.id === action.id) {
            return Object.assign({}, item, { isFinished: !_.get(item, 'isFinished') });
          }
          return item;
        })||[];
        return {
            ...state,
            pickerArr,
        }
      case 'SET_VISIBILITY_FILTER': 
        const filterTag = action.filterTag;
        return {
            ...state,
            filterTag,
        };
      default:
        return state || {};
    }
  };

  export default reducer

2.store.js

import React from 'react';
import reducer from './reducer'

// compose执行顺序就是从后往前
const compose = (...funcs) => {
  if (funcs.length === 0) return arg => arg
  return funcs.reduce((v, cur) => (...args) => (v(cur(...args))))
}

function applyMiddleware(...args) {
  // 将中间件们变成一个数组
  const middlewares = Array.from({ length: args.length }, (_, _key) => args[_key]);
  return function (createStore) {
    return function () {
      const store = createStore.apply(void 0, arguments);
      const middlewareAPI = {
        getState: store.getState,
        dispatch: store.dispatch,
      };
      // map遍历中间件, 执行监听器函数, 形成新数组
      const chain = middlewares.map((middleware) => middleware(middlewareAPI));
      // 展开中间件,调整执行顺序,并传入store.dispatch
      const dispatch = compose(...chain)(store.dispatch);
      // 返回需要的存储数据(将dispatch合并进store对象)
      return {
        ...store,
        dispatch,
      };
    };
  };
}

function legacy_createStore(reducer, preloadedState) {
  let state = preloadedState || null;
  const listeners = [];
  const subscribe = (fn) => listeners.push(fn);
  const getState = () => state;
  const dispatch = (action) => {
    const state1 = reducer(state, action);
    state = state1
    // 因为是在获取到最新的state的值之后有执行的监听回调, 所以使用store.subscribe可以监听到最新的state的值!!!
    listeners.forEach((fn) => fn());
    return state
  }

  return { getState, dispatch, subscribe }
}

function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function') {
    return preloadedState(legacy_createStore)(reducer)
  }
  if (typeof enhancer === 'function') {
    return enhancer(legacy_createStore)(reducer, preloadedState)
  }
  return legacy_createStore(reducer, preloadedState)
}

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument)
    }
    return next(action)
  }
}

const thunk = createThunkMiddleware('xxxxxx');
const store = applyMiddleware(thunk)(createStore)(reducer);
// 或者
// const store = createStore(reducer, applyMiddleware(thunk));
// 或者
// const store = createStore(reducer);

console.log(store, 'oldState======')
store.subscribe(() => {
  const newState = store.getState()
  // 数据可能变化,需要监听最新的
  console.log(newState, 'newState====');
})

export default store;

二 关于context API的使用

1. MyContext

import React from "react";

const MyContext = React.createContext({});

export default MyContext

2.  createContext

import React, {ReactNode, memo} from "react";

// 已经使用了React.createContext, 这个可以忽略, 只是为了展示createContext功能的简单代码
const createContext = ({}) => {
  let value = {};

  const Provider = memo((props: {
    children: ReactNode;
    value:{ 
      dispatch: (arg1:any)=>void; 
      getState:() => any;
    };
  }) => {
    value = props.value;

    return <>{props.children}</>;
  });

  const Consumer = memo(({ children }: { children: any }) => {
    return <>{typeof children === "function" ? children(value) : children}</>;
  });

  return { Provider, Consumer };
};

export default createContext;

3. ContextProvider

import React, { useState } from "react";
import MyContext from "./MyContext";
import _ from "lodash";
import store from "./store";

const useReducer = (state0, dispatch0) => {
  const [state, dispatch] = useState(state0);
  const dispatch1 = (action) => {
    dispatch0(action);
    dispatch(store.getState());
  };
  return [state, dispatch1]
}

// 父组件
const ContextProvider = ({ children }) => {
  const [state, dispatch] = useReducer(store.getState(), store.dispatch);
  
  return <MyContext.Provider value={{
    getState: () => state,
    dispatch
  }}>{children}</MyContext.Provider>;
};

export default ContextProvider;

4. connect

import React from "react";
import MyContext from "./MyContext";
import _ from "lodash";

// 模拟react-redux的 connect高阶函数
const connect = (mapStateToProps, mapDispatchToProps) => {
  return (Component) => (props) =>
    wrapper(Component, { mapStateToProps, mapDispatchToProps, ...props });
};

const wrapper = (Comp, props) => {
  const { mapStateToProps, mapDispatchToProps, ...rest } = props;

  return (
    <MyContext.Consumer>
      {(store) => {
        const dispatchs = mapDispatchToProps(_.get(store, 'dispatch'));
        let states1 = mapStateToProps(_.get(store, 'getState') ? _.get(store, 'getState')(): {});

        return <Comp {...{ ...states1, ...dispatchs, ...rest }} />;
      }}
    </MyContext.Consumer>
  );
};

export default connect;

三 组件验证效果

1. Todo

import React from "react";
import TodoInput from "./TodoInput";
import TodoList from "./TodoList";
import ContextProvider from "./ContextProvider";

// 父组件
const Todo = () => {
  return (
    <ContextProvider>
      <TodoInput />
      <TodoList />
    </ContextProvider>
  );
};
export default Todo;

2. TodoList

import React, { useEffect } from "react";
import TodoItem from "./TodoItem";
import _ from "lodash";
import connect from "./connect";
import { mapStateTotProps } from "./mapStateToProps";
import { mapDispatchToProps } from "./mapDispatchToProps";
import styles from './TodoList.scss'

const TodoList = (props) => {
  const { todoList } = props;
  console.log(styles, 'TodoList-styles', props)

  return (
    <>
      <p className={styles.title}>checckbox-list: </p>
      <div className="todo-list">
        {_.map(todoList, (item) => (
          <TodoItem key={_.get(item, "id")} todo={item || {}} />
        ))}
      </div>
      <hr />
    </>
  );
};

export default connect(mapStateTotProps, mapDispatchToProps)(TodoList);

3.TodoItem

import _ from 'lodash';
import React from "react";
import connect from './connect';
import { mapStateTotProps } from "./mapStateToProps";
import { mapDispatchToProps } from "./mapDispatchToProps";

// 孙子组件
const TodoItem = (props: any) => {
    const { todo, changeTodo } = props;
    // 改变事项状态
    const handleChange = () => {
        changeTodo(_.get(todo, 'id'));
    }
    
    return (
        <div className="todo-item">
            <input type="checkbox" checked={todo.isFinished} onChange={handleChange} />
            <span style={{ textDecoration: _.get(todo, 'isFinished') ? 'line-through' : 'none' }}>{todo.text}</span>
        </div>
    )
}

export default connect(mapStateTotProps, mapDispatchToProps)(TodoItem);

4.TodoInput

import React, { useState } from "react";
import connect from './connect';
import { mapStateTotProps } from "./mapStateToProps";
import { mapDispatchToProps } from "./mapDispatchToProps";
import styles from './TodoInput.scss'

// 子组件
const TodoInput = (props) => {
  // console.log(styles, 'styles', props)
  const [text, setText] = useState("");
  const {
    addTodo,
    showAll,
    showFinished,
    showNotFinish,
  } = props;

  const handleChangeText = (e: React.ChangeEvent) => {
    setText((e.target as HTMLInputElement).value);
  };

  const handleAddTodo = () => {
    if (!text) return;
    addTodo({
      id: new Date().getTime(),
      text: text,
      isFinished: false,
    });
    setText("");
  };

  return (
    <div className={styles["todo-input"]}>
      <input
        type="text"
        placeholder="请输入代办事项"
        onChange={handleChangeText}
        value={text}
        className="aaa"
      />
      <button className={styles.btn} onClick={handleAddTodo}>+添加</button>
      <button className={styles.btn} onClick={showAll}>show all</button>
      <button className={styles.btn} onClick={showFinished}>show finished</button>
      <button className={styles.btn} onClick={showNotFinish}>show not finish</button>
    </div>
  );
};

export default connect(mapStateTotProps, mapDispatchToProps)(TodoInput);

5. App组件引入Todo组件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/407651.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LDR6020芯片驱动未来:TYPE-C桌面显示器的新篇章

TYPE-C接口桌面显示器&#xff0c;与传统显示器截然不同。它不仅在视频传输方面表现出色&#xff0c;还融入了创新的充电功能。利用显示器的DC电源&#xff0c;可以轻松转换成PD协议&#xff0c;为笔记本、任天堂等HOST设备提供稳定的充电服务。 兼容性&#xff1a;连接无忧 …

解决SpringAMQP工作队列模型程序报错:WARN 48068:Failed to declare queue: simple.queue

这里写目录标题 1.运行环境2.报错信息3.解决方案4.查看解决之后的效果 1.运行环境 使用docker运行了RabbitMQ的服务器&#xff1a; 在idea中导入springAMQP的jar包&#xff0c;分别编写了子模块生产者publisher&#xff0c;消费者consumer&#xff1a; 1.在publisher中运行测试…

element ui 虚拟滚动 滚动到底部加载下一页

引入element ui的InfiniteScroll 无限滚动插件 <div class"news-left"><div class"infinite-list-wrapper" style"overflow:auto;height: 70vh"><ul class"list" v-infinite-scroll"load"><li :class&…

【mysql】1000w数据量的分页查询SQL,如何优化提升性能?

文章目录 优化场景特别注意&#xff01;&#xff01;&#xff01;有前提&#xff0c;谨慎使用 优化场景 当表数据量非常大时&#xff0c;需要进行分页查询如果慢的时候&#xff0c;可以考虑优化下。 假设一页展示10条&#xff0c;查询第10w条后面的数据时候变慢了… 优化思路&…

Java智慧工地云综合管理平台SaaS源码 助力工地实现精细化管理

目录 智慧工地系统介绍 1、可视化大屏 2、视频监控 3、Wi-Fi安全教育 4、环境监测 5、高支模监测 6、深基坑监测 7、智能水电监测 8、塔机升降安全监测 智慧工地系统功能模块 1、基础数据管理 2、考勤管理 3、安全隐患管理 4、视频监控 5、塔吊监控 6、升降机监…

内容安全补充

第十一天 密码学 近现代加密算法 古典加密技术 --- 算法保密原则 近现代加密技术 --- 算法公开&#xff0c;密钥保密 对称加密算法&#xff0c;非对称加密算法 对称加密 --- 加密和解密的过程中使用的是同一把密钥。 所以&#xff0c;对称加密所使用的算法一定是一种双向…

【Qt】鼠标拖拽修改控件尺寸---八个方位修改

前提 在开发一个类似qdesiger的项目中 使用QGraphicsProxyWidget将Qt基础控件作为item放在场景视图中显示和编辑 创建自定义类继承QGraphicsProxyWidget&#xff0c;管理控件 成员变量 有控件的xywh等&#xff0c;其中x、y坐标存储是基于最底层widgetitem的 坐标系 x轴以右为正…

15-36V降压充电光伏MPPT充电方案

1.MPPT原理--简介 MPPT&#xff0c;全称为Maximum Power Point Tracking&#xff0c;即最大功点跟踪&#xff0c;它是一种通过调节电气模块的工作状态&#xff0c;使光伏板能够输出更多电能的电气系统能够将太阳能电池板发出的直流电有效地贮存在蓄电池中&#xff0c;可有效地…

Mac怎么运行赛博朋克2077,使用Game Porting Toolkit

Game Porting Toolkit通过转译的方式&#xff0c;将Direct3D指令翻译成Metal指令&#xff0c;让不少Windows游戏都能够在Apple Silicon Mac上成功运行。作为一款开发者工具&#xff0c;运行起来自然有不少的坑&#xff0c;本文手把手带你在M芯片Mac上运行赛博朋克2077。 CrossO…

数据库期末简答题速成-救命专用

简答题&#xff1a; 第 1 章 绪论 DBMS 的主要功能有哪些&#xff1f; 答&#xff1a;数据库管理系统&#xff08;DBMS&#xff09;是位于操作系统与用户之间的一个数据管理软件&#xff0c;它主要功能包括以下几个方面&#xff1a; 数据定义功能 DBMS提供数据描述语言&…

(响应数据)学习SpringMVC的第三天

响应数据 一 . 传统同步业务数据响应 1.1 请求资源转发与请求资源重定向的区别 请求资源转发时,froward:可不写 二 . 前后端分离异步方式 回写json格式的字符串 1 用RestController代替Controller与 ResponseBody 2 . 直接返回user对象实体 , 即可向 前端ajax 返回json字…

day02_java基础_变量_数据类型等

零、今日内容 1 HelloWorld程序 2 idea使用 3 变量 4 数据类型 5 String 一、复习 班规班纪。。。。。 安装jdk JDK 是开发工具 JRE 是运行代码 JDK包含JRE 配置环境变量 二、HelloWorld程序 前提&#xff1a;JDK已经安装配置完毕&#xff0c;有了这些环境就敲代码 代码…

Protocol Buffers v21.12 安装 ( linux 系统 )

下载 Protocol Buffers v21.12 Protocol Buffers v21.12 解压 tar zxvf protobuf-cpp-3.21.12.tar.gz执行 进入解压目录&#xff0c;执行下面configure可执行程序&#xff0c;目的是监测安装环境&#xff0c;生成makefile ./configure执行完后可以检查是否生成makefile文件 构…

kaggle网站简单介绍

Kaggle 是一个面向数据科学和机器学习爱好者的在线平台&#xff0c;它提供了一个用于数据科学竞赛、数据集分享和模型训练的环境。这个平台由 Kaggle Inc. 运营&#xff0c;Kaggle Inc. 是一家位于美国加州旧金山的人工智能公司。 Kaggle 最有特色的功能之一是举办各种数据科学…

MongoDB实战 – 用Python访问MongoDB数据库

MongoDB实战 – 用Python访问MongoDB数据库 MongoDB in Action – Access MongoDB Databases with Python By JacksonML Python语言功能强大众所周知&#xff0c;在数据库管理领域也无所不能。MongoDB是文档数据库&#xff0c;属于NoSQL数据库的一种&#xff0c;在业界也非常…

Java实现就医保险管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

多层的二叉树结构如何快速写出其前序、中序、后序。

问题描述&#xff1a;多层的二叉树结构如何快速写出其前序、中序、后序。 问题解答&#xff1a;从顶部的二叉树&#xff0c;依次往下写&#xff0c;先写出第一层的二叉树&#xff0c;然后再写第二层的二叉树。当然按照的规则还是前序根左右&#xff0c;中序左根右&#xff0c;后…

Shiro 1.2.4反序列化漏洞

一、shiro描述 Apache Shiro是一个强大且易用的Java安全框架&#xff0c;执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API&#xff0c;可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序 二、漏洞原理 AES加密的密钥Key被硬…

K线实战分析系列之六:启明星——空方力量减弱信号

KK线实战分析系列之六&#xff1a;启明星——空方力量减弱信号 一、星线二、多种反转形态三、启明星形态四、启明星形态的总结 一、星线 星线在单根K线形态上是属于纺锤线&#xff0c;之所以被称为星线&#xff0c;主要是因为它在行情当中的相对位置&#xff0c;区别于其他纺锤…

postman测试上传文件、导出excel的方法

按照如下操作步骤执行就可以了&#xff1a; 1、PostMan测试接口实现上传文件 第一步&#xff1a; 打开postman&#xff0c;将上传方式改为POST&#xff0c;再点击下【Body】 第二步&#xff1a; 然后&#xff0c;我们点击里面的【form-data】选项(如图所示)。 第三步&#xff…