react+ts【项目实战一】配置项目/路由/redux

1、项目搭建

1、创建项目

  • 1、该项目使用的是ts创建的 所以需要加上--template typescript
    • create-react-app kiki_ts_react_music --template typescript
  • 2、整理项目结构 删除一些自己用不到的文件
  • 在这里插入图片描述
    在这里插入图片描述

1.2 配置项目

1.2.1 更换icon

在这里插入图片描述

1.2.2 更换项目名称

在index.html文件里面

在这里插入图片描述

1.2.1 配置项目别名

  • 1、npm i -D @craco/craco
  • 2、在根文件创建 craco.config.ts
const path = require("path");
const CracoLessPlugin = require("craco-less");

// path.resolve返回当前文件的绝对路径 拼接+dir
const resolve = (dir) => path.resolve(__dirname, dir);
module.exports = {
 plugins: [{ plugin: CracoLessPlugin }],
 webpack: {
   alias: {
     "@": resolve("src"),
   },
 },
};
  • 3、修改tsconfig.json
    在这里插入图片描述
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  • 4、修改 package.json
  "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
  },

1.3 代码规范

1.3.1 集成editorconfig配置

EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。

  • 1、在根目录下创建.editorconfig文件
# http://editorconfig.org

root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行尾的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false

**同时需要安装插件 **EditorConfig for VS Code

在这里插入图片描述

1.3.2 使用prettier工具

Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 1.安装prettier

    npm install prettier -D

  • 2、配置.prettierrc文件:在根目录下创建该文件
    在这里插入图片描述

{
  "useTabs": false,
  "tabWidth": 2,
  "printWidth": 80,
  "singleQuote": true,
  "trailingComma": "none",
  "semi": false
}

  • 3、创建.prettierignore忽略文件 在根目录下
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*

  • 4、在package.json中配置一个scripts:
    "prettier": "prettier --write ."

执行 npm run prettier就会将项目全部按照prettier的配置进行格式化

1.4 项目结构

在这里插入图片描述

1.5 对css进行重置

  • 1、下载normalize.css
    cnpm install normalize.css
    在index.tsx里面引入import 'normalize.css'

  • 2、使用less
    cnpm install craco-less

const path = require('path')
const CracoLessPlugin = require('craco-less')

const resolve = (dir) => path.resolve(__dirname, dir)
module.exports = {
 plugins: [{ plugin: CracoLessPlugin }],
 webpack: {
   alias: {
     '@': resolve('src')
   }
 }
}

在这里插入图片描述

  • 3、配置自定义的css
    在这里插入图片描述
    最后都在index.jsx中引入
import 'normalize.css'
import '@/assets/css/index.less'

1.6 注入router

npm install react-router-dom

  • 在tsx中 使用到dom的页面都需要引入import React from 'react'

  • router/index.tsx

import React from 'react'
import type { RouteObject } from 'react-router-dom'
import Discover from '@/views/discover'
import Mime from '@/views/mime'

const routes: RouteObject[] = [
  { path: '/', element: <Mime /> },
  { path: '/discover', element: <Discover /> }
]

export default routes

  • index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'
import { BrowserRouter } from 'react-router-dom'
import 'normalize.css'
import '@/assets/css/index.less'

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
)

  • index.tsx
import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>hahah</h1>
        <Link to="/discover">发现音乐</Link>
        <Suspense fallback="正在加载">{useRoutes(routes)}</Suspense>
      </header>
    </div>
  )
}

export default App

1.7 定义TS组件的规范

import React, { memo } from 'react'
import type { ReactNode } from 'react'

// 定义传进来的props类型
interface IProps {
  // 在之前的版本props默认会有children是插槽 在后来取消了得自己写
  children?: ReactNode
  name?: string
  age?: number
}

const Download: React.FC<IProps> = (props) => {
  return (
    <div>
      {props.children}
      <h1>{props.age}</h1>
      <h1>{props.name}</h1>
    </div>
  )
}

export default memo(Download)

import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'
import Download from './views/download'

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>hahah</h1>
        <Link to="/discover">发现音乐</Link>

        <Download name="kiki">
          <h1>我是downLoad的插槽</h1>
        </Download>

        <Suspense fallback="正在加载">{useRoutes(routes)}</Suspense>
      </header>
    </div>
  )
}

export default App

1.8 创建代码片段

首选项=>设置代码片段=>react-ts
在这里插入图片描述

