比较全面的typescript + react 开发教程

前言

本教程不用react-script这种工具,用的webpack + eslint(可选) + prettier(可选) + babel,使用编辑器用的vscode。
以下会涉及到ts语法和reduxreact-router-dom的一些技巧,也是入门时学习到的。

准备工作

depend

# babel
yarn add @babel/core @babel/preset-reac @babel/preset-typescript @babel/preset-env -D
# webpack
yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader css-loader fork-ts-checker-webpack-plugin -D
# react-hot-reload
yarn add @pmmmwh/react-refresh-webpack-plugin react-refresh type-fest -D
# react 相关
yarn add react react-dom react-redux react-router-dom redux-thunk
# 以上的这些react相关全部装个type,例子
# yarn add @types/react @types/react-dom ... -D
# eslint 可选
yarn add babel-eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-react-hooks -D

envfile

tsconfig.json

这里我的工程文件都放在./src

{
    
    
  "compilerOptions": {
    
    
    "baseUrl": ".",
    "paths": {
    
    
      "@root/*": ["src/*"]
    },
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "lib": [
      "es6",
      "es7",
      "dom"
    ],
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true
  },
  "include": [
    "./src/**/*"
  ]
}

.babelrc

{
    
    
  "presets": [
    "@babel/preset-env",
    "@babel/preset-typescript",
    "@babel/preset-react"
  ]
}

.eslintrc.js

module.exports = {
    
    
  root: true,
  env: {
    
    
    commonjs: true,
    es6: true,
    browser: true,
    node: true,
  },
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint', 'prettier'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
    'plugin:prettier/recommended',
    'prettier',
  ],
  parserOptions: {
    
    
    ecmaFeatures: {
    
    
      jsx: true,
    },
  },
  ignorePatterns: ['dist', 'lib'],
  rules: {
    
    
    '@typescript-eslint/no-var-requires': 0,
    '@typescript-eslint/no-unused-vars': 0,
    'prefer-const': 0,
    '@typescript-eslint/no-explicit-any': 0,
    'react-hooks/exhaustive-deps': 0,
  },
}

.prettierrc

{
    
    
  "semi": false,
  "singleQuote": true
}

webpack.dev.js

const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')

module.exports = {
    
    
  entry: './src/index.tsx',
  output: {
    
    
    filename: 'app.js',
    path: path.join(__dirname, './dist'),
    publicPath: '/',
  },
  context: path.resolve(__dirname, './src'),
  devtool: 'cheap-module-source-map',
  devServer: {
    
    
    hot: true,
    // enable HMR on the server
    compress: true,
    contentBase: path.resolve(__dirname, './src'),
    // match the output path
    publicPath: URL,
    historyApiFallback: true,
  },
  mode: 'development',
  module: {
    
    
    rules: [
      {
    
    
        test: /\.[tj]sx?$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
    
    
        test: /\.css$/i,
        use: [
          'style-loader',
          {
    
    
            loader: 'css-loader',
            options: {
    
    
              modules: {
    
    
                mode: 'global',
                localIdentName: '[name]__[local]--[hash:base64:5]',
              },
              importLoaders: 2,
            },
          },
        ],
      },
    ],
  },
  resolve: {
    
    
    alias: {
    
    
      '@root': path.resolve(__dirname, './src'),
    },
    extensions: ['.ts', '.tsx', '.js', '.json'],
  },
  plugins: [
  // 用来检测开发中ts语法错误问题
    new ForkTsCheckerWebpackPlugin({
    
    
      typescript: {
    
    
        configFile: path.resolve(__dirname, './tsconfig.json'),
      },
    }),
    new ReactRefreshWebpackPlugin(),
    new HTMLWebpackPlugin({
    
    
      template: './src/app.html',
      filename: 'index.html',
    }),
  ],
}

基本开发

index.tsx

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import App from '@root/App'

ReactDOM.render(<App />, document.getElementById('app'))

App.tsx

import * as React from 'react'
import {
    
     Provider } from 'react-redux'
import {
    
     BrowserRouter, Route, Switch } from 'react-router-dom'
// 这里redux下面会给写法
import store from './reducers'

const App: React.FC = function App(props) {
    
    
  return (
    <Provider store={
    
    store}>
      <BrowserRouter>
        <Switch>
          <Route path="/a1">
            <div>page a1</div>
          </Route>
          <Route path="/">
            <div>home</div>
          </Route>
        </Switch>
      </BrowserRouter>
    </Provider>
  )
}
export default App

redux相关

这里以多个模块组合为例

/reducers/folder.ts

