搭建一个react框架项目

搭建一个react全家桶项目

  • 版本大全


"antd": "^4.20.5"

"axios": "^0.27.2"

"react": "^18.1.0"

"react-redux": "^8.0.1"

"react-router-dom": "^6.3.0"

"webpack": "^5.64.4"

  • 创建项目


安装react cli

1
npm install -g create-react-app  

创建项目

1
2
#  app 为该项目名称
create-react-app app

启动项目

1
2
cd app
npm start

这样就能够访问react初始界面了。hello world react!

  • 引入Ant Design UI组件库


项目引入Ant Design

1
npm install antd --save

配置Ant Design按需加载

使用 babel-plugin-import方式进行按需加载

暴露配置文件:

​ 当我们使用react的脚手架创建了一个react项目后,一定会有一个疑问,那就是为什么create-react-app创建的项目没有webpack.config.js文件!这时就可以用如下命令把配置文件暴露出来。

1
npm run eject

​ 在 package.json 中配置 babel (需要安装 babel-plugin-import

1
npm install babel-plugin-import --save-dev

​ babel 配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"babel": {
"presets": [
"react-app"
],
"plugins": [
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}
]
]
}

​ 使用 babel-plugin-import 的 style 配置来引入样式,需要将配置值从 'style': 'css' 改为 'style': true,这样会引入 less 文件。

配置Ant Design中文语言,antD默认English

src/index.js入口文件配置

1
2
3
4
5
6
7
import { ConfigProvider } from 'antd';
import zh_CN from 'antd/lib/locale-provider/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';

moment.locale('zh-cn');

另外需要使用 ConfigProvider 组件把 根组件 包裹起来

1
2
3
4
<ConfigProvider locale={zh_CN}>
<App />
</ConfigProvider>

  • 安装less预处理器


1
npm install less less-loader --save-dev

如果没有暴露配置文件的话需查看package.json文件中 “scripts”中的“eject”中的”react-scripts eject",然后去node_modules中找到react-scripts文件夹中的config中的webpack.config.js配置less

如果有暴露出来的话直接在根目录下的config中找到上面所述文件进行修改即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
test: /\.less$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader', // translates CSS into CommonJS
},
{
loader: 'less-loader', // compiles Less to CSS
options: {
lessOptions: { // If you are using less-loader@5 please spread the lessOptions to options directly
modifyVars: {
// 'primary-color': '#1DA57A',
// 'link-color': '#1DA57A',
// 'border-radius-base': '2px',
// or
// hack: `true; @import "your-less-file-path.less";`, // Override with less file
},
javascriptEnabled: true,
},
},
},
],
},

配置位置如下:

  • 安装 CSS resets: Normalize.css ( 样式重置 )


1
npm install normalize.css --save

安装完成后在入口文件 index.js 中引入即可。

1
import 'normalize.css';
  • 安装配置 axios、qs


1
npm install axios qs --save

在根目录 public 文件夹下创建 config.js 文件

1
2
3
window.g = {
API_URL: 'http://192.168.1.1:8080/' // 在这里设置请求IP地址及端口,后续封装的请求都通统一使用这个
};

并下下图位置中插入此配置文件,用于后面统一修改或者统一配置请求的地址,会在 request.js 文件中使用这个配置

配置请求拦截器、响应拦截器: request.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
import axios from 'axios';

import { message, Modal } from 'antd';

const { confirm } = Modal;

const service = axios.create({
baseURL: window.g.API_URL,
timeout: 60000, // 请求超时时间
headers: {
'Content-Type': 'application/json'
}
});

/**
* http request 拦截器
*/
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
return config
},
(error) => {
console.error('请求发生了错误', error);
return Promise.reject(error);
}
);

/**
* http response 拦截器
*/
service.interceptors.response.use(
(res) => {
// 在这里配置响应拦截器
if(res.data.code === '9527'){
confirm({
title: res.data.msg,
closable:false,
onOk() {
window.location.href = '/login';
}
});
}else{
return res.data;
}


},
(err) => {
console.error('响应发生了错误', err);
return Promise.reject(err);
}
);

/**
* 封装get方法
* @param url 请求url
* @param params 请求参数
* @returns {Promise}
*/
export function get(url, params = {}) {
return new Promise((resolve, reject) => {
service.get(url, {
params: params,
}).then((response) => {
landing(url, params, response.data);
resolve(response);
})
.catch((error) => {
reject(error);
});
});
}