生成代码片段的网站
https://snippet-generator.app/?description=&tabtrigger=&snippet=&mode=vscode
在这里插入图片描述

import React, { memo } from 'react'
import type { FC, ReactNode } from 'react'

interface IProps {
  children?: ReactNode
}

const Template: FC<IProps> = () => {
  return <div>Template</div>
}

export default memo(Template)

1.9 二级路由和懒加载

  • discover页面
import React, { memo, Suspense } from 'react'
import type { FC, ReactNode } from 'react'
import { Outlet, Link } from 'react-router-dom'

interface IProps {
  children?: ReactNode
}

const Discover: FC<IProps> = () => {
  return (
    <div>
      <div>
        <Link to="/discover/recommend">推荐</Link>
        <Link to="/discover/ranking">排行榜</Link>
        <Link to="/discover/songs">歌单</Link>
        <Link to="/discover/djradio">主播电台</Link>
        <Link to="/discover/artist">歌手</Link>
        <Link to="/discover/album">新碟上架</Link>
      </div>
      {/* 二级路由也可以用suspense */}
      <Suspense fallback="正在加载">
        <Outlet />
      </Suspense>
    </div>
  )
}

export default memo(Discover)

  • App.jsx
import React, { Suspense } from 'react'
import { Link, useRoutes } from 'react-router-dom'
import routes from './router'
import Download from './views/download'

function App() {
  return (
    <div className="App">
      <div className="nav">
        <Link to="/discover">发现音乐</Link>
        <Link to="/mine">我的音乐</Link>
        <Link to="/focus">关注</Link>
        <Link to="/download">下载客户端</Link>
      </div>
      <Suspense fallback="正在加载">{useRoutes(routes)}</Suspense>
      <div className="main"></div>
    </div>
  )
}

export default App

在这里插入图片描述

1.10 redux-reduxtk

cnpm install @reduxjs/toolkit react-redux

  • index.tsx 提供Provide
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import 'normalize.css'
import '@/assets/css/index.less'
import store from './store'

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>
)

  • store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import { useSelector, useDispatch, TypedUseSelectorHook } from 'react-redux'
import counterReducer from './modules/counter'

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
})

// 获取函数的返回类型
type GetStateFnType = typeof store.getState
// 获取函数返回类型的类型
type IRootState = ReturnType<GetStateFnType>
type DispatchType = typeof store.dispatch

export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
export const useAppDisPatch: () => DispatchType = useDispatch

export default store


  • store/count.ts
import { createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 1,
    message: 'hello'
  },
  reducers: {
    changeMessageAction(state, { payload }) {
      state.message = payload
    }
  }
})

export const { changeMessageAction } = counterSlice.actions
export default counterSlice.reducer

  • 使用的页面
import React, { memo, Suspense } from 'react'
import type { FC, ReactNode } from 'react'
import { Outlet, Link } from 'react-router-dom'
import { useAppDisPatch, useAppSelector } from '@/store'
import { shallowEqual } from 'react-redux'
import { changeMessageAction } from '@/store/modules/counter'

interface IProps {
  children?: ReactNode
}

const Discover: FC<IProps> = () => {
  const { count, message } = useAppSelector(
    (state) => ({
      count: state.counter.count,
      message: state.counter.message
    }),
    shallowEqual
  )

  const dispatch = useAppDisPatch()

  const changeMessage = (message: string) => {
    dispatch(changeMessageAction(message))
  }
  return (
    <div>
      <div>
        {count}=={message}
        <button onClick={() => changeMessage('修改message')}>
          修改message
        </button>
        <Link to="/discover/recommend">推荐</Link>
        <Link to="/discover/ranking">排行榜</Link>
        <Link to="/discover/songs">歌单</Link>
        <Link to="/discover/djradio">主播电台</Link>
        <Link to="/discover/artist">歌手</Link>
        <Link to="/discover/album">新碟上架</Link>
      </div>
      {/* 二级路由也可以用suspense */}
      <Suspense fallback="正在加载">
        <Outlet />
      </Suspense>
    </div>
  )
}

export default memo(Discover)

1.10 axios的封装

在这里插入图片描述

  • request/index.ts
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { HYRequestConfig } from './type'

// 拦截器: 蒙版Loading/token/修改配置

/**
 * 两个难点:
 *  1.拦截器进行精细控制
 *    > 全局拦截器
 *    > 实例拦截器
 *    > 单次请求拦截器
 *
 *  2.响应结果的类型处理(泛型)
 */

