react+typescript+antd搭建有登录验证的管理后台
在上一篇文章中,我介绍了如何使用express+mongodb构建后端项目 ,现在我再来跟大家介绍如何使用react+typescript+antd来构建前端项目
使用create-react-app生成react的typescript项目框架
- 技术栈
react
typescript
antd
react-router-dom
react-redux
canvas
ES6
cookie
- 目录结构
assets——存放静态资源
components——存放公共组件
pages——存放页面
store——存放状态管理数据流文件
utils——存放函数工具
api.ts——定义api接口文件
App.css——定义全局css
App.ts——定义项目入口组件
global.d.ts——定义全局的声明文件
index.tsx——入口文件
router.ts——路由配置表
- 全局安装create-react-app
npm install -g create-react-app
- 我们将创建一个名为 react-antd-ts 的新项目:
npx create-react-app antd-demo-ts --typescript
cd react-antd-ts
npm start
此时浏览器会访问 http://localhost:3000/ ,看到 Welcome to React 的界面就算成功了
- 引入按需加载antd
npm install antd --save
此时我们需要对 create-react-app 的默认配置进行自定义,这里我们使用 react-app-rewired (一个对 create-react-app 进行自定义配置的社区解决方案,而不需要eject暴露react的webpack配置文件)。
引入 react-app-rewired 并修改 package.json 里的启动配置。由于新的 [email protected] 版本的关系,你还需要安装 customize-cra。
npm install react-app-rewired customize-cra --save
修改package.json
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
- 使用 babel-plugin-import
babel-plugin-import 是一个用于按需加载组件代码和样式的 babel 插件(原理),现在我们尝试安装它并修改 config-overrides.js 文件。
npm install babel-plugin-import --save
然后在项目根目录创建一个 config-overrides.js 用于修改默认配置
config-overrides.js
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
- 使用react-hot-loader实现代码修改热更新
如果你试一下在现有项目上修改代码,发现整个页面都会刷新,这并不是我们想要的,如果想局部更新,我们可以使用react-hot-loader来实现
npm install --save react-hot-loader
修改App.tsx文件:
App.tsx
import React from 'react';
import logo from './logo.svg';
import { hot } from 'react-hot-loader/root'
import './App.css';
const App: React.FC = () => {
return (
Edit src/App.tsx
and save to reload.
Learn React
);
}
export default process.env.NODE_ENV === "development" ? hot(App) : App
- 使用less
npm install --save less-loader less
修改config-overrides.js:
const { override, fixBabelImports,addLessLoader,addPostcssPlugins } = require('customize-cra');
const rewireReactHotLoader = require('react-app-rewire-hot-loader-for-customize-cra')
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
addLessLoader({
strictMath: true,
noIeCompat: true,
localIdentName: '[local]--[hash:base64:5]' // if you use CSS Modules, and custom `localIdentName`, default is '[local]--[hash:base64:5]'.
}),
addPostcssPlugins([require('autoprefixer')]),
rewireReactHotLoader()
);
致此,可以引用antd的组件了;注意:如果在构建的时候出现 import React from 'react' 的报错,可以npm install @types/react进行解决;这里给大家科普一下typescript里面的声明文件知识,如果引入的模块提示没有找到声明文件,一般可以用npm install @types/xxxx(模块名称)来解决。关于引入模块声明文件的相关知识,可以看看以下两篇文章:
https://www.tslang.cn/docs/ha...
https://www.tslang.cn/docs/ha...
(为节省篇幅,以下只讲一些重点以及一些注意事项,所以会将一些已经写好的组件和页面放到项目中,不再叙述这些页面跟组件的构建,最后会附上项目的git地址,可以自行参考)
- 路由配置
单页应用中的重要部分,就是路由系统。由于不同普通的页面跳转刷新,因此单页应用会有一套自己的路由系统需要维护。
我们当然可以手写一个路由系统,但是,为了快速有效地创建于管理我们的应用,我们可以选择一个好用的路由系统。本文选择了react-router 4。这里需要注意,在v4版本里,react-router将WEB部分的路由系统拆分至了react-router-dom,因此需要npmreact-router-dom
npm i --save react-router-dom
本例中我们使用react-router中的BrowserRouter组件包裹整个App应用,在其中使用Route组件用于匹配不同的路由时加载不同的页面组件。(也可以使用HashRouter,顾名思义,是使用hash来作为路径)react-router推荐使用BrowserRouter,BrowserRouter需要history相关的API支持。
首先,需要在App.tsx中添加BrowserRouter组件,并将Route组件放在BrowserRouter内。其中Route组件接收两个属性:path和component,分别是匹配的路径与加载渲染的组件,把App.tsx修改如下:
App.tsx
import React from 'react';
import {BrowserRouter,Route,Switch} from 'react-router-dom';
import {store} from './store';
import {Provider} from 'react-redux';
import Index from './components/Index';
import Login from './pages/login';
import Err from './pages/err';
import './App.css';
import { hot } from 'react-hot-loader/root'
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
const App: React.FC = () => {
return (
);
}
export default process.env.NODE_ENV === "development" ? hot(App) : App
- 使用redux来管理数据流
redux是flux架构的一种实现,redux并不是完全依附于react的框架,实际上redux是可以和任何UI层框架相结合的。因此,为了更好得结合redux与react,对redux-flow中的store有一个更好的全局性管理,我们还需要使用react-redux
npm install --save redux
npm install --save react-redux
同时,为了更好地创建action和reducer,我们还会在项目中引入redux-actions:一个针对redux的一个FSA工具箱,可以相应简化与标准化action与reducer部分。当然,这是可选的
npm install --save redux-actions
注意:如果出现以下情况,上面有提到,需要 npm install @types/react-redux
来解决
1、创建对应的action。
action是一个object类型,对于action的结构有Flux有相关的标准化建议FSA
一个action必须要包含type属性,同时它还有三个可选属性error、payload和meta。
(1) type属性相当于是action的标识,通过它可以区分不同的action,其类型只能是字符串常量或Symbol。
(2) payload属性是可选的,可以使任何类型。payload可以用来装载数据;在error为true的时候,payload一般是用来装载错误信息。
(3) error属性是可选的,一般当出现错误时其值为true;如果是其他值,不被理解为出现错误。
(4) meta属性可以使任何类型,它一般会包括一些不适合在payload中放置的数据。
使用redux-actions对actions进行创建与管理
(1) createAction(type, payloadCreator = Identity, ?metaCreator)
(2) createAction相当于对action创建器的一个包装,会返回一个FSA,使用这个返回的FSA可以创建具体的action。
(3) payloadCreator是一个function,处理并返回需要的payload;如果空缺,会使用默认方法。如果传入一个Error对象则会自动将action的error属性设为true
以下以全局控制侧边栏的收开为例,创建action和reducer
在根目录下新建store文件夹,创建types.ts文件,用于存放action 的唯一标识符
store/types.ts
export const TOGGLE_SIDE_BAR = 'TOGGLE_SIDE_BAR';
创建models.ts,用于存放store中初始状态的接口声明
store/models.ts
export interface AppModel {
collapsed:boolean;
}
创建actions.ts,用于对所有action进行管理
store/actions.ts
import {createAction} from 'redux-actions';
import {AppModel} from './models';
import {TOGGLE_SIDE_BAR} from './types';
const toggleSideBar = createAction(
TOGGLE_SIDE_BAR,
(collapsed: boolean) => {return {collapsed}}
);
export {
toggleSideBar
}
创建reducers.ts
store/reducers.ts
import {handleActions} from 'redux-actions';
import {TOGGLE_SIDE_BAR} from './types';
import {AppModel} from './models'
// 初始的状态,就像react中组件内的初始状态,只不过这个是全局的。
const initialState: AppModel = {
collapsed:false
};
export const toggleSideBarReducer = handleActions({
[TOGGLE_SIDE_BAR]: (state:any, action:any) => {
return {
...state,
collapsed: !state.collapsed
}
}
}, initialState);
创建index.ts,用于组合store
store/index.ts
import { AppModel } from './models';
import { combineReducers } from 'redux';
import { toggleSideBarReducer } from './reducers';
import { Store, createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
// import {History} from 'history';
interface RootState {
app: AppModel
}
const rootReducer = combineReducers({
app: toggleSideBarReducer
});
export function store(initialState?:any): Store {
// store 中间件,根据个人需求添加
const middleware = applyMiddleware(thunkMiddleware)
return createStore(
rootReducer,
initialState,
middleware
) as Store;
}
- api请求
1、在package.json里面加入
"proxy": "http://localhost:3001",
2、创建api.ts,对全局接口请求进行定义
import http from './utils/http';
const getUserList=()=>{
return http.get('/getUserList')
}
const register=(params:{name:string,password:string,phone:number,type:number})=>{
return http.post('/register',params)
}
const login=(params:{password:string,phone:number})=>{
return http.post('/login',params)
}
const logout=()=>{
return http.post('/logout')
}
const userInfo=()=>{
return http.post('/userInfo')
}
export {
getUserList,
register,
login,
logout,
userInfo
}
3、创建utils文件夹,新建http.ts文件,对axios进行全局设置
npm install --save axios
utils/http.ts
import qs from 'qs'
import { message } from 'antd';
import axios,{ AxiosResponse, AxiosRequestConfig } from 'axios'
import { Modal } from 'antd';
import {createBrowserHistory} from 'history';
const axiosConfig: AxiosRequestConfig = {
// 请求后的数据处理
transformResponse: [(data: AxiosResponse) => {
return data
}],
transformRequest: [(data: any) => {
return qs.stringify(data)
}],
// 超时设置s
timeout: 30000,
// 跨域是否带Token
withCredentials: true,
responseType: 'json',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
// 取消重复请求
const pending: Array<{
url: string,
cancel: any
}> = []
const cancelToken = axios.CancelToken
const removePending = (config: any) => {
// tslint:disable-next-line:forin
for (const p in pending) {
const item: any = p
const list: any = pending[p]
// 当前请求在数组中存在时执行函数体
if (list.url === config.url + '&request_type=' + config.method) {
// 执行取消操作
list.cancel()
// 从数组中移除记录
pending.splice(item, 1)
}
}
}
const service = axios.create(axiosConfig)
// 添加请求拦截器
service.interceptors.request.use(
(config: any) => {
removePending(config)
config.cancelToken = new cancelToken((c: any) => {
pending.push({ url: config.url + '&request_type=' + config.method, cancel: c })
})
return config
},
(error: any) => {
return Promise.reject(error)
}
)
// 返回状态判断(添加响应拦截器)
service.interceptors.response.use(
(res: any) => {
removePending(res.config)
if (res.data) {
// LoadingInterface.close();
if (res.status === 200) {
if (res.data.status === 200) {
return res.data.data
} else if (res.data.status === 403) {
// 未登录或者token过期,重定向到登录页面
Modal.info({
title: '通知',
content:'登录信息已过期,请重新登录',
onOk(){
const history = createBrowserHistory()
history.push('/login')
window.location.reload()
}
})
} else {
message.error(res.data.message)
return Promise.reject(res.data.message)
}
} else {
message.error( res.statusText )
return Promise.reject(res.statusText)
}
}
},
(error: any) => {
message.error('请求失败,请稍后再试')
return Promise.reject(error)
}
)
export default service
好嘞,基本的东西就介绍到这里了,完整的项目可以到我的git上去下载;
后端git地址:https://github.com/SuperMrBea...
前端git地址:https://github.com/SuperMrBea...
项目在线预览地址:http://www.wxdriver.com (账号:15900000000 密码:123456)
上一篇文章的地址:https://segmentfault.com/a/11...