从0到1带你搭建react+ts项目

  • 读本篇文章你可以了解到:
    • 如何创建一个react+ts项目
    • 如何在项目中使用prettier + eslint 校验
    • 如何给redux配置ts校验
    • 如何给路由配置ts校验
    • 如何利用ts声明函数式组件/类组件

创建项目

react+ts项目 ts可以单独配置,但是会有一定的弊端

  1. webpack是隐藏的 需要 通过eject暴露出去在单独配置 较为复杂
  2. typescript react 项目配置有难度
    • 需要配置 tsc (typescript Compiler) 用于检测ts
    • Babel 转换ts为js

所以我们选择用create-react-app –template

这会配置所需的ts环境,在我们的目录下除了tsconfig还会多出文件react-app.d.ts,该文件为我们添加了一些声明,让我们使用的时候不会报错

配置别名

  1. 方法1 配置 eject 暴露webpack
  2. 使用craco 配置
    • 安装 npm install @craco/craco@alpha -D

      默认安装的@craco/craco 针对react版本比较低 所以下载最新的 支持react-scripts”: “5.0.1”

    • 安装之后 我们通过通过craco给webpack配置项目别名
      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"),
      },
      },
      };
      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"),
          },
        },
      };
      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"), }, }, };
    • 更改package.json 使用craco启动项目 应用我们配置的东西
      "scripts": {
      "start": "craco start",
      "build": "craco build",
      "test": "craco test",
      "eject": "react-scripts eject"
      }
         "scripts": {
          "start": "craco start",
          "build": "craco build",
          "test": "craco test",
          "eject": "react-scripts eject"
        }
      "scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" }
    • 问题出现:ts并不识别别名 手动打开tsconfig.json文件修改下配置
      //首先配置tsconfig的baseUrl属性 设置当前目录
      "baseUrl": ".",
      // 然后配置paths 支持别名
      "paths":{
      "@/*":\["src/*"]
      }
       //首先配置tsconfig的baseUrl属性 设置当前目录
      "baseUrl": ".",
         // 然后配置paths 支持别名
       "paths":{
       "@/*":\["src/*"]
       }
      //首先配置tsconfig的baseUrl属性 设置当前目录 "baseUrl": ".", // 然后配置paths 支持别名 "paths":{ "@/*":\["src/*"] }

配置格式化和校验

这里我们使用prettier 和 eslint 实现我们的格式化和校验

配置prettier

vscode 配置默认prettier 快捷键‘ctrl+, ’ 搜索editor default 勾选code fomatter为prettier (要先在vscode下载插件prettier)

  • 安装prettier npm install prettier -D
  • 创建.prettierrc并配置所需要的一些校验 -rc后缀 runtime (运行时检测)
    {
    "useTabs": false,
    "tabWidth": 2,
    "printWidth": 80,
    "singleQuote": true,
    "trailingComma": "none",
    "semi": false
    }
    {
      "useTabs": false,
      "tabWidth": 2,
      "printWidth": 80,
      "singleQuote": true,
      "trailingComma": "none",
      "semi": false
    }
    { "useTabs": false, "tabWidth": 2, "printWidth": 80, "singleQuote": true, "trailingComma": "none", "semi": false }
  • 在package.json中配置prettier

script字段中添加配置项: “prettier”:”prettier –write .” 运行npm run prettier 即可把所有文件使用prettier格式化

  • 配置一个忽略文件 不让prettier格式化

创建.prettierignore文件 不需要prettier格式化的文件放在里面 例如