class HYRequest {
  instance: AxiosInstance

  // request实例 => axios的实例
  constructor(config: any) {
    this.instance = axios.create(config)

    // 每个instance实例都添加拦截器
    this.instance.interceptors.request.use(
      (config) => {
        // loading/token
        return config
      },
      (err) => {
        return err
      }
    )
    this.instance.interceptors.response.use(
      (res) => {
        return res.data
      },
      (err) => {
        return err
      }
    )

    // 针对特定的hyRequest实例添加拦截器
    this.instance.interceptors.request.use(
      config.interceptors?.requestSuccessFn,
      config.interceptors?.requestFailureFn
    )
    this.instance.interceptors.response.use(
      config.interceptors?.responseSuccessFn,
      config.interceptors?.responseFailureFn
    )
  }

  // 封装网络请求的方法
  // T => IHomeData
  request<T = any>(config: HYRequestConfig<T>) {
    // 单次请求的成功拦截处理
    if (config.interceptors?.requestSuccessFn) {
      config = config.interceptors.requestSuccessFn(config)
    }

    // 返回Promise
    return new Promise<T>((resolve, reject) => {
      this.instance
        .request<any, T>(config)
        .then((res) => {
          // 单词响应的成功拦截处理
          if (config.interceptors?.responseSuccessFn) {
            res = config.interceptors.responseSuccessFn(res)
          }
          resolve(res)
        })
        .catch((err) => {
          reject(err)
        })
    })
  }

  get<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: 'GET' })
  }
  post<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: 'POST' })
  }
  delete<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: 'DELETE' })
  }
  patch<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: 'PATCH' })
  }
}

export default HYRequest

  • request/type.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'

// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {
  requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestFailureFn?: (err: any) => any
  responseSuccessFn?: (res: T) => T
  responseFailureFn?: (err: any) => any
}

export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: HYInterceptors<T>
}

  • config/index.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'

// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {
  requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestFailureFn?: (err: any) => any
  responseSuccessFn?: (res: T) => T
  responseFailureFn?: (err: any) => any
}

export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: HYInterceptors<T>
}

环境变量也可以通过配置文件 但是前面需要加上REACT_APP_…

在这里插入图片描述

  • service/index.ts
import { BASE_URL, TIME_OUT } from './config'
import HYRequest from './request'

const hyRequest = new HYRequest({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
  interceptors: {
    requestSuccessFn: (config: any) => {
      return config
    }
  }
})

export default hyRequest

在这里插入图片描述

  • 使用的页面
import React, { memo, useEffect, useState } from 'react'
import type { FC, ReactNode } from 'react'
import hyRequest from '@/service'

interface IProps {
  children?: ReactNode
}

export interface IBannerData {
  imageUrl: string
  targetId: number
  targetType: number
  titleColor: string
  typeTitle: string
  url: string
  exclusive: boolean
  scm: string
  bannerBizType: string
}

const Recommend: FC<IProps> = () => {
  const [banners, setBanners] = useState<IBannerData[]>([])

  // 测试网络请求
  useEffect(() => {
    hyRequest
      .get({
        url: '/banner'
      })
      .then((res) => {
        setBanners(res.banners)
      })
  }, [])

  return (
    <div>
      {banners.map((item, index) => {
        return <div key={index}>{item.imageUrl}</div>
      })}
    </div>
  )
}

export default memo(Recommend)

  • 可以在这个页面自动生成类型定义
    https://transform.tools/json-to-typescript

1.11 类组件和TS的结合

import React, { PureComponent } from 'react'
/**
 * state:
 * props:
 */

interface IProps {
  name: string
  age?: number
}

interface IState {
  message: string
  counter: number
}

class Demo02 extends PureComponent<IProps, IState> {
  name = 'aaaa'
  state = {
    message: 'Hello World',
    counter: 99
  }

  // getSnapshotBeforeUpdate() {
  //   return { address: '庐山' }
  // }

  // componentDidUpdate(
  //   prevProps: Readonly<IProps>,
  //   prevState: Readonly<IState>,
  //   snapshot?: ISnapshot | undefined
  // ): void {}

  // constructor(props: IProps) {
  //   super(props)

  //   // this.state = {
  //   //   message: 'Hello World',
  //   //   counter: 100
  //   // }
  // }

