react的状态管理(全局通信)简单钩子方法

一. recoil

useProvider文件:

import { atom, useRecoilState } from 'recoil';

const initState = atom({
    key: 'initState',
    default: {
        state: [],
    },
})

// 将业务逻辑拆分到一个单独文件中,方便进行状态管理
export interface StateProps {
    id: number;
    text: string;
    isFinished: boolean;
}
export interface ActionProps {
    type: string;
    [key: string]: any;
}
export const reducer = (state: StateProps[], action: ActionProps) => {
    console.log(state, action)
    switch (action.type) {
        case 'ADD':
            return [...state, action.todo];
        case 'CHANGESTATUS':
            return state.map(item => {
                if (item.id === action.id) {
                    return Object.assign({}, item, { isFinished: !item.isFinished })
                }
                return item;
            });
        default:
            return state;
    }
}

export const useProvider = () => {
    // 改变todo
    const [context, dispatch]: any = useRecoilState(initState);

    const changeTodo = (id: number) => {
        const todoList = reducer(context.state, { type: 'CHANGESTATUS', id: id })
        dispatch({ state: todoList });
    }
    // 添加todo
    const addTodo = (todo: StateProps) => {
        const todoList = reducer(context.state, { type: 'ADD', todo })
        dispatch({ state: todoList });
    }
    return { changeTodo, addTodo, todoList: context.state };
}

Todo组件:

import { TodoInput } from "./TodoInput";
import { TodoList } from "./TodoList";

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

TodoInput组件:

import { useState } from "react";
import _ from 'lodash';
import { useProvider } from "./useProvider";


// 子组件
export const TodoInput = () => {
    const [text, setText] = useState('');
    const {addTodo} = useProvider();
    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="todo-input">
            <input type="text" placeholder="请输入代办事项" onChange={handleChangeText} value={text} />
            <button style={{ marginLeft: '10px' }} onClick={handleAddTodo} >+添加</button>
        </div>
    )
}

TodoItem组件:

import { useProvider } from "./useProvider";
import _ from 'lodash';

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

TodoList组件:

import { TodoItem } from "./TodoItem";
import { useProvider } from "./useProvider";
import _ from 'lodash';

export const TodoList = () => {
    const {todoList} = useProvider();
    return (
        <div className="todo-list">
            {_.map(todoList, item => <TodoItem key={_.get(item, 'id')} todo={item||{}} />)}
        </div>
    )
}

然后在App组件引入Todo组件<Todo />

import { RecoilRoot } from 'recoil';
import './App.css';
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import { Todo } from './recoilProvider/Todo';



const App:React.FC = ()=> {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <div className='App'>
            <Todo />
          </div>
        </React.Suspense>
      </ErrorBoundary>
  </RecoilRoot>
  );
}

export default App;

效果图如下:

二.context状态管理:

useProvider文件:

import { createContext, useContext } from "react";

// 将业务逻辑拆分到一个单独文件中,方便进行状态管理
export interface StateProps {
    id: number;
    text: string;
    isFinished: boolean;
}
export interface ActionProps {
    type: string;
    [key: string]: any;
}
export const reducer = (state: StateProps[], action: ActionProps) => {
    console.log(state, action)
    switch (action.type) {
        case 'ADD':
            return [...state, action.todo];
        case 'CHANGESTATUS':
            return state.map(item => {
                if (item.id === action.id) {
                    return Object.assign({}, item, { isFinished: !item.isFinished })
                }
                return item;
            });
        default:
            return state;
    }
}


export interface ContextProps {
    state: StateProps[];
    dispatch: React.Dispatch<ActionProps>;
}
// const MyContext = createContext<ContextProps | null>(null); // 泛型写法
export const MyContext = createContext({} as ContextProps); // 断言写法

export const useProvider = () => {
    // 改变todo
    const context = useContext(MyContext);
    const changeTodo = (id: number) => {
        context.dispatch({ type: 'CHANGESTATUS', id: id });
    }
    // 添加todo
    const addTodo = (todo: StateProps) => {
        context.dispatch({ type: 'ADD', todo });
    }
    return { changeTodo, addTodo, todoList: context.state, context };
}

ContextProvider文件:

import { useContext, useReducer } from "react";
import { MyContext, StateProps, reducer } from "./useProvider";


const ContextProvider = (props: React.PropsWithChildren<{}>) => {
    const context = useContext(MyContext);
    const initState: StateProps[] = context.state || [];
    const [state, dispatch] = useReducer(reducer, initState);

    return (
        <MyContext.Provider value={{ state, dispatch }} >
            {/* 插槽内容 */}
            {props.children}
        </MyContext.Provider>
    )
}