/build/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
/build/*
.local
.output.js
/node_modules/**


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


/public/*
/build/* .local .output.js /node_modules/** **/*.svg **/*.sh /public/*
配置eslint
我们使用npx eslint --init配置我们的eslint校验
提示是否要配置@eslint/create-config 选择y
1. 之后会有三个选项 第一个是仅仅显示不符合规范 第二个 显示并找到对应的问题 第三个 检查语法 找到错误 并作代码格式化
一般选择第二个 格式化我们交给prettier
2. 使用那种模块化方案检测 一般是es module 可以根据需求选择
3. 选择框架
4. 是否使用typescript
5. 运行在node 或者是 浏览器环境下 可同时选择 (建议同时选择 因为有些包会用到node 向导出使用的module.export等等)
6. 生产eslintconfig文件准备以那种方式显示\
7. 提示是否安装相应的插件 yes
使用什么样的包管理工具
安装 若安装失败 需要查看安装版本的冲突问题 然后进行解决(若对版本不了解 需要尽快解决 建议使用chatgpt)
如果您已成功安装并配置了ESLint,但在未配置Prettier的情况下仍然只显示警告而不显示错误,可能是因为ESLint和Prettier之间的集成并未完全生效。
我们需要配置Eslint集成Prettier
我们使用npx eslint --init配置我们的eslint校验   
提示是否要配置@eslint/create-config  选择y
    1. 之后会有三个选项   第一个是仅仅显示不符合规范  第二个 显示并找到对应的问题   第三个 检查语法 找到错误 并作代码格式化
     一般选择第二个   格式化我们交给prettier
    2. 使用那种模块化方案检测  一般是es module  可以根据需求选择
    3. 选择框架
    4. 是否使用typescript
    5. 运行在node 或者是 浏览器环境下  可同时选择 (建议同时选择 因为有些包会用到node 向导出使用的module.export等等)
    6. 生产eslintconfig文件准备以那种方式显示\
    7. 提示是否安装相应的插件   yes
     使用什么样的包管理工具
     安装 若安装失败 需要查看安装版本的冲突问题 然后进行解决(若对版本不了解 需要尽快解决 建议使用chatgpt)
     如果您已成功安装并配置了ESLint,但在未配置Prettier的情况下仍然只显示警告而不显示错误,可能是因为ESLint和Prettier之间的集成并未完全生效。
     我们需要配置Eslint集成Prettier
我们使用npx eslint --init配置我们的eslint校验 提示是否要配置@eslint/create-config 选择y 1. 之后会有三个选项 第一个是仅仅显示不符合规范 第二个 显示并找到对应的问题 第三个 检查语法 找到错误 并作代码格式化 一般选择第二个 格式化我们交给prettier 2. 使用那种模块化方案检测 一般是es module 可以根据需求选择 3. 选择框架 4. 是否使用typescript 5. 运行在node 或者是 浏览器环境下 可同时选择 (建议同时选择 因为有些包会用到node 向导出使用的module.export等等) 6. 生产eslintconfig文件准备以那种方式显示\ 7. 提示是否安装相应的插件 yes 使用什么样的包管理工具 安装 若安装失败 需要查看安装版本的冲突问题 然后进行解决(若对版本不了解 需要尽快解决 建议使用chatgpt) 如果您已成功安装并配置了ESLint,但在未配置Prettier的情况下仍然只显示警告而不显示错误,可能是因为ESLint和Prettier之间的集成并未完全生效。 我们需要配置Eslint集成Prettier
结合eslint和prettier
  1. npm install eslint-plugin-prettier eslint-config-prettier -D

  2. .eslint.js配置 extends : “plugin:prettier/recommended”

  3. .eslint.js文件

module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:prettier/recommended",
],
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: {
"@typescript-eslint/no-var-requires": "off",
},
};
        module.exports = {
          env: {
            browser: true,
            es2021: true,
            node: true,
          },
          extends: [
            "eslint:recommended",
            "plugin:@typescript-eslint/recommended",
            "plugin:react/recommended",
            "plugin:prettier/recommended",
          ],
          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: {
            "@typescript-eslint/no-var-requires": "off",
          },
        };
module.exports = { env: { browser: true, es2021: true, node: true, }, extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react/recommended", "plugin:prettier/recommended", ], 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: { "@typescript-eslint/no-var-requires": "off", }, };

此时就能成功实现prettier显示报错 并结合eslint

配置路由:

  • 安装 npm install react-router-dom

  • 在route文件夹目录建立index.tsx文件,因为要引入组件,我们需要写jsx,jsx不能直接在ts文件中被解析,同时我们需要引入React解析jsx

    • 所以 引入React: import React, { lazy } from ‘react’
  • 由于使用ts 我们创建的route需要类型来校验
    故引入类型RouteObject

1. 故 route/index.tsx
import {Navigate,RouteObject} from 'react-router-dom'
懒加载的形式:
const Discover = lazy(() => import('@/views/discover'))
const routes:RouteObject[] = [
{
//配置默认跳转
path:'/',
element:<Navigate to="/xxx" />
},
{
path:'/discover',
element:<Discover />, //(xxx组件)
children:[
//xxx 多级路由配置
]
},
]
export default routes
2. 在App.tsx使用路由
配置 我们使用useRoutes
App.tsx
```javascript
function App{
return (<div>{useRoutes(routes)}</div>)
}
**注意:useRoutes使用要被hashRouter或者BrowerRouter包裹 否则会报错
则 进入 index.tsx文件中 引入HashRouter包裹根组件App**
导入 import { HashRouter } from 'react-router-dom'
<HashRouter>
<App />
</HashRouter>
1. 故 route/index.tsx
import {Navigate,RouteObject} from 'react-router-dom'
懒加载的形式:
const Discover = lazy(() => import('@/views/discover'))


const routes:RouteObject[] = [
    {
        //配置默认跳转
        path:'/',
        element:<Navigate to="/xxx" />
    },
    {
        path:'/discover',
        element:<Discover />,   //(xxx组件)
        children:[
            //xxx  多级路由配置
        ]
    },
]
export default routes

2. 在App.tsx使用路由
配置  我们使用useRoutes
App.tsx

```javascript
function App{
   return (<div>{useRoutes(routes)}</div>)
}
**注意:useRoutes使用要被hashRouter或者BrowerRouter包裹 否则会报错
则 进入 index.tsx文件中 引入HashRouter包裹根组件App**
     导入 import { HashRouter } from 'react-router-dom'
       <HashRouter>
        <App />
      </HashRouter>
1. 故 route/index.tsx import {Navigate,RouteObject} from 'react-router-dom' 懒加载的形式: const Discover = lazy(() => import('@/views/discover')) const routes:RouteObject[] = [ { //配置默认跳转 path:'/', element:<Navigate to="/xxx" /> }, { path:'/discover', element:<Discover />, //(xxx组件) children:[ //xxx 多级路由配置 ] }, ] export default routes 2. 在App.tsx使用路由 配置 我们使用useRoutes App.tsx ```javascript function App{ return (<div>{useRoutes(routes)}</div>) } **注意:useRoutes使用要被hashRouter或者BrowerRouter包裹 否则会报错 则 进入 index.tsx文件中 引入HashRouter包裹根组件App** 导入 import { HashRouter } from 'react-router-dom' <HashRouter> <App /> </HashRouter>
  • 懒加载

如果做懒加载 很可能东西还没下载就被要求展示,这样会导致报错,所以应该显示一些应急的东西,我们使用Suspense 我们可以直接在app.tsx中进行实现,单独对每个router包裹也可以,这里我们直接对App包裹

App.tsx

// 导入
import React, { Suspense } from 'react'
function App() {
return (
<Suspense fallback="xxx">
<div className="App">{useRoutes(routes)}</div>
</Suspense>
)
}
// 导入
import React, { Suspense } from 'react'


function App() {
  return (
    <Suspense fallback="xxx">
      <div className="App">{useRoutes(routes)}</div>
    </Suspense>
  )
}
// 导入 import React, { Suspense } from 'react' function App() { return ( <Suspense fallback="xxx"> <div className="App">{useRoutes(routes)}</div> </Suspense> ) }

声明一个ts组件

函数组件
为props声明类型
ReactNode 联合类型 既有普通标签元素(ReactFragement)也有React中的一些组件的
类型 所以针对props的children属性 我们使用该标签
ReactNode 联合类型 既有普通标签元素(ReactFragement)也有React中的一些组件的
类型 所以针对props的children属性 我们使用该标签
ReactNode 联合类型 既有普通标签元素(ReactFragement)也有React中的一些组件的 类型 所以针对props的children属性 我们使用该标签
interface IProps {
name: string
age: number
children?: ReactNode
}
interface IProps {
  name: string
  age: number
  children?: ReactNode
}
interface IProps { name: string age: number children?: ReactNode }
为函数组件声明类型:

为什么要声明 在直接直接使用内置的调用时获取提示 比如Page.defaultProps

故组件声明为
const Page: React.FC<IProps> = () => {
return <div>Page</div>
}
故组件声明为
const Page: React.FC<IProps> = () => {
return <div>Page</div>
}
故组件声明为 const Page: React.FC<IProps> = () => { return <div>Page</div> }

故该组件声明的代码为index.tsx

import React, { memo } from 'react'
import type {FC, ReactNode } from 'react'
interface IProps {
name: string
age: number
children?: ReactNode
}
const Page: FC<IProps> = () => {
return <div>Page</div>
}
Page.defaultProps = {
name: 'xxx',
age: 18
}
export default memo(Page)
import React, { memo } from 'react'
import type {FC, ReactNode } from 'react'
interface IProps {
  name: string
  age: number
  children?: ReactNode
}


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

Page.defaultProps = {
  name: 'xxx',
  age: 18
}
export default memo(Page)
import React, { memo } from 'react' import type {FC, ReactNode } from 'react' interface IProps { name: string age: number children?: ReactNode } const Page: FC<IProps> = () => { return <div>Page</div> } Page.defaultProps = { name: 'xxx', age: 18 } export default memo(Page)
类组件结合ts使用
import React, { PureComponent } from 'react'
interface IProp {
name: string
age: number
}
interface IState {
name: string
age: number
}
export class test extends PureComponent<IProp, IState> {
constructor(props: IProp) {
super(props)
this.state = {
name: 'myName',
age: 20
}
}
render() {
return <div>test</div>
}
}
export default test
import React, { PureComponent } from 'react'


interface IProp {
  name: string
  age: number
}
interface IState {
  name: string
  age: number
}


export class test extends PureComponent<IProp, IState> {
  constructor(props: IProp) {
    super(props)
    this.state = {
      name: 'myName',
      age: 20
    }
  }
  render() {
    return <div>test</div>
  }
}

export default test
import React, { PureComponent } from 'react' interface IProp { name: string age: number } interface IState { name: string age: number } export class test extends PureComponent<IProp, IState> { constructor(props: IProp) { super(props) this.state = { name: 'myName', age: 20 } } render() { return <div>test</div> } } export default test

配置redux

  • 安装 npm install redux , @reduxjs/toolkit, react-redux

在App.tsx文件中 我如果直接使用useSelector
例如:

useSelector((state)=>{
return {
xxxx
}
})
//ts会报错 state的类型错误
 useSelector((state)=>{
   return {
      xxxx
   }
})
//ts会报错 state的类型错误 
useSelector((state)=>{ return { xxxx } }) //ts会报错 state的类型错误
  • 那么如何给state赋予类型呢
    两种方法

    • 方法1:
      我们都知道state来自于 store.getState(),

      故store中的getState里面的返回值就是我们想要的类型

      所以 在store的index文件夹下 我们获取这个类型 导出使用

      1. type IRootState = ReturnType<typeof store.getState>
      或者2. type IRootState = typeof store.getState()
      export type { IRootState }
      在我们的组件中
      import { IRootState } from './store'
      const { count } = useSelector((state: IRootState) => {
      return { count: state.counter.count }
      }, shallowEqual)
      1. type IRootState = ReturnType<typeof store.getState> 
      或者2. type IRootState = typeof store.getState()
      export type { IRootState }
      
      在我们的组件中
      import { IRootState } from './store'
        const { count } = useSelector((state: IRootState) => {
          return { count: state.counter.count }
        }, shallowEqual)
      1. type IRootState = ReturnType<typeof store.getState> 或者2. type IRootState = typeof store.getState() export type { IRootState } 在我们的组件中 import { IRootState } from './store' const { count } = useSelector((state: IRootState) => { return { count: state.counter.count } }, shallowEqual)

      缺点很明显 每次使用都要导入类型IRootState,如果我们不想每次都导入 那该怎么做呢

      答:
      给useSelctor声明一个函数标签类型 让传入函数参数默认使用该类型

      好在 react-redux库中为我们直接提供了该类型 TypedUseSelectorHook

    • 方法2 : 推荐使用

    export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
    // 我们还可以在里面封装一下 usedispatch 和 showEquall
    type DispatchType = typeof store.dispatch
    export const useAppdispatch:() => DispatchType = useDispatch
    export const shallowEqualApp = showEquall //封装这两个的主要目的是为了导入的时候不再从react-redux库中再引入 单独引用这个文件就好
    <!-- 我们在其他文件中 只需要导入useAppSelector 即可获得完整的校验 -->
    <!-- 用法如下: -->
    const { count } = useAppSelector((state) => {
    return { count: state.counter.count }
    }, shallowEqual)
    export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector
    // 我们还可以在里面封装一下 usedispatch 和  showEquall
    type DispatchType =  typeof store.dispatch
    export const useAppdispatch:() => DispatchType   = useDispatch  
    export const shallowEqualApp = showEquall   //封装这两个的主要目的是为了导入的时候不再从react-redux库中再引入 单独引用这个文件就好
    
    <!-- 我们在其他文件中 只需要导入useAppSelector 即可获得完整的校验 -->
    <!-- 用法如下: -->
     const { count } = useAppSelector((state) => {
        return { count: state.counter.count }
      }, shallowEqual)
    export const useAppSelector: TypedUseSelectorHook<IRootState> = useSelector // 我们还可以在里面封装一下 usedispatch 和 showEquall type DispatchType = typeof store.dispatch export const useAppdispatch:() => DispatchType = useDispatch export const shallowEqualApp = showEquall //封装这两个的主要目的是为了导入的时候不再从react-redux库中再引入 单独引用这个文件就好 <!-- 我们在其他文件中 只需要导入useAppSelector 即可获得完整的校验 --> <!-- 用法如下: --> const { count } = useAppSelector((state) => { return { count: state.counter.count } }, shallowEqual)

    在redux 中的单个包中
    针对每个的createSlice中存在的 initialState
    若initialState 里类型较为复杂,类似于数组,对象,联合类型,我们必须声明typescript类型,以便获得更好的类型推导和可读性,若不这么做 在使用state时可能会报错

  • 针对reducers里 我们同样可以对action进行声明类型

    • 但这需要借助reduxjs/toolkit下的PayloadAction实现
      代码如下
    import { createSlice, PayloadAction } from '@reduxjs/toolkit'
    const counterReducer = createSlice({
    name: 'counter',
    initialState: {
    count: 17
    },
    reducers: {
    changeCount(state, { payload }: PayloadAction<number>) {
    state.count = payload
    }
    }
    })
    export const { changeCount } = counterReducer.actions
    export default counterReducer.reducer
    import { createSlice, PayloadAction } from '@reduxjs/toolkit'
    
    
    const counterReducer = createSlice({
    name: 'counter',
    initialState: {
    count: 17
    },
    reducers: {
    changeCount(state, { payload }: PayloadAction<number>) {
    state.count = payload
    }
    
    }
    })
    export const { changeCount } = counterReducer.actions
    export default counterReducer.reducer
    import { createSlice, PayloadAction } from '@reduxjs/toolkit' const counterReducer = createSlice({ name: 'counter', initialState: { count: 17 }, reducers: { changeCount(state, { payload }: PayloadAction<number>) { state.count = payload } } }) export const { changeCount } = counterReducer.actions export default counterReducer.reducer
  • 在index.jsx中 配置Provider,目的是使全局可以拿到store

import store from './store'
import { Provider } from 'react-redux/es/exports'
<Provider store={store}>
<HashRouter>
<App />
</HashRouter>
</Provider>
import store from './store'
import { Provider } from 'react-redux/es/exports'


 <Provider store={store}>
      <HashRouter>
        <App />
      </HashRouter>
    </Provider>
import store from './store' import { Provider } from 'react-redux/es/exports' <Provider store={store}> <HashRouter> <App /> </HashRouter> </Provider>

如果本文对你有帮助的话,请留个赞再走吧!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYcvJm3h' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片