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

这篇具有很好参考价值的文章主要介绍了react+ts【项目实战一】配置项目/路由/redux。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、项目搭建

1、创建项目

  • 1、该项目使用的是ts创建的 所以需要加上--template typescript
    • create-react-app kiki_ts_react_music --template typescript
  • 2、整理项目结构 删除一些自己用不到的文件
  • react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css
    react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

1.2 配置项目

1.2.1 更换icon

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

1.2.2 更换项目名称

在index.html文件里面

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

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
    react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css
    "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

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

1.3.2 使用prettier工具

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

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css
react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

  • 1.安装prettier

    npm install prettier -D

  • 2、配置.prettierrc文件:在根目录下创建该文件
    react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

{
  "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 项目结构

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

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')
   }
 }
}

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

  • 3、配置自定义的css
    react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,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
react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

生成代码片段的网站
https://snippet-generator.app/?description=&tabtrigger=&snippet=&mode=vscode
react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

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

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

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的封装

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

  • 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_…

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css

  • 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

react+ts【项目实战一】配置项目/路由/redux,react,react.js,javascript,前端,css文章来源地址https://www.toymoban.com/news/detail-826319.html

  • 使用的页面
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

到了这里,关于react+ts【项目实战一】配置项目/路由/redux的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包