export default ContextProvider;

Todo组件:

import ContextProvider from "./ContextProvider";
import { TodoInput } from "./TodoInput";
import { TodoList } from "./TodoList";

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

TodoInput,  TodoItem和TodoList 组件不变(使用recoil那块的文件代码)

App组件使用:

直接使用<Todo />就行;

效果图如下:

点击添加按钮,新增一列,点击多选框(选中)中划线一行,取消选中该行就恢复正常

三. redux

1-1. 普通方式

useProvider文件:

import { useState } from "react";
import { createStore } from "redux";

// 将业务逻辑拆分到一个单独文件中,方便进行状态管理
export interface StateProps {
  id: number;
  text: string;
  isFinished: boolean;
}
export interface ActionProps {
  type: string;
  [key: string]: any;
}
export const reducer = (
  state: StateProps[],
  action: ActionProps
) => {
  console.log(state, action);
  switch (action.type) {
    case "ADD":
      return [...state, action.todo];
    case "CHANGESTATUS":
      return state.map((item) => {
        if (item.id === action.id) {
          return Object.assign({}, item, { isFinished: !item.isFinished });
        }
        return item;
      });
    default:
      return state;
  }
};

export interface ContextProps {
  state: StateProps[];
  dispatch: React.Dispatch<ActionProps>;
}

export const store = createStore(reducer, []);
export const useProvider = () => {
    const[todoList, setTodoList] = useState(store.getState())
    // 改变todo
    const changeTodo = (id: number) => {
        store.dispatch({ type: 'CHANGESTATUS', id: id });
    }
    // 添加todo
    const addTodo = (todo: StateProps) => {
        store.dispatch({ type: 'ADD', todo });
    }
    store.subscribe(()=> {
        setTodoList(store.getState())
    })

    return { changeTodo, addTodo, todoList, store };
}

ReduxProvider.tsx文件:

import { Provider } from "react-redux";
import { store } from "./useProvider";

const ReduxProvider = (props) => {
  return <Provider store={store}>{props.children}</Provider>;
};

export default ReduxProvider;

Todo.tsx文件:

import ReduxProvider from "./ReduxProvider";
import { TodoInput } from "./TodoInput";
import { TodoList } from "./TodoList";

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

TodoInput,  TodoItem和TodoList 组件不变

App.tsx使用:

const App:React.FC = ()=> {
  return <Todo />
}

export default App;

缺点:

数据更新, 视图跟着更新靠的却是store.subsribe(() => { 给useState的变量赋值 }); 不符合逻辑习惯, 理论上应该修改数据, 随之页面自动渲染, 不建议依靠store.subsribe

效果图如下:

2-1. @reduxjs/toolkit用法

注意:

1)  普通用法需要store.subscribe再赋值给useState,  通过useState的变量才能让页面正常随着数据变得更新

2)  useDispatch, useSelector解决这个痛点

useProvider.tsx钩子文件

import {configureStore, createSlice} from '@reduxjs/toolkit'
import _ from 'lodash'
import { useDispatch, useSelector } from "react-redux";

// 将业务逻辑拆分到一个单独文件中,方便进行状态管理
export interface StateProps {
  id: number;
  text: string;
  isFinished: boolean;
}
export interface ActionProps {
  type: string;
  [key: string]: any;
}

export const storeSlice = createSlice({
  name: 'todoList',
  initialState: {
    value: []
  },
  reducers: {
    toggle: (state, action) => {
      const id = _.get(action, 'payload.id')
      const  arr =  state.value
      state.value = _.map(arr, item => {
        if (item.id === id) {
          return Object.assign({}, item, { isFinished: !item.isFinished });
        }
        return item;
      })

      return state
    },
    add: (state, action) => {
      console.log(state, action,)
      const arr =  state.value as any || [] ;
      const todo = _.get(action, 'payload.todo')
      if(todo) {
        state.value = todo? [...arr, todo]: arr
      }
     
      return state
    },
  }
})

export const store = configureStore({reducer: storeSlice.reducer})

export const useProvider = () => {
    const { toggle, add } = storeSlice.actions;
    const todoList = useSelector((state) => { 
      return _.get(state, 'value');
    });
    const dispatch = useDispatch();
    // 改变todo
    const changeTodo = (id: number) => {
        dispatch((toggle({ id })));
    }
    // 添加todo
    const addTodo = (todo: StateProps) => {
        dispatch(add({ todo }));
    }

    return { changeTodo, addTodo, todoList, store };
}

