• react的状态管理简单钩子方法


    一. recoil

    useProvider文件:

    1. import { atom, useRecoilState } from 'recoil';
    2. const initState = atom({
    3. key: 'initState',
    4. default: {
    5. state: [],
    6. },
    7. })
    8. // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    9. export interface StateProps {
    10. id: number;
    11. text: string;
    12. isFinished: boolean;
    13. }
    14. export interface ActionProps {
    15. type: string;
    16. [key: string]: any;
    17. }
    18. export const reducer = (state: StateProps[], action: ActionProps) => {
    19. console.log(state, action)
    20. switch (action.type) {
    21. case 'ADD':
    22. return [...state, action.todo];
    23. case 'CHANGESTATUS':
    24. return state.map(item => {
    25. if (item.id === action.id) {
    26. return Object.assign({}, item, { isFinished: !item.isFinished })
    27. }
    28. return item;
    29. });
    30. default:
    31. return state;
    32. }
    33. }
    34. export const useProvider = () => {
    35. // 改变todo
    36. const [context, dispatch]: any = useRecoilState(initState);
    37. const changeTodo = (id: number) => {
    38. const todoList = reducer(context.state, { type: 'CHANGESTATUS', id: id })
    39. dispatch({ state: todoList });
    40. }
    41. // 添加todo
    42. const addTodo = (todo: StateProps) => {
    43. const todoList = reducer(context.state, { type: 'ADD', todo })
    44. dispatch({ state: todoList });
    45. }
    46. return { changeTodo, addTodo, todoList: context.state };
    47. }

    Todo组件:

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

    TodoInput组件:

    1. import { useState } from "react";
    2. import _ from 'lodash';
    3. import { useProvider } from "./useProvider";
    4. // 子组件
    5. export const TodoInput = () => {
    6. const [text, setText] = useState('');
    7. const {addTodo} = useProvider();
    8. const handleChangeText = (e: React.ChangeEvent) => {
    9. setText((e.target as HTMLInputElement).value);
    10. }
    11. const handleAddTodo = () => {
    12. if (!text) return;
    13. addTodo({
    14. id: new Date().getTime(),
    15. text: text,
    16. isFinished: false,
    17. })
    18. setText('');
    19. }
    20. return (
    21. <div className="todo-input">
    22. <input type="text" placeholder="请输入代办事项" onChange={handleChangeText} value={text} />
    23. <button style={{ marginLeft: '10px' }} onClick={handleAddTodo} >+添加</button>
    24. </div>
    25. )
    26. }

    TodoItem组件:

    1. import { useProvider } from "./useProvider";
    2. import _ from 'lodash';
    3. // 孙子组件
    4. export const TodoItem = ({ todo }: {
    5. todo:any;
    6. key: any;
    7. }) => {
    8. const {changeTodo} = useProvider();
    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: todo.isFinished ? 'line-through' : 'none' }}>{todo.text}</span>
    17. </div>
    18. )
    19. }

    TodoList组件:

    1. import { TodoItem } from "./TodoItem";
    2. import { useProvider } from "./useProvider";
    3. import _ from 'lodash';
    4. export const TodoList = () => {
    5. const {todoList} = useProvider();
    6. return (
    7. <div className="todo-list">
    8. {_.map(todoList, item => <TodoItem key={_.get(item, 'id')} todo={item||{}} />)}
    9. </div>
    10. )
    11. }

    然后在App组件引入Todo组件

    1. import { RecoilRoot } from 'recoil';
    2. import './App.css';
    3. import React from 'react';
    4. import ErrorBoundary from './ErrorBoundary';
    5. import { Todo } from './recoilProvider/Todo';
    6. const App:React.FC = ()=> {
    7. return (
    8. <RecoilRoot>
    9. <ErrorBoundary>
    10. <React.Suspense fallback={<div>Loading...</div>}>
    11. <div className='App'>
    12. <Todo />
    13. </div>
    14. </React.Suspense>
    15. </ErrorBoundary>
    16. </RecoilRoot>
    17. );
    18. }
    19. export default App;

    效果图如下:

    二.context状态管理:

    useProvider文件:

    1. import { createContext, useContext } from "react";
    2. // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    3. export interface StateProps {
    4. id: number;
    5. text: string;
    6. isFinished: boolean;
    7. }
    8. export interface ActionProps {
    9. type: string;
    10. [key: string]: any;
    11. }
    12. export const reducer = (state: StateProps[], action: ActionProps) => {
    13. console.log(state, action)
    14. switch (action.type) {
    15. case 'ADD':
    16. return [...state, action.todo];
    17. case 'CHANGESTATUS':
    18. return state.map(item => {
    19. if (item.id === action.id) {
    20. return Object.assign({}, item, { isFinished: !item.isFinished })
    21. }
    22. return item;
    23. });
    24. default:
    25. return state;
    26. }
    27. }
    28. export interface ContextProps {
    29. state: StateProps[];
    30. dispatch: React.Dispatch<ActionProps>;
    31. }
    32. // const MyContext = createContext<ContextProps | null>(null); // 泛型写法
    33. export const MyContext = createContext({} as ContextProps); // 断言写法
    34. export const useProvider = () => {
    35. // 改变todo
    36. const context = useContext(MyContext);
    37. const changeTodo = (id: number) => {
    38. context.dispatch({ type: 'CHANGESTATUS', id: id });
    39. }
    40. // 添加todo
    41. const addTodo = (todo: StateProps) => {
    42. context.dispatch({ type: 'ADD', todo });
    43. }
    44. return { changeTodo, addTodo, todoList: context.state, context };
    45. }

    ContextProvider文件:

    1. import { useContext, useReducer } from "react";
    2. import { MyContext, StateProps, reducer } from "./useProvider";
    3. const ContextProvider = (props: React.PropsWithChildren<{}>) => {
    4. const context = useContext(MyContext);
    5. const initState: StateProps[] = context.state || [];
    6. const [state, dispatch] = useReducer(reducer, initState);
    7. return (
    8. <MyContext.Provider value={{ state, dispatch }} >
    9. {/* 插槽内容 */}
    10. {props.children}
    11. </MyContext.Provider>
    12. )
    13. }
    14. export default ContextProvider;

    Todo组件:

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

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

    App组件使用:

    直接使用就行;

    效果图如下:

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

    三. redux

    1-1. 普通方式

    useProvider文件:

    1. import { useState } from "react";
    2. import { createStore } from "redux";
    3. // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    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. export const reducer = (
    14. state: StateProps[],
    15. action: ActionProps
    16. ) => {
    17. console.log(state, action);
    18. switch (action.type) {
    19. case "ADD":
    20. return [...state, action.todo];
    21. case "CHANGESTATUS":
    22. return state.map((item) => {
    23. if (item.id === action.id) {
    24. return Object.assign({}, item, { isFinished: !item.isFinished });
    25. }
    26. return item;
    27. });
    28. default:
    29. return state;
    30. }
    31. };
    32. export interface ContextProps {
    33. state: StateProps[];
    34. dispatch: React.Dispatch<ActionProps>;
    35. }
    36. export const store = createStore(reducer, []);
    37. export const useProvider = () => {
    38. const[todoList, setTodoList] = useState(store.getState())
    39. // 改变todo
    40. const changeTodo = (id: number) => {
    41. store.dispatch({ type: 'CHANGESTATUS', id: id });
    42. }
    43. // 添加todo
    44. const addTodo = (todo: StateProps) => {
    45. store.dispatch({ type: 'ADD', todo });
    46. }
    47. store.subscribe(()=> {
    48. setTodoList(store.getState())
    49. })
    50. return { changeTodo, addTodo, todoList, store };
    51. }

    ReduxProvider.tsx文件:

    1. import { Provider } from "react-redux";
    2. import { store } from "./useProvider";
    3. const ReduxProvider = (props) => {
    4. return <Provider store={store}>{props.children}</Provider>;
    5. };
    6. export default ReduxProvider;

    Todo.tsx文件:

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

    TodoInput,  TodoItem和TodoList 组件不变

    App.tsx使用:

    1. const App:React.FC = ()=> {
    2. return <Todo />
    3. }
    4. export default App;

    缺点:

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

    效果图如下:

    2-1. @reduxjs/toolkit用法

    注意:

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

    2)  useDispatch, useSelector解决这个痛点

    useProvider.tsx钩子文件

    1. import {configureStore, createSlice} from '@reduxjs/toolkit'
    2. import _ from 'lodash'
    3. import { useDispatch, useSelector } from "react-redux";
    4. // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    5. export interface StateProps {
    6. id: number;
    7. text: string;
    8. isFinished: boolean;
    9. }
    10. export interface ActionProps {
    11. type: string;
    12. [key: string]: any;
    13. }
    14. export const storeSlice = createSlice({
    15. name: 'todoList',
    16. initialState: {
    17. value: []
    18. },
    19. reducers: {
    20. toggle: (state, action) => {
    21. const id = _.get(action, 'payload.id')
    22. const arr = state.value
    23. state.value = _.map(arr, item => {
    24. if (item.id === id) {
    25. return Object.assign({}, item, { isFinished: !item.isFinished });
    26. }
    27. return item;
    28. })
    29. return state
    30. },
    31. add: (state, action) => {
    32. console.log(state, action,)
    33. const arr = state.value as any || [] ;
    34. const todo = _.get(action, 'payload.todo')
    35. if(todo) {
    36. state.value = todo? [...arr, todo]: arr
    37. }
    38. return state
    39. },
    40. }
    41. })
    42. export const store = configureStore({reducer: storeSlice.reducer})
    43. export const useProvider = () => {
    44. const { toggle, add } = storeSlice.actions;
    45. const todoList = useSelector((state) => {
    46. return _.get(state, 'value');
    47. });
    48. const dispatch = useDispatch();
    49. // 改变todo
    50. const changeTodo = (id: number) => {
    51. dispatch((toggle({ id })));
    52. }
    53. // 添加todo
    54. const addTodo = (todo: StateProps) => {
    55. dispatch(add({ todo }));
    56. }
    57. return { changeTodo, addTodo, todoList, store };
    58. }

    ReduxProvider组件

    1. import { Provider } from "react-redux";
    2. import { store } from "./useProvider";
    3. const ReduxProvider = (props) => {
    4. return <Provider store={store}>{props.children}</Provider>;
    5. };
    6. export default ReduxProvider;

    Todo组件

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

     TodoInput,  TodoItem和TodoList 组件不变

    App.tsx使用:

    1. const App:React.FC = ()=> {
    2. return <Todo />
    3. }
    4. export default App;

    效果图如下:

    3-1. redux-presist长效存储

    useProvider文件:

    1. import { configureStore, createSlice } from "@reduxjs/toolkit";
    2. import _ from "lodash";
    3. import { useDispatch, useSelector } from "react-redux";
    4. import { persistStore, persistReducer } from "redux-persist";
    5. import storage from "redux-persist/lib/storage";
    6. // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    7. export interface StateProps {
    8. id: number;
    9. text: string;
    10. isFinished: boolean;
    11. }
    12. export interface ActionProps {
    13. type: string;
    14. [key: string]: any;
    15. }
    16. export const storeSlice = createSlice({
    17. name: "todoList",
    18. initialState: {
    19. value: [],
    20. },
    21. reducers: {
    22. toggle: (state, action) => {
    23. const id = _.get(action, "payload.id");
    24. const arr = state.value;
    25. state.value = _.map(arr, (item) => {
    26. if (item.id === id) {
    27. return Object.assign({}, item, { isFinished: !item.isFinished });
    28. }
    29. return item;
    30. });
    31. return state;
    32. },
    33. add: (state, action) => {
    34. console.log(state, action);
    35. const arr = (state.value as any) || [];
    36. const todo = _.get(action, "payload.todo");
    37. if (todo) {
    38. state.value = todo ? [...arr, todo] : arr;
    39. }
    40. return state;
    41. },
    42. },
    43. });
    44. const reducer = storeSlice.reducer;
    45. // 定义持久化配置
    46. const persistConfig = { key: storeSlice.name, storage };
    47. // 创建持久化reducer
    48. export const persistedReducer = persistReducer(persistConfig, reducer);
    49. export const store = configureStore({
    50. reducer: persistedReducer, // 解决了序列化问题
    51. middleware: (getDefaultMiddleware) =>
    52. getDefaultMiddleware({ serializableCheck: false }),
    53. });
    54. // 创建持久化存储器
    55. export const persistor = persistStore(store);
    56. export const useProvider = () => {
    57. const { toggle, add } = storeSlice.actions;
    58. const todoList = useSelector((state) => {
    59. return _.get(state, "value");
    60. });
    61. const dispatch = useDispatch();
    62. // 改变todo
    63. const changeTodo = (id: number) => {
    64. dispatch(toggle({ id }));
    65. };
    66. // 添加todo
    67. const addTodo = (todo: StateProps) => {
    68. dispatch(add({ todo }));
    69. };
    70. return { changeTodo, addTodo, todoList, store };
    71. };

    ReduxProvider组件:

    1. import { Provider } from "react-redux";
    2. import { persistor, store } from "./useProvider";
    3. import { PersistGate } from "redux-persist/integration/react";
    4. import React from "react"; // 有些编译会还要求必须把react引入, 没有使用也要引入
    5. const ReduxProvider = (props) => {
    6. return (
    7. <Provider store={store}>
    8. <PersistGate loading={null} persistor={persistor}>
    9. {props.children}
    10. </PersistGate>
    11. </Provider>
    12. );
    13. };
    14. export default ReduxProvider;

    Todo组件:

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

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

    App.tsx组件:

    1. import React, { lazy } from "react";
    2. // import Todo from './reduxPersistProvider/Todo';
    3. const Todo = lazy(() => import("./reduxPersistProvider/Todo"));
    4. const App: React.FC = () => {
    5. return <Todo />;
    6. };
    7. export default App;

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

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

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

    redux toolkit文档地址:

    createSlice | Redux Toolkit

    useProvider.ts

    1. import {
    2. configureStore,
    3. createAsyncThunk,
    4. createSlice,
    5. } from "@reduxjs/toolkit";
    6. import _ from "lodash";
    7. import { useDispatch, useSelector } from "react-redux";
    8. import { persistStore, persistReducer } from "redux-persist";
    9. import storage from "redux-persist/lib/storage";
    10. import axios from "./api";
    11. // 异步函数
    12. export const fetchHomeMultidataAction = createAsyncThunk(
    13. "todoList/remmend",
    14. async (extraInfo, { dispatch }) => {
    15. console.log(extraInfo, "dispatch", 66666666);
    16. const { data } = await axios.get("home_page");
    17. return data;
    18. }
    19. );
    20. export const clearRecommendsAction = createAsyncThunk(
    21. "todoList/clear-recommends",
    22. async (extraInfo, params) => {
    23. console.log(extraInfo, params, "clear-recommends");
    24. const arr = await Promise.resolve([]);
    25. return arr;
    26. }
    27. );
    28. // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    29. export interface StateProps {
    30. id: number;
    31. text: string;
    32. isFinished: boolean;
    33. }
    34. export interface ActionProps {
    35. type: string;
    36. [key: string]: any;
    37. }
    38. export const storeSlice = createSlice({
    39. name: "todoList",
    40. initialState: {
    41. checkboxList: [],
    42. searchRecomments: [],
    43. filterTag: "",
    44. },
    45. reducers: {
    46. toggle: (state, action) => {
    47. const id = _.get(action, "payload.id");
    48. const arr = state.checkboxList;
    49. state.checkboxList = _.map(arr, (item) => {
    50. if (item.id === id) {
    51. return Object.assign({}, item, { isFinished: !item.isFinished });
    52. }
    53. return item;
    54. });
    55. return state;
    56. },
    57. add: (state, action) => {
    58. const arr = (state.checkboxList as any) || [];
    59. const todo = _.get(action, "payload.todo");
    60. if (todo) {
    61. state.checkboxList = todo ? [...arr, todo] : arr;
    62. }
    63. return state;
    64. },
    65. filter: (state, { payload }) => {
    66. console.log(payload, "payload");
    67. state.filterTag = payload;
    68. return state;
    69. },
    70. },
    71. // 异步处理
    72. // extraReducers: {
    73. // // 处于padding状态时回调
    74. // [fetchHomeMultidataAction.pending](state, { payload }) {
    75. // console.log("正处于pending状态", state, payload);
    76. // return state;
    77. // },
    78. // // 处于fulfilled状态时回调
    79. // [fetchHomeMultidataAction.fulfilled](state, { payload }) {
    80. // console.log("已经处于fulfilled状态", state, payload);
    81. // state.searchRecomments = _.get(payload, "searchRecomments");
    82. // return state;
    83. // },
    84. // // 处于rejected状态时回调
    85. // [fetchHomeMultidataAction.rejected](state, { payload }) {
    86. // console.log("正处于rejected状态", state, payload);
    87. // return state;
    88. // },
    89. // // 清空数组
    90. // [clearRecommendsAction.fulfilled](state, { payload }) {
    91. // console.log("clearRecommendsAction--fullfilled", state, payload);
    92. // state.searchRecomments = [];
    93. // return state;
    94. // },
    95. // },
    96. extraReducers: ({ addCase }) => {
    97. // 处于padding状态时回调
    98. addCase(fetchHomeMultidataAction.pending, (state, { payload }) => {
    99. console.log("正处于pending状态", state, payload);
    100. return state;
    101. })
    102. // 处于fulfilled状态时回调
    103. .addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
    104. console.log("已经处于fulfilled状态", state, payload);
    105. state.searchRecomments = _.get(payload, "searchRecomments");
    106. return state;
    107. })
    108. // 处于rejected状态时回调
    109. .addCase(fetchHomeMultidataAction.rejected, (state, { payload }) => {
    110. console.log("正处于rejected状态", state, payload);
    111. return state;
    112. })
    113. // 清空数组
    114. .addCase(clearRecommendsAction.fulfilled, (state, { payload }) => {
    115. console.log("clearRecommendsAction--fullfilled", state, payload);
    116. state.searchRecomments = [];
    117. return state;
    118. });
    119. },
    120. });
    121. const reducer = storeSlice.reducer;
    122. // 定义持久化配置
    123. const persistConfig = { key: storeSlice.name, storage };
    124. // 创建持久化reducer
    125. export const persistedReducer = persistReducer(persistConfig, reducer);
    126. export const store = configureStore({
    127. reducer: persistedReducer, // 解决了序列化问题
    128. middleware: (getDefaultMiddleware) =>
    129. getDefaultMiddleware({ serializableCheck: false }),
    130. });
    131. // 创建持久化存储器
    132. export const persistor = persistStore(store);
    133. export const useProvider = () => {
    134. const { add, toggle, filter } = storeSlice.actions;
    135. // 不同类型的 todo 列表
    136. const getVisibleTodos = (todos, filter) => {
    137. switch (filter) {
    138. case "SHOW_ALL": // 全部显示
    139. return todos;
    140. case "SHOW_FINISHED":
    141. return todos.filter((t) => t.isFinished);
    142. case "SHOW_NOT_FINISH":
    143. return todos.filter((t) => !t.isFinished);
    144. default:
    145. return todos;
    146. }
    147. };
    148. const todoList = useSelector((state) => {
    149. const arr = _.get(state, "checkboxList") || [];
    150. const filterTag = _.get(state, "filterTag");
    151. return getVisibleTodos(arr, filterTag) as StateProps[] || [];
    152. });
    153. const searchRecomments = useSelector((state) => {
    154. return _.get(state, "searchRecomments");
    155. });
    156. const dispatch = useDispatch();
    157. // 改变todo
    158. const changeTodo = (id: number) => {
    159. dispatch(toggle({ id }));
    160. };
    161. // 添加todo
    162. const addTodo = (todo: StateProps) => {
    163. dispatch(add({ todo }));
    164. };
    165. // 筛选todo列表
    166. const onFilterTodoList = (filterTag) => {
    167. dispatch(filter(filterTag));
    168. };
    169. const showAll = () => onFilterTodoList("SHOW_ALL");
    170. const showFinished = () => onFilterTodoList("SHOW_FINISHED");
    171. const showNotFinish = () => onFilterTodoList("SHOW_NOT_FINISH");
    172. // 异步获取推荐数据
    173. const asyncGetRemments = () => {
    174. dispatch(fetchHomeMultidataAction([]));
    175. };
    176. const asyncClearRemments = () => {
    177. dispatch(clearRecommendsAction([]));
    178. };
    179. return {
    180. changeTodo,
    181. addTodo,
    182. todoList,
    183. store,
    184. searchRecomments,
    185. asyncGetRemments,
    186. showAll,
    187. showFinished,
    188. showNotFinish,
    189. asyncClearRemments,
    190. };
    191. };

    ReduxProvider组件:

    1. import { Provider } from "react-redux";
    2. import { persistor, store } from "./useProvider";
    3. import { PersistGate } from "redux-persist/integration/react";
    4. import React from "react"; // 有些编译会还要求必须把react引入, 没有使用也要引入
    5. const ReduxProvider = (props) => {
    6. return (
    7. <Provider store={store}>
    8. <PersistGate loading={null} persistor={persistor}>
    9. {props.children}
    10. </PersistGate>
    11. </Provider>
    12. );
    13. };
    14. export default ReduxProvider;

    Todo组件

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

    TodoInput组件

    1. import React, { useState } from "react";
    2. import { useProvider } from "./useProvider";
    3. import "./TodoInput.less";
    4. // 子组件
    5. const TodoInput = () => {
    6. const [text, setText] = useState("");
    7. const {
    8. addTodo,
    9. asyncGetRemments,
    10. showAll,
    11. showFinished,
    12. showNotFinish,
    13. asyncClearRemments,
    14. } = useProvider();
    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={asyncGetRemments}>异步获取推荐列表数据</button>
    37. <button onClick={asyncClearRemments}>前端清空推荐列表</button>
    38. <button onClick={showAll}>show all</button>
    39. <button onClick={showFinished}>show finished</button>
    40. <button onClick={showNotFinish}>show not finish</button>
    41. </div>
    42. );
    43. };
    44. export default TodoInput;

    TodoItem组件

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

    TodoList组件

    1. import { TodoItem } from "./TodoItem";
    2. import { useProvider } from "./useProvider";
    3. import _ from "lodash";
    4. import React from "react";
    5. const TodoList = () => {
    6. const { todoList, searchRecomments } = useProvider();
    7. return (
    8. <>
    9. <p>checckbox-list: </p>
    10. <div className="todo-list">
    11. {_.map(todoList, (item) => (
    12. <TodoItem key={_.get(item, "id")} todo={item || {}} />
    13. ))}
    14. </div>
    15. <hr/>
    16. <p>推荐列表recommends-list: </p>
    17. <ul className="recommends-list">
    18. {_.map(searchRecomments, (item) => (
    19. <li key={_.get(item, "value")}>{_.get(item, "label")}</li>
    20. ))}
    21. </ul>
    22. </>
    23. );
    24. };
    25. export default TodoList

    App组件:

    1. import React, { Suspense, lazy } from "react";
    2. // import Todo from "./reduxAsyncProvider/Todo";
    3. import { Spin } from "antd";
    4. const Todo = lazy(() => import("./reduxAsyncProvider/Todo"));
    5. const App: React.FC = () => {
    6. return (
    7. <Suspense fallback={<Spin />}>
    8. <Todo />
    9. </Suspense>
    10. );
    11. };
    12. export default App;

    api.ts文件:

    1. import axios from "axios";
    2. const instance = axios.create({
    3. baseURL: '/api', // 设置请求的前缀
    4. })
    5. instance.interceptors.response.use((response)=> {
    6. const {data:_data} = response;
    7. const {data, code,msg} = _data
    8. if(code !== 0) {
    9. console.log(code, msg, data, response)
    10. return Promise.reject(response)
    11. }
    12. return response.data
    13. })
    14. export default instance

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

    1) app.js

    1. const path = require('path')
    2. const jsonServer = require('json-server')
    3. const router = require('./router')
    4. const db = require('./db')()
    5. const server = jsonServer.create()
    6. const middlewares = jsonServer.defaults({
    7. static: path.join(__dirname, '../public')
    8. })
    9. server.use(middlewares)
    10. // req.body
    11. server.use(jsonServer.bodyParser)
    12. server.use((req, res, next) => {
    13. const json = res.json.bind(res)
    14. res.success = (data) => {
    15. return json({
    16. code: 0,
    17. msg: '请求成功',
    18. data
    19. })
    20. }
    21. res.fail = (msg, code = -1, data) => {
    22. return json({
    23. code,
    24. msg,
    25. data
    26. })
    27. }
    28. next()
    29. })
    30. router(server)
    31. const jsonRouter = jsonServer.router(db)
    32. server.use((req, res, next) => {
    33. setTimeout(next, 1000)
    34. })
    35. server.use('/api', jsonRouter)
    36. server.listen(8000, () => {
    37. console.log('======JSON Server is running at 8000')
    38. })

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

    1. const homePage = require('./home_page')
    2. function responseData(data) {
    3. return {
    4. code: 0,
    5. msg: '请求成功',
    6. data,
    7. }
    8. }
    9. module.exports = () => {
    10. return {
    11. // 客户端调用axios.get('/api/home_page').then(res=>{})
    12. home_page: responseData(homePage()),
    13. }
    14. }

    3) data/home_page.js文件:

    1. module.exports = () => {
    2. return {
    3. searchRecomments: [
    4. {
    5. value: 0,
    6. label: '牛腩',
    7. },
    8. {
    9. value: 1,
    10. label: '色拉',
    11. },
    12. {
    13. value: 2,
    14. label: '奶茶',
    15. },
    16. {
    17. value: 3,
    18. label: '西瓜汁',
    19. },
    20. ],
    21. banner: [
    22. {
    23. imgUrl: '/imgs/index_page/transformer-banner.png',
    24. },
    25. ],
    26. transformer: [
    27. {
    28. label: '美食外卖',
    29. imgUrl: '/imgs/index_page/transformer-icon1.png',
    30. },
    31. {
    32. label: '超市便利',
    33. imgUrl: '/imgs/index_page/transformer-icon2.png',
    34. },
    35. {
    36. label: '美食团购',
    37. imgUrl: '/imgs/index_page/transformer-icon3.png',
    38. },
    39. {
    40. label: '丽人/医美',
    41. imgUrl: '/imgs/index_page/transformer-icon4.png',
    42. },
    43. {
    44. label: '休闲玩乐',
    45. imgUrl: '/imgs/index_page/transformer-icon5.png',
    46. },
    47. {
    48. label: '下午茶',
    49. imgUrl: '/imgs/index_page/transformer-icon6.png',
    50. },
    51. {
    52. label: '水果',
    53. imgUrl: '/imgs/index_page/transformer-icon7.png',
    54. },
    55. {
    56. label: '鲜花绿植',
    57. imgUrl: '/imgs/index_page/transformer-icon8.png',
    58. },
    59. {
    60. label: '买菜',
    61. imgUrl: '/imgs/index_page/transformer-icon9.png',
    62. },
    63. {
    64. label: '甜品饮品',
    65. imgUrl: '/imgs/index_page/transformer-icon10.png',
    66. },
    67. {
    68. label: '全城购',
    69. imgUrl: '/imgs/index_page/transformer-icon11.png',
    70. },
    71. {
    72. label: '送药上门',
    73. imgUrl: '/imgs/index_page/transformer-icon12.png',
    74. },
    75. {
    76. label: '0元领水果',
    77. imgUrl: '/imgs/index_page/transformer-icon13.png',
    78. },
    79. {
    80. label: '天天赚现金',
    81. imgUrl: '/imgs/index_page/transformer-icon14.png',
    82. },
    83. {
    84. label: '冲吧饿小宝',
    85. imgUrl: '/imgs/index_page/transformer-icon15.png',
    86. },
    87. ],
    88. scrollBarInfoList: [
    89. {
    90. type: 'bean',
    91. badge: '赚豆',
    92. detail: `今天再下<span class="info-num">1</span>单赚<span class="info-num">400</span>吃货豆`,
    93. btn: '领任务',
    94. },
    95. {
    96. type: 'hongbao',
    97. badge: '红包',
    98. detail: `你有<span class="info-num">4</span>张总<span class="info-num">43.5</span>元红包即将到期`,
    99. btn: '去查看',
    100. },
    101. ],
    102. countdown: {
    103. time: 24 * 60 * 60 * 1000,
    104. goods: {
    105. imgUrl: '/imgs/index_page/count-down-p.png',
    106. name: '腊鸡腿菜饭 + 卤香干 + 冰红茶',
    107. price: 19.8,
    108. oldPrice: 28.9,
    109. },
    110. },
    111. activities: [
    112. '/imgs/index_page/activity/01.png',
    113. '/imgs/index_page/activity/02.png',
    114. '/imgs/index_page/activity/03.png',
    115. ]
    116. }
    117. }

    4) node服务器的package.json

    1. {
    2. "name": "mock-server",
    3. "version": "1.0.0",
    4. "description": "",
    5. "main": "index.js",
    6. "scripts": {
    7. "test": "echo \"Error: no test specified\" && exit 1",
    8. "server": "nodemon delay 1000ms ./src/app.js"
    9. },
    10. "author": "",
    11. "license": "ISC",
    12. "dependencies": {
    13. "axios": "^1.5.0",
    14. "json-server": "^0.17.3"
    15. },
    16. "devDependencies": {
    17. "nodemon": "^3.0.1"
    18. }
    19. }

    node服务器效果图如下:

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

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

    在webpack.dev.js配置一下:

    1. devServer: {
    2. port: 8111,
    3. progress: true, // 显示打包的进度条
    4. contentBase: distPath, // 根目录
    5. open: true, // 自动打开浏览器
    6. compress: true, // 启动 gzip 压缩
    7. // 设置代理
    8. proxy: {
    9. // 将本地 /api/xxx 代理到 localhost:服务器端口号1/api/xxx
    10. '/api': 'http://localhost:8000',
    11. // 将本地 /api2/xxx 代理到 localhost:服务器端口号2/xxx
    12. '/api2': {
    13. target: 'http://localhost:8111',
    14. pathRewrite: {
    15. '/api2': ''
    16. }
    17. }
    18. }
    19. }

    5. connect模式

    ProviderUtils文件

    注意:

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

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

    1. import { combineReducers, createStore } from "redux";
    2. // 将业务逻辑拆分到一个单独文件中,方便进行状态管理
    3. export interface StateProps {
    4. id: number;
    5. text: string;
    6. isFinished: boolean;
    7. }
    8. export interface ActionProps {
    9. type: string;
    10. [key: string]: any;
    11. }
    12. // 新增列表数据和改变数组数据
    13. export const reducer = (state: StateProps[] | [], action: ActionProps) => {
    14. console.log(state, action);
    15. switch (action.type) {
    16. case "ADD":
    17. return [...state, action.todo];
    18. case "CHANGESTATUS":
    19. return state.map((item) => {
    20. if (item.id === action.id) {
    21. return Object.assign({}, item, { isFinished: !item.isFinished });
    22. }
    23. return item;
    24. });
    25. default:
    26. return state || [];
    27. }
    28. };
    29. export interface ContextProps {
    30. state: StateProps[];
    31. dispatch: React.Dispatch<ActionProps>;
    32. }
    33. const todos = reducer;
    34. const visibilityFilter = (state = 'SHOW_ALL', action) => {
    35. switch (action.type) {
    36. // 设置显示类型(所有、完成、未完成)
    37. case 'SET_VISIBILITY_FILTER':
    38. return action.filter
    39. // 默认是 SHOW_ALL
    40. default:
    41. return state
    42. }
    43. }
    44. export const combineStore = createStore(combineReducers({ todos, visibilityFilter }));
    45. // 不同类型的 todo 列表
    46. const getVisibleTodos = (todos, filter) => {
    47. switch (filter) {
    48. case "SHOW_ALL": // 全部显示
    49. return todos;
    50. case "SHOW_FINISHED":
    51. return todos.filter((t) => t.isFinished);
    52. case "SHOW_NOT_FINISH":
    53. return todos.filter((t) => !t.isFinished);
    54. default:
    55. return todos;
    56. }
    57. };
    58. export const mapStateToProps = (state) => {
    59. return {
    60. // 根据完成状态,筛选数据
    61. todoList: getVisibleTodos(state.todos, state.visibilityFilter),
    62. };
    63. };
    64. export const mapDispatchToProps = (dispatch) => {
    65. const changeTodo = (id: number) => {
    66. dispatch({ type: "CHANGESTATUS", id });
    67. };
    68. // 添加todo
    69. const addTodo = (todo: StateProps) => {
    70. dispatch({ type: "ADD", todo });
    71. };
    72. // 显示已完成的
    73. const showFinished = () => {
    74. dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_FINISHED' });
    75. }
    76. // 显示未完成的
    77. const showNotFinish = () => {
    78. dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_NOT_FINISH' });
    79. }
    80. // 显示全部完成的
    81. const showAll = () => {
    82. dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_ALL' });
    83. }
    84. return {
    85. addTodo,
    86. // 切换完成状态
    87. changeTodo,
    88. showFinished,
    89. showNotFinish,
    90. showAll,
    91. };
    92. };

    ReduxProvider组件

    1. import { Provider } from "react-redux";
    2. import {combineStore} from './ProviderUtils';
    3. const ReduxProvider = (props) => {
    4. return <Provider store={combineStore}>{props.children}</Provider>;
    5. };
    6. export default ReduxProvider

    Todo组件

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

    TodoInput组件

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

    TodoItem组件

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

    TodoList组件

    1. import { TodoItem } from "./TodoItem";
    2. import _ from "lodash";
    3. import { connect } from "react-redux";
    4. import { mapDispatchToProps, mapStateToProps } from "./ProviderUtils";
    5. const TodoList0 = (props) => {
    6. const { todoList } = props;
    7. return (
    8. <div className="todo-list">
    9. {_.map(todoList, (item) => (
    10. <TodoItem key={_.get(item, "id")} todo={item || {}} />
    11. ))}
    12. </div>
    13. );
    14. };
    15. const TodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList0);
    16. export { TodoList };

    App组件使用:

    1. const App:React.FC = ()=> {
    2. return <Todo />
    3. }
    4. export default App;

    connect模式梳理一下流程:

    1.  ProviderUtils文件: 定义combineStore:
    1. 1) 引入import { combineReducers, createStore } from "redux";
    2. 2)  定义reducer方法们:
    3. // todos:新增,修改列表方法(返回列表数据, 否则返回state)
    4. const todos = (state: StateProps[] | [], action: ActionProps) => {
    5.   console.log(state, action);
    6.   switch (action.type) {
    7.     case "ADD":
    8.       return [...state, action.todo];
    9.     case "CHANGESTATUS":
    10.       return state.map((item) => {
    11.         if (item.id === action.id) {
    12.           return Object.assign({}, item, { isFinished: !item.isFinished });
    13.         }
    14.         return item;
    15.       });
    16.     default:
    17.       return state || [];
    18.   }
    19. };
    20. // visibilityFilter: 筛选列表方法(返回筛选器, 否则返回state)
    21. const visibilityFilter = (state = 'SHOW_ALL', action) => {
    22.   switch (action.type) {
    23.     // 设置显示类型(所有、完成、未完成)
    24.     case 'SET_VISIBILITY_FILTER':
    25.       return action.filter
    26.     default:
    27.       return state
    28.   }
    29. }
    30. 3) 得到combineStore
    31. export const combineStore = createStore(combineReducers({ todos, visibilityFilter }));

    2. 完成ReduxProvider组件

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

    2) combineStore从第1步获取

    const ReduxProvider = (props) => {

            return {props.children};

    };

    3. 使用ReduxProvider:

    export const Todo = () => {
        return (
           
               
               
           

        )
    }

    4. 子组件们使用connect:

    import { connect } from "react-redux";

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

    export default 子组件-新;

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

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

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

    1. // 不同类型的 todo 列表
    2. const getVisibleTodos = (todos, filter) => {
    3. switch (filter) {
    4. case "SHOW_ALL": // 全部显示
    5. return todos;
    6. case "SHOW_FINISHED":
    7. return todos.filter((t) => t.isFinished);
    8. case "SHOW_NOT_FINISH":
    9. return todos.filter((t) => !t.isFinished);
    10. default:
    11. return todos;
    12. }
    13. };
    14. export const mapStateToProps = (state) => {
    15. return {
    16. // 根据完成状态,筛选数据
    17. todoList: getVisibleTodos(state.todos, state.visibilityFilter),
    18. };
    19. };
    20. export const mapDispatchToProps = (dispatch) => {
    21. const changeTodo = (id: number) => {
    22. dispatch({ type: "CHANGESTATUS", id });
    23. };
    24. // 添加todo
    25. const addTodo = (todo: StateProps) => {
    26. dispatch({ type: "ADD", todo });
    27. };
    28. // 显示已完成的
    29. const showFinished = () => {
    30. dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_FINISHED' });
    31. }
    32. // 显示未完成的
    33. const showNotFinish = () => {
    34. dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_NOT_FINISH' });
    35. }
    36. // 显示全部完成的
    37. const showAll = () => {
    38. dispatch({ type: "SET_VISIBILITY_FILTER", filter: 'SHOW_ALL' });
    39. }
    40. return {
    41. addTodo,
    42. // 切换完成状态
    43. changeTodo,
    44. showFinished,
    45. showNotFinish,
    46. showAll,
    47. };
    48. };

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

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

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

    以TodoItem组件为例:

    1. const TodoItem0 = (props) => {
    2. // 注意: todo是父组件直接传过来的数据, changeTodo是mapDispatchToProps钩子return出来的方法!!!
    3. const { todo, changeTodo } = props;
    4. const handleChange = () => {
    5. changeTodo(_.get(todo, "id"));
    6. };
    7. return (
    8. <div className="todo-item">
    9. <input
    10. type="checkbox"
    11. checked={todo.isFinished}
    12. onChange={handleChange}
    13. />
    14. <span
    15. style={{ textDecoration: todo.isFinished ? "line-through" : "none" }}
    16. >
    17. {todo.text}
    18. </span>
    19. </div>
    20. );
    21. };
    22. const TodoItem = connect(mapStateToProps, mapDispatchToProps)(TodoItem0);
    23. export { TodoItem };

    connect模式效果图如下:

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

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

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

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

  • 相关阅读:
    nodejs+java+python家乡美食分享推荐网站系统vue+elementui
    Linux--环境变量
    自己封装 vue3+ts 组件库并且发布到 NPM
    实时选品系统实现的难点
    关于conda占C盘内存的问题
    个人信息保护专业人员认证(CCRC-PIPP)
    proxy代理服务
    Vue_Bug NPM下载速度过慢
    教你怎么爬元气桌面的壁纸和视频
    Python: 数据类型转换总结(list-np.array-torch.tensor)
  • 原文地址:https://blog.csdn.net/qq_42750608/article/details/132948353