当前位置: 首页 > news >正文

从 0 开始搭建 React 框架

webpack 配置

不再赘述,可参考前三个文章(wenpack5 基本使用 1 - 3

使用 react

安装 react、react-dom、@babel/preset-react

yarn add react react-dom @babel/preset-react
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body><div id="root"></div>
</body>
</html>
// index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';const container = document.getElementById('root'),root = createRoot(container);root.render(<React.StrictMode><App /></React.StrictMode>
);
// class 组件
import React, { PureComponent } from 'react';class App extends PureComponent {constructor(props) {super(props);this.state = {name: 'zhangsan',age: 20};this.changeToLisi = this.changeToLisi.bind(this);}changeToLisi() {this.setState({name: 'lisi'});}changeAge = () => {this.setState({age: 30});}render() {return <div><p>Tis is a class component.</p><p>姓名:{ this.state.name }</p><p>年龄:{ this.state.age }</p><button onClick={this.changeToLisi}>切换名字</button><button onClick={this.changeAge}>切换年龄</button></div>;}
}export default App;
// 函数组件
import React from 'react';const App = () => {return <p>This is react compoment</>;
};export default App;
// .babelrc
{"presets": ["@babel/preset-env", // 不配置这个 class 组件写箭头函数会报错"@babel/preset-react"],"plugins": ["@babel/plugin-transform-runtime"]
}
module.exports = {module: {rules: [// 处理 js,将 es6 转为 es5{test: /\.(js|jsx)$/, // 追加 jsxexclude: /node_modules/,use: ['babel-loader']}}}
};

配置 eslint 和 prettier

webpack 基本配置中已安装 eslint、eslint-webpack-plugin、@babel/eslint-parser、eslint-plugin-import、stylelint-webpack-plugin

安装 react 相关的 eslinteslint-plugin-react、eslint-plugin-react-hooks

yarn add eslint-plugin-react、eslint-plugin-react-hooks

安装 prettier、eslint-config-prettier、eslint-plugin-prettier

yarn add prettier eslint-config-prettier eslint-plugin-prettier -D
// .prettierrc.js
module.exports = {// 行宽 default:80printWidth: 110,// tab 宽度 default:2tabWidth: 4,// 使用 tab 键 default:falseuseTabs: false,// 语句行末是否添加分号 default:truesemi: true,// 是否使用单引号 default:falsesingleQuote: true,// 对象需要引号在加 default:"as-needed"quoteProps: 'as-needed',// jsx单引号 default:falsejsxSingleQuote: true,// 最后一个对象元素加逗号 default:"es5"// trailingComma: 'es5',trailingComma: 'none',// 在对象字面量声明所使用的的花括号后({)和前(})输出空格 default:truebracketSpacing: true,// 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素)。default:falsejsxBracketSameLine: true,// (x) => {} 是否要有小括号 default:"always"arrowParens: 'avoid',// default:0rangeStart: 0,// default:InfinityrangeEnd: Infinity,// default:falseinsertPragma: false,// default:falserequirePragma: false,// 不包装 markdown text default:"preserve"proseWrap: 'never',// HTML空白敏感性 default:"css"htmlWhitespaceSensitivity: 'strict',// 在 *.vue 文件中 Script 和 Style 标签内的代码是否缩进 default:falsevueIndentScriptAndStyle: true,// 末尾换行符 default:"lf"endOfLine: 'auto',// default:"auto"embeddedLanguageFormatting: 'auto',overrides: [{files: '*.md',options: {tabWidth: 2}}]
};
module.exports = {root: true,env: {browser: true,node: true},globals: {},extends: ['eslint:recommended','plugin:prettier/recommended','plugin:react/recommended','plugin:react-hooks/recommended'],plugins: ['import', 'prettier'],parserOptions: {parser: '@babel/eslint-parser',sourceType: 'module',ecmaVersion: 2021,ecmaFeatures: {jsx: true,experimentalObjectRestSpread: true}},rules: [// ...]
};

配置 redux

官方已推荐使用 @reduxjs/toolkit(地址:https://cn.redux.js.org/introduction/why-rtk-is-redux-today)

使用传统 redux

安装 redux、react-redux、redux-thunk

yarn add redux react-redux redux-thunk
// src/store-redux/index.js
import {createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { thunk } from 'redux-thunk';
import { reducer as page1Reducer } from '@pages/page1/reducer';const combinedReducerObj = {page1: combineReducers(page1Reducer)
};const store = createStore(combineReducers(combinedReducerObj),compose(applyMiddleware(thunk)
));export default store;
// src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store-redux/index';
import Page1 from '@pages/page1/index.jsx';const App = () => {return <Provider store={store}><Page1 /></Provider>;
};export default App;
// src/pages/page1/reducer.js// 定义 actionType
const ADD_TODO = 'ADD_TODO';
const TODO_TOGGLED = 'TODO_TOGGLED';
const SET_DATA = 'SET_DATA';// 定义 reducer
const todos = (state = [{ id: 1, text: 'zhangsan', completed: false }], action) => {switch (action.type) {case ADD_TODO:return state.concat({id: action.payload.id,text: action.payload.text,completed: false});case TODO_TOGGLED:return state.map(todo => {if (todo.id !== action.payload.id) return todoreturn {...todo,completed: !todo.completed}});default:return state}
};const dataList = (state = [], action) => {if (action.type === SET_DATA) {return action.payload;}return state;
};export const reducer = {todos,dataList
};export const actionTypes = {ADD_TODO,TODO_TOGGLED,SET_DATA
};
// src/pages/page1/actions.js
import { actionTypes } from './reducer';
import { getDataService } from './service';// 同步 action
const addTodo = ({ text }) => {return {type: actionTypes.ADD_TODO,payload: {text,id: Math.random()}};
};const todoToggled = ({ id }) => {return {type: actionTypes.TODO_TOGGLED,payload: {id}};
};const setDataList = data => {return {type: actionTypes.SET_DATA,payload: data};
};// 异步 action
const thunkGetDataList = (data) => {return (dispatch, getState) => {// dispatch(addTodo({ text: 'lisi'}));const page1State = getState().page1;console.log(page1State);getDataService().then(res => {dispatch(setDataList(res.data))});};
};export default {// 同步 actionaddTodo,todoToggled,// 异步 actionsetDataList,thunkGetDataList
};
// src/pages/page1/service.js
// 模拟接口获取数据
export const getDataService  = (params) => {console.log(params);return new Promise((resolve, reject) => {const res= {data: [{id: 1,name: 'zhangsan',age: 20},{id: 2,name: 'lisi',age: 30}]};setTimeout(() => {resolve(res);}, 3000)});
};
// src/pages/page1/index.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import actions from './actions';const Page1 = ({todos,dataList,addTodo,getData}) => {useEffect(() => {getData();}, []);return <div><p>This is App Component.</p>{dataList.map((item, index) => {return (<div key={item.id}><p>id: {item.id};姓名: {item.name};年龄:{item.age}</p></div>);})}<p>-------------------------------------------</p>{todos.map((item, index) => {return (<div key={item.id}><p>id: {item.id};text: {item.text};compoleted: {item.completed}</p></div>);})}<button onClick={addTodo}>添加</button></div>;
};const mapStateToProps = (state, props) => {// let stateCommon = state.common;const statePage1 = state.page1;return {todos: statePage1.todos,dataList: statePage1.dataList};},mapDispatchToProps = (dispatch, props) => {return {addTodo: () => {dispatch(actions.addTodo({ id: 4, text: 'zzz' }));},getData: () => {dispatch(actions.thunkGetDataList());}};};export default connect(mapStateToProps,mapDispatchToProps
)(React.memo(Page1));

使用 @reduxjs/toolkit

安装 @reduxjs/toolkit、react-redux

yarn add @reduxjs/toolkit react-redux

官方使用方案

创建 store
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import commonReducer from './common/reducer';
import HomeReducer from '@pages/home/reducer';export const store = configureStore({// 合并所有的 reducerreducer: {common: commonReducer,home: HomeReducer},// 解决 redux 无法序列化 数据时的报错middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false })
});export default store.dispatch;
创建 reducerSlice
  • configureStore 创建 store
  • 在根节点通过 Provider 传入 store
  • createSlice 创建 reducer 切片:传入 name、初始状态、定义 reducer,生成 reduceractions
  • 页面上使用:通过 useSlector 获取状态;useDispatch 分发 action
// src/page2/reducer.js
import { createSlice } from '@reduxjs/toolkit';const todosSlice = createSlice({name: 'page2', // 命名空间initialState: {counter: 0,todoList: []}, // 初始值reducers: {counterIncrement: state => {// Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它// 并不是真正的改变状态值,因为它使用了 Immer 库// 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的// 不可变的状态state.counter += 1;},counterDecrement: state => {state.counter -= 1;},counterIncrementByAmount: (state, action) => {state.counter += action.payload;},todoListAdded(state, action) {state.todoList.push({id: action.payload.id,text: action.payload.text,completed: false});},todoListToggled(state, action) {const todoItem = state.todoList.find(todo => todo.id === action.payload);todoItem.completed = !todoItem.completed;}}
});// 普通 action
export const {counterDecrement,counterIncrement,counterIncrementByAmount,todoListAdded,todoListToggled
} = todosSlice.actions;export const actions = todosSlice.actions;// reducer
export default todosSlice.reducer;
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import page2Reducer from '@pages/page2/reducer';export const store = configureStore({// 传入所有的 reducer, configureStore 会帮我们自动合并 reducerreducer: {page2: page2Reducer}
});
// src/pages/page2/index.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { actions } from './reducer';const Page2 = () => {const { counter, todoList } = useSelector(state => state.page2), // 获取状态dispatch = useDispatch();return <div><p>This is App Component.</p><p>计数器:{ counter }</p>{todoList.map((item, index) => {return (<div key={item.id}><p>id: {item.id}</p><p>text: {item.text}</p><p>completed: {String(item.completed)}</p></div>);})}<button onClick={() => dispatch(actions.counterIncrement())}>计数器添加</button><button onClick={() => dispatch(actions.counterDecrement())}>计数器减少</button><button onClick={() => dispatch(actions.counterIncrementByAmount(5))}>计数器每次+5</button><button onClick={() => dispatch(actions.todoListAdded({ id: 1, text: 'zhangsan' }))}>添加</button><button onClick={() => dispatch(actions.todoListToggled(1))}>切换状态</button></div>;
};export default React.memo(Page2);
处理异步操作 createAsyncThunk

传统 redux 需要使用安装 redux-thunk
redux toolkit 使用 createAsyncThunk API 简化异步调用。

// src/pages/page2/asyncActions.js
import {  createAsyncThunk } from '@reduxjs/toolkit';
import { actions } from './reducer';// 模拟接口获取数据
const getDataService  = (params) => {console.log(params);return new Promise((resolve, reject) => {const res= {data: [{id: 1,name: 'zhangsan',age: 20},{id: 2,name: 'lisi',age: 30}]};setTimeout(() => {resolve(res);}, 3000)});
};// 定义异步 action
export const asyncGetData = createAsyncThunk('page2/fetchPosts', async (payload, { dispatch, getState }) => {// 通过 payload 可以接收数据const { params } = payload;// 通过 getState 可以获取 storeconst { counter } = getState().page2;console.log(counter);// 通过 dispatch 可以分发 action// 在 createAsyncThunk 可以 dispatch 普通的 actiondispatch(actions.counterIncrement());// 在 createAsyncThunk 也可以 dispatch 异步 actiondispatch(asyncSetLocale({ locale: 'pl' }));const response = await getDataService(params);// 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 statereturn {dataList: response.data};
});export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {return {locale: payload.locale,menuList: [{id: 11,menuName: '电站'}]};});// 导出异步 actions
const asyncActions = {asyncGetData,asyncSetLocale
};export default asyncActions;
// src/pages/page2/reducer.js
import { createSlice } from '@reduxjs/toolkit';
import asyncActions from './asyncAtions';const todosSlice = createSlice({name: 'page2', // 命名空间initialState: {counter: 0,todoList: [],dataList: [], // 接口返回的数据locale: 'en',menuList: []}, // 初始值reducers: {counterIncrement: state => {// Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它// 并不是真正的改变状态值,因为它使用了 Immer 库// 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的// 不可变的状态state.counter += 1;},counterDecrement: state => {state.counter -= 1;},},extraReducers: builder => {builder.addCase(asyncActions.asyncGetData.fulfilled, (state, action) => {// 接收 createAsyncThunk 中 return 的数据const { dataList } = action.payload;// 设置 statestate.dataList = dataList;});builder.addCase(asyncActions.asyncSetLocale.fulfilled, (state, action) => {const {locale,menuList} = action.payload;state.locale = locale;state.menuList = menuList;});}
});// 普通 action
export const {counterDecrement,counterIncrement,
} = todosSlice.actions;export const actions = todosSlice.actions;// reducer
export default todosSlice.reducer;
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import asyncActions from './asyncAtions';const Home = () => {const { counter, todoList, locale, menuList, dataList } = useSelector(state => state.page2), // 获取状态dispatch = useDispatch();useEffect(() => {// 调用异步 actiondispatch(asyncActions.asyncGetData({ query: '参数1' }));}, []);return <div><p>This is App Component.</p>{/* <p>计数器:{ counter }</p> */}<p>当前语言: { locale }</p>{dataList.map((item, index) => {return (<div key={item.id}><p>id: {item.id};姓名: {item.name};年龄:{item.age}</p></div>);})}<p>-------------------------------------------</p>{menuList.map((item, index) => {return (<div key={item.id}><p>id: {item.id};菜单: {item.menuName}</p></div>);})}</div>;
};export default React.memo(Home);

简化 reducerSlice 和 createAsyncThunk

简化 createAsyncThunk

由于 createAsyncThunk 内部就可以直接 dispatch 一个普通的 action,就可以直接在这里面通过 dispatch(actions.setXxx('zhangsan')) 的方式修改 state,不需要 return 数据,这样就不用在 createSlice 中编写 extraReducers

// src/pages/page2/asyncActions.js
import {  createAsyncThunk } from '@reduxjs/toolkit';
import { actions } from './reducer';// 模拟接口获取数据
const getDataService  = (params) => {console.log(params);return new Promise((resolve, reject) => {const res= {data: [{id: 1,name: 'zhangsan',age: 20},{id: 2,name: 'lisi',age: 30}]};setTimeout(() => {resolve(res);}, 3000)});
};// 定义异步 action
export const asyncGetData = createAsyncThunk('page2/fetchPosts', async (payload, { dispatch, getState }) => {// 通过 payload 可以接收数据const { params } = payload;// 通过 getState 可以获取 storeconst { counter } = getState().page2;console.log(counter);// 通过 dispatch 可以分发 action// 在 createAsyncThunk 可以 dispatch 普通的 actiondispatch(actions.counterIncrement());// 在 createAsyncThunk 也可以 dispatch 异步 actiondispatch(asyncSetLocale({ locale: 'pl' }));const response = await getDataService(params);// 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 state// return {//     dataList: response.data// };// 直接在这里 setStatedispatch(actions.setDataList(response.data));
});export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {// return {//     locale: payload.locale,//     menuList: [{//         id: 11,//         menuName: '电站'//     }]// };// 直接在这里 setStatedispatch(actions.setLocale(payload.locale));dispatch(actions.setMenuList([{ id: 11, menuName: '电站' }]));});// 导出异步 actions
const asyncActions = {asyncGetData,asyncSetLocale
};export default asyncActions;
// src/pages/page2/reducer.js
import { createSlice } from '@reduxjs/toolkit';
import asyncActions from './asyncAtions';const todosSlice = createSlice({name: 'page2', // 命名空间initialState: {counter: 0,todoList: [],dataList: [], // 接口返回的数据locale: 'en',menuList: []}, // 初始值reducers: {counterIncrement: state => {// Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它// 并不是真正的改变状态值,因为它使用了 Immer 库// 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的// 不可变的状态state.counter += 1;},counterDecrement: state => {state.counter -= 1;},setDataList(state, action) {state.dataList = action.payload;},setLocale(state, action) {state.locale = action.payload;},setMenuList(state, action) {state.menuList = action.payload;}},// extraReducers: builder => {//     builder.addCase(asyncActions.asyncGetData.fulfilled, (state, action) => {//         // 接收 createAsyncThunk 中 return 的数据//         const { dataList } = action.payload;////         // 设置 state//         state.dataList = dataList;//     });////     builder.addCase(asyncActions.asyncSetLocale.fulfilled, (state, action) => {//         const {//             locale,//             menuList//         } = action.payload;////         state.locale = locale;//         state.menuList = menuList;//     });// }
});export const actions = todosSlice.actions;// reducer
export default todosSlice.reducer;
// src/pages/page2/index.jsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import asyncActions from './asyncAtions';const Home = () => {const { counter, todoList, locale, menuList, dataList } = useSelector(state => state.page2), // 获取状态dispatch = useDispatch();useEffect(() => {dispatch(asyncActions.asyncGetData({ query: '参数1' }));}, []);return <div><p>This is App Component.</p>{/* <p>计数器:{ counter }</p> */}<p>当前语言: { locale }</p>{dataList.map((item, index) => {return (<div key={item.id}><p>id: {item.id};姓名: {item.name};年龄:{item.age}</p></div>);})}<p>-------------------------------------------</p>{menuList.map((item, index) => {return (<div key={item.id}><p>id: {item.id};菜单: {item.menuName}</p></div>);})}</div>;
};export default React.memo(Home);

如果你还是想在 cerateAsyncThunk 中通过 return 的形式返回状态。那么可以优化 extraReducers 的写法,前提是 return 一个对象。

// src/store/createExtraReducers.js
export const createExtraReducers = (actions = {}) => {const extraReducers = {};for (const action in actions) {if (actions.hasOwnProperty(action)) {// action 的异步任务执行完成才修改 stateextraReducers[actions[action].fulfilled] = (state, { payload }) => {for (const key in payload) {if (Object.hasOwnProperty.call(payload, key)) {if (key !== 'callback') {state[key] = payload[key];} else {payload.callback && payload.callback();}}}};}}return extraReducers;
};
// src/pages/page2/reducer.js
import { createSlice } from '@reduxjs/toolkit';
import asyncActions from './asyncAtions';
import { createExtraReducers } from '@store/createExtraReducers.js';const extraReducers = createExtraReducers(asyncActions);const todosSlice = createSlice({name: 'page2', // 命名空间initialState: {counter: 0,todoList: [],dataList: [], // 接口返回的数据locale: 'en',menuList: []}, // 初始值reducers: {counterIncrement: state => {// Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它// 并不是真正的改变状态值,因为它使用了 Immer 库// 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的// 不可变的状态state.counter += 1;},counterDecrement: state => {state.counter -= 1;},setDataList(state, action) {state.dataList = action.payload;},setLocale(state, action) {state.locale = action.payload;},setMenuList(state, action) {state.menuList = action.payload;}},extraReducers: builder => {for (const actionCase in extraReducers) {if (extraReducers.hasOwnProperty(actionCase)) {builder.addCase(actionCase, (state, action) =>extraReducers[actionCase](state, action));}}}
});export const actions = todosSlice.actions;// reducer
export default todosSlice.reducer;
import {  createAsyncThunk } from '@reduxjs/toolkit';
import { actions } from './reducer';// 模拟接口获取数据
const getDataService  = (params) => {console.log(params);return new Promise((resolve, reject) => {const res= {data: [{id: 1,name: 'zhangsan',age: 20},{id: 2,name: 'lisi',age: 30}]};setTimeout(() => {resolve(res);}, 3000)});
};// 定义异步 action
export const asyncGetData = createAsyncThunk('page2/fetchPosts', async (payload, { dispatch, getState }) => {// 通过 payload 可以接收数据const { params } = payload;// 通过 getState 可以获取 storeconst { counter } = getState().page2;console.log(counter);// 通过 dispatch 可以分发 action// 在 createAsyncThunk 可以 dispatch 普通的 actiondispatch(actions.counterIncrement());// 在 createAsyncThunk 也可以 dispatch 异步 actiondispatch(asyncSetLocale({ locale: 'pl' }));const response = await getDataService(params);// 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 statereturn {dataList: response.data};
});export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {return {locale: payload.locale,menuList: [{id: 11,menuName: '电站'}]};});// 导出异步 actions
const asyncActions = {asyncGetData,asyncSetLocale
};export default asyncActions;
简化 createSlice

由于需要给每个 state 定义至少一个修改 state 的方法,为了简化这一步,其实每个 state 只需要定义一个 setState 方法就可以了,处理数据可以放在外部去处理,我们只要传递 newState 即可。

比如定一个 name,修改它直接通过 dispatch(actions.setName('zhangsan'))

这样就可以封装一个统一的方法,来帮我们自动生成 state 对应的 setState,就不需要每个模块去单独写一遍,提升开发效率。

// src/store/createReducerSlice.js
// 工具方法
import { createSlice, createAction } from '@reduxjs/toolkit';// 为每个 state 创建一个对应的 setState 方法,
// 如果 state 是 name,那么修改 name 则通过 setName 进行修改
// dispatch(simpleActions.setName('zhangsan'))
const createSetState = initialState => {const reducers = {};for (const key in initialState) {if (initialState.hasOwnProperty(key)) {const keyName = 'set' + key.substring(0, 1).toUpperCase() + key.substring(1);reducers[keyName] = (state, { payload }) => {state[key] = payload;};}}return reducers;},createReducerSlice = ({pageIdentify, // 页面 idinitialState = {}, // 定义 state 初始值reducers = {}, // 可选项,自定义 reducer。其他更复杂的操作,比如对数组的添加/删除,如果不想在外部处理,那就在这里定义extraReducers = {} // 可选项,如果 createAsyncThunk 中 return 了需要修改的 state,那么需要传递 extraReducers,统一修改 state;}) => {const updateState = createAction('updateState'),reducerSlice = createSlice({name: pageIdentify,initialState: initialState,// 简单reducerreducers: {// 简单 reducer: 一次只能修改一个状态...createSetState(initialState),// 其他更复杂的操作,比如对数组的添加/删除,如果不想在外部处理,那就在这里定义...reducers},extraReducers: builder => {// 复杂 reducer 一次修改多个 statebuilder.addCase(updateState, (state, { payload }) => {for (const stateKey in payload) {if (payload.hasOwnProperty(stateKey)) {state[stateKey] = payload[stateKey];}}});for (const actionCase in extraReducers) {if (extraReducers.hasOwnProperty(actionCase)) {builder.addCase(actionCase, (state, action) =>extraReducers[actionCase](state, action));}}}});// 自定义 caseReducer:通过 dispatch(caseReducer.updateState({ name: 'zhangsan', age: 20 })) 可一次修改多个 statereducerSlice.caseReducer = {updateState};return reducerSlice;};export default createReducerSlice;
// src/pages/page2/asyncActions.js
import {  createAsyncThunk } from '@reduxjs/toolkit';
import { actions } from './reducer';// 模拟接口获取数据
const getDataService  = (params) => {console.log(params);return new Promise((resolve, reject) => {const res= {data: [{id: 1,name: 'zhangsan',age: 20},{id: 2,name: 'lisi',age: 30}]};setTimeout(() => {resolve(res);}, 3000)});
};// 定义异步 action
export const asyncGetData = createAsyncThunk('page2/fetchPosts', async (payload, { dispatch, getState }) => {// 通过 payload 可以接收数据const { params } = payload;dispatch(asyncSetLocale({ locale: 'pl' }));const response = await getDataService(params);dispatch(actions.setDataList(response.data));
});export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {dispatch(actions.setLocale(payload.locale));dispatch(actions.setMenuList([{ id: 11, menuName: '电站' }]));});// 导出异步 actions
const asyncActions = {asyncGetData,asyncSetLocale
};export default asyncActions;
// src/pages/page2/reducer.js
// import asyncActions from './asyncAtions';
// import { createExtraReducers } from '@store/createExtraReducers.js';
import createReducerSlice  from '@store/createReducerSlice.js';// const extraReducers = createExtraReducers(asyncActions);// 使用二次封装的 createReducerSlice
const reducerSlice = createReducerSlice({pageIdentify: 'page2',initialState: {counter: 0,todoList: [],dataList: [], // 接口返回的数据locale: 'en',menuList: []}, // 初始值// extraReducers
});// 普通 actions
export const actions = reducerSlice.actions;
export const caseReducer = reducerSlice.caseReducer;// reducer
export default reducerSlice.reducer;

配置 react-router

安装 react-router-dom

yarn add react-router-dom
// src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import { HashRouter, Navigate, Route, Routes } from 'react-router-dom';
import { store } from './store/index';
import Index from '@pages/index.jsx';
import Login from "./pages/login";
import Register from "./pages/register";
import Home from "./pages/home";
import Station from "./pages/station";
import Device from "./pages/device";const App = () => {return <HashRouter><Provider store={store}><Routes><Route path="/login" element={<Login />} /><Route path="/register" element={<Register />} />{/* 这里是嵌套路由 */}<Route path="/" element={<Index />}>{/* <Route path="/" element={<Navigate to="/home" />} /> 是为了告诉路由 访问 / 的时候跳转哪个组件 */}<Route path="/" element={<Navigate to="/home" />} /><Route path="/home" element={<Home />} /><Route path="/station" element={<Station />} /><Route path="/device" element={<Device />} /></Route></Routes></Provider></HashRouter>;
};export default App;
import React from 'react';
import Header from './components/header';
import Footer from './components/Footer';
import { Outlet } from "react-router-dom";const Index = () => {return (<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}><Header /><div style={{ width: '100%', flex: 1 }}>{/*  子路由占位  */}<Outlet /></div><Footer /></div>);
};export default React.memo(Index);

配置 Antd5

基本配置

安装 antd、babel-plugin-import

yarn add antd babel-plugin-import

babel-plugin-import 可以实现 antd 按需加载

{"presets": [["@babel/preset-env",{"useBuiltIns": "usage","corejs": {"version": 3}}],"@babel/preset-react"],"plugins": [// 配置 antd 按需加载// 如果要使用 antd 的 theme,需要去掉 babel-plugin-import// [//    "import",//    {//        "libraryName": "antd",//       "libraryDirectory": "es",//        "style": true//    }// ],"@babel/plugin-transform-runtime","@babel/plugin-transform-class-properties"]
}

自定义 antd 主题色

// src/index.js
import React from 'react';
import { Provider } from 'react-redux';
import { createRoot } from 'react-dom/client';
import App from './App.js';
import { store } from './store';
import './css/global.css';const container = document.getElementById('root'),root = createRoot(container);root.render(<React.StrictMode><Provider store={store}><App /></Provider></React.StrictMode>
);
// App.js
import React from 'react';
import { ConfigProvider } from 'antd';
import { HashRouter, Navigate, Route, Routes } from 'react-router-dom';
import Index from '@pages/index.jsx';
import useAntdTheme from '@utils/useAntdTheme';
import Login from './pages/login';
import Register from './pages/register';
import Home from './pages/home';
import Station from './pages/station';
import Device from './pages/device';const App = () => {const { antdThemeCustom } = useAntdTheme();return (<HashRouter><ConfigProvider theme={antdThemeCustom}><Routes><Route path='/login' element={<Login />} /><Route path='/register' element={<Register />} /><Route path='/' element={<Index />}><Route path='/' element={<Navigate to='/home' />} /><Route path='/home' element={<Home />} /><Route path='/station' element={<Station />} /><Route path='/device' element={<Device />} /></Route></Routes></ConfigProvider></HashRouter>);
};export default App;
// src/css/theme.js
export default {light: {customYellow1: '#f3cb60',customYellow2: '#332c0a'},night: {customYellow1: '#390d91',customYellow2: '#06046b'},pink: {customYellow1: '#d19dee',customYellow2: '#8d5baf'}
};
// src/common/utils/useAntdTheme.js
import themeMap from '../../css/theme.js';
import { useSelector } from 'react-redux';const useAntdTheme = () => {const { theme } = useSelector(state => state.common),themeColor = theme && themeMap[theme] ? themeMap[theme] : themeMap.light,antdThemeCustom = {token: {colorPrimary: themeColor.customYellow1},components: {// Button: {//     colorPrimary: themeColor.customYellow1,//     algorithm: true // 启用算法// }}};return {antdThemeCustom,themeColor};
};export default useAntdTheme;
// src/pages/home/index.jsx
import React from 'react';
import { Button } from 'antd';
import { useDispatch } from 'react-redux';
import { simpleActions as commonSimpleActions } from '@store/common/reducer';
import useAntdTheme from '../../common/utils/useAntdTheme';
import styles from './style.scss';const Home = () => {const dispatch = useDispatch(),{ themeColor } = useAntdTheme();console.log(themeColor);return (<div><p className={styles.text}>This is App Component.</p><p style={{ color: themeColor.customYellow1 }}>自定义主题色</p><Button type={'primary'}>antd 主题色按钮</Button><br /><button onClick={() => dispatch(commonSimpleActions.setTheme('night'))}>切换为深色</button><button onClick={() => dispatch(commonSimpleActions.setTheme('light'))}>切换为浅色</button><button onClick={() => dispatch(commonSimpleActions.setTheme('pink'))}>切换为粉色</button></div>);
};export default React.memo(Home);

相关文章:

从 0 开始搭建 React 框架

webpack 配置 不再赘述&#xff0c;可参考前三个文章&#xff08;wenpack5 基本使用 1 - 3&#xff09; 使用 react 安装 react、react-dom、babel/preset-react yarn add react react-dom babel/preset-react<!DOCTYPE html> <html lang"en"> <h…...

网站地址怎么改成HTTPS?

现在&#xff0c;所有类型的网站都需要通过 HTTPS 协议进行安全连接&#xff0c;而实现这一目标的唯一方法是使用 SSL 证书。如果您不将 HTTP 转换为 HTTPS&#xff0c;浏览器和应用程序会将您网站的连接标记为不安全。 但用户询问如何将我的网站从 HTTP 更改为 HTTPS。在此页…...

Blender教程(基础)-面的细分与删除、挤出选区-07

一、Blender之面的细分 新建一个立方体&#xff0c;在编辑模式下、选中一个面。 在选中的面上单击右键弹出细分选项&#xff0c;选择细分。 在选中细分后、会默认细分1次。修改细分次数在左下角 二、Blender之面的删除 选择中需要操作的面&#xff0c;在英文状态下按X键弹…...

QT自制软键盘 最完美、最简单、支持中文输入(二)

目录 一、前言 二、本自制虚拟键盘特点 三、中文输入原理 四、组合键输入 五、键盘事件模拟 六、界面 七、代码 7.1 frmKeyBoard 头文件代码 7.2 frmKeyBoard 源文件代码 八、使用示例 九、效果 十、结语 一、前言 由于系统自带虚拟键盘不一定好用&#xff0c;也不一…...

SpringCloud_学习笔记_1

SpringCloud01 1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.0.学习目标 了解微服务架构的优缺点 1.1.单体架构 单体架构&#xff…...

容器算法迭代器初识

#include<iostream> using namespace std; #include<vector> //vetor容器存放内置数据类型 void test01() {//创建了一个vector容器&#xff0c;数组 vector<int> v;//向容器中插入数据v.push_back (10);//尾插 v.push_back (20);v.push_back (30);v.push_ba…...

瑞_力扣LeetCode_二叉搜索树相关题

文章目录 说明题目 450. 删除二叉搜索树中的节点题解递归实现 题目 701. 二叉搜索树中的插入操作题解递归实现 题目 700. 二叉搜索树中的搜索题解递归实现 题目 98. 验证二叉搜索树题解中序遍历非递归实现中序遍历递归实现上下限递归 题目 938. 二叉搜索树的范围和题解中序遍历…...

python爬虫爬取网站

流程&#xff1a; 1.指定url(获取网页的内容) 爬虫会向指定的URL发送HTTP请求&#xff0c;获取网页的HTML代码&#xff0c;然后解析HTML代码&#xff0c;提取出需要的信息&#xff0c;如文本、图片、链接等。爬虫请求URL的过程中&#xff0c;还可以设置请求头、请求参数、请求…...

c# Get方式调用WebAPI,WebService等接口

/// <summary> /// 利用WebRequest/WebResponse进行WebService调用的类 /// </summary> public class WebServiceHelper {//<webServices>// <protocols>// <add name"HttpGet"/>// <add name"HttpPost"/>// …...

银行数据仓库体系实践(11)--数据仓库开发管理系统及开发流程

数据仓库管理着整个银行或公司的数据&#xff0c;数据结构复杂&#xff0c;数据量庞大&#xff0c;任何一个数据字段的变化或错误都会引起数据错误&#xff0c;影响数据应用&#xff0c;同时业务的发展也带来系统不断升级&#xff0c;数据需求的不断增加&#xff0c;数据仓库需…...

微信小程序引导用户打开定位授权通用模版

在需要使用位置信息的页面&#xff08;例如 onLoad 或 onShow 生命周期函数&#xff09;中调用 wx.getSetting 方法检查用户是否已经授权地理位置权限&#xff1a; Page({onLoad: function() {wx.getSetting({success: res > {if (res.authSetting[scope.userLocation]) {/…...

JVM篇----第十篇

系列文章目录 文章目录 系列文章目录前言一、JAVA 强引用二、JAVA软引用三、JAVA弱引用四、JAVA虚引用五、分代收集算法前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧…...

DevSecOps 参考模型介绍

目录 一、参考模型概述 1.1 概述 二、参考模型分类 2.1 DevOps 组织型模型 2.1.1 DevOps 关键特性 2.1.1.1 模型特性图 2.1.1.2 特性讲解 2.1.1.2.1 自动化 2.1.1.2.2 多边协作 2.1.1.2.3 持续集成 2.1.1.2.4 配置管理 2.1.2 DevOps 生命周期 2.1.2.1 研发过程划分…...

什么是okhttp?

OkHttp简介&#xff1a; OkHttp 是一个开源的、高效的 HTTP 客户端库&#xff0c;由 Square 公司开发和维护。它为 Android 和 Java 应用程序提供了简单、强大、灵活的 HTTP 请求和响应的处理方式。OkHttp 的设计目标是使网络请求变得更加简单、快速、高效&#xff0c;并且支持…...

R语言基础学习-02 (此语言用途小众 用于数学 生物领域 基因分析)

变量 R 语言的有效的变量名称由字母&#xff0c;数字以及点号 . 或下划线 _ 组成。 变量名称以字母或点开头。 变量名是否正确原因var_name2.正确字符开头&#xff0c;并由字母、数字、下划线和点号组成var_name%错误% 是非法字符2var_name错误不能数字开头 .var_name, var.…...

CTF-WEB的入门真题讲解

EzLogin 第一眼看到这个题目我想着用SQL注入 但是我们先看看具体的情况 我们随便输入admin和密码发现他提升密码不正确 我们查看源代码 发现有二个不一样的第一个是base64 意思I hava no sql 第二个可以看出来是16进制转化为weak通过发现是个弱口令 canyouaccess 如果…...

【C项目】顺序表

简介&#xff1a;本系列博客为C项目系列内容&#xff0c;通过代码来具体实现某个经典简单项目 适宜人群&#xff1a;已大体了解C语法同学 作者留言&#xff1a;本博客相关内容如需转载请注明出处&#xff0c;本人学疏才浅&#xff0c;难免存在些许错误&#xff0c;望留言指正 作…...

【Docker】在Windows下使用Docker Desktop创建nginx容器并访问默认网站

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…...

详讲api网关之kong的基本概念及安装和使用(二)

consul的服务注册与发现 如果不知道consul的使用&#xff0c;可以点击上方链接&#xff0c;这是我写的关于consul的一篇文档。 upstreamconsul实现负载均衡 我们知道&#xff0c;配置upstream可以实现负载均衡&#xff0c;而consul实现了服务注册与发现&#xff0c;那么接下来…...

取消Vscode在输入符号时自动补全

取消Vscode在输入符号时自动补全 取消Vscode在输入符号时自动补全问题演示解决方法 取消Vscode在输入符号时自动补全 问题演示 在此状态下输入/会直接自动补全, 如下图 笔者想要达到的效果为可以正常输入/而不进行补全, 如下图 解决方法 在设置->文本编辑器->建议, 取消…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...