ReduxProvider组件

import { Provider } from "react-redux";
import { store } from "./useProvider";

const ReduxProvider = (props) => {
  return <Provider store={store}>{props.children}</Provider>;
};

export default ReduxProvider;

Todo组件

import ReduxProvider from "./ReduxProvider";
import { TodoInput } from "./TodoInput";
import { TodoList } from "./TodoList";

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

 TodoInput,  TodoItem和TodoList 组件不变

App.tsx使用:

const App:React.FC = ()=> {
  return <Todo />
}

export default App;

效果图如下:

3-1. redux-presist长效存储

useProvider文件:

import { configureStore, createSlice } from "@reduxjs/toolkit";
import _ from "lodash";
import { useDispatch, useSelector } from "react-redux";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";

// 将业务逻辑拆分到一个单独文件中,方便进行状态管理
export interface StateProps {
  id: number;
  text: string;
  isFinished: boolean;
}
export interface ActionProps {
  type: string;
  [key: string]: any;
}

export const storeSlice = createSlice({
  name: "todoList",
  initialState: {
    value: [],
  },
  reducers: {
    toggle: (state, action) => {
      const id = _.get(action, "payload.id");
      const arr = state.value;
      state.value = _.map(arr, (item) => {
        if (item.id === id) {
          return Object.assign({}, item, { isFinished: !item.isFinished });
        }
        return item;
      });

      return state;
    },
    add: (state, action) => {
      console.log(state, action);
      const arr = (state.value as any) || [];
      const todo = _.get(action, "payload.todo");
      if (todo) {
        state.value = todo ? [...arr, todo] : arr;
      }

      return state;
    },
  },
});

const reducer = storeSlice.reducer;

// 定义持久化配置
const persistConfig = { key: storeSlice.name, storage };
// 创建持久化reducer
export const persistedReducer = persistReducer(persistConfig, reducer);
export const store = configureStore({
  reducer: persistedReducer, // 解决了序列化问题
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }),
});
// 创建持久化存储器
export const persistor = persistStore(store);

export const useProvider = () => {
  const { toggle, add } = storeSlice.actions;
  const todoList = useSelector((state) => {
    return _.get(state, "value");
  });
  const dispatch = useDispatch();
  // 改变todo
  const changeTodo = (id: number) => {
    dispatch(toggle({ id }));
  };
  // 添加todo
  const addTodo = (todo: StateProps) => {
    dispatch(add({ todo }));
  };

  return { changeTodo, addTodo, todoList, store };
};

ReduxProvider组件:

import { Provider } from "react-redux";
import { persistor, store } from "./useProvider";
import { PersistGate } from "redux-persist/integration/react";
import React from "react"; // 有些编译会还要求必须把react引入, 没有使用也要引入

const ReduxProvider = (props) => {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        {props.children}
      </PersistGate>
    </Provider>
  );
};

export default ReduxProvider;

Todo组件:

import React from "react";
import ReduxProvider from "./ReduxProvider";
import { TodoInput } from "./TodoInput";
import { TodoList } from "./TodoList";

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

TodoInput,  TodoItem和TodoList 组件不变, 如果报错: Todo.tsx:8 Uncaught ReferenceError: React is not defined, 就把import React from "react";加上, 没有使用React也引入一下就不会报错了

App.tsx组件:

import React, { lazy } from "react";
// import Todo from './reduxPersistProvider/Todo';
const Todo = lazy(() => import("./reduxPersistProvider/Todo"));

const App: React.FC = () => {  
  return <Todo />;
};

export default App;

效果图如下(就不用担心刷新页面数据就丢失了):

适用于需要存储字典对象等功能

4-1. 客户端: redux异步action

redux toolkit文档地址:

createSlice | Redux Toolkit

useProvider.ts

import {
  configureStore,
  createAsyncThunk,
  createSlice,
} from "@reduxjs/toolkit";
import _ from "lodash";
import { useDispatch, useSelector } from "react-redux";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import axios from "./api";

// 异步函数
export const fetchHomeMultidataAction = createAsyncThunk(
  "todoList/remmend",
  async (extraInfo, { dispatch }) => {
    console.log(extraInfo, "dispatch", 66666666);
    const { data } = await axios.get("home_page");
    return data;
  }
);

export const clearRecommendsAction = createAsyncThunk(
  "todoList/clear-recommends",
  async (extraInfo, params) => {
    console.log(extraInfo, params, "clear-recommends");
    const arr = await Promise.resolve([]);
    return arr;
  }
);

