一.创建项目文件
npx create-react-app react-ts-app --template typescript
二.安装依赖
2.1.安装ant-design前端框架+sass/scss
npm i sass scss antd --save
2.2.安装完成后将src中的css改成scss
App.css => App.scss
index.css => index.scss
相应代码中的import也需要调整。
2.3.安装react-app-rewired、customize-dev插件
npm i react-app-rewired customize-dev --save-dev
2.3.1.react-app-rewired
- 作用:React App Rewired 允许您在不弹出
react-scripts
的情况下修改 CRA 的默认配置。CRA 通过react-scripts
封装了 Webpack 和其他构建工具的配置,但是通常情况下这些配置是隐藏的,不容易修改。 - 使用场景:当您需要自定义 Webpack 配置,例如添加额外的 loader、plugin、alias 等,或者修改默认的 Babel 配置,都可以通过 react-app-rewired 实现。
- 如何使用:通过在项目中引入
react-app-rewired
和相关的配置文件,您可以在config-overrides.js
(默认名称)中修改 Webpack 配置,而不是直接编辑 CRA 自动生成的配置文件。
2.3.2.customize-cra
- 作用:
customize-cra
是一个基于react-app-rewired
的插件,提供了一些预定义的配置函数,使得定制 CRA 的配置更为简单和模块化。 - 使用场景:如果您只需执行一些常见的定制化操作(如修改 Babel 配置、添加额外的 loader 或 plugin),
customize-cra
提供的 API 可以帮助您更快速地实现这些操作,而无需自行编写复杂的 Webpack 配置。
2.4.替换脚本命令
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
2.5.在根目录创建config.overrides.js文件,然后配置webpack @指向src路径
const {
override,
addWebpack
} = require('customize-cra') //导入override,addWebpack这俩函数用于修改webpack
const path = require('path') //导入node.js核心模块path,处理和转换文件
module.export = override( //导出配置,将 @ 符号映射到项目根目录下的 src 文件夹
addWebpack({
'@': path.resolve(__dirname,'src')
})
)
2.6.需要ts生效@写法得在根目录下面创建tscofig.extend.json文件
{
"compileOnSave": {
"baseUrl":".",
"paths":{
"@/*":[
"src/*"
]
}
}
}
2.7.然后再在tsconfig.json中添加映射
{
"extend":"./tsconfig.extend.json",
"compileOnSave":{}
}
三.安装Redux,使用 Redux 的主要作用是管理应用的状态
npm i @reduxjs/toolkit react-redux
3.1.再在src中添加以下文件层级
src
├─ app
│ └─ store.ts
├─ features
│ ├─ counter
│ │ └─ counterSlice.ts
3.2 counterSlice.ts中添加以下代码
import {creatSlice} from '@redux.toolkit'
export interface CounterState { //定义状态类型
value:number
}
export initialState = createSlice({ //初始化状态
value:0
})
export const counterSlice = createSlice({ //创建 Redux slice counterSlice
name:'counter',
initialState,
reducers:{ //reducers 是一个包含多个 reducer 函数的对象,
increment:(state) => { //每个 reducer 函数负责处理一个特定的 action 类
state.value += state.value //型对状态的变化
},
decrement:(state) => {
state.value -= state.value
},
incrementByAmount:(state,action) => {
state.value += action.payload
}
}
})
export const {increment,decrement,incrementByAmount} = counterSlice.action
export default counterSlice.reducer
3.3.接下来就可以将counterSlice导入store.ts中
import {configureStore} from '@redux.toolkit'
import counterSlice from '@/features/counter/counterSlics'
export default configureStore({
reducer:{
counter:counterReducer // 可以添加更多模块
}
})
3.4.在src/index.tsx中全局配置store
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.scss';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './app/store';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
四.使用Eslint + Prettier 作为代码检查自动格式化
vscode需要安装 Eslint + Prettier插件
4.1.安装依赖命令
npm i eslint@7.32.0 @typescript-eslint/eslint-plugin@5.0.0 @typescript-eslint/parser@5.0.0 @eslint/js@9.0.0 globals@15.0.0 --save-dev
npm i eslint-config-prettier eslint-plugin-prettier prettier --save-dev
4.2.在根目录新建以下文件
├─ .vscode
│ └─ settings.json
├─ .eslintignore
├─ .eslintrc.js
├─ prettier.config.js
4.3.setting.json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
4.4. .eslintignore
# .eslintignore
build/
dist/
node_modules/
4.5. .eslintrc.js
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended'
],
settings: {
react: {
version: '999.999.999' //消除npm run lint时的警告信息
}
},
overrides: [
{
env: {
node: true
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script'
}
}
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['@typescript-eslint', 'react'],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': ['off']
}
};
4.6. prettier.config.js
// prettier.config.js
module.exports = {
// 一行最多 100 字符
printWidth: 100,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: 'none',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// 换行符使用 lf
endOfLine: 'lf'
};
然后重启vscode。
五.路由配置+登录拦截+ant
样例+redux
样例
5.1.添加多个目录结构
src
├─ components
│ └─ withRouter.tsx
├─ router
│ └─ index.tsx
├─ pages
│ ├─ layout
│ │ ├─ index.scss
│ │ └─ index.tsx
│ ├─ home
│ │ └─ index.tsx
│ ├─ 404
│ │ └─ index.tsx
│ ├─ login
│ │ └─ index.tsx
│ ├─ table
│ │ └─ index.tsx
六.安装路由
6.1. 安装依赖
npm i react-router-dom
6.2.在目录中添加路由高阶组件WithRouter.tsx
import Login from '../pages/login/index';
import Layout from '../pages/layout/index';
import Home from '../pages/home/index';
import Other from '../pages/other/index';
import NotPage from '../pages/404/index';
import { useRoutes, Navigate, RouteObject } from 'react-router-dom';
import React from 'react';
function getItem(
path: RouteObject['path'],
element: RouteObject['element'],
children?: RouteObject['children']
): RouteObject {
return {
path,
element,
children
} as RouteObject;
}
// 设置路由的地方
const routers: RouteObject[] = [
getItem('/login', <Login></Login>),
getItem('/', <Layout></Layout>, [
getItem('/', <Home></Home>),
getItem('/other', <Other></Other>)
]),
getItem('*', <NotPage></NotPage>)
];
const isUserAuthenticated = () => {
const token = localStorage.getItem('token');
return token && token.length > 0;
};
const AuthRoute: React.FC<React.PropsWithChildren> = (props: React.PropsWithChildren) => {
if (!isUserAuthenticated()) {
return (
<div>
{props.children}
<Navigate to="/login" />
</div>
);
}
return <div>{props.children}</div>;
};
AuthRoute.displayName = 'AuthRoute';
const RouterInterceptor: React.FC = (props: React.PropsWithChildren) => {
const routerEls = useRoutes(routers);
return <AuthRoute>{routerEls}</AuthRoute>;
};
RouterInterceptor.displayName = 'RouterInterceptor';
export default RouterInterceptor;
6.3.添加路由拦截器和路由配置
import Login from '../pages/login/index';
import Layout from '../pages/layout/index';
import Home from '../pages/home/index';
import Other from '../pages/other/index';
import NotPage from '../pages/404/index';
import { useRoutes, Navigate, RouteObject } from 'react-router-dom';
import React from 'react';
function getItem(
path: RouteObject['path'],
element: RouteObject['element'],
children?: RouteObject['children']
): RouteObject {
return {
path,
element,
children
} as RouteObject;
}
// 设置路由的地方
const routers: RouteObject[] = [
getItem('/login', <Login></Login>),
getItem('/', <Layout></Layout>, [
getItem('/', <Home></Home>),
getItem('/other', <Other></Other>)
]),
getItem('*', <NotPage></NotPage>)
];
const isUserAuthenticated = () => {
const token = localStorage.getItem('token');
return token && token.length > 0;
};
const AuthRoute: React.FC<React.PropsWithChildren> = (props: React.PropsWithChildren) => {
if (!isUserAuthenticated()) {
return (
<div>
{props.children}
<Navigate to="/login" />
</div>
);
}
return <div>{props.children}</div>;
};
AuthRoute.displayName = 'AuthRoute';
const RouterInterceptor: React.FC = (props: React.PropsWithChildren) => {
const routerEls = useRoutes(routers);
return <AuthRoute>{routerEls}</AuthRoute>;
};
RouterInterceptor.displayName = 'RouterInterceptor';
export default RouterInterceptor;
6.4.ant Form + Table
结合的页面 table/index.tsx
import React, { useState } from 'react';
import { Button, Table, Form, Row, Col, Space, Input } from 'antd';
import { DownOutlined } from '@ant-design/icons';
import type { TableColumnsType, TablePaginationConfig } from 'antd';
interface DataType {
key: React.Key;
name: string;
age: number;
address: string;
}
const columns: TableColumnsType<DataType> = [
{
title: 'Name',
dataIndex: 'name'
},
{
title: 'Age',
dataIndex: 'age'
},
{
title: 'Address',
dataIndex: 'address'
}
];
const data: DataType[] = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`
});
}
interface SearchFormFields {
label: string;
name: string;
element: any;
rule?: object[];
}
const fields: SearchFormFields[] = [
{
label: 'Name',
name: 'name',
element: <Input placeholder="请输入" />
},
{
label: 'Age',
name: 'age',
element: <Input placeholder="请输入" />
},
{
label: 'Address',
name: 'address',
element: <Input placeholder="请输入" />
}
];
const TableList: React.FC = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loading, setLoading] = useState(false);
const [expand, setExpand] = useState(false);
const [form] = Form.useForm();
const getFields = () => {
const count = expand ? fields.length : 3;
const children = [];
for (let i = 0; i < count; i++) {
const element = fields[i];
children.push(
<Col span={8} key={i}>
<Form.Item name={element.name} label={element.label} rules={element.rule}>
{element.element}
</Form.Item>
</Col>
);
}
return children;
};
const pageSetting: TablePaginationConfig = {
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `Total ${total} items`
};
const start = () => {
setLoading(true);
// ajax request after empty completing
setTimeout(() => {
setSelectedRowKeys([]);
setLoading(false);
}, 1000);
};
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
console.log('selectedRowKeys changed: ', newSelectedRowKeys);
setSelectedRowKeys(newSelectedRowKeys);
};
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange
};
const formStyle: React.CSSProperties = {
maxWidth: 'none',
padding: 24
};
const onFinish = (values: any) => {
console.log('Received values of form: ', values);
};
const hasSelected = selectedRowKeys.length > 0;
return (
<div>
<Form form={form} name="advanced_search" style={formStyle} onFinish={onFinish}>
<Row gutter={24}>{getFields()}</Row>
<div style={{ textAlign: 'right' }}>
<Space size="small">
<Button type="primary" htmlType="submit">
Search
</Button>
<Button
onClick={() => {
form.resetFields();
}}
>
Clear
</Button>
{fields.length > 3 ? (
<a
style={{ fontSize: 12 }}
onClick={() => {
setExpand(!expand);
}}
>
<DownOutlined rotate={expand ? 180 : 0} /> Collapse
</a>
) : (
<span></span>
)}
</Space>
</div>
</Form>
<Table
pagination={pageSetting}
rowSelection={rowSelection}
columns={columns}
dataSource={data}
/>
</div>
);
};
export default TableList;
6.5.修改App.tsx
import React from 'react';
import { ConfigProvider } from 'antd';
import dayjs from 'dayjs'
import zhCN from 'antd/locale/zh_CN'
import './App.scss';
import { useRoutes } from 'react-router-dom';
import RouterInterceptor from '@/router/index'
const App: React.FC = () => {
return (
<ConfigProvider>
<div className='App'>
<RouterInterceptor />
</div>
</ConfigProvider>
)
}
export default App
6.7.修改index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.scss';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './app/store';
import { BrowserRouter } from 'react-router-dom'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();