• 手写redux的connect方法, 使用了subscribe获取最新数据


    一. 公共方法文件

    1. connect文件

    1. import React, { useState } from "react";
    2. import MyContext from "./MyContext";
    3. import _ from "lodash";
    4. // 模拟react-redux的 connect高阶函数
    5. const connect = (mapStateToProps, mapDispatchToProps) => {
    6. return (Component) => props => wrapper(Component, {mapStateToProps, mapDispatchToProps, ...props});
    7. };
    8. const wrapper = (Comp, props) => {
    9. const {mapStateToProps, mapDispatchToProps, ...rest} = props
    10. return <MyContext.Consumer>
    11. {store => {
    12. const dispatch = _.get(store, 'dispatch');
    13. const dispatchs = mapDispatchToProps(dispatch);
    14. // store.subscribe监听store.getState()获取最新的值
    15. const [states, setStates] = useState({})
    16. store.subscribe(() => {
    17. const state1 = store.getState();
    18. setStates(mapStateToProps(state1))
    19. });
    20. return <Comp {...{...states, ...dispatchs, ...rest}}/>;
    21. }}
    22. </MyContext.Consumer>
    23. }
    24. export default connect;

    2. MyContext文件:

    1. import React from "react";
    2. import createContext from './createContext'
    3. const MyContext = createContext({});
    4. export default MyContext

    3. createContext文件:

    1. import React from "react";
    2. const createContext = ({}) => {
    3. let value = {};
    4. const Provider = (props) => {
    5. value = props.value;
    6. return <>{props.children}</>;
    7. };
    8. const Consumer = ({ children }: { children: any }) => {
    9. return <>{typeof children === "function" ? children(value) : children}</>;
    10. };
    11. return { Provider, Consumer };
    12. };
    13. export default createContext;

    4. reducer文件

    1. // 新增列表数据和改变数组数据
    2. // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    3. import _ from 'lodash';
    4. export interface StateProps {
    5. id: number;
    6. text: string;
    7. isFinished: boolean;
    8. }
    9. export interface ActionProps {
    10. type: string;
    11. [key: string]: any;
    12. }
    13. interface IStateObjectProps {
    14. pickerArr: StateProps[];
    15. filterTag: 'SHOW_ALL'|'SHOW_FINISHED'|'SHOW_NOT_FINISH';
    16. dispatch: any;
    17. }
    18. const reducer = (state: IStateObjectProps, action: ActionProps) => {
    19. console.log(state, action, 'reducer');
    20. const pickerArr0 = _.get(state, 'pickerArr')||[];
    21. switch (action.type) {
    22. case "ADD":
    23. return {
    24. ...state,
    25. pickerArr: [...pickerArr0, _.get(action, 'todo')]
    26. };
    27. case "CHANGESTATUS":
    28. const pickerArr = _.map(pickerArr0, (item) => {
    29. if (item.id === action.id) {
    30. return Object.assign({}, item, { isFinished: !_.get(item, 'isFinished') });
    31. }
    32. return item;
    33. })||[];
    34. return {
    35. ...state,
    36. pickerArr,
    37. }
    38. case 'SET_VISIBILITY_FILTER':
    39. const filterTag = action.filterTag;
    40. return {
    41. ...state,
    42. filterTag,
    43. };
    44. default:
    45. return state || {};
    46. }
    47. };
    48. export default reducer

    5. mapStateToProps文件:

    1. import React from "react";
    2. import _ from "lodash";
    3. import store from "./store";
    4. // 不同类型的 todo 列表
    5. const getVisibleTodos = (todos, filter) => {
    6. switch (filter) {
    7. case "SHOW_ALL": // 全部显示
    8. return todos;
    9. case "SHOW_FINISHED":
    10. return todos.filter((t) => t.isFinished);
    11. case "SHOW_NOT_FINISH":
    12. return todos.filter((t) => !t.isFinished);
    13. default:
    14. return todos;
    15. }
    16. };
    17. export const mapStateTotProps = (state) => {
    18. // console.log(state, 'mapStateTotProps', store)
    19. return {
    20. todoList: getVisibleTodos(_.get(state, 'pickerArr')||[], _.get(state, 'filterTag'))|| [],
    21. }
    22. }

    6. mapDispatchToProps文件

    1. import React from "react";
    2. import _ from "lodash";
    3. import { StateProps } from "./reducer";
    4. export const mapDispatchToProps = (dispatch) => {
    5. // console.log(dispatch, 'mapDispatchToProps============')
    6. // 筛选todo列表
    7. const onFilterTodoList = (filterTag) => {
    8. dispatch({ type: 'SET_VISIBILITY_FILTER', filterTag, });
    9. };
    10. const changeTodo = (id: number) => {
    11. dispatch({ type: "CHANGESTATUS", id: id });
    12. };
    13. // 添加todo
    14. const addTodo = (todo: StateProps) => {
    15. dispatch({ type: "ADD", todo });
    16. };
    17. const showAll = () => onFilterTodoList("SHOW_ALL");
    18. const showFinished = () => onFilterTodoList("SHOW_FINISHED");
    19. const showNotFinish = () => onFilterTodoList("SHOW_NOT_FINISH");
    20. return {
    21. changeTodo,
    22. addTodo,
    23. showAll,
    24. showFinished,
    25. showNotFinish,
    26. };
    27. }

    由mapStateToProps文件和mapDispatchToProps文件可知, 我们需要想办法获取最新的state, 和通用的dispatch方法, 也就是以下所说的store文件里面的默认导出对象:

    7. store文件:

    1. import React from 'react';
    2. import reducer from './reducer'
    3. function createStore(reducer) {
    4. let state = null;
    5. const listeners = [];
    6. const subscribe = (fn) => listeners.push(fn);
    7. const getState = () => state;
    8. const dispatch = (action) => {
    9. const state1 = reducer(state, action);
    10. state = state1
    11. // 因为是在获取到最新的state的值之后有执行的监听回调, 所以使用store.subscribe可以监听到最新的state的值!!!
    12. listeners.forEach((fn) => fn());
    13. return state
    14. }
    15. // dispatch({})
    16. return { getState, dispatch, subscribe, reducer }
    17. }
    18. const store = createStore(reducer)
    19. console.log(store.getState(), 'oldState======')
    20. store.subscribe(() => {
    21. const newState = store.getState()
    22. // 数据可能变化,需要监听最新的
    23. console.log(newState, 'newState====');
    24. })
    25. export default store;

    8. ContextProvider组件:

    1. import React from "react";
    2. import MyContext from "./MyContext";
    3. import store from "./store";
    4. import _ from "lodash";
    5. // 父组件
    6. const ContextProvider = ({ children }) => {
    7. return <MyContext.Provider value={store}>{children}MyContext.Provider>;
    8. };
    9. export default ContextProvider;

    二. 使用公共文件

    1.  TodoInput组件

    1. import React, { useState } from "react";
    2. import "./TodoInput.scss";
    3. import connect from './connect';
    4. import { mapStateTotProps } from "./mapStateToProps";
    5. import { mapDispatchToProps } from "./mapDispatchToProps";
    6. // 子组件
    7. const TodoInput = (props) => {
    8. const [text, setText] = useState("");
    9. const {
    10. addTodo,
    11. showAll,
    12. showFinished,
    13. showNotFinish,
    14. } = props;
    15. const handleChangeText = (e: React.ChangeEvent) => {
    16. setText((e.target as HTMLInputElement).value);
    17. };
    18. const handleAddTodo = () => {
    19. if (!text) return;
    20. addTodo({
    21. id: new Date().getTime(),
    22. text: text,
    23. isFinished: false,
    24. });
    25. setText("");
    26. };
    27. return (
    28. <div className="todo-input">
    29. <input
    30. type="text"
    31. placeholder="请输入代办事项"
    32. onChange={handleChangeText}
    33. value={text}
    34. />
    35. <button onClick={handleAddTodo}>+添加</button>
    36. <button onClick={showAll}>show all</button>
    37. <button onClick={showFinished}>show finished</button>
    38. <button onClick={showNotFinish}>show not finish</button>
    39. </div>
    40. );
    41. };
    42. export default connect(mapStateTotProps, mapDispatchToProps)(TodoInput);

    2. TodoList组件

    1. import React from "react";
    2. import TodoItem from "./TodoItem";
    3. import _ from "lodash";
    4. import connect from "./connect";
    5. import { mapStateTotProps } from "./mapStateToProps";
    6. import { mapDispatchToProps } from "./mapDispatchToProps";
    7. const TodoList = (props) => {
    8. const { todoList } = props;
    9. return (
    10. <>
    11. <p>checckbox-list: </p>
    12. <div className="todo-list">
    13. {_.map(todoList, (item) => (
    14. <TodoItem key={_.get(item, "id")} todo={item || {}} />
    15. ))}
    16. </div>
    17. <hr />
    18. </>
    19. );
    20. };
    21. export default connect(mapStateTotProps, mapDispatchToProps)(TodoList);

    3. TodoItem组件

    1. import _ from 'lodash';
    2. import React from "react";
    3. import connect from './connect';
    4. import { mapStateTotProps } from "./mapStateToProps";
    5. import { mapDispatchToProps } from "./mapDispatchToProps";
    6. // 孙子组件
    7. const TodoItem = (props: any) => {
    8. const { todo, changeTodo } = props;
    9. // 改变事项状态
    10. const handleChange = () => {
    11. changeTodo(_.get(todo, 'id'));
    12. }
    13. return (
    14. <div className="todo-item">
    15. <input type="checkbox" checked={todo.isFinished} onChange={handleChange} />
    16. <span style={{ textDecoration: _.get(todo, 'isFinished') ? 'line-through' : 'none' }}>{todo.text}</span>
    17. </div>
    18. )
    19. }
    20. export default connect(mapStateTotProps, mapDispatchToProps)(TodoItem);

    4. Todo组件:

    1. import React from "react";
    2. import TodoInput from "./TodoInput";
    3. import TodoList from "./TodoList";
    4. // 父组件
    5. const Todo = () => {
    6. return (
    7. <>
    8. <TodoInput />
    9. <TodoList />
    10. </>
    11. );
    12. };
    13. export default Todo;

    5. App组件使用ContextProvider包裹Todo组件

    1. import React from "react";
    2. import Todo from './mockConnectProvider/Todo'
    3. import ContextProvider from './mockConnectProvider/ContextProvider'
    4. const App: React.FC = () => {
    5. return (
    6. <ContextProvider>
    7. <Todo />
    8. </ContextProvider>
    9. );
    10. };
    11. export default App;

    效果图如下:

    三. 使用recoil模拟connect

    1.  App.tsx文件

    1. import React from "react";
    2. import Todo from "./mockConnectProvider/Todo";
    3. import { RecoilRoot } from "recoil";
    4. const App: React.FC = () => {
    5. return (
    6. <RecoilRoot>
    7. <Todo />
    8. </RecoilRoot>
    9. );
    10. };
    11. export default App;

    2. Todo.tsx

    1. import React from "react";
    2. import TodoInput from "./TodoInput";
    3. import TodoList from "./TodoList";
    4. import ContextProvider from "./ContextProvider";
    5. // 父组件
    6. const Todo = () => {
    7. return (
    8. <ContextProvider>
    9. <TodoInput />
    10. <TodoList />
    11. </ContextProvider>
    12. );
    13. };
    14. export default Todo;

    3. connect.tsx

    1. import React from "react";
    2. import MyContext from "./MyContext";
    3. import _ from "lodash";
    4. import { atom, useRecoilState } from "recoil";
    5. const connectState1 = atom({
    6. key: "connect-state1",
    7. default: {},
    8. });
    9. // 模拟react-redux的 connect高阶函数
    10. const connect = (mapStateToProps, mapDispatchToProps) => {
    11. return (Component) => (props) =>
    12. wrapper(Component, { mapStateToProps, mapDispatchToProps, ...props });
    13. };
    14. const useReducer = (store) => {
    15. const [state, setState] = useRecoilState(connectState1);
    16. const dispatch = (action) => {
    17. const state1 = store.dispatch(action);
    18. setState((state0: any) => ({
    19. ...state0,
    20. ...state1,
    21. }));
    22. };
    23. return [state, dispatch];
    24. };
    25. const wrapper = (Comp, props) => {
    26. const { mapStateToProps, mapDispatchToProps, ...rest } = props;
    27. return (
    28. <MyContext.Consumer>
    29. {(store) => {
    30. const [state, dispatch] = useReducer(store);
    31. const dispatchs = mapDispatchToProps(dispatch);
    32. let states1 = mapStateToProps(state);
    33. return <Comp {...{ ...states1, ...dispatchs, ...rest }} />;
    34. }}
    35. </MyContext.Consumer>
    36. );
    37. };
    38. export default connect;

    其他文件直接取一, 二的文件就行

    原理: 

    1) RecoilRoot标签类似redux的Provider标签, 有了它, 方便全局组件通信

    2) useRecoilState钩子可以设置同源下的全局变量, 自然一处修改了, 所有组件都能触发更新

    四. context模拟connect

    1. App.tsx

    1. import React from "react";
    2. import Todo from "./mockConnectProvider/Todo";
    3. const App: React.FC = () => {
    4. return (
    5. <>
    6. <Todo />
    7. </>
    8. );
    9. };
    10. export default App;

    2. Todo.tsx

    1. import React from "react";
    2. import TodoInput from "./TodoInput";
    3. import TodoList from "./TodoList";
    4. import ContextProvider from "./ContextProvider";
    5. // 父组件
    6. const Todo = () => {
    7. return (
    8. <ContextProvider>
    9. <TodoInput />
    10. <TodoList />
    11. </ContextProvider>
    12. );
    13. };
    14. export default Todo;

    3. MyContext文件

    1. import React from "react";
    2. const MyContext = React.createContext({});
    3. export default MyContext

    4. ContextProvider.tsx

    1. import React, { useContext } from "react";
    2. import MyContext from "./MyContext";
    3. import _ from "lodash";
    4. import reducer from "./reducer";
    5. // 父组件
    6. const ContextProvider = ({ children }) => {
    7. const context:any = useContext(MyContext);
    8. const [state, dispatch] = React.useReducer(reducer, context);
    9. return <MyContext.Provider value={{getState:() => state, dispatch}}>{children}</MyContext.Provider>;
    10. };
    11. export default ContextProvider;

    5. connect.tsx

    1. import React from "react";
    2. import MyContext from "./MyContext";
    3. import _ from "lodash";
    4. // 模拟react-redux的 connect高阶函数
    5. const connect = (mapStateToProps, mapDispatchToProps) => {
    6. return (Component) => (props) =>
    7. wrapper(Component, { mapStateToProps, mapDispatchToProps, ...props });
    8. };
    9. const wrapper = (Comp, props) => {
    10. const { mapStateToProps, mapDispatchToProps, ...rest } = props;
    11. return (
    12. <MyContext.Consumer>
    13. {(store) => {
    14. const dispatchs = mapDispatchToProps(_.get(store, 'dispatch'));
    15. let states1 = mapStateToProps(_.get(store, 'getState') ? _.get(store, 'getState')(): {});
    16. return <Comp {...{ ...states1, ...dispatchs, ...rest }} />;
    17. }}
    18. </MyContext.Consumer>
    19. );
    20. };
    21. export default connect;

    6. 其他需要的组件文件从一, 二中查找,

    注意: 此种方法不需要store文件了

  • 相关阅读:
    前端开发、后端开发与全栈开发:构建现代网站的全面视角
    Python学习笔记第四十五天(NumPy 排序、条件刷选函数)
    【附源码】计算机毕业设计JAVA红河旅游信息服务系统
    创建计划协议、维护创建计划、收货
    VUE 配置环境变量
    B22-9-5
    Vue3 - Suspense 组件介绍及使用方法
    LuatOS-SOC接口文档(air780E)--adc - 数模转换
    集成仿真软件 PLEXOS 9.0 授权永久完美
    在公司逮到一个阿里10年的测试开发,聊过之后大彻大悟...
  • 原文地址:https://blog.csdn.net/qq_42750608/article/details/133915530