// 定义state
export type FolderDstate = {
    
    
  folders: {
    
    
  	name: string
  }[]
  isLoading: boolean
  loaded: boolean
}
let dstate: FolderDstate = {
    
    
  folder: [],
  isLoading: false,
  loaded: false,
}

// 集合所有action
export type FolderAction =
  | FolderLoad
  | FolderUpdate

type FolderLoad = {
    
    
  type: 'folder/load'
  folders: {
    
    
  	name: string
  }[]
}
type FolderUpdate = {
    
    
  type: 'folder/update'
  isLoading?: boolean
  loaded?: boolean
}

let folder = (state = dstate, action: FolderAction): FolderDstate => {
    
    
  switch (action.type) {
    
    
    case 'folder/update': {
    
    
      return {
    
    
        ...state,
        isLoading: action.isLoading ?? state.isLoading,
        loaded: action.loaded ?? state.loaded,
      }
    }
    case 'folder/load': {
    
    
      return {
    
    
        ...state,
        folders: [...action.folders],
      }
    }
}
export default folder

这里用的switch是因为vscode会有case提示
在这里插入图片描述
并且每块case中的变量都是相对独立于上面定义的type的
在这里插入图片描述
在这里插入图片描述

/reducers/index.ts

import {
    
     combineReducers, createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import folder, {
    
     FolderAction, FolderDstate } from './folder'

let rootReducer = combineReducers({
    
    
  folder,
})

let store = (function configureStore() {
    
    
// 这里是添加插件使chrome的redux插件能检测并使用
// 这里window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__会被ts爆出错误,以下会给解决方案
  const middlewares = [thunkMiddleware]
  const middlewaresEnhancer = applyMiddleware(...middlewares)
  const composeEnhancers =
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

  const store = createStore(
    rootReducer,
    composeEnhancers(middlewaresEnhancer)
  )
  return store
})()

export default store
// 这里放进去所有的模块默认state type
export type StateBase = {
    
    
  folder: FolderDstate
  //other: otherDstate
}
// 这里放进去所有的action集合,用|连接
export type RootActions = FolderAction /*| otherAction */

redux-connect用法

import * as React from 'react'
import {
    
     connect, ConnectedProps } from 'react-redux'
// --- 这里的Dispatch最好用一个ts文件集中管理,这里方便就放一起了 ---
import {
    
     RootActions, StateBase } from '@root/reducers'
import {
    
     Dispatch } from 'redux'

export const loadFolder = (isForce = false) => (
  dispatch: Dispatch<RootActions>,
  getState: () => StateBase
): Promise<void> => {
    
    
  const state = getState()
  if (state.folder.isLoading || (!isForce && state.user.loaded)) return
  dispatch({
    
     type: 'folder/load', folders: [] })
  dispatch({
    
     type: 'folder/update', loaded:true  })
}
// -------------

// 定义传入参数,ConnectedProps是把connect里的所有方法和变量都打包一起了
type Props = ConnectedProps<typeof connector> & {
    
    
	//自定义参数
}

const Comp1: React.FC<Props> = (props) => {
    
    
    React.useEffect(()=>{
    
    
    	props.loadFolder()
    },[])
	return <div>{
    
    `${
      
      props.loaded}`}</div>
}

const mapStateToProps = (state: StateBase) => {
    
    
  return {
    
    
    loaded: state.folder.loaded,
  }
}

const mapDispatchToProps = {
    
    
  loadFolder
}

let connector = connect(mapStateToProps, mapDispatchToProps)

export default connector(Comp1)

loadFolder里的dispatch方法提示,都会根据type提示需要的参数
在这里插入图片描述
在这里插入图片描述

单独带提示的dispatch useDp.ts

import {
    
     RootActions } from '@root/reducers'
import {
    
     useDispatch } from 'react-redux'

export const useDp = (): React.Dispatch<RootActions> =>
  useDispatch<React.Dispatch<RootActions>>()

效果
在这里插入图片描述

关于ts的一些东西

全局变量

./src下随便建个什么的global.d.ts,放一些ts没法检测的全局变量

interface Window {
    
    
  __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: (...any: any) => any
}

将type变成全部可选的新type

export type DeepPartial<T> = {
    
    
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
// 这里FolderDstate的所有属性就变成全选了
type n = DeepPartial<FolderDstate>

动态key

如果用了eslint使用object类型他会报错,这样就是动态key了

type dykey = {
    
    
	[key:string]:any
}

end

npx webpack serve --config webpack.dev.js 就ok了

猜你喜欢

转载自blog.csdn.net/qq_41535611/article/details/112007126
今日推荐