如何新建react项目

98 阅读4分钟

项目环境:ReactVitetypescriptReact i18nAnt DesignCommitlintstyled-components``React i18nyarnredux@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

这里预配置srcsrc/assets/imagessrc/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,目前仅测试enzh

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 ProviderPersistGate

// 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;