Vite+React(Vue)+TS+ESLint+Prettier

1 篇文章 0 订阅
1 篇文章 0 订阅

npm的使用

npm config get registry                                                                 //查看镜像

npm config set registry https://registry.npmmirror.com                 //切换淘宝镜像

npm install -s element-ui@2.5.1                                                   //安装指定版本依赖

npm install -s element-ui@latest                                                  //安装最新版本依赖

npm update -s element-ui@latest                                                //更新依赖版本

配置ESlint

      创建项目

npm init vite

      安装ESLint

npm init @eslint/config

安装完成后生成一个.eslintrc.cjs的文件

module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    //'plugin:prettier/recommended',
    //'plugin:react/jsx-runtime'
  ],
  overrides: [
    {
      env: {
        node: true
      },
      files: ['.eslintrc.{js,cjs}'],
      parserOptions: {
        sourceType: 'script'
      }
    }
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  plugins: [
          '@typescript-eslint', 
          'react', 
          //'prettier'
        ],
  rules: {
    //'prettier/prettier': 'error',
    //'arrow-body-style': 'off',
    //'prefer-arrow-callback': 'off'
  },
  //settings: {
  //  react: {
  //    version: 'detect'
  // }
  //}
}

      安装Prettier

npm i prettier -D

创建.prettierrc.cjs文件

module.exports = {
  printWidth: 80,
  tabWidth: 2,
  useTabs: false,
  singleQuote: false,
  semi: true,
  trailingComma: "es5",
  bracketSpacing: true,
};

      安装ESLint + Prettier关联插件

npm i eslint-config-prettier eslint-plugin-prettier -D

更改.eslintrc.cjs文件

module.exports = {
  //使用微信的某些能力时,导入min.js后使用/^wx\..*/报错的解决。
  globals: { wx: 'readonly' },
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',  // 'plugin:vue/vue3-essential',
    'plugin:prettier/recommended',
    'plugin:react/jsx-runtime' // 'eslint-config-prettier'
  ],
  overrides: [
    {
      env: {
        node: true
      },
      files: ['.eslintrc.{js,cjs}'],
      parserOptions: {
        sourceType: 'script'
      }
    }
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  plugins: ['@typescript-eslint', 'react', 'prettier'],
  rules: {
    'prettier/prettier': 'error',
    'arrow-body-style': 'off',
    'prefer-arrow-callback': 'off'
  },
  settings: {
    react: {
      version: 'detect'
    }
  }
}

      安装Vite---ESLint(可以省略)

npm i vite-plugin-eslint -D

然后在 vite.config.ts 引入插件

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import viteEslint from 'vite-plugin-eslint'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    viteEslint({
      failOnError: false
    })
  ]
})

 注意:新版eslint

        如果使用新版eslint,生成的文件会变成 eslint.config.js

import globals from "globals";
import pluginJs from "@eslint/js";
import pluginVue from "eslint-plugin-vue";

// 预定义配置
// import babelParser from "@typescript-eslint/parser";
import tseslint from 'typescript-eslint'
import commpnParser from 'vue-eslint-parser'
import prettier from 'eslint-plugin-prettier'


