项目环境:React
、Vite
、typescript
、React i18n
、Ant Design
、Commitlint
、styled-components``React i18n
、yarn
、redux
、@reduxjs/toolkit
1. 初始化项目
创建项目目录并初始化 Vite 项目:
mkdir my-react-project
cd my-react-project
yarn create vite
选择框架和模板:
输入项目名称,选择模版:React
+ TypeScript
2. 安装 React 和 TypeScript 相关依赖
安装 React、TypeScript 及其类型定义
yarn add react react-dom
yarn add --dev typescript @types/react @types/react-dom
3. 配置@
别名
要在 Vite 项目中配置路径别名 @
,需要修改 Vite 的配置文件 vite.config.ts
,并更新 TypeScript 的配置文件 tsconfig.json
。
3.1. 配置 Vite 别名
3.1.1. 安装必要的依赖:
yarn add @types/node --dev
3.1.2. 更新 Vite 配置文件 vite.config.ts
:
这里预配置src
、src/assets/images
、src/locale
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, 'src'),
+ '@images': path.resolve(__dirname, 'src/assets/images'),
+ '@locale': path.resolve(__dirname, 'src/locale'),
+ },
+ },
})
3.2. 配置 TypeScript 路径别名
3.2.1. 更新 TypeScript 配置文件 tsconfig.json
打开 tsconfig.json
文件,添加路径别名配置:
{
"compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"],
+ "@images/*": ["src/assets/images/*"],
+ "@locale": ["src/locale/*"],
+ },
// 其他配置项...
},
"include": ["src"]
}
3.3. 使用路径别名
- import { getStorage } from './locale/order/zh.ts';
+ import { getStorage } from '@locale/order/zh.ts';
4. 编辑本地存储文件
新建utils/storage.ts
文件
export enum StorageKeyEnum {
LANG = 'LANG'
}
export const setStorage = <ValueType, >(
key: StorageKeyEnum,
value: ValueType,
type?: 'session' | 'locale',
) => {
const setType = type ?? 'locale';
const valueString = JSON.stringify(value);
setType === 'session' && sessionStorage.setItem(key, valueString);
setType === 'locale' && localStorage.setItem(key, valueString);
};
export const getStorage = (key: string) => {
if (sessionStorage.getItem(key) || localStorage.getItem(key)) {
if ((sessionStorage.getItem(key) || localStorage.getItem(key)) === 'undefined') {
return undefined;
}
return JSON.parse(sessionStorage.getItem(key) || (localStorage.getItem(key) as string));
}
return null;
};
export const removeStorage = (key: string) => {
if (sessionStorage.getItem(key)) {
sessionStorage.removeItem(key);
}
if (localStorage.getItem(key)) {
localStorage.removeItem(key);
}
};
export const clearStorage = () => {
sessionStorage.clear();
localStorage.clear();
};
5. 安装 React i18n
5.1. 安装 React i18n 和 i18next:
yarn add react-i18next i18next
5.2. 配置 React i18n:
在 src
目录下创建 locale
文件夹, 并新建index.ts
以及测试目录order
,目前仅测试en
与zh
5.3. 编辑locale/index.ts
文件
import i18n, { Resource } from 'i18next';
import { initReactI18next } from 'react-i18next';
interface ITranslation {
[key: string]: string
}
const cache: Resource = {};
const modules = import.meta.glob('./**/*.ts', { eager: true })
for(const path in modules) {
const module = (modules[path] as { default: ITranslation }).default;
const lastPoint = path.lastIndexOf('.');
const lastLevel = path.lastIndexOf('/');
const firstLevel = path.indexOf('/');
const index = path.substring(lastLevel + 1, lastPoint);
const name = path.substring(firstLevel + 1, lastLevel);
cache[index] = {
translation: {
[name]: module,
...(cache[index] as { translation: ITranslation })?.translation }
}
}
i18n.use(initReactI18next).init({
lng: 'zh',
resources: cache,
});
export default i18n;
5.4. 切换和使用语言
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
function App() {
const { t, i18n } = useTranslation();
return (
<>
<div>
{t('order.product')}
</div>
<div className="card">
<button onClick={() => i18n.changeLanguage('en')}>
change
</button>
</>
)
}
export default App
6. 配置git信息
git init
git remote add origin [your address]
7. 配置 Commitlint
- 待进行
8. 配置组件库antd
yarn add antd
使用:
import { Button as AntButton } from 'antd';
<AntButton type='primary'>你好</AntButton>
9. 配置styled-components
9.1. 安装 Ant Design
和 styled-components:
yarn add styled-components @types/styled-components
9.2. 创建示例组件:
在 src
目录下创建 components
文件夹并添加一个示例组件 Button.tsx
:
import styled from 'styled-components';
const StyledButton = styled(AntButton)`
background-color: #4CAF50;
border-color: #4CAF50;
&:hover {
background-color: #45A049;
border-color: #45A049;
}
`;
<StyledButton>StyledButton</StyledButton>
10. 创建一个可编辑的svg
预览组件
11. 配置入口文件和路由
11.1. 新建src/router/index
import App from "@/App";
import {
createBrowserRouter,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
]);
export default router;
11.2. 正常使用
import { RouterProvider } from "react-router-dom";
import router from "./router/index.tsx";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
12. 添加Redux
12.1. 安装依赖
yarn add @reduxjs/toolkit react-redux redux-persist
12.2. 配置 Redux Store 和 Redux Persist
创建一个 Redux Store 并配置 Redux Persist。
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist';
import productReducer from './slices/productSlice';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
// 合并slice, 把所有reducer进行合并
const rootReducer = combineReducers({
product: productReducer,
});
// 1. 持久化配置
const persistConfig = {
key: '__T_G__', // key,唯一标识
storage,
};
// 2.创建持久化的 reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
// 3.创建 Redux store
const store = configureStore({
reducer: persistedReducer, // 创建 Redux store,并应用中间件和 reducer
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
// 忽略序列化检查 - 配置中间件以忽略特定的 redux-persist 动作(如 FLUSH、REHYDRATE 等),避免序列化检查错误
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
// 4.创建 persistor - 创建一个 persistor 对象,用于控制持久化进程。
const persistor = persistStore(store);
// 5. 类型推断, 依据 store 推断出 `RootState` 和 `AppDispatch` 类型
type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;
// 自定义的钩子,确保在使用 Redux 时能够利用 TypeScript 的类型检查和自动补全功能, 无需每次都引入RootState
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export { store, persistor };
12.3. 创建一个 Slice
创建一个 Redux Slice 来管理状态。
// src/store/slices/productSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Product } from '@/types/product';
export interface ProductState {
products: Product[];
}
const initialState: ProductState = {
products: [],
};
const productSlice = createSlice({
name: 'product',
initialState,
reducers: {
addProduct(state, action: PayloadAction<Product>) {
state.products.push(action.payload);
},
removeProduct(state, action: PayloadAction<number>) {
state.products = state.products.filter(product => product.id !== action.payload);
},
updateProduct(state, action: PayloadAction<Product>) {
const index = state.products.findIndex(product => product.id === action.payload.id);
if (index !== -1) {
state.products[index] = action.payload;
}
},
},
});
export const { addProduct, removeProduct, updateProduct } = productSlice.actions;
export default productSlice.reducer;
12.4. 配置 Redux Provider
和 PersistGate
在根组件中配置 Redux Provider
和 PersistGate
。
// src/App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from '@/store';
import ProductForm from '@/components/ProductForm';
const App = () => {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ProductForm />
</PersistGate>
</Provider>
);
};
export default App;
12.5. 5. 在组件中使用 Redux
在组件中使用 Redux 来管理状态。
// src/components/ProductForm.tsx
import React, { useRef } from 'react';
import { Input } from 'antd';
import SModalForm from '@/components/SModalForm';
import SUpload from '@/components/SUpload';
import { useTranslation } from 'react-i18next';
import { useDispatch, useAppSelector } from 'react-redux';
import { addProduct } from '@/store/slices/productSlice';
import { Product } from '@/types/product';
import { FormInstance } from 'antd/es/form';
interface ProductFormProps {
handleCancel: () => void;
handleOk: (data: Product) => void;
data: Product | null;
leftTitle: React.ReactNode;
}
const ProductForm = (props: ProductFormProps) => {
const { t } = useTranslation();
const formRef = useRef<FormInstance>(null);
const dispatch = useAppDispatch();
const products = useAppSelector(state => state.product.products);
const handleOk = (data: Product) => {
dispatch(addProduct(data));
};
return (
<SModalForm<Product>
formItems={[
{
label: t("product.proIcon"),
name: "icon",
children: (
<SUpload
handleUploaded={(fileId) =>
formRef.current?.setFieldValue("icon", fileId)
}
/>
),
required: true,
},
{
label: t("product.proName"),
name: "name",
children: <Input />,
required: true,
},
{
label: t("product.proModel"),
name: "model",
children: <Input />,
required: true,
},
{
label: t("product.proDesc"),
name: "desc",
children: <GRichText />,
required: true,
},
]}
ref={formRef}
handleCancel={props.handleCancel}
handleOk={handleOk}
data={props.data}
leftTitle={props.leftTitle}
/>
);
};
export default ProductForm;