npm create-react-app "myReactProgram" --template typescript
备注:若是已有项目想要引入ts的话
安装:npm install typescript --save-dev
初始化配置文件:npx tsc --init
(会生成tsconfig.json文件)
配置tsconfig.json 文件
{
// 编译选项
"compilerOptions": {
// 生成代码的语言版本:将我们写的 TS 代码编译成哪个版本的 JS 代码
// 命令行: tsc --target es5 11-测试TS配置文件.ts
"target": "es5",
// 指定要包含在编译中的 library
"lib": ["dom", "dom.iterable", "esnext"],
// 允许 ts 编译器编译 js 文件
"allowJs": true,
// 跳过类型声明文件的类型检查
"skipLibCheck": true,
// es 模块 互操作,屏蔽 ESModule 和 CommonJS 之间的差异
"esModuleInterop": true,
// 允许通过 import x from 'y' 即使模块没有显式指定 default 导出
"allowSyntheticDefaultImports": true,
// 开启严格模式
"strict": true,
// 对文件名称强制区分大小写
"forceConsistentCasingInFileNames": true,
// 为 switch 语句启用错误报告
"noFallthroughCasesInSwitch": true,
// 生成代码的模块化标准
"module": "esnext",
// 模块解析(查找)策略
"moduleResolution": "node",
// 允许导入扩展名为.json的模块
"resolveJsonModule": true,
// 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件
"isolatedModules": true,
// 编译时不生成任何文件(只进行类型检查)
"noEmit": true,
// 指定将 JSX 编译成什么形式
"jsx": "react-jsx"
},
// 指定允许 ts 处理的目录
"include": ["src"]
}
npm install --save react-router-dom
在index.tsx文件中用HashRouter包裹
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import Root from './root';
ReactDOM.render(
<HashRouter>
<Root />
</HashRouter>,
document.getElementById('root')
)
路由表配置(src/routes/index.tsx)
import { Navigate } from 'react-router-dom';
import Login from '../views/Login';
import Home from '../views/Home';
const routes = [
{
path: '/login',
auth: false,
component: <Login />,
},
{
path: '/home',
auth: true,
component: <Home />,
},
{
path: '/*',
component: <Navigate to='/login' replace={true} />,
},
];
export default routes;
在root.tsx文件中注册
注: 生成路由时遍历做了路由拦截–未登录时则跳转回登录页,导出路由
// 路由守卫-未登录则跳转至登录页
const RouteNav = (param: any) => {
return param.map(
(item: {
path: string;
auth: boolean;
component: ReactNode;
child?: any;
}) => {
return (
<Route
path={item.path}
element={
item.path === pathname && item.auth && !isLogin ? (
<Navigate to={'/login'} replace={true}></Navigate>
) : (
item.component
)
}
key={item.path}
>
{item?.child && RouteNav(item.child)}
</Route>
);
}
);
};
return (
<Fragment>
{pathname === '/login' ? null : <Header />}
<div className='main'>
{pathname === '/login' ? null : (
<SideMenu menuData={_sideMenuData}></SideMenu>
)}
<div>
<Routes>{RouteNav(routes)}</Routes>
</div>
</div>
</Fragment>
);
yarn add axios
request.ts
文件
import axios from 'axios';
const BASE_ERROR_MSG = '数据请求失败';
// 创建 axios 实例
const request = axios.create({
baseURL: 'https://trct.test.yhjcare.com', //请求公共地址
timeout: 10000,
});
// 请求拦截
request.interceptors.request.use((config: any) => {
const token = window.localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = token;
}
// 租户ID
config.headers['tenantId'] = '1';
return config;
});
// 响应拦截
request.interceptors.response.use(
(res) => {
const { status, data: { errorCode = BASE_ERROR_MSG } = {} } = res;
// 请求成功
if (status === 200) {
// 返回数据
return res.data;
} else {
// 请求异常
throw res;
}
},
(err = {}) => {
// 统一提示异常 --之后补充
const { message = '' } = err;
}
);
export default request;
api.ts
文件
import request from './request';
export const getUserInfo = ({ method = 'post', url = '', data = {} } = {}) =>
request({
url,
method,
// params,
data: data,
});
const BASE_ERROR_MSG = '数据请求失败';
// 请求队列
const _task: any = {};
export const api = async ({
type = '',
body: { method = 'post', url = '', data = {}, params = {} },
then = () => {},
}: any) => {
if (_task[type]) {
return {};
}
try {
_task[type] = 1;
const res: any = await request({ url, method, data, params });
const { errorCode = '', errorMessage = BASE_ERROR_MSG } = res;
// 统一提示日常
if (errorCode !== '0000') {
// 待补充
}
then(res);
_task[type] = 0;
return res;
} catch (error) {
_task[type] = 0;
return {};
}
};
import { api } from '../../utils/api';
async function getUserData() {
const res: any = await api({
type: 'getUserData',
body: {
url: '',
data: { username: '', password: '',},
},
});
console.log(res);
}
yarn add react-redux @reduxjs/toolkit
store/index.ts
/* app/store.ts */
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import getUserInfoReducer from './asyncSlice';
export const store = configureStore({
reducer: {
userInfo: getUserInfoReducer
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, Action<string>>;
store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './index';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
异步请求发送及获取结果更改redux数据中状态
asyncSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { api } from '../utils/api';
export interface CounterState {
isLogin: number;
}
const initialState: CounterState = {
isLogin: 0,
};
export const getUserInfo = createAsyncThunk(
'fetchUserInfo/getUserInfo',
async (params: any) => {
const reponse = await api({
type: 'getUserInfo',
body: { url: '/meet/backend/admin/login', data: params },
});
return reponse.data;
}
);
export const getUserInfoSlice = createSlice({
name: 'login',
initialState,
reducers: {
changeLoginState: (state) => {
state.isLogin = 1;
},
},
extraReducers: (builder) => {
builder
.addCase(getUserInfo.pending, (state) => {
state.isLogin = 0;
})
.addCase(getUserInfo.fulfilled, (state, action) => {
const token = action.payload?.token;
window.localStorage.setItem('token', token);
if (token) {
state.isLogin = 1;
}
})
.addCase(getUserInfo.rejected, (state) => {
state.isLogin = 0;
});
},
});
// export const { changeLoginState } = getUserInfoSlice.actions;
export default getUserInfoSlice.reducer;
import { createFromIconfontCN } from '@ant-design/icons';
const CustomIcon = createFromIconfontCN({
scriptUrl: '/static/iconfont.js', //图标路径
});
<CustomIcon style={{ fontSize: '18px' }} type='icon-refresh'/ />
yarn add dotenv-cli -D
.env.development
NODE_ENV="development"
REACT_APP_ENV="development"
REACT_APP_API='https://api.development.com'
.env.test
NODE_ENV="test"
REACT_APP_ENV='test'
REACT_APP_API='https://api.test.com'
.env.production
NODE_ENV="production"
REACT_APP_ENV='production'
REACT_APP_API='https://api.production.com'
"scripts": {
"start": "react-app-rewired start",
"start:test": "dotenv -e .env.test react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject",
"build:mock": "dotenv -e .env.development react-app-rewired build",
"build:test": "dotenv -e .env.test react-app-rewired build",
"build:prod": "dotenv -e .env.production react-app-rewired build"
},
封装的axios请求中,如request.ts文件,请求的公共地址记得改
const request = axios.create({
baseURL: process.env.REACT_APP_API, //请求公共地址
timeout: 10000,
});
const path = require('path');
const paths = require('react-scripts/config/paths');
// 非侵入式修改打包配置插件
const { override, addBabelPlugin, addWebpackAlias, overrideDevServer } = require('customize-cra');
// 打包环境和接口环境
let [env, api = ''] = process.env.npm_lifecycle_event.split(':');
// 开发环境打包
const start = env === 'start';
// 生产环境打包
const babel = env === 'babel';
// 修改输出文件夹为相对路径
paths.publicUrlOrPath = start ? '/' : './';
module.exports = {
webpack: override(
// 配置路径别名
addWebpackAlias({
'@': path.join(__dirname, 'src'),
components: path.join(__dirname, 'src/components'),
css: path.join(__dirname, 'src/assets/css'),
img: path.join(__dirname, 'src/assets/img')
}),
addBabelPlugin(['@babel/plugin-proposal-decorators', { legacy: true }]),
(config) => {
// ES6语法加强编译
config.entry = [
require.resolve('core-js/stable'),
require.resolve('regenerator-runtime/runtime'),
// MOCK数据
...(api === 'mock' ? [require.resolve('./mock')] : []),
config.entry
];
// 开发环境配置
if (start) {
// 可追踪源文件
config.devtool = 'eval-source-map';
} else {
// 生产环境不要开发调试工具
config.devtool = false;
// 设置输出的JS文件只生成一个文件
config.optimization.splitChunks = {};
config.optimization.runtimeChunk = false;
// 设置不要生成 xxx.js.LICENSE.txt
config['optimization']['minimizer'][0].options.extractComments = false;
// 打包到线上环境使用,压缩图片和清理调试信息
if (babel) {
// 清理日志
const compress = config['optimization']['minimizer'][0].options.minimizer.options.compress;
compress.drop_console = true;
compress.drop_debugger = true;
}
}
return config;
}
),
devServer: overrideDevServer((config) => {
return {
...config,
proxy: {
'/api': {
target: 'http://xx.xxxx.cn/api',
changeOrigin: true,
secure: false,
pathRewrite: {
'^/api': ''
}
}
}
};
})
};
yarn add mockjs
index.js
// 首先引入Mock
const Mock = require('mockjs');
// 设置拦截ajax请求的相应时间
Mock.setup({
timeout: '200-1000',
});
let configArray = [];
// 使用webpack的require.context()遍历所有mock文件
const files = require.context('.', true, /\.js$/);
files.keys().forEach((key) => {
if (key === './index.js') return;
configArray = configArray.concat(files(key).default);
});
// 注册所有的mock服务
configArray.forEach((item = '') => {
for (const [path, target] of Object.entries(item)) {
const protocol = path.split('|');
Mock.mock(new RegExp('^' + protocol[1] + '\\b'), protocol[0], target);
}
});
模拟接口文件,如login.js
export default {
'post|/user/grantAuth/login': () => {
return {
data: {
token:
'eyJhjHzREza1_yH_u49vweNlN5Ef3QEnafKM5-sKnse0a9X9Q',
},
errorCode: '0000',
errorMessage: 'success',
success: true,
};
},
};
yarn add sass -D
yarn add eslint -D
eslint-plugin-react用于react项目中eslint规则的配置
yarn add eslint-plugin-react -D
将package.json中的eslintConfig修改为一下内容
"eslintConfig": {
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": "react-app",
"rules": {
"indent": [
"error",
2,
{
"SwitchCase": 1,
"flatTernaryExpressions": true,
"ignoredNodes": [
"JSXElement *"
]
}
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"warn",
"single"
],
"semi": [
"warn",
"always"
],
"no-alert": "off",
"no-console": "off",
"import/no-anonymous-default-export": [
2,
{
"allowObject": true
}
]
},
"parserOptions": {
"parser": "babel-eslint"
}
},
yarn add react-app-rewired@
2.1.8 babel-plugin-import customize-cra -D
{
"compilerOptions": {
"esModuleInterop": true,
},
}
--save 简写 -S 安装生产环境下的依赖包
--save-dev 简写 -D 安装在开发环境下的依赖包
“Login”表示值,但在此处用作类型。是否指“类型 Login”?
解决办法:
此处路由表我用的是.ts文件,修改为.tsx文件之后就好了(原理不太懂)