// 将业务逻辑拆分到一个单独文件中,方便进行状态管理
export interface StateProps {
  id: number;
  text: string;
  isFinished: boolean;
}
export interface ActionProps {
  type: string;
  [key: string]: any;
}

export const storeSlice = createSlice({
  name: "todoList",
  initialState: {
    checkboxList: [],
    searchRecomments: [],
    filterTag: "",
  },
  reducers: {
    toggle: (state, action) => {
      const id = _.get(action, "payload.id");
      const arr = state.checkboxList;
      state.checkboxList = _.map(arr, (item) => {
        if (item.id === id) {
          return Object.assign({}, item, { isFinished: !item.isFinished });
        }
        return item;
      });

      return state;
    },
    add: (state, action) => {
      const arr = (state.checkboxList as any) || [];
      const todo = _.get(action, "payload.todo");
      if (todo) {
        state.checkboxList = todo ? [...arr, todo] : arr;
      }

      return state;
    },
    filter: (state, { payload }) => {
      console.log(payload, "payload");
      state.filterTag = payload;

      return state;
    },
  },
  // 异步处理
  // extraReducers: {
  //   // 处于padding状态时回调
  //   [fetchHomeMultidataAction.pending](state, { payload }) {
  //     console.log("正处于pending状态", state, payload);
  //     return state;
  //   },
  //   // 处于fulfilled状态时回调
  //   [fetchHomeMultidataAction.fulfilled](state, { payload }) {
  //     console.log("已经处于fulfilled状态", state, payload);
  //     state.searchRecomments = _.get(payload, "searchRecomments");
  //     return state;
  //   },
  //   // 处于rejected状态时回调
  //   [fetchHomeMultidataAction.rejected](state, { payload }) {
  //     console.log("正处于rejected状态", state, payload);
  //     return state;
  //   },
  //   // 清空数组
  //   [clearRecommendsAction.fulfilled](state, { payload }) {
  //     console.log("clearRecommendsAction--fullfilled", state, payload);
  //     state.searchRecomments = [];
  //     return state;
  //   },
  // },
  extraReducers: ({ addCase }) => {
    // 处于padding状态时回调
    addCase(fetchHomeMultidataAction.pending, (state, { payload }) => {
      console.log("正处于pending状态", state, payload);
      return state;
    })
      // 处于fulfilled状态时回调
      .addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
        console.log("已经处于fulfilled状态", state, payload);
        state.searchRecomments = _.get(payload, "searchRecomments");
        return state;
      })
      // 处于rejected状态时回调
      .addCase(fetchHomeMultidataAction.rejected, (state, { payload }) => {
        console.log("正处于rejected状态", state, payload);
        return state;
      })
      // 清空数组
      .addCase(clearRecommendsAction.fulfilled, (state, { payload }) => {
        console.log("clearRecommendsAction--fullfilled", state, payload);
        state.searchRecomments = [];
        return state;
      });
  },
});

const reducer = storeSlice.reducer;

// 定义持久化配置
const persistConfig = { key: storeSlice.name, storage };
// 创建持久化reducer
export const persistedReducer = persistReducer(persistConfig, reducer);
export const store = configureStore({
  reducer: persistedReducer, // 解决了序列化问题
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ serializableCheck: false }),
});
// 创建持久化存储器
export const persistor = persistStore(store);

export const useProvider = () => {
  const { add, toggle, filter } = storeSlice.actions;

  // 不同类型的 todo 列表
  const getVisibleTodos = (todos, filter) => {
    switch (filter) {
      case "SHOW_ALL": // 全部显示
        return todos;
      case "SHOW_FINISHED":
        return todos.filter((t) => t.isFinished);
      case "SHOW_NOT_FINISH":
        return todos.filter((t) => !t.isFinished);
      default:
        return todos;
    }
  };

  const todoList = useSelector((state) => {
    const arr = _.get(state, "checkboxList") || [];
    const filterTag = _.get(state, "filterTag");
    return getVisibleTodos(arr, filterTag) as StateProps[] || [];
  });
  const searchRecomments = useSelector((state) => {
    return _.get(state, "searchRecomments");
  });
  const dispatch = useDispatch();
  // 改变todo
  const changeTodo = (id: number) => {
    dispatch(toggle({ id }));
  };
  // 添加todo
  const addTodo = (todo: StateProps) => {
    dispatch(add({ todo }));
  };

  // 筛选todo列表
  const onFilterTodoList = (filterTag) => {
    dispatch(filter(filterTag));
  };

  const showAll = () => onFilterTodoList("SHOW_ALL");
  const showFinished = () => onFilterTodoList("SHOW_FINISHED");
  const showNotFinish = () => onFilterTodoList("SHOW_NOT_FINISH");

  // 异步获取推荐数据
  const asyncGetRemments = () => {
    dispatch(fetchHomeMultidataAction([]));
  };
  const asyncClearRemments = () => {
    dispatch(clearRecommendsAction([]));
  };

  return {
    changeTodo,
    addTodo,
    todoList,
    store,
    searchRecomments,
    asyncGetRemments,
    showAll,
    showFinished,
    showNotFinish,
    asyncClearRemments,
  };
};

