一. 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文档地址:
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展示所有数据