export default [
  {languageOptions: { globals: {...globals.browser, ...globals.node} }},
  pluginJs.configs.recommended,
  ...pluginVue.configs["flat/essential"],
  ...tseslint.configs.recommended,
  {
    ignores: [
      '**/*.config.js',
      'dist/**',
      'node_modules/**',
      '!**/eslint.config.js',
    ],
    languageOptions: {
      // 1.11 定义可用的全局变量
      globals: globals.browser,
      // 1.12 扩展
      // ecmaVersion: "latest",
      // sourceType: "module",
      parser: commpnParser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
        parser: '@typescript-eslint/parser',
        jsxPragma: 'React',
        ecmaFeatures: {
          jsx: true,
        },
      },
    },
  },
  {
    plugins: {
      prettier,
    },
    rules: {
      // 开启这条规则后,会将prettier的校验规则传递给eslint,这样eslint就可以按照prettier的方式来进行代码格式的校验
      'prettier/prettier': 'error',
      // eslint(https://eslint.bootcss.com/docs/rules/)
      'no-var': 'error', // 要求使用 let 或 const 而不是 var
      'no-multiple-empty-lines': ['warn', { max: 2 }], // 不允许多个空行
      'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
      'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
      'no-unexpected-multiline': 'error', // 禁止空余的多行
      'no-useless-escape': 'off', // 禁止不必要的转义字符
      // typeScript (https://typescript-eslint.io/rules)
      '@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
      '@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
      '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
      '@typescript-eslint/no-non-null-assertion': 'off',
      '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
      '@typescript-eslint/semi': 'off',
      // eslint-plugin-vue (https://eslint.vuejs.org/rules/)
      'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
      'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用
      'vue/no-mutating-props': 'off', // 不允许组件 prop的改变
      'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
    },
  },
];

配置@路径

在tsconfig.json中

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

//以下代码为需要增加的,顺便允许使用any类型
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "noImplicitAny": false,  //允许使用any

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

在 vite.config.ts 中

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import viteEslint from 'vite-plugin-eslint'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  server: {
    port: 3333
  },
  plugins: [react(), viteEslint()],

//resolve配置对象为需要新增的
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  }
})

注意:

        如果报错:无法找到模块“path”的声明文件,找不到名称“__dirname”

        安装@types/node

npm install --save-dev @types/node

安装Tailwind CSS

> npm install -D tailwindcss postcss autoprefixer
> npx tailwindcss init

      tailwind.config.js

        生成tailwind.config.js文件,并修改为

/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,vue,html}'],
  theme: {
    extend: {}
  },
  plugins: []
}

     postcss.config.cjs

       创建postcss.config.cjs文件

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}

     main.css

       在main.css中