ReduxProvider组件:

import { Provider } from "react-redux";
import { persistor, store } from "./useProvider";
import { PersistGate } from "redux-persist/integration/react";
import React from "react"; // 有些编译会还要求必须把react引入, 没有使用也要引入

const ReduxProvider = (props) => {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        {props.children}
      </PersistGate>
    </Provider>
  );
};

export default ReduxProvider;

Todo组件

import React, { lazy } from "react";
// import ReduxProvider from "./ReduxProvider";
// import TodoList from "./TodoList";
// import TodoInput from "./TodoInput";

const ReduxProvider = lazy(() => import("./ReduxProvider"));
const TodoList = lazy(() => import("./TodoList"));
const TodoInput = lazy(() => import("./TodoInput"));

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

TodoInput组件

import React, { useState } from "react";
import { useProvider } from "./useProvider";
import "./TodoInput.less";

// 子组件
const TodoInput = () => {
  const [text, setText] = useState("");
  const {
    addTodo,
    asyncGetRemments,
    showAll,
    showFinished,
    showNotFinish,
    asyncClearRemments,
  } = useProvider();
  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="todo-input">
      <input
        type="text"
        placeholder="请输入代办事项"
        onChange={handleChangeText}
        value={text}
      />
      <button onClick={handleAddTodo}>+添加</button>
      <button onClick={asyncGetRemments}>异步获取推荐列表数据</button>
      <button onClick={asyncClearRemments}>前端清空推荐列表</button>
      <button onClick={showAll}>show all</button>
      <button onClick={showFinished}>show finished</button>
      <button onClick={showNotFinish}>show not finish</button>
    </div>
  );
};

export default TodoInput;

TodoItem组件

import { useProvider } from "./useProvider";
import _ from 'lodash';
import React from "react";

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

TodoList组件

import { TodoItem } from "./TodoItem";
import { useProvider } from "./useProvider";
import _ from "lodash";
import React from "react";

const TodoList = () => {
  const { todoList, searchRecomments } = useProvider();
  return (
    <>
      <p>checckbox-list: </p>
      <div className="todo-list">
        {_.map(todoList, (item) => (
          <TodoItem key={_.get(item, "id")} todo={item || {}} />
        ))}
      </div>
      <hr/>
      <p>推荐列表recommends-list: </p>
      <ul className="recommends-list">
        {_.map(searchRecomments, (item) => (
          <li key={_.get(item, "value")}>{_.get(item, "label")}</li>
        ))}
      </ul>
    </>
  );
};

export default TodoList

App组件:

import React, { Suspense, lazy } from "react";
// import Todo from "./reduxAsyncProvider/Todo";
import { Spin } from "antd";
const Todo = lazy(() => import("./reduxAsyncProvider/Todo"));

const App: React.FC = () => {
  return (
    <Suspense fallback={<Spin />}>
      <Todo />
    </Suspense>
  );
};

export default App;

api.ts文件:

import axios from "axios";


const instance = axios.create({
    baseURL: '/api', // 设置请求的前缀
})

instance.interceptors.response.use((response)=> {
    const {data:_data} = response;
    const {data, code,msg} = _data
    if(code !== 0) {
        console.log(code, msg, data, response)
        return Promise.reject(response)
    }
    return response.data
})

export default instance

4-2. nodejs的mock服务器部分(也可以自行引入data/home_page.js使用):

1) app.js

const path = require('path')
const jsonServer = require('json-server')
const router = require('./router')
const db = require('./db')()

const server = jsonServer.create()

const middlewares = jsonServer.defaults({
    static: path.join(__dirname, '../public')
})
server.use(middlewares)
// req.body
server.use(jsonServer.bodyParser)


