创建react18-typescript项目

搭建react-typescript项目-模版项目github地址

基础项目生成

  • create-react-app脚手架创建typescript项目

    $ npx create-react-app my-react --template typescript

项目配置

使用craco (配置webpack,无需eject)

  • 安装craco

    $ yarn add @craco/craco

  • 项目根目录创建craco配置文件: craco.config.js 或者.cracorc

    	// 配置内容
    	const { whenProd, loaderByName } = require('@craco/craco');
    	const path = require('path');
    	const CracoLessPlugin = require('craco-less'); // 加载less
    	const sassResourcesLoader = require('craco-sass-resources-loader'); // 全局加载sass文件
    	const WebpackBar = require('webpackbar'); // 打包进度条
    	const TerserPlugin = require('terser-webpack-plugin'); // 代码压缩: 清除日志|特定函数等
    	
    	module.exports = {
    	  webpack: {
    	    alias: {
    	      '@': path.resolve(__dirname, 'src'),
    	      '@api': path.resolve(__dirname, 'src/api'),
    	      '@hooks': path.resolve(__dirname, 'src/hooks'),
    	      '@pages': path.resolve(__dirname, 'src/pages'),
    	      '@common': path.resolve(__dirname, 'src/common'),
    	      '@component': path.resolve(__dirname, 'src/components'),
    	    },
    	    plugins: [
    	      new WebpackBar(),
    	      ...whenProd(
    	        () => [
    	          new TerserPlugin({
    	            terserOptions: {
    	              sourceMap: true,
    	              compress: {
    	                drop_console: whenProd(() => true), // 生产环境中清除console.*这些函数的调用
    	                drop_debugger: whenProd(() => true), // 生产环境中清除debugger
    	                pure_funcs: ['console.log'], // 干掉特定的函数比如console.info,那用pure_funcs来处理
    	              },
    	              format: {
    	                comments: false, //删除注释
    	              },
    	            },
    	          }),
    	        ],
    	        []
    	      ),
    	    ],
    	  },
    	  plugins: [
    	    {
    	      plugin: CracoLessPlugin,
    	      options: {
    	        lessLoaderOptions: {
    	          lessOptions: {
    	            modifyVars: {}, // 定义变量
    	            javascriptEnabled: true,
    	          },
    	        },
    	        modifyLessModuleRule(lessModuleRule, context) {
    	          lessModuleRule.test = /\.module\.(less)$/;
    	          const cssLoader = lessModuleRule.use.find(loaderByName('css-loader'));
    	          cssLoader.options.importLoaders = 4;
    	          cssLoader.options.modules = {
    	            localIdentName: '[local]_[hash:base64:5]',
    	          };
    	          return lessModuleRule;
    	        },
    	      },
    	    },
    	    {
    	      plugin: sassResourcesLoader,
    	      options: {
    	        resources: [
    	          './src/common/scss/public.scss',
    	          './src/common/scss/variable.scss',
    	        ],
    	      },
    	    },
    	  ],
    	  devServer: (devServerConfig) => {
    	    return {
    	      ...devServerConfig,
    	      proxy: {
    	        '/api': {
    	          target: 'http://xxx.cn',
    	          changeOrigin: true,
    	          pathRewrite: {
    	            '^/api': '',
    	          },
    	        },
    	      },
    	    };
    	  },
    	};
    
    
  • 配置启动命令(package.json):

    "scripts": {
        "start": "craco start",
        "build": "craco build",
        "test": "craco test",
        "eject": "react-scripts eject"
      },
    
  • 配置alias(路径别名)

    // .cracorc.js
    webpack: {
      alias: {
        '@': path.resolve(__dirname, 'src'),
        '@pages': path.resolve(__dirname, 'src/pages'),
        '@common': path.resolve(__dirname, 'src/common'),
        '@component': path.resolve(__dirname, 'src/components'),
      },
    }
    
    // tsconfig.json
    {
      "compilerOptions": {
        "target": "es5",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noFallthroughCasesInSwitch": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx"
      },
      "extends": "./path.tsconfig.json", // 添加路径别名配置
      "include": ["src"]
    }
    
    // path.tsconfig.json
    {
      "compilerOptions": {
        "baseUrl": "./",
        "paths": {
          "@/*": ["src/*"],
          "@pages/*": ["src/pages/*"],
          "@common/*": ["src/common/*"],
          "@component/*": ["src/components"]
        }
      }
    }
    
  • 配置打包进度条(webpackbar)

    // .cracorc.js
    const WebpackBar = require('webpackbar'); // 打包进度条
    module.exports = {
    	webpack: {
    		plugins: [new WebpackBar()],
    	}
    }
    
  • 安装插件terser-webpack-plugin:

    $ yarn add terser-webpack-plugin -D

  • 配置日志打印

    // .cracorc.js
    const TerserPlugin = require('terser-webpack-plugin'); // 清除日志|特定函数等
    module.exports = {
      webpack: {
    	plugins: [
          new TerserPlugin({
            terserOptions: {
              sourceMap: true,
              compress: {
                drop_console: whenProd(() => true), // 干生产环境中清除console.*这些函数的调用
                drop_debugger: whenProd(() => true), // 生产环境中清除debugger
                pure_funcs: ['console.log'], // 干掉特定的函数比如console.info,那用pure_funcs来处理
              },
              format: {
                comments: false, //删除注释
              },
            },
          }),
        ],
      }
    }
    

