本片文章主要是如何搭建一个企业级项目需要的大致的配置,跟着这篇文章练完手你可以学会配置如下库:
- 集成 antd
- 路径别名 @
- react-router
- styled-components
- redux
- 配置全局 axios
- eslint
- prettier
- 自动风格校验
- router 鉴权和守卫
- lodash
- react-loadable
我希望屏幕前的你不要收藏一下就完事了,如果您没有搭建项目的经验请跟着本片文章练一遍,这是初中级前端走向高级前端必经之路!!!看完这篇文章的您要是有什么好的建议请评论!咱们共同成长!
脚手架搭建项目
create-react-app 项目名称 --typescript
项目搭建成功后删除一些不必要的文件,这里我删除的少,这里没啥规范,根据情况自己删除。
- App.css
- App.test.tsx
- index.css
- log.svg
删除文件后的目录:看看就行!
新建目录
/src
├─ /common <-- 全局公用目录
| ├─ /fonts <-- 字体文件目录
| ├─ /images <-- 图片文件目录
| └─ /style <-- 公用样式文件目录
| | ├─ frame.css <-- 全部公用样式(引入其他css)
| | ├─ reset.css <-- 清零样式
| | └─ global.css <-- 全局公用样式
├─ /components <-- 公共模块组件目录
├─ /pages <-- 页面组件目录
├─ /store <-- 存放根store目录
├─ /utils <-- 存放工具目录
├─ App.js <-- 项目主模块
└─ index.js <-- 项目入口文件
- frame.css
/* 全部公用样式,主要是引用其他的样式 */
@import './global.css';
@import './reset.css';
- reset.css
/* 全局清除的样式,可以在此文件中重置antd的一些样式 */
- global.css
/* 全局公用的样式 */
/* 全局公用的样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
*::before,
*::after {
box-sizing: border-box;
}
a {
color: inherit;
text-decoration: none;
}
a:active,
a:hover {
outline: 0;
}
img {
display: block;
}
input,
button {
font-family: inherit;
}
ol,
ul {
list-style: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
- index.tsx
...
// 引入全局样式
import "./common/style/frame.css";
...
在浏览器中F12检查元素就可以看到我们的全局的样式代码,就证明全局样式已经生效。由于操作过于简单请自行查看!!!
集成 antd
- 首先下载 antd
npm install antd babel-plugin-import
- 高级配置
npm install react-app-rewired customize-cra
有了cra我们就不需要将webpack的配置文件释放出来了,毕竟这个过程是不可逆的。
- 修改 package.json 文件
/* 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",
- "eject": "react-scripts eject",
+ "eject": "react-app-rewired eject"
}
- 根目录创建 config-overrides.js
/** config-overrides.js */
const {
override,
fixBabelImports
} = require("customize-cra");
module.exports = override(
// 按需加载组件代码和样式
fixBabelImports("import", {
libraryName: "antd",
libraryDirectory: "es",
style: true,
}),
);
- 定制主题
自定义主题需要用到 less 变量覆盖功能
npm install less less-loader
继续配置config-overrides.js文件
/** config-overrides.js */
const {
override,
fixBabelImports,
addLessLoader,
} = require("customize-cra");
module.exports = override(
// 按需加载组件代码和样式
fixBabelImports("import", {
libraryName: "antd",
libraryDirectory: "es",
style: true,
}),
// 加载 less 样式
addLessLoader({
lessOptions: {
javascriptEnabled: true,
modifyVars: { "@primary-color": "#1DA57A" },
},
})
);
- 配置路径别名
const {
override,
fixBabelImports,
addWebpackAlias,
addLessLoader,
addDecoratorsLegacy
} = require("customize-cra");
const path = require("path");
module.exports = override(
// 按需加载组件代码和样式
fixBabelImports("import", {
libraryName: "antd",
libraryDirectory: "es",
style: true,
}),
// 将提供的别名信息添加到webpack的别名部分。传递一个包含任意数量的对象文字,整个对象将被合并进来。
addWebpackAlias({ "@": path.resolve(__dirname, "src") }),
// 加载 less 样式
addLessLoader({
lessOptions: {
javascriptEnabled: true,
modifyVars: { "@primary-color": "#1DA57A" },
},
}),
// 在传统模式下添加装饰器。
addDecoratorsLegacy()
);
在根目录新建paths.json文件
/**paths.json*/
{
"compilerOptions": {
+ "baseUrl": ".", // 设置baseUrl来告诉编译器到哪里去查找模块。 所有非相对模块导入都会被当做相对于 baseUrl。
+ "paths": {
"@/*": ["src/*"] // 请注意"paths"是相对于"baseUrl"进行解析。
}
}
}
修改tsconfig.json文件
/**tsconfig.json*/
...
+ "extends": "./paths.json"
集成antd差不多就结束了,现在赶紧试一试组件和路径别名@吧!!!
- 修改 index.tsx
// 引入全局样式
- import "./common/style/frame.css";
+ import "@/common/style/frame.css";
- 修改 App.tsx
+ import { Button } from "antd";
...
+ <Button>按钮</Button>
操作完毕,npm start 试一试!操作太简单,自行查看!!!
添加 styled-components
npm install styled-components @types/styled-components
在components文件夹下我们创建Navigate文件夹,开始书写我们的全局导航栏
├─ /components <-- 公共模块组件目录
| └─ /Navigate <-- 全局公用导航栏
| | ├─ style.ts <-- 导航样式
| | └─ index.tsx <-- 导航栏组件
/** style.ts */
import styled from "styled-components";
export const NavContainer = styled.div`
background: linear-gradient(90deg, #53a0fd, #5bd7d8);
height: 50px;
color: #fff;
display: flex;
justify-content: flex-start;
line-height: 50px;
padding: 0 20px;
a + a {
margin-left: 25px;
}
`;
书写全局导航栏组件
/** index.tsx */
import * as React from "react";
// 样式
import { NavContainer } from "./style";
export interface NavigateProps {}
export interface NavigateState {}
class Navigate extends React.Component<NavigateProps, NavigateState> {
constructor(props: NavigateProps) {
super(props);
this.state = {};
}
render() {
return <NavContainer>导航菜单</NavContainer>;
}
}
export default Navigate;
调用导航栏组件
/**App.tsx*/
...
// 组件
+ import Navigate from "@/components/Navigate/index";
...
render() {
return (
<div>
<Navigate />
<Button>按钮</Button>
</div>
);
}
添加 react-router
npm install react-router-dom @types/react-router-dom
目录结构:
├─ /pages <-- 页面文件
| ├─ /Home <-- Home文件
| | └─ index.tsx <-- Home页面
| └─ /User <-- User文件
| | └─ index.tsx <-- User页面
书写Home页面需要的代码:
/** Home index.tsx*/
import * as React from "react";
export interface HomeProps {}
export interface HomeState {}
class Home extends React.Component<HomeProps, HomeState> {
constructor(props: HomeProps) {
super(props);
this.state = {};
}
render() {
return (
<div>
<h1>Home页面</h1>
</div>
);
}
}
export default Home;
书写User页面代码:
/** User index.tsx*/
import * as React from "react";
export interface UserProps {}
export interface UserState {}
class User extends React.Component<UserProps, UserState> {
constructor(props: UserProps) {
super(props);
this.state = {};
}
render() {
return (
<div>
<h1>User页面</h1>
</div>
);
}
}
export default User;
修改导航栏组件Navigate:
/** Navigate index.tsx*/
...
+ import { Link } from "react-router-dom";
...
<NavContainer>
<Link to="/home">Home</Link>
<Link to="/user">User</Link>
</NavContainer>
修改App.tsx:
...
+ import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
} from "react-router-dom";
// 组件
...
+ import Home from "@/pages/Home";
+ import User from "@/pages/User";
...
render() {
return (
<Router>
<div>
<Navigate />
<Switch>
<Route path="/home" component={Home} />
<Route path="/user" component={User} />
<Redirect from="/" to="/home" />
</Switch>
</div>
</Router>
);
}
添加了路由和样式,我们npm start 看看!
添加 js-cookie
这个没啥讲的,就下载就Ok了,主要是用它判断登录状态之类的!
npm install js-cookie @types/js-cookie
配置全局的 Axios
npm install axios
在utils目录下添加api.ts文件,配置全局的Axios
├─ /utils <-- 公共工具目录
| └─ api.ts <-- axios请求文件
/**utils api.ts */
import axios, { AxiosRequestConfig, AxiosError, AxiosResponse } from "axios";
import Cookies from "js-cookie";
import { message } from "antd";
//错误代码的枚举
const enum HTTP_ERROR_CODE {
CODE_400 = "请求错误(400)",
CODE_401 = "未授权,请重新登陆(401)",
CODE_403 = "拒绝访问(403)",
CODE_404 = "请求出错(404)",
CODE_406 = "请求参数错误(406)",
CODE_408 = "请求超时(408)",
CODE_500 = "服务器错误(500)",
CODE_501 = "服务器未实现(501)",
CODE_502 = "网络错误(502)",
CODE_503 = "服务不可用(503)",
CODE_504 = "网络超时(504)",
}
// 服务器错误代码
const enum SERVER_ERROR_CODE {
CODE_50000 = "服务器错误!",
CODE_51000 = "未授权,请重新登录",
}
// 全局axios配置
axios.defaults.baseURL = "";
axios.defaults.withCredentials = true; //表示跨域请求时是否需要使用凭证
axios.defaults.headers.common["Content-Type"] = "application/json";
axios.defaults.timeout = 60 * 1000; // 接口响应时间为1分钟
// http request 拦截器
axios.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 可以再此进行全局的请求接口Loading展示
// TODO--> Loading
// 验证toKen
const token = Cookies.get("");
if (!token) config.headers.common.token = token;
return config;
},
(error: AxiosError) => {
message.error(error);
return Promise.reject(error);
}
);
// http response 拦截器
axios.interceptors.response.use(
(response: AxiosResponse<any>) => {
// 清除全局的Loading状态
// TODO
return response;
},
(error: AxiosError) => {
// 清除全局的Loading状态
// TODO
// 在这里输出响应的错误信息,这个只是例子
message.error(HTTP_ERROR_CODE.CODE_400);
return Promise.reject(error);
}
);
// 全局的接口的权限解析
const permissionParser = (res: any): Promise<any> => {
return new Promise(async (resole, reject) => {
if (res && res.data && res.data.code === 51000) {
message.warning(SERVER_ERROR_CODE["CODE_51000"]);
Cookies.remove("");
window.location.href = "/login";
reject(SERVER_ERROR_CODE["CODE_51000"]);
} else {
resole(res);
}
});
};
// 默认解析
const defaultParser = (res: any): Promise<any> => {
return new Promise((resolve, reject) => {
if (res && res.data && res.data.code) {
const { code, data } = res.data;
if (code === 20000) {
resolve(data);
} else if (code === 20001) {
resolve(res.data.message);
} else {
message.error(res.data.message);
reject(res.data.message);
}
} else {
message.error("接口错误!");
reject("接口错误!");
}
});
};
// get
export const get = (url: string, query?: object): Promise<any> => {
return new Promise((resolve) => {
axios(url, {
params: query,
})
.then(permissionParser)
.then(defaultParser)
.then((res) => resolve(res))
.catch((e) => console.error(e));
});
};
// post
export const post = (url: string, data?: object, query?: object) => {
return new Promise((resolve) => {
axios(url, {
method: "POST",
params: query,
data,
})
.then(permissionParser)
.then(defaultParser)
.then((res) => resolve(res))
.catch((e) => console.error(e));
});
};
// put
export const put = (url: string, data?: object, query?: object) => {
return new Promise((resolve) => {
axios(url, {
method: "PUT",
params: query,
data,
})
.then(permissionParser)
.then(defaultParser)
.then((res) => resolve(res))
.catch((e) => console.error(e));
});
};
// delete
export const del = (url: string, data?: object, query?: object) => {
return new Promise((resolve) => {
axios(url, {
method: "DELETE",
params: query,
data,
})
.then(permissionParser)
.then(defaultParser)
.then((res) => resolve(res))
.catch((e) => console.error(e));
});
};
// callAPI
export const callApi = (url: string, options: object) => {
return new Promise((resolve) => {
axios(url, options)
.then(permissionParser)
.then(defaultParser)
.then((res) => resolve(res))
.catch((e) => console.error(e));
});
};
全局Axios配置完毕
ESLint+Prettier 统一代码风格
npm install -D eslint@6.6.0 typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy
在你的项目的根目录下创建一个 .eslintrc.js 文件
module.exports = {
extends: ["alloy", "alloy/react", "alloy/typescript"],
env: {
// 你的环境变量(包含多个预定义的全局变量)
//
browser: true,
node: true,
// mocha: true,
jest: true,
// jquery: true
},
globals: {
// 你的全局变量(设置为 false 表示它不允许被重新赋值)
//
// myGlobal: false
},
rules: {
// 自定义你的规则
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'no-dupe-keys': 'off',
'no-var': 'error',
complexity: 'off',
indent: 'off',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/explicit-member-accessibility': 'off',
'prefer-promise-reject-errors': 'off',
'no-eq-null': 'off',
eqeqeq: 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'no-async-promise-executor': 'off',
'@typescript-eslint/no-loss-of-precision': 'off',
'default-case-last': 'off',
'grouped-accessor-pairs': 'off',
'no-constructor-return': 'off',
'no-dupe-else-if': 'off',
'no-promise-executor-return': 'off',
'no-setter-return': 'off',
'no-unreachable-loop': 'off',
'no-useless-backreference': 'off',
},
};
下载Prettier
npm install prettier -D
在你的项目的根目录下创建一个 .prettierrc.js 文件
module.exports = {
// 一行最多 120 字符
printWidth: 120,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾需要有逗号
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// vue 文件中的 script 和 style 内不用缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf
endOfLine: 'lf',
// 格式化嵌入的内容
embeddedLanguageFormatting: 'auto',
};
更改package.json
/**package.json*/
...
"scripts": {
...
+ "format": "prettier src --write",
+ "lint": "eslint --ext .tsx,.ts --fix -f table ./src"
}
...
npm run format 和 npm run lint 试试!
npm run format
npm run lint
成功的样子:
当你lint的时候估计会遇到一些格式的报错,一定不要慌,我们可以先把eslint报出来的校验规则先off掉,就像 'no-dupe-keys': 'off' 一样,在.eslintrc.js中的rules中将对应的校验规则off掉,千万不要慌,我们配置的绝对没错,坑我已经踩过了,跟着我的配置绝对可以跑通!
自动化风格校验
npm install -D husky lint-staged glob
这里为什么要下载 glob呢?
点击这里查看原因
修改package.json
/**package.json*/
...
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{ts,tsx}": [
"eslint",
"prettier --write",
"git add"
]
},
...
要想使用自动化检验,你的项目中必须要有git!!
使用风格校验:
git add .
git commit -m "commit:..."
成功的样子:
redux
说实话,redux的类型定义比Mobx的类型定义麻烦好多
npm install redux react-redux @types/react-redux
在store文件下添加一下文件
├─ /store <-- 根store文件夹
| ├─ actions.ts <-- 根action文件
| ├─ index.ts <-- 根store文件
| ├─ reducer.ts <-- 根reducer文件
| └─ storeInterface.ts <-- 根store接口文件
书写接口文件storeInterface.ts
/**storeInterface.ts*/
// 这个文件主要是定义根store的类型
// type类型
export const ADDITION = 'ADDITION';
// eslint-disable-next-line
export type ADDITION = typeof ADDITION;
export const REDUCE = 'REDUCE';
// eslint-disable-next-line
export type REDUCE = typeof REDUCE;
// action 类型
export interface AdditionAction {
type: ADDITION;
}
export interface ReduceAction {
type: REDUCE;
}
// action 类型归一
export type TodoAction = AdditionAction | ReduceAction;
书写根reducer文件reducer.ts
/**reducer.ts*/
import { TodoAction, ADDITION, REDUCE } from '@/store/storeInterface';
// 根store
const defaultStore = {
count: 0,
};
// 推测根store类型
type rootData = Readonly<typeof defaultStore>;
export default (store: rootData = defaultStore, action: TodoAction): rootData => {
switch (action.type) {
case ADDITION: {
let { count } = store;
count++;
return {
...store,
count,
};
}
case REDUCE: {
let { count } = store;
count--;
return {
...store,
count,
};
}
default: {
return { ...store };
}
}
};
书写根store文件index.ts
/**index.ts*/
import { createStore, combineReducers } from 'redux';
import { devToolsEnhancer } from 'redux-devtools-extension/logOnlyInProduction';
// 根store
import Reducer from '@/store/reducer';
// 将子 Reducer 合并成一个大的函数
const staticStore = combineReducers({
init: Reducer,
});
// 创建store
const store = createStore(staticStore, devToolsEnhancer({}));
export type RootState = ReturnType<typeof staticStore>;
export default store;
// 动态注入的类型我不会定义,会的大佬请赐教!!!!
// const createReducer = (asyncReducers) => {
// return combineReducers({
// ...staticReducers,
// ...asyncReducers,
// });
// };
// const store = createStore(createReducer(), devToolsEnhancer());
// store.asyncReducers = {};
// export const injectReducer = (key, asyncReducer) => {
// store.asyncReducers[key] = asyncReducer;
// store.replaceReducer(createReducer(store.asyncReducers));
// };
书写action文件actions.ts
/**actions.ts*/
import { AdditionAction, ReduceAction, ADDITION, REDUCE } from '@/store/storeInterface';
export const additionAction = (): AdditionAction => ({
type: ADDITION,
});
export const reduceAction = (): ReduceAction => ({
type: REDUCE,
});
添加根store在index.tsx
/**index.ts*/
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
// 组件
import App from './App';
// 引入全局样式
import '@/common/style/frame.css';
+ // 引入store
+ import { Provider } from 'react-redux';
+ import store from '@/store';
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root'),
);
serviceWorker.unregister();
在Home页面引入根store
/**Home index.tsx*/
import * as React from 'react';
import { Button } from 'antd';
// 引入根store----说实话下面这些代码直接丑哭了,不知道哪个大佬能够斧正一下
import { connect, ConnectedProps } from 'react-redux';
import { RootState } from '@/store';
import { additionAction, reduceAction } from '@/store/actions';
const mapStateToProps = (state: RootState) => {
const {
init: { count },
} = state;
return {
count,
};
};
const mapDispatchToProps = { additionAction, reduceAction };
const connector = connect(mapStateToProps, mapDispatchToProps);
// 下面的代码主要是定义props的类型
type PropsFromRedux = ConnectedProps<typeof connector>;
type HomeProps = {} & PropsFromRedux;
export interface HomeState {}
class Home extends React.Component<HomeProps, HomeState> {
constructor(props: HomeProps) {
super(props);
this.state = {};
}
render() {
return (
<div>
<h1>Home页面</h1>
<h2>根store中的count-- {this.props.count}</h2>
<br />
<Button type="primary" onClick={this.props.additionAction}>
增加
</Button>
<Button type="primary" onClick={this.props.reduceAction}>
减少
</Button>
</div>
);
}
}
export default connector(Home);
redux添加完毕。
路由鉴权和守卫
其实路由鉴权很简单,它本质就是判断,满足权限渲染Route,Link。不满足权限就不渲染。只是写法不同而已,思想就是这么个思想。
在src目录下新建router目录
/src
| ├─ /router <-- 根router文件夹
| | ├─ routerHOC.tsx <-- 存放router高阶组件文件
| | └─ routePrivilegeConfig.ts <-- 权限路由配置文件
在pages目录下新建两个目录,用来新建两个页面。
├─ /pages <-- 页面文件
| ├─ /Home <-- Home文件
| | └─ index.tsx <-- Home页面
| ├─ /User <-- User文件
| | └─ index.tsx <-- User页面
| ├─ /Admin <-- Admin文件
| | └─ index.tsx <-- Admin页面
| └─ /Login <-- Login文件
| | └─ index.tsx <-- Login页面
Admin页面代码:
/**Admin index.tsx*/
import React from 'react';
export default function Admin() {
return (
<div>
<h1>Admin 页面</h1>
</div>
);
}
Login页面代码:
/**Login index.tsx*/
import React from 'react';
export default function Login() {
return (
<div>
<h1>Login页面</h1>
</div>
);
}
路由配置代码:
/**router routePrivilegeConfig.ts*/
import React from 'react';
// 组件
import Home from '@/pages/Home';
import User from '@/pages/User';
import Admin from '@/pages/Admin';
import Login from '@/pages/Login';
export interface Router {
path: string | string[];
component: React.ComponentType<any>;
exact: boolean;
role: string; // 需要的权限
backUrl: string; // 不满足权限跳回的页面
lintTitle: string; // link的名称
}
const routerConfig: Router[] = [
{
path: '/login',
component: Login,
exact: false,
role: 'all',
backUrl: '/login',
lintTitle: 'Login',
},
{
path: '/user',
component: User,
exact: false,
role: 'User',
backUrl: '/login',
lintTitle: 'User',
},
{
path: '/admin',
component: Admin,
exact: false,
role: 'Admin',
backUrl: '/login',
lintTitle: 'Admin',
},
{
path: '/home',
component: Home,
exact: false,
role: 'User',
backUrl: '/login',
lintTitle: 'Home',
},
];
export default routerConfig;
Link,Route的高阶组件:
/**router routerHOC.ts*/
import React from 'react';
import { Redirect, Link, Route, RouteProps } from 'react-router-dom';
import Cookies from 'js-cookie';
// 用来判断用户现在是否登陆
// eslint-disable-next-line
const judgmentLogin = () => {
const token = Cookies.get('键名');
return token ? true : false;
};
type RProps = {
backUrl: string;
role: string;
} & RouteProps;
export function RouteHOC(props: RProps) {
const { backUrl, role, ...otherProps } = props;
// 如果用户的登陆失效,则跳转对应的页面
// if (['all', 'User'].includes(role) && judgmentLogin())
// ['User'] 这个数组里面存储的就是后端返回的个人权限,然后和路由配置中的权限比较。
if (['User'].includes(role)) {
return <Route {...otherProps} />;
} else if (['all'].includes(role)) {
return <Route {...otherProps} />;
} else {
return <Redirect to={backUrl} />;
}
}
interface LProps {
role: string;
lintTitle: string;
path: string | string[];
component?: React.ComponentType<any>;
replace?: boolean;
innerRef?: React.Ref<HTMLAnchorElement>;
}
export function LinkHOC(props: LProps) {
const { role, lintTitle, path } = props;
return ['all', 'User'].includes(role) ? <Link to={path as string}>{lintTitle}</Link> : null;
}
修改App.tsx文件
/**App.tsx*/
import React from 'react';
import { BrowserRouter as Router, Switch, Redirect } from 'react-router-dom';
// 组件
import Navigate from '@/components/Navigate';
// 路由权限配置
import { RouteHOC } from '@/router/routerHOC';
import rPC from '@/router/routePrivilegeConfig';
export interface AppProps {}
export interface AppState {}
class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
this.state = {};
}
render() {
return (
<Router>
<div>
<Navigate />
<Switch>
{rPC.map((item, index) => (
<RouteHOC key={index} {...item} />
))}
<Redirect from="/" to="/home" />
</Switch>
</div>
</Router>
);
}
}
export default App;
修改Navigate.tsx文件
/**Navigate.tsx 导航栏组件*/
import * as React from 'react';
// 路由权限配置
import { LinkHOC } from '@/router/routerHOC';
import rPC from '@/router/routePrivilegeConfig';
// 样式
import { NavContainer } from './style';
export interface NavigateProps {}
export interface NavigateState {}
class Navigate extends React.Component<NavigateProps, NavigateState> {
constructor(props: NavigateProps) {
super(props);
this.state = {};
}
render() {
return (
<NavContainer>
{rPC.map((item, index) => (
<LinkHOC key={index} {...item} />
))}
</NavContainer>
);
}
}
export default Navigate;
下载 lodash
npm install @types/lodash lodash
react-loadable 下载
npm install @types/react-loadable react-loadable
在components目录下创建Loading目录,书写全局的loading状态
├─ /components <-- 公共模块组件目录
| ├─ /Loading <-- 全局公用loading
| | └─ index.tsx <-- loading组件
| └─ /Navigate <-- 全局公用导航栏
| | ├─ style.ts <-- 导航样式
| | └─ index.tsx <-- 导航栏组件
书写全局的loading组件:
/**/Loading index.tsx */
import React from "react";
import styled, { keyframes } from "styled-components";
const StyledLoader = styled.div`
display: block;
background-color: rgba(255, 255, 255, 0.2);
width: 100%;
height: 100%;
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 10000;
display: flex;
justify-content: center;
align-items: center;
opacity: 1;
text-align: center;
`;
const Wrapper = styled.div`
width: 200px;
height: 100px;
display: inline-flex;
flex-direction: column;
justify-content: space-around;
`;
const spinner = keyframes`
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
`;
const Inner = styled.div`
width: 48px;
height: 48px;
margin: 0 auto;
text-indent: -12345px;
border-top: 1px solid rgba(0, 0, 0, 0.08);
border-right: 1px solid rgba(0, 0, 0, 0.08);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
border-left: 1px solid rgba(0, 0, 0, 0.7);
border-radius: 50%;
z-index: 100001;
animation: ${spinner} 600ms infinite linear;
`;
const Text = styled.div`
width: 200px;
height: 20px;
text-align: center;
font-size: 12px;
letter-spacing: 4px;
color: #000;
margin-top: 10px;
`;
export default function Loading() {
return (
<StyledLoader>
<Wrapper>
<Inner />
<Text>LOADING</Text>
</Wrapper>
</StyledLoader>
);
}
接下来就是在每个组件的目录下创建Loadable.ts文件:例如在Admin页面
├─ /pages <-- 页面文件
| ├─ /Home <-- Home文件
| | ├─ Loadable.ts <-- react-loadable文件
| | └─ index.tsx <-- Home页面
| ├─ /User <-- User文件
| | ├─ Loadable.ts <-- react-loadable文件
| | └─ index.tsx <-- User页面
| ├─ /Admin <-- Admin文件
| | ├─ Loadable.ts <-- react-loadable文件
| | └─ index.tsx <-- Admin页面
| └─ /Login <-- Login文件
| | ├─ Loadable.ts <-- react-loadable文件
| | └─ index.tsx <-- Login页面
Loadable.ts 每个文件都一样,对组件使用react-loadable:
/**Loadable.ts react-loadable文件*/
import Loadable from "react-loadable";
import Loading from "@/components/Loading";
export default Loadable({
loader: () => import("./index"),
loading: Loading,
});
修改组件的引入方式:修改routePrivilegeConfig.ts
...
// 组件
import Home from '@/pages/Home/Loadable';
import User from '@/pages/User/Loadable';
import Admin from '@/pages/Admin/Loadable';
import Login from '@/pages/Login/Loadable';
...
ok,npm start试试吧!!!
如果您看完之后收获了很多,请不要吝啬您的点赞!您的点赞是我前进最大的动力!!如果您觉得文章中有不正确的地方请指正!