server.use((req, res, next) => {
    const json = res.json.bind(res)
    res.success = (data) => {
        return json({
            code: 0,
            msg: '请求成功',
            data
        })
    }
    res.fail = (msg, code = -1, data) => {
        return json({
            code,
            msg,
            data
        })
    }
    next()
})

router(server)
const jsonRouter = jsonServer.router(db)
server.use((req, res, next) => {
    setTimeout(next, 1000)
})
server.use('/api', jsonRouter)

server.listen(8000, () => {
    console.log('======JSON Server is running at 8000')
})

2) db,js => 查看获取数据路径

const homePage = require('./home_page')
function responseData(data) {
    return {
        code: 0,
        msg: '请求成功',
        data,
    }
}

module.exports = () => {
    return {
        // 客户端调用axios.get('/api/home_page').then(res=>{})
        home_page: responseData(homePage()),
    }
}

3) data/home_page.js文件:

module.exports = () => {
    return {
      searchRecomments: [
        {
          value: 0,
          label: '牛腩',
        },
        {
          value: 1,
          label: '色拉',
        },
        {
          value: 2,
          label: '奶茶',
        },
        {
          value: 3,
          label: '西瓜汁',
        },
      ],
      banner: [
        {
          imgUrl: '/imgs/index_page/transformer-banner.png',
        },
      ],
      transformer: [
        {
          label: '美食外卖',
          imgUrl: '/imgs/index_page/transformer-icon1.png',
        },
        {
          label: '超市便利',
          imgUrl: '/imgs/index_page/transformer-icon2.png',
        },
        {
          label: '美食团购',
          imgUrl: '/imgs/index_page/transformer-icon3.png',
        },
        {
          label: '丽人/医美',
          imgUrl: '/imgs/index_page/transformer-icon4.png',
        },
        {
          label: '休闲玩乐',
          imgUrl: '/imgs/index_page/transformer-icon5.png',
        },
        {
          label: '下午茶',
          imgUrl: '/imgs/index_page/transformer-icon6.png',
        },
        {
          label: '水果',
          imgUrl: '/imgs/index_page/transformer-icon7.png',
        },
        {
          label: '鲜花绿植',
          imgUrl: '/imgs/index_page/transformer-icon8.png',
        },
        {
          label: '买菜',
          imgUrl: '/imgs/index_page/transformer-icon9.png',
        },
        {
          label: '甜品饮品',
          imgUrl: '/imgs/index_page/transformer-icon10.png',
        },
        {
          label: '全城购',
          imgUrl: '/imgs/index_page/transformer-icon11.png',
        },
        {
          label: '送药上门',
          imgUrl: '/imgs/index_page/transformer-icon12.png',
        },
        {
          label: '0元领水果',
          imgUrl: '/imgs/index_page/transformer-icon13.png',
        },
        {
          label: '天天赚现金',
          imgUrl: '/imgs/index_page/transformer-icon14.png',
        },
        {
          label: '冲吧饿小宝',
          imgUrl: '/imgs/index_page/transformer-icon15.png',
        },
      ],
      scrollBarInfoList: [
        {
          type: 'bean',
          badge: '赚豆',
          detail: `今天再下<span class="info-num">1</span>单赚<span class="info-num">400</span>吃货豆`,
          btn: '领任务',
        },
        {
          type: 'hongbao',
          badge: '红包',
          detail: `你有<span class="info-num">4</span>张总<span class="info-num">43.5</span>元红包即将到期`,
          btn: '去查看',
        },
      ],
      countdown: {
        time: 24 * 60 * 60 * 1000,
        goods: {
          imgUrl: '/imgs/index_page/count-down-p.png',
          name: '腊鸡腿菜饭 + 卤香干 + 冰红茶',
          price: 19.8,
          oldPrice: 28.9,
        },
      },
      activities: [
        '/imgs/index_page/activity/01.png',
        '/imgs/index_page/activity/02.png',
        '/imgs/index_page/activity/03.png',
      ]
    }
  }

4) node服务器的package.json

{
  "name": "mock-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "server": "nodemon delay 1000ms ./src/app.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.5.0",
    "json-server": "^0.17.3"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

node服务器效果图如下:

客户端从这个node服务器获取数据的方法:

axios.get('/api/home_page').then(res=>{}).catch(err=>{});

在webpack.dev.js配置一下:

 devServer: {
        port: 8111,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩
        // 设置代理
        proxy: {
            // 将本地 /api/xxx 代理到 localhost:服务器端口号1/api/xxx
            '/api': 'http://localhost:8000',

            // 将本地 /api2/xxx 代理到 localhost:服务器端口号2/xxx
            '/api2': {
                target: 'http://localhost:8111',
                pathRewrite: {
                    '/api2': ''
                }
            }
        }
    }

5. connect模式

ProviderUtils文件

注意:

1) 因为逻辑简单且放在一个文件对于阅读逻辑代码, 更省事, 实际项目中应该分模块整理文件

