不再赘述,可参考前三个文章(wenpack5
基本使用 1 - 3
)
安装 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>Documenttitle>
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)$/, // 追加 jsx
exclude: /node_modules/,
use: ['babel-loader']
}
}
}
};
在webpack
基本配置中已安装 eslint、eslint-webpack-plugin、@babel/eslint-parser、eslint-plugin-import、stylelint-webpack-plugin
。
安装 react
相关的 eslint
:eslint-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:80
printWidth: 110,
// tab 宽度 default:2
tabWidth: 4,
// 使用 tab 键 default:false
useTabs: false,
// 语句行末是否添加分号 default:true
semi: true,
// 是否使用单引号 default:false
singleQuote: true,
// 对象需要引号在加 default:"as-needed"
quoteProps: 'as-needed',
// jsx单引号 default:false
jsxSingleQuote: true,
// 最后一个对象元素加逗号 default:"es5"
// trailingComma: 'es5',
trailingComma: 'none',
// 在对象字面量声明所使用的的花括号后({)和前(})输出空格 default:true
bracketSpacing: true,
// 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素)。default:false
jsxBracketSameLine: true,
// (x) => {} 是否要有小括号 default:"always"
arrowParens: 'avoid',
// default:0
rangeStart: 0,
// default:Infinity
rangeEnd: Infinity,
// default:false
insertPragma: false,
// default:false
requirePragma: false,
// 不包装 markdown text default:"preserve"
proseWrap: 'never',
// HTML空白敏感性 default:"css"
htmlWhitespaceSensitivity: 'strict',
// 在 *.vue 文件中 Script 和 Style 标签内的代码是否缩进 default:false
vueIndentScriptAndStyle: 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: [
// ...
]
};
官方已推荐使用 @reduxjs/toolkit
(地址:https://cn.redux.js.org/introduction/why-rtk-is-redux-today)
安装 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 todo
return {
...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 {
// 同步 action
addTodo,
todoToggled,
// 异步 action
setDataList,
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、react-redux
yarn add @reduxjs/toolkit react-redux
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import commonReducer from './common/reducer';
import HomeReducer from '@pages/home/reducer';
export const store = configureStore({
// 合并所有的 reducer
reducer: {
common: commonReducer,
home: HomeReducer
},
// 解决 redux 无法序列化 数据时的报错
middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false })
});
export default store.dispatch;
configureStore
创建 store
Provider
传入 store
createSlice
创建 reducer
切片:传入 name
、初始状态、定义 reducer
,生成 reducer
和 actions
;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 会帮我们自动合并 reducer
reducer: {
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);
传统 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 可以获取 store
const { counter } = getState().page2;
console.log(counter);
// 通过 dispatch 可以分发 action
// 在 createAsyncThunk 可以 dispatch 普通的 action
dispatch(actions.counterIncrement());
// 在 createAsyncThunk 也可以 dispatch 异步 action
dispatch(asyncSetLocale({ locale: 'pl' }));
const response = await getDataService(params);
// 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 state
return {
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;
// 设置 state
state.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(() => {
// 调用异步 action
dispatch(asyncActions.asyncGetData({ query: '参数1' }));
}, []);
return <div>
<p>This is App Component.</p>
{/* 计数器:{ counter }
*/}
<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);
由于 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 可以获取 store
const { counter } = getState().page2;
console.log(counter);
// 通过 dispatch 可以分发 action
// 在 createAsyncThunk 可以 dispatch 普通的 action
dispatch(actions.counterIncrement());
// 在 createAsyncThunk 也可以 dispatch 异步 action
dispatch(asyncSetLocale({ locale: 'pl' }));
const response = await getDataService(params);
// 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 state
// return {
// dataList: response.data
// };
// 直接在这里 setState
dispatch(actions.setDataList(response.data));
});
export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {
// return {
// locale: payload.locale,
// menuList: [{
// id: 11,
// menuName: '电站'
// }]
// };
// 直接在这里 setState
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 { 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>
{/* 计数器:{ counter }
*/}
<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 的异步任务执行完成才修改 state
extraReducers[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 可以获取 store
const { counter } = getState().page2;
console.log(counter);
// 通过 dispatch 可以分发 action
// 在 createAsyncThunk 可以 dispatch 普通的 action
dispatch(actions.counterIncrement());
// 在 createAsyncThunk 也可以 dispatch 异步 action
dispatch(asyncSetLocale({ locale: 'pl' }));
const response = await getDataService(params);
// 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 state
return {
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;
由于需要给每个 state 定义至少一个修改 state 的 reducer,为了简化这一步,其实每个 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, // 页面 id
initialState = {}, // 定义 state 初始值
reducers = {}, // 可选项,自定义 reducer。其他更复杂的操作,比如对数组的添加/删除,如果不想在外部处理,那就在这里定义
extraReducers = {} // 可选项,如果 createAsyncThunk 中 return 了需要修改的 state,那么需要传递 extraReducers,统一修改 state;
}) => {
const updateState = createAction('updateState'),
reducerSlice = createSlice({
name: pageIdentify,
initialState: initialState,
// 简单reducer
reducers: {
// 简单 reducer: 一次只能修改一个状态
...createSetState(initialState),
// 其他更复杂的操作,比如对数组的添加/删除,如果不想在外部处理,那就在这里定义
...reducers
},
extraReducers: builder => {
// 复杂 reducer 一次修改多个 state
builder.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 })) 可一次修改多个 state
reducerSlice.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-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="/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);