使用eslint

  • 安装eslint

    $ yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -D

  • 创建.eslintrc.js配置文件

    module.exports = {
      parser: '@typescript-eslint/parser',
      extends: ['plugin:@typescript-eslint/recommended', 'prettier'],
      plugins: ['@typescript-eslint', 'react', 'prettier'],
      rules: {
        // // hooks依赖项不能为空
        'react-hooks/exhaustive-deps': 'off',
        'no-console': 'warn',
        // 缩进2
        indent: 'off',
        // 使用 === 替代 ==
        eqeqeq: [2, 'allow-null'],
        'no-unused-vars': 'off',
      },
    };
    
    

使用prettier

  • 安装prettier

    $ yarn add prettier eslint-config-prettier eslint-plugin-prettier -D

  • 创建.prettierrc文件

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2
}

使用scss

$ yarn add sass-loader node-sass
  • 使用craco引入全局scss

  • 安装sass

    $ yarn add -D craco-sass-resources-loader

    // .cracorc.js
    const sassResourcesLoader = require('craco-sass-resources-loader');
    plugins: [
      {
        plugin: sassResourcesLoader,
        options: {
          resources: [
            './src/common/scss/public.scss',  // 全局样式
            './src/common/scss/variable.scss', // 全局变量
          ],
        },
      },
    ]
    

使用less

  • 安装less

    $ yarn add craco-less -S
    $ yarn add less less-loader -D

  • craco配置less (注意: 将less配置放在sass)

  • 注意: 将less配置放置于sass配置前, 使用antd的情况下控制台报错: Failed to parse source map: ‘webpack://antd/./components/time-picker/style/index.less’ URL is not supported,
    猜测可能是sass-loader与less(因为antd使用less样式,自己想使用sass,所以都安装了)起了冲突

    // .cracorcjs
    module.exports = {
      webpack: {},
      plugins: [
        {
          plugin: CracoLessPlugin,
          options: {
            lessLoaderOptions: {
              lessOptions: {
                modifyVars: {}, // 定义变量
                javascriptEnabled: true,
              },
            },
            modifyLessModuleRule(lessModuleRule, context) {
              lessModuleRule.test = /\.module\.(less)$/;
              const cssLoader = lessModuleRule.use.find(loaderByName('css-loader'));
              cssLoader.options.importLoaders = 4;
              cssLoader.options.modules = {
                localIdentName: '[local]_[hash:base64:5]',
              };
              return lessModuleRule;
            },
          },
        },
        {
          plugin: sassResourcesLoader,
          options: {
            resources: [
              './src/common/scss/public.scss',
              './src/common/scss/variable.scss',
            ],
          },
        },
      ],
    }
    

使用axios

  • 安装axios

    $ yarn add axios

  • 配置跨域

    // .cracorc.js
    devServer: (devServerConfig) => {
       return {
         ...devServerConfig,
         proxy: {
           '/api': {
             target: 'http://xxx.cn',
             changeOrigin: true,
             pathRewrite: {
               '^/api': '',
             },
           },
         },
       };
     }
    
    // 请求接口
    axios.get('api/manage/code')
    实际访问地址: http://xxx.cn/manage/code
    

使用ant design

  • 安装ant design

    $ yarn add antd

  • 引入样式文件

    // index.tsx
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App';
    import { ConfigProvider } from 'antd'; // antd全局配置插件
    import zhCN from 'antd/es/locale/zh_CN'; // antd中文包
    import 'moment/locale/zh-cn'; // 日期选择中文
    import 'antd/dist/antd.css'; // antd通用样式
    // import { Button } from 'antd'; // 按需引入插件
    
    const root = ReactDOM.createRoot(
      document.getElementById('root') as HTMLElement
    );
    root.render(
      <React.StrictMode>
        <ConfigProvider locale={zhCN}>
          <App />
        </ConfigProvider>
      </React.StrictMode>
    );
    