2) connect模式未做成钩子文件, 读懂原理后可以自行改成钩子

import { combineReducers, createStore } from "redux";

// 将业务逻辑拆分到一个单独文件中,方便进行状态管理
export interface StateProps {
  id: number;
  text: string;
  isFinished: boolean;
}
export interface ActionProps {
  type: string;
  [key: string]: any;
}
// 新增列表数据和改变数组数据
export const reducer = (state: StateProps[] | [], action: ActionProps) => {
  console.log(state, action);
  switch (action.type) {
    case "ADD":
      return [...state, action.todo];
    case "CHANGESTATUS":
      return state.map((item) => {
        if (item.id === action.id) {
          return Object.assign({}, item, { isFinished: !item.isFinished });
        }
        return item;
      });
    default:
      return state || [];
  }
};

export interface ContextProps {
  state: StateProps[];
  dispatch: React.Dispatch<ActionProps>;
}

const todos = reducer;
const visibilityFilter = (state = 'SHOW_ALL', action) => {
  switch (action.type) {
    // 设置显示类型(所有、完成、未完成)
    case 'SET_VISIBILITY_FILTER':
      return action.filter

    // 默认是 SHOW_ALL
    default:
      return state
  }
}
export const combineStore = createStore(combineReducers({ todos, visibilityFilter }));

// 不同类型的 todo 列表
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case "SHOW_ALL": // 全部显示
      return todos;
    case "SHOW_FINISHED":
      return todos.filter((t) => t.isFinished);
    case "SHOW_NOT_FINISH":
      return todos.filter((t) => !t.isFinished);
    default:
      return todos;
  }
};

export const mapStateToProps = (state) => {
  return {
    // 根据完成状态,筛选数据
    todoList: getVisibleTodos(state.todos, state.visibilityFilter),
  };
};

export const mapDispatchToProps = (dispatch) => {
  const changeTodo = (id: number) => {
    dispatch({ type: "CHANGESTATUS", id });
  };
  // 添加todo
  const addTodo = (todo: StateProps) => {
    dispatch({ type: "ADD", todo });
  };

  // 显示已完成的
  const showFinished = () => {
    dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_FINISHED' });
  }
  // 显示未完成的
  const showNotFinish = () => {
    dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_NOT_FINISH' });
  }
  // 显示全部完成的
  const showAll = () => {
    dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_ALL' });
  }


  return {
    addTodo,
    // 切换完成状态
    changeTodo,
    showFinished,
    showNotFinish,
    showAll,
  };
};

ReduxProvider组件

import { Provider } from "react-redux";
import {combineStore} from './ProviderUtils';

const ReduxProvider = (props) => {
  return <Provider store={combineStore}>{props.children}</Provider>;
};

export default  ReduxProvider

Todo组件

import ReduxProvider from "./ReduxProvider";
import { TodoInput } from "./TodoInput";
import { TodoList } from "./TodoList";

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

TodoInput组件

import { useState } from "react";
import _ from "lodash";
import { mapDispatchToProps, mapStateToProps } from "./ProviderUtils";
import { connect } from "react-redux";

// 子组件
const TodoInput0 = (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="todo-input">
      <input
        type="text"
        placeholder="请输入代办事项"
        onChange={handleChangeText}
        value={text}
      />
      <button style={{ marginLeft: "10px" }} onClick={handleAddTodo}>
        +添加
      </button>
      <button style={{ marginLeft: "10px" }} onClick={showAll}>
        show all
      </button>
      <button style={{ marginLeft: "10px" }} onClick={showFinished}>
        show finished
      </button>
      <button style={{ marginLeft: "10px" }} onClick={showNotFinish}>
        show not finish
      </button>
    </div>
  );
};

const TodoInput = connect(mapStateToProps, mapDispatchToProps)(TodoInput0);
export { TodoInput };

TodoItem组件

import { mapDispatchToProps, mapStateToProps } from "./ProviderUtils";
import _ from "lodash";
import { connect } from "react-redux";