@tailwind base;
@tailwind components;
@tailwind utilities;
//定义主题
:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  background-color: #fff;
  color: #000;
}
html[theme='dark'] {
  background-color: #000;
  color: #fff;
}
@media (prefers-color-scheme: light){
// 系统颜色为亮色时
}
*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html,body,#root{
  width: 100%;
  height: 100%;
}
html{
  font-size: calc(26vw / 7.5);
}
@media screen and (min-width:750px) {
  html{
    font-size: 26px;
  }
}
.overflow{
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.overflow-1{
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
}
.safe {
  padding-bottom: calc(env(safe-area-inset-bottom));
}

 在VUE中使用 @apply

<style scoped>
.contioner {
  @apply h-full w-full
}
</style>

定义主题

//js----------------------------------------------------
//拿到当前系统的主题是否为亮色
const match = matchMedia('(prefers-color-scheme:light)');
//系统的主题颜色发生改变
match.addEventListener('change', () => {
  console.log('是否为亮色?', match.matches);
});

//css-------------------------------------------------
//定义主题
:root {
  background-color: #fff;
  color: #000;
}
html[theme='dark'] {
  background-color: #000;
  color: #fff;
}
@media (prefers-color-scheme: light){
// 系统颜色为亮色时
}

封装网络请求

      useRequest.ts

/* eslint-disable @typescript-eslint/no-explicit-any */
import { router } from "@/router";
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";

interface BaseReturn<T> {
  data: T;
  code: number;
  msg: string;
}

type NODE_ENV = "development" | "production";
enum Url {
  development = "/api",
  production = "https://127.0.0.1:8080",
}
const node_env = process.env.NODE_ENV as NODE_ENV;

class Request {
  private instance: AxiosInstance;
  constructor(config: AxiosRequestConfig) {
    this.instance = axios.create(config);
    this.instance.interceptors.request.use((config) => {
      config.headers.Authorization = localStorage.getItem("token") || "";
      return config;
    });
    this.instance.interceptors.response.use((res) => {
      if (res.data.err_code !== 200) {
        console.log("请求出错=========");
        if (res.data.err_code === 401) {
          localStorage.removeItem("token");
          router.replace("/");
        }
        if (res.data.err_code === 403) {
          router.replace("/");
        }
      }
      return res.data;
    });
  }
  request<T>(config: AxiosRequestConfig) {
    return new Promise<BaseReturn<T>>((resolve) => {
      this.instance.request<any, BaseReturn<T>>(config).then((res) => {
        resolve(res);
      });
    });
  }
}
const useBaseRequest = new Request({
  baseURL: Url[node_env],
  timeout: 50000,
});
const useGet = <T>(url: string, params: any = {}) => {
  return useBaseRequest.request<T>({
    url,
    params,
    method: "GET",
  });
};
const usePost = <T>(url: string, data: any = {}) => {
  return useBaseRequest.request<T>({
    url,
    data,
    method: "POST",
  });
};
export { useBaseRequest, useGet, usePost };

      vite.config.ts

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import Components from "unplugin-vue-components/vite";
import vueJsx from "@vitejs/plugin-vue-jsx";

const base_url = "https://127.0.0.1:8080";

export default defineConfig({
  server: {
    proxy: {
      //请求转发=====================================
      "/api": {
        target: base_url,
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },
  plugins: [
    vue(),
    vueJsx(),
    Components({
      dts: true,
      resolvers: [
        (name) => {
          if (name.startsWith("My")) {
            return `@/components/${name}.vue`;
          }
        },
      ],
    }),
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

使用.env文件的变量

import.meta.env.VITE_PORT

图片懒加载 

    <div class="lazy-load-img overflow-hidden relative">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
        <img src="./img/pixel.gif" data-url="https://i02piccdn.sogoucdn.com/bff0ff31443fef2c">
    </div>
    const imgs = document.getElementsByTagName('img')
    const io = new IntersectionObserver(function (entires) {
        //图片进入视口时就执行回调
        entires.forEach(item => {
            // 获取目标元素
            const oImg = item.target
            // 当图片进入视口的时候,就赋值图片的真实地址
            if (item.intersectionRatio > 0 && item.intersectionRatio <= 1) {
                oImg.setAttribute('src', oImg.getAttribute('data-url')!)
                io.unobserve(oImg)
            }
        })
    })
    Array.from(imgs).forEach(element => {
        io.observe(element)  //给每一个图片设置监听
    });

状态管理

vue使用pinia,react使用redux

H5项目键盘弹出与tabbar冲突

const docmHeight =  ref(0)
const hidshow = ref(true)

onMounted(() => {
    docmHeight.value = document.documentElement.clientHeight;//获取当前屏幕高度
    window.onresize = () => {//屏幕高度变化时判断
      return (() => {
        let showHeight = document.body.clientHeight;
        hidshow.value = docmHeight.value > showHeight ? false : true;
      })();
    };
})

大屏适配(定义缩放比例)

//缩放元素element事先定义为居中
//定义缩放比例
function getScale(w = 1920, h = 1080) {
    const ww = window.innerWidth / w           //document.documentElement.clientWidth
    const wh = window.innerHeight / h          //document.documentElement.clientHeight
    return ww < wh ? wh/ww : ww/wh;                  //return { ww, wh }
}
//transform只写一项的话会覆盖掉原有的transform样式
window.onresize = () => {
    document.body.style.transform = `scale(${getScale()}) translate(-50%,-50%)`
}

Vue

        建议目录结构

参考我的vue模板

        配置自动引入组件

安装unplugin-vue-components

npm install -D unplugin-vue-components

vite.config.ts

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
//自动引入组件
import Components from "unplugin-vue-components/vite";

export default defineConfig({
  plugins: [
    vue(),
    //自动引入组件
    Components({
      resolvers: [
        (name) => {
          if (name.startsWith("My")) {
            return `@/components/${name}.vue`;
          }
        },
      ],
    }),
  ],
  //src路径别名
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

为自动引入的组件标注类型 

    创建src/components/types/components.d.ts文件

import MyHelloWorld from "../MyHelloWorld.vue";
declare module "@vue/runtime-core" { //如果不生效,使用"vue"模块,或修改tsconfig.json
  export interface GlobalComponents {
    MyHelloWorld: typeof MyHelloWorld;
  }
}

      修改tsconfig.json:在include项加入配置项"./components.d.ts"

React 

        常用hook

useState

const [count, setCount] = useState(0)
setCount(new_val)
setCount((old_val)=>{return old_val+1})

useRef 

import { useRef } from 'react';
function MyComponent() {
  const number = useRef(0)
  const inputRef = useRef(null)
  return (
    <div>
      <input type="text" ref={inputRef} />
    </div>
  );
}

 useReducer 

import React, { useReducer } from 'react';
// 定义reducer函数
const reducer = (state, {type,payload}) => {
  switch(type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'decrement':
      return { ...state, count: state.count - 1 };
    default:
      throw new Error();
  }
}
// 在组件中使用 useReducer
const Counter = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
};

 useTransition 

import React, { useState, useTransition } from 'react';
function Example() {
  const [show, setShow] = useState(false);
  const [isPending, startTransition] = useTransition({ timeoutMs: 1000 });
  const handleClick = () => {
    startTransition(() => {
      setShow(!show);
    });
  }
  return (
    <div>
      <button onClick={handleClick} disabled={isPending}>
        {show ? '隐藏' : '显示'}
      </button>
      {isPending ? '正在加载...' : null}
      {show ? <div>这是要显示的内容</div> : null}
    </div>
  );
}

useDeferredValue  

import { useState, useDeferredValue } from 'react';  
function MyComponent(props) {
  const [data, setData] = useState([]);
  const deferredData = useDeferredValue(data);   
  // ...
  return (<div>内容</div>)
}

 useEffect

useEffect(() => {
    return ()=>{}
}, []);

useContext 

const TestContext = React.createContext();
//父组件
<TestContext.Provider 
	value={{
		username: 'superawesome',
	}}
>
	<div className="test">
		<Navbar />
		<Messages />
	</div>
<TestContext.Provider/>
//-------------------------------------------
//子组件
const Navbar = () => {
	const { username } = useContext(TestContext);
	return (
		<div>
			<p>{username}</p>
		</div>
	)
}

 useMemo和useCallback

//useMemo
import React, { useState, useMemo } from 'react';
function ExpensiveComponent(props) {
  const [count, setCount] = useState(0);
  // 计算结果
  const expensiveResult = useMemo(() => {
    return computeExpensiveValue(props.input);
  }, [props.input]);
  return <div>{expensiveResult}</div>;
}

//useCallback
import React, { useState, useCallback } from 'react';
function ParentComponent() {
  const [count, setCount] = useState(0);
  // 缓存的函数
  const increment = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, [count]);
  return (
    <div>
      <button onClick={increment}>Increment</button>
    </div>
  )
}

useImperativeHandle和forwardRef

//父
import { useRef } from "react";
import Child from "./child ";
export default () => {
  const childRef = useRef();
  const childAdd = () => {
    //父组件调用子组件内部 add函数
    childRef.current.add();
  }
  return (
    <div>
      <div>父组件</div>
      <Child  ref={childRef}></C>
      <button onClick={childAdd}>调用子组件里面的add方法</button>
    </div>
  );
};
//----------------------------------
//子
import React, { useImperativeHandle, useState, forwardRef  } from 'react'
//就可以接收到父组件传过来的ref
export default forwardRef((props, ref) => {
  const [num, setNum] = useState(1)
  const add = () => {
    setNum(num + 1)
  }
  useImperativeHandle(ref, () => {
    return {
      add
    }
    //依赖的变量
  }, [num])
  return (
    <div>
      <div>{num}</div>
    </div>
  )
})

        建议目录结构

参考我的react模板

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值