/**
* 封装post请求
* @param url
* @param data
* @returns {Promise}
*/

export function post(url, data={}) {
return new Promise((resolve, reject) => {
service.post(url, data).then(
(response) => {
//关闭进度条
resolve(response);
},
(err) => {
message.error(err.msg);
reject(err);
}
);
});
}

/**
* 封装patch请求
* @param url
* @param data
* @returns {Promise}
*/
export function patch(url, data = {}) {
return new Promise((resolve, reject) => {
service.patch(url, data).then(
(response) => {
resolve(response);
},
(err) => {
msag(err);
reject(err);
}
);
});
}

/**
* 封装put请求
* @param url
* @param data
* @returns {Promise}
*/

export function put(url, data = {}) {
return new Promise((resolve, reject) => {
service.put(url, data).then(
(response) => {
resolve(response);
},
(err) => {
msag(err);
reject(err);
}
);
});
}

/**
* 封装delete请求
* @param url
* @param data
* @returns {Promise}
*/

export function del(url, data) {
return new Promise((resolve, reject) => {
service.delete(url, {data}).then(
(response) => {
//关闭进度条
resolve(response);
},
(err) => {
reject(err);
}
);
});
}

//统一接口处理,返回数据
// eslint-disable-next-line import/no-anonymous-default-export
export default function (fecth, url, param) {
// eslint-disable-next-line no-unused-vars
let _data = "";
return new Promise((resolve, reject) => {
switch (fecth) {
case "get":
get(url, param)
.then(function (response) {
resolve(response);
})
.catch(function (error) {
console.log("get request GET failed.", error);
reject(error);
});
break;
case "post":
post(url, param)
.then(function (response) {
resolve(response);
})
.catch(function (error) {
console.log("get request POST failed.", error);
reject(error);
});
break;
case "del":
del(url, param)
.then(function (response) {
resolve(response);
})
.catch(function (error) {
console.log("get request delete failed.", error);
reject(error);
});
break;
case "put":
put(url, param)
.then(function (response) {
resolve(response);
})
.catch(function (error) {
console.log("get request delete failed.", error);
reject(error);
});
break;
case "patch":
patch(url, param)
.then(function (response) {
resolve(response);
})
.catch(function (error) {
console.log("get request delete failed.", error);
reject(error);
});
break;
default:
break;
}
});
}

//失败提示
function msag(err) {
// if (err && err.response) {
// switch (err.response.status) {
// case 400:
// alert(err.response.data.error.details);
// break;
// case 401:
// alert("未授权,请登录");
// break;

// case 403:
// alert("拒绝访问");
// break;

// case 404:
// alert("请求地址出错");
// break;

// case 408:
// alert("请求超时");
// break;

// case 500:
// alert("服务器内部错误");
// break;

// case 501:
// alert("服务未实现");
// break;

// case 502:
// alert("网关错误");
// break;

// case 503:
// alert("服务不可用");
// break;

// case 504:
// alert("网关超时");
// break;

// case 505:
// alert("HTTP版本不受支持");
// break;
// default:
// }
// }
}

/**
* 查看返回的数据
* @param url
* @param params
* @param data
*/
function landing(url, params, data) {
if (data.code === -1) {
}
}

管理Api文件:

请求的使用方式如下图所示:

1
2
3
4
5
6
7
8
import request from '../utils/request.js'

/**
* 添加某个接口,并调用封装好的请求方法
*/
function FindDictPage(data){
return request("post",'user/v1/dictionary/FindDictPage',data);
}

在某个组中使用这个接口~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { FindDictPage } from '../../apis/user';
function DictionaryTable(props) {

const [tableData, setTableData] = React.useState([]) // 表格数据

// 获取分页表格数据API
const getTableData = (params) => {
FindDictPage(params)
.then(res => {
if (res.code === '200') {
let data = res.data.list
setTableData(data) //修改data
setTableTotal(res.data.total) //修改dataTotal
}
})
}
  • 配置跨域


1
npm install http-proxy-middleware --save

src 下新建 setupProxy.js

1
2
3
4
5
6
7
8
9
10
11
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
);
};
  • 安装配置路由 React-router


