手写redux和applyMiddleware中间件react示例

news2024/10/5 21:14:17

目录

一 核心代码

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组件

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot197基于springboot的毕业设计系统的开发

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的毕业设计系统的开发 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 …

餐饮管理系统的设计与实现

餐饮管理系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

金融知识分享系列之:五日线

金融知识分享系列之&#xff1a;五日线 一、股票均线二、五日线三、五日线加量能三、五日线案例四、五日线案例五、五日线案例六、五日线案例七、五日线案例八、五日线案例 一、股票均线 股票均线是一种用于平滑股票价格的指标。它是根据一段时间内的股票价格计算得出的平均值…

只需三步即可更改centos7系统语言,centos7系统语言更换,centos7系统中文互换

只需三步即可更改centos7系统语言,centos7系统语言更换,centos7系统中文互换 操作系统&#xff1a;centOS7.8 64位 ssh登录工具:FinalShell FinalShell可以点此下载 先查看系统的默认语言 locale #zh_CN 中文如何验证是中文&#xff0c;可以使用umtui来验证 umtui是一款…

5 buuctf解题

命令执行 [BJDCTF2020]EasySearch1 打开题目 尝试弱口令&#xff0c;发现没有用 扫描一下后台&#xff0c;最后用御剑扫描到了index.php.swp 访问一下得到源码 源码如下 <?phpob_start();function get_hash(){$chars ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu…

浅析Linux设备驱动:DMA内存映射

文章目录 概述DMA与Cache一致性DMA映射类型一致性DMA映射dma_alloc_coherent 流式DMA映射dma_map_single数据同步操作dma_direct_sync_single_for_cpudma_direct_sync_single_for_device 相关参考 概述 现代计算机系统中&#xff0c;CPU访问内存需要经过Cache&#xff0c;但外…

Javaweb之SpringBootWeb案例之配置优先级的详细解析

1. 配置优先级 在我们前面的课程当中&#xff0c;我们已经讲解了SpringBoot项目当中支持的三类配置文件&#xff1a; application.properties application.yml application.yaml 在SpringBoot项目当中&#xff0c;我们要想配置一个属性&#xff0c;可以通过这三种方式当中…

Android LinearLayout 如何让子元素靠下居中对齐 center bottom

Android LinearLayout 如何让子元素靠下居中对齐 center bottom 首先你需要知道两个知识点&#xff1a; android:layout_gravity 指定的是当前元素在父元素中的位置android:gravity 指定的是当前元素子元素的排布位置 比如&#xff1a; 有这么一个布局&#xff0c;我需要让…

IDEA创建java项目

1. 创建单个项目 1.1 点击New Project 刚安装好会进入下面的创建页面&#xff0c;选择直接New Project创建新项目。 如果后续打开IDEA&#xff0c;并且上次的项目存在&#xff0c;则会打默认开上次的项目&#xff0c;此时可以选择File -> New->Project创建新项目。 …

《TCP/IP详解 卷一》第4章 地址解析协议ARP

目录 4.1 引言 4.2 一个例子 4.3 ARP缓存 4.4 ARP帧格式 4.5 ARP例子 4.6 ARP缓存超时 4.7 代理ARP 4.8 免费ARP和地址冲突检测 4.9 ARP命令 4.10 使用ARP设置嵌入式设备IPv4地址 4.11 与ARP相关攻击 4.12 总结 4.1 引言 地址解析&#xff1a; IPv4&#xff1a;AR…

fpga_RGB模型与硬件加速思维

一 RGB模型 人眼之所以可以看到各种颜色的光&#xff0c;主要是红绿蓝三种感光细胞综合感觉的结果&#xff0c;而红绿蓝三色被称为三原色。 饱和度均为100%的RGB能组合成8种颜色&#xff0c;计算机处理的BMP图片为24bit的位图&#xff0c;即每一通道的颜色可以组合为2的8次方&a…

什么是智慧公厕?智慧公厕改善城市环境,提升居民生活质量

智慧公厕是指通过信息化、数字化和智慧化技术手段&#xff0c;对公共厕所进行高效管理和服务。它利用先进的科技手段&#xff0c;提升城市环境、改善居民生活质量&#xff0c;成为现代城市建设的重要组成部分。下面将以智慧公厕源头厂家广州中期科技有限公司&#xff0c;大量精…

Gitflow:一种依据 Git 构建的分支管理工作流程模式

文章目录 前言Gitflow 背景Gitflow 中的分支模型Gitflow 的版本号管理简单模拟 Gitflow 工作流 前言 Gitflow 工作流是一种版本控制流程&#xff0c;主要适用于较大规模的团队。这个流程在团队中进行合作时可以避免冲突&#xff0c;并能快速地完成项目&#xff0c;因此在很多软…

Unity(第三部)新手绘制地形

1、创建地形 游戏对象3d对象地形 2、功能 1、 红框内按键为创建相邻地形、点击后相近地形会呈现高亮框、点击高亮区域可以快速创建地形 每块地形面积是1km*1km 2、第二个按钮是修改地形 下面的选择是修改类型 选项含义描述Raise or Lower Terrain升高或降低地形单击左键可…

分布式ID开源框架简单介绍

UidGenerator(百度) 一款基于 Snowflake(雪花算法)的唯一 ID 生成器。 UidGenerator 对 Snowflake(雪花算法)进行了改进&#xff0c;生成的唯一 ID 组成如下&#xff1a; sign(1bit):符号位&#xff08;标识正负&#xff09;&#xff0c;始终为 0&#xff0c;代表生成的 ID…

STM32 TCP实现OTA

芯片&#xff1a;stm32f407 开发平台&#xff1a;stm32cubeide 上位机开发平台&#xff1a;visual studio 2017 1. FLASH分配 将flash划分为四个部分&#xff1a; bootloader: 0x8000000-0x800ffff app1: 0x8010000-0x805ffff app2: …

Qt Android sdk配置报错解决

使用的jdk8总是失败&#xff0c;报错command tools run以及platform sdk等问题。后来主要是设置jdk版本为17&#xff0c;就配置生效了。Android sdk路径可以选用Android Studio自带的&#xff0c;但是也要在Qt中点击“设置SDK”按钮做必要的下载更新等。 编译器这里会自动检测到…

Java设计模式 | 简介

设计模式的重要性&#xff1a; 软件工程中&#xff0c;设计模式&#xff08;design pattern&#xff09;是对软件设计中普遍存在&#xff08;反复出现&#xff09;的各种问题&#xff0c;所提出的解决方案。 这个术语由埃里希 伽玛&#xff08;Erich Gamma&#xff09;等人在1…

每日一题——LeetCode1512.好数对的数目

方法一 暴力循环 var numIdenticalPairs function(nums) {let ans 0;for (let i 0; i < nums.length; i) {for (let j i 1; j < nums.length; j) {if (nums[i] nums[j]) {ans;}}}return ans; }; 消耗时间和内存情况&#xff1a; 方法二&#xff1a;组合计数 var …

【GPTs分享】GPTs分享之consensus

大家好&#xff0c;元宵节快乐&#xff0c;今天给大家分享的GPTs是consensus。consensu号称无需关键字即可搜索2亿文章&#xff0c;而且给出的链接绝对保真&#xff0c;不再是胡编乱造的&#xff0c;而且能够根据指定主题辅助编写论文或者博客。 简介 consensus使用chat.cons…