// 孙子组件
const TodoItem0 = (props) => {
  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: todo.isFinished ? "line-through" : "none" }}
      >
        {todo.text}
      </span>
    </div>
  );
};
const TodoItem = connect(mapStateToProps, mapDispatchToProps)(TodoItem0);
export { TodoItem };

TodoList组件

import { TodoItem } from "./TodoItem";
import _ from "lodash";
import { connect } from "react-redux";
import { mapDispatchToProps, mapStateToProps } from "./ProviderUtils";

const TodoList0 = (props) => {
  const { todoList } = props;

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

const TodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList0);
export { TodoList };

App组件使用:

const App:React.FC = ()=> {
  return <Todo />
}

export default App;

connect模式梳理一下流程:

1.  ProviderUtils文件: 定义combineStore:
1) 引入import { combineReducers, createStore } from "redux";

2)  定义reducer方法们:

// todos:新增,修改列表方法(返回列表数据, 否则返回state)

const todos = (state: StateProps[] | [], action: ActionProps) => {
  console.log(state, action);
  switch (action.type) {
    case "ADD":
      return [...state, action.todo];
    case "CHANGESTATUS":
      return state.map((item) => {
        if (item.id === action.id) {
          return Object.assign({}, item, { isFinished: !item.isFinished });
        }
        return item;
      });
    default:
      return state || [];
  }
};

// visibilityFilter: 筛选列表方法(返回筛选器, 否则返回state)

const visibilityFilter = (state = 'SHOW_ALL', action) => {
  switch (action.type) {
    // 设置显示类型(所有、完成、未完成)
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

3) 得到combineStore

export const combineStore = createStore(combineReducers({ todos, visibilityFilter }));

2. 完成ReduxProvider组件

1) Provider从 "react-redux"获取;

2) combineStore从第1步获取

const ReduxProvider = (props) => {

        return <Provider store={combineStore}>{props.children}</Provider>;

};

3. 使用ReduxProvider:

export const Todo = () => {
    return (
        <ReduxProvider>
            <TodoInput />
            <TodoList  />
        </ReduxProvider>
    )
}

4. 子组件们使用connect:

import { connect } from "react-redux";

const 子组件-新 = connect(mapStateToProps, mapDispatchToProps)(子组件);

export default 子组件-新;

注意: 因为子组件们使用了mapStateToProps, mapDispatchToProps, 可以直接写在子组件, 也可以写在公共文件

5. 追加mapStateToProps, mapDispatchToProps方法到ProviderUtils文件中

本文为了减少文件, 追加到ProviderUtils文件中

// 不同类型的 todo 列表
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case "SHOW_ALL": // 全部显示
      return todos;
    case "SHOW_FINISHED":
      return todos.filter((t) => t.isFinished);
    case "SHOW_NOT_FINISH":
      return todos.filter((t) => !t.isFinished);
    default:
      return todos;
  }
};

export const mapStateToProps = (state) => {
  return {
    // 根据完成状态,筛选数据
    todoList: getVisibleTodos(state.todos, state.visibilityFilter),
  };
};

export const mapDispatchToProps = (dispatch) => {
  const changeTodo = (id: number) => {
    dispatch({ type: "CHANGESTATUS", id });
  };
  // 添加todo
  const addTodo = (todo: StateProps) => {
    dispatch({ type: "ADD", todo });
  };

  // 显示已完成的
  const showFinished = () => {
    dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_FINISHED' });
  }
  // 显示未完成的
  const showNotFinish = () => {
    dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_NOT_FINISH' });
  }
  // 显示全部完成的
  const showAll = () => {
    dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_ALL' });
  }


  return {
    addTodo,
    // 切换完成状态
    changeTodo,
    showFinished,
    showNotFinish,
    showAll,
  };
};

6. 最后可以在子组件打印props查看:

1) props可以正常获取本身父组件传给它的数据和方法

2) props可以解构出mapStateToProps, mapDispatchToProps钩子return的数据变量和方法

以TodoItem组件为例:

const TodoItem0 = (props) => {
    // 注意: todo是父组件直接传过来的数据, changeTodo是mapDispatchToProps钩子return出来的方法!!!
  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: todo.isFinished ? "line-through" : "none" }}
      >
        {todo.text}
      </span>
    </div>
  );
};
const TodoItem = connect(mapStateToProps, mapDispatchToProps)(TodoItem0);
export { TodoItem };

connect模式效果图如下:

1) 添加数据后选中某些数据

2) 点击show finished显示被选中的那部分数据

3) 点击show not finish显示未被选中的那些数据

4) 点击show all展示所有数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值