目录

一 核心代码

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

手写redux和applyMiddleware中间件react示例-LMLPHP

02-25 07:06