  render(): React.ReactNode {
    return (
      <div>
        name: {this.props.name}
        age: {this.props.age}
        message: {this.state.message}
        counter: {this.state.counter}
      </div>
    )
  }
}

export default Demo02


1.12 redux和ts的结合

import { createSlice, PayloadAction } from '@reduxjs/toolkit'

interface IState {
  count: number
  message: string
  address: string
  height: number
  direction: 'left' | 'right' | 'up' | 'down'
  names: string[]
}

const initialState: IState = {
  count: 100,
  message: 'Hello Redux',
  address: '广州市',
  height: 1.88,
  direction: 'left',
  names: []
}

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    changeMessageAction(state, { payload }: PayloadAction<string>) {
      state.message = payload
    }
  }
})

export const { changeMessageAction } = counterSlice.actions
export default counterSlice.reducer

<think> 我们参考上述引用,结合用户需求(使用React、TypeScript、Reduxreact-router-dom创建项目并进行初始化配置)来给出详细步骤。 注意:引用中提到了不同版本(如react-router-dom有5.2.0和6.x),我们这里以较新且常见的版本为例,但会注意版本差异。 步骤概览: 1. 创建React + TypeScript项目 2. 安装Redux相关依赖(包括reduxreact-redux、@reduxjs/toolkit等) 3. 安装react-router-dom 4. 配置Redux Store(包括创建store、slice等) 5. 配置react-router-dom(路由设置) 6.Redux和Router集成到项目中(使用Provider和Router组件包裹应用) 7. (可选)配置持久化存储(如redux-persist) 详细步骤: 1. 创建React + TypeScript项目 使用Create React App,并指定TypeScript模板: ```bash npx create-react-app my-app --template typescript cd my-app ``` 2. 安装Redux相关依赖 根据引用[3],使用Redux Toolkit(官方推荐)和react-redux: ```bash npm install @reduxjs/toolkit react-redux ``` 3. 安装react-router-dom 根据引用[2]和[3],注意版本差异。这里我们安装react-router-dom v6(最新稳定版): ```bash npm install react-router-dom@6 ``` 同时安装类型声明(TypeScript项目需要): ```bash npm install --save-dev @types/react-router-dom ``` 4. 配置Redux Store 在src目录下创建store文件夹,并创建index.ts文件: ```ts // src/store/index.ts import { configureStore } from '@reduxjs/toolkit'; // 这里假设我们有个counterReducer,实际中替换成你自己的reducer import counterReducer from './features/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, // 其他reducer }, }); // 导出RootState和AppDispatch类型,以便在组件中使用 export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch; ``` 创建slice(示例): ```ts // src/store/features/counterSlice.ts import { createSlice } from '@reduxjs/toolkit'; export const counterSlice = createSlice({ name: 'counter', initialState: { value: 0, }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, }, }); export const { increment, decrement } = counterSlice.actions; export default counterSlice.reducer; ``` 5. 配置react-router-dom 在src目录下创建路由配置文件,例如src/routes.tsx(或直接写在App.tsx中,但建议分离): ```tsx // src/routes.tsx import { Routes, Route } from 'react-router-dom'; import HomePage from './pages/Home'; import AboutPage from './pages/About'; const AppRoutes = () => ( <Routes> <Route path="/" element={<HomePage />} /> <Route path="/about" element={<AboutPage />} /> </Routes> ); export default AppRoutes; ``` 创建页面组件(例如Home和About)在src/pages目录下。 6. 集成Redux和Router到项目中 修改src/index.tsx,使用Provider提供store,使用BrowserRouter提供路由: ```tsx // src/index.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import { store } from './store'; import { Provider } from 'react-redux'; 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> ); ``` 在App.tsx中引入路由: ```tsx // src/App.tsx import AppRoutes from './routes'; function App() { return ( <div className="App"> <AppRoutes /> </div> ); } export default App; ``` 7. (可选)配置持久化存储 如果希望Redux状态持久化(如引用[3]中的redux-persist),可以安装并配置: ```bash npm install redux-persist ``` 修改store配置(src/store/index.ts): ```ts import { configureStore } from '@reduxjs/toolkit'; import { persistStore, persistReducer } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; // 默认使用localStorage import counterReducer from './features/counterSlice'; const persistConfig = { key: 'root', storage, // 可以选择哪些reducer需要持久化 whitelist: ['counter'] }; const persistedReducer = persistReducer(persistConfig, counterReducer); export const store = configureStore({ reducer: { counter: persistedReducer, // 替换原来的counterReducer // 其他reducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, // 避免持久化相关的序列化检查警告 }), }); export const persistor = persistStore(store); ``` 然后修改index.tsx,添加PersistGate组件: ```tsx import { persistor } from './store'; import { PersistGate } from 'redux-persist/integration/react'; root.render( <React.StrictMode> <Provider store={store}> <PersistGate loading={null} persistor={persistor}> <BrowserRouter> <App /> </BrowserRouter> </PersistGate> </Provider> </React.StrictMode> ); ``` 注意:以上步骤是个基本配置,实际项目中可能需要根据需求调整。 相关问题: 1. 如何在不同React Router版本中配置路由? 2. Redux Toolkit相比传统Redux有什么优势? 3. 如何优化Redux持久化存储的性能? </think> 以下是使用 React、TypeScript、Reduxreact-router-dom 创建项目并进行初始化配置的详细步骤: ### 、创建项目基础结构 1. **创建 React+TypeScript 项目** ```bash npx create-react-app my-app --template typescript cd my-app ``` 2. **安装核心依赖** ```bash npm install react-router-dom @types/react-router-dom redux react-redux @reduxjs/toolkit redux-thunk ``` ### 二、Redux 配置 1. **创建 store 结构** ```markdown src/ ├── store/ │ ├── index.ts # Store 主文件 │ └── features/ # Redux 切片目录 │ └── counterSlice.ts ``` 2. **配置 Store (src/store/index.ts)** ```ts import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './features/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, // 添加其他 reducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(/* 自定义中间件 */), }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch; ``` 3. **创建示例 Slice (src/store/features/counterSlice.ts)** ```ts import { createSlice, PayloadAction } from '@reduxjs/toolkit'; interface CounterState { value: number; } const initialState: CounterState = { value: 0 }; const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action: PayloadAction<number>) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; export default counterSlice.reducer; ``` ### 三、路由配置 1. **创建路由文件 (src/AppRouter.tsx)** ```tsx import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import HomePage from './pages/Home'; import AboutPage from './pages/About'; import Layout from './components/Layout'; const AppRouter = () => ( <Router> <Routes> <Route path="/" element={<Layout />}> <Route index element={<HomePage />} /> <Route path="about" element={<AboutPage />} /> <Route path="*" element={<div>404 Not Found</div>} /> </Route> </Routes> </Router> ); export default AppRouter; ``` 2. **创建页面组件 (示例: src/pages/Home.tsx)** ```tsx import { useAppSelector } from '../store'; const HomePage = () => { const count = useAppSelector(state => state.counter.value); return ( <div> <h1>Home Page</h1> <p>Current count: {count}</p> </div> ); }; export default HomePage; ``` ### 四、集成到应用入口 **修改 src/index.tsx** ```tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import AppRouter from './AppRouter'; import { store } from './store'; import './index.css'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( <React.StrictMode> <Provider store={store}> <AppRouter /> </Provider> </React.StrictMode> ); ``` ### 五、补充配置 1. **TypeScript 类型增强 (src/types.d.ts)** ```ts import { RootState, AppDispatch } from './store'; declare module 'react-redux' { interface DefaultRootState extends RootState {} export const useDispatch: () => AppDispatch; } ``` 2. **路由布局组件 (src/components/Layout.tsx)** ```tsx import { Outlet, Link } from 'react-router-dom'; const Layout = () => ( <div> <nav> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> </ul> </nav> <hr /> <Outlet /> {/* 子路由渲染位置 */} </div> ); export default Layout; ``` ### 六、运行项目 ```bash npm start ``` ### 关键配置说明 1. **Redux 优化**:使用 Redux Toolkit 简化 store 配置,自动整合 Thunk 中间件 2. **类型安全**:通过 `RootState` 和 `AppDispatch` 类型确保类型安全 3. **路由嵌套**:使用 `<Outlet>` 实现布局组件嵌套路由 4. **模块化结构**:分离 store/features 实现功能模块化 [^1]: 此配置方案基于 react-router-dom v6 和 Redux Toolkit 最佳实践,支持动态路由加载和状态持久化扩展 [^2]: 类型声明增强确保组件内 `useSelector` 和 `useDispatch` 的类型安全 [^3]: 路由配置采用嵌套布局模式,便于扩展复杂路由结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值