使用react-router-dom(v6)

  • 安装react-router-dom

    $ yarn add react-router-dom

  • 根组件index.tsx引入路由

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter, Route, Routes } from 'react-router-dom';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    import { ConfigProvider } from 'antd';
    import zhCN from 'antd/es/locale/zh_CN';
    import 'moment/locale/zh-cn'; // 日期选择中文
    import 'antd/dist/antd.css';
    import Test from './pages/test';
    import Home from './pages/home';
    
    const root = ReactDOM.createRoot(
      document.getElementById('root') as HTMLElement
    );
    
    root.render(
      // <React.StrictMode>
      <ConfigProvider locale={zhCN}>
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<App />}>
              <Route path="test" element={<Test />} />
              <Route path="home" element={<Home />} />
            </Route>
            <Route path="/system" element={<System />} />
            {/* 当匹配到/redirect-path路由时,重定向到/system */}
            <Route path="/redirect-path" element={<Navgate to={'/system'} />} />
          </Routes>
        </BrowserRouter>
      </ConfigProvider>
      // </React.StrictMode>
    );
    
    // 注意: 不要在存在子路由的路由中使用重定向,否则会无限循环匹配而错误,如:
    // 当匹配到路由/root时,重定向到/other, 但是/root/test也会被匹配从而重定向
    <Route path="/root" element={<Navgate to={'/other'} />}>
      <Route path="test" element={<Test />} />
      <Route path="home" element={<Home />} />
    </Route>
    <Route path="/other" element={<Other />} />
    

使用redux

  • 安装redux:

    yarn add @reduxjs/toolkit
    yarn add react-redux

  • 创建store
    src/store/login/loginSlice.ts

    import { createSlice } from '@reduxjs/toolkit';
    
    const { actions, reducer: LoginReducer } = createSlice({
      // 命名空间,作为action type前缀
      name: 'login',
      // 初始化状态数据
      initialState: {
        token: 'test.token',
        userInfo: {}, // 用户信息
        routeList: [], // 用户路由
      },
      // reducer更新函数  dispatch使用的action函数
      reducers: {
        setToken: (state, { payload }) => {
          state.token = payload;
        },
      },
    });
    
    // 异步操作
    export const asyncOption = (payload: any) => {
      return async (dispatch: any, getState: any) => {
        setTimeout(() => {
          dispatch(setToken(payload));
        }, 3000);
      };
    };
    
    // 导出action函数
    export const { setToken } = actions;
    
    // 导出reducer, 创建store
    export default LoginReducer;
    
    

    src/store/index.ts

    // 创建store
    import { configureStore } from '@reduxjs/toolkit';
    import LoginReducer from '@/store/login/loginSlice';
    
    export const store = configureStore({
      reducer: {
        login: LoginReducer,
      },
    });
    
    // 使用redux状态
    import React from 'react';
    import { Button } from 'antd';
    import { setToken } from '@/store/login/loginSlice';
    import { useDispatch, useSelector } from 'react-redux';
    import './index.scss';
    
    export default function Test() {
      const dispatch = useDispatch();
      const { token } = useSelector((state: any) => state.login);
    
      return (
        <div className="box">
          token: {token}
          <Button
            type="primary"
            onClick={() => dispatch(setToken(new Date().getTime()))}
          >
            antd Button
          </Button>
        </div>
      );
    }
    
    

使用dayjs(日期转化)

  • 安装dayjs

    $ yarn add dayjs

  • 使用dayjs

    import dayjs from 'dayjs';
    // 获取时间戳
    const date = dayjs('2022-08-16 22:32:45').format('YYYY-MM-DD'); // 2022-08-16
    cosnt prevWeek = dayjs(date).subtract(1, 'week'); // 2022-08-09
    const dateTime = dayjs(prevWeek).valueOf(); // 获取指定日期的时间戳(13位) 1660003200000
    

数据深拷贝-lodash

  • 安装lodash

    $ yarn add lodash

  • 简单实用

    import { useEffect, useRef } from 'react';
    import _ from 'lodash';
    
    export default function Test() {
      const complexObj = useRef<any>({
        age: 18,
        date: new Date(),
      });
    
      useEffect(() => {
        const cloneObj = JSON.parse(JSON.stringify(complexObj.current)); // 使用JSON方式深拷贝数据, date被转化为字符串
        const lodashObj = _.cloneDeep(complexObj.current); // 使用lodash深拷贝数据 date值与源数据一致(Date类型)
        console.log(cloneObj, lodashObj, complexObj.current);
      }, []);
    
      return (
        <div className="box">
          <div className="inner">Test page!</div>
        </div>
      );
    }
    
    

    JSON拷贝与lodash拷贝效果

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值