1
npm install react-router-dom --save  // 我这里是"react-router-dom": "^6.3.0"

src中新建utils文件夹,并建立request.js

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import YourComponent from 'your-component-path';

export default function () {
return (
<Router>
<Route path='/' component={YourComponent} />
</Router>
);
}

然后在应用入口index.js里引用使用这个 router.js

1
2
3
4
import Router from './utils/router';
export default function Index() {
return <Router />;
}
  • 安装配置redux


1
npm install redux redux-thunk react-redux --save

src 目录下新建 redux 文件夹,作为配置 redux 的目录:

actions: 针对不同功能模块进行配置的 actions 文件放在此目录

reducers: 针对不同功能模块进行配置的 reducers 文件放在此目录

reducers.js: 把所有 reducers 结合起来

store.js: 对 redux 的配置文件

types.js: 存放 Actions 中所需要的 type 属性值

各类文件

types.js

1
2
3
4
5
export const MYTODO = 'MYTODO';
export const MYLIST = 'MYLIST';
export const OTHERTODO = 'OTHERTODO';
export const OTHERLIST = 'OTHERLIST';

myReducer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { MYTODO, MYLIST } from '../types';
const initState = {
myTodos: [],
list: []
// ...etc.
};
export default function (state = initState, action) {
switch (action.type) {
case MYTODO:
return {
...state,
myTodos: action.payload
};
case MYLIST:
return {
...state,
list: action.payload
};
default:
return state;
}
}

myActions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { MYTODO, MYLIST } from '../types';

export const myTodos = (params) => (dispatch, getState) => {
// const { myState, otherState } = getState();
dispatch({
type: MYTODO,
payload: params
});
};
export const handleMyList = (params) => (dispatch, getState) => {
// const { myState, otherState } = getState();
dispatch({
type: MYLIST,
payload: params
});
};

otherReducer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { OTHERTODO, OTHERLIST } from '../types';
const initState = {
otherTodos: [],
list: []
// ...etc.
};
export default function (state = initState, action) {
switch (action.type) {
case OTHERTODO:
return {
...state,
otherTodos: action.payload
};
case OTHERLIST:
return {
...state,
list: action.payload
};
default:
return state;
}
}

otherActions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { OTHERTODO, OTHERLIST } from '../types';

export const otherTodos = (params) => (dispatch, getState) => {
// const { myState, otherState } = getState();
dispatch({
type: OTHERTODO,
payload: params
});
};
export const handleOtherList = (params) => (dispatch, getState) => {
// const { myState, otherState } = getState();
dispatch({
type: OTHERLIST,
payload: params
});
};

reducers.js

1
2
3
4
5
6
7
8
import { combineReducers } from 'redux';
import myReducer from './reducers/myReducer';
import otherReducer from './reducers/otherReducer';

export default combineReducers({
myState: myReducer,
otherState: otherReducer
});

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';

const initState = {};
const middleware = [thunk];

const composeEnhancers =
typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;

const enhancer =
process.env.NODE_ENV === 'development'
? composeEnhancers(applyMiddleware(...middleware))
: applyMiddleware(...middleware);

export const store = createStore(reducers, initState, enhancer);

以上 Redux 基本配置完成,下面是调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { myTodos, handleMyList } from './path/redux/actions/myActions';

function MyTodosComponent() {
useEffect(() => {
// 通过this.props 访问 state
console.log(this.props.myTodos);
console.log(this.props.list);
// 调用 actions
const todos = [
{
id: 1,
todo: 'say hello world'
}
];
this.props.myTodos(todos);
const list = [
{
id: 1,
text: 'test'
}
];
this.props.handleMyList(list);
}, []);
return (
<div>
{this.props.todos.map((item, index) => {
return (
<div>
id:{item.id}, todo:{item.todo}
</div>
);
})}
</div>
);
}
// 类型检查
MyTodosComponent.propTypes = {
myTodos: PropTypes.array.isRequired,
list: PropTypes.array.isRequired
};
// 把redux中的state绑定到组件的props上
const mapStateToProps = (state) => {
const { myTodos, list } = state.myState;
return {
myTodos,
list
};
};
// 把redux和组件结合起来,使组件能在props中访问到state和actions
export default connect(mapStateToProps, {
myTodos,
handleMyList
})(MyTodosComponent);