搭建 vite + vue3 + tsx 项目

锁死 npm 版本号

npm config set save-prefix=''

1. 创建项目

以下命令二选一

pnpm create [email protected] mangosteen-fe-1 -- --template vue-ts
npm create [email protected] mangosteen-fe-1 -- --template vue-ts

然后进入项目,分别运行

pnpm run dev
pnpm run build

运行 build 的时候报错

解决方法:在 tsconfig.json 里添加

{
  "compilerOptions": {
  +  "skipLibCheck": true,
    }
}

build path

把 HTML、CSS、JS 部署到 GitHub 或服务器时必须配置 build path
配置规则见文档
在哪里配
vite.config.js 里添加 base: '/' 或 '/reponame/' 等

run preview

  • 运行目的
    看看 dist 目录是否能正常运行
  • 大约等价于
pnpm i http-server
http-server -p 4173 dist

2.部署到 Github

1). 将我们的 dist 目录上传,然后把 dist 目录的路径添加到 vite.config.ts 的 base 字段里

export default defineConfig({
 +  base: '/bill-fe/dist/',
})

2). 重新运行

pnpm run build

3). push
4). 删除远程的 dist 目录
将我们的 dist 加入到 ignore 里,然后运行

git rm -r --cached dist

然后再重新 add commit push

3. template vs tsx

template 写法


tsx 写法
1). 新建一个 .tsx 文件

import { defineComponent, ref } from 'vue';

export const App = defineComponent({
  setup() {
    const refCount = ref(0);
    const onClick = () => {
      refCount.value += 1;
    }
  // 这里需要返回一个函数
    return () => (
      <>
        
{refCount.value}
) } })

2). 安装 @vitejs/plugin-vue-jsx 插件

pnpm i -D @vitejs/plugin-vue-jsx

3). 在 vite.config.ts 里配置 vueJsx

import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
  plugins: [
+    vueJsx({
      transformOn: true,
      mergeProps: true,
    })
  ]
})

4. 引入 vue router 4

1). 安装

pnpm i vue-router@4

2). 使用

  • main.ts
import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router';
import {App} from './App';
import { Bar } from './views/Bar';
import { Foo } from './views/Foo';

const routes = [
  {
    path: '/', component: Foo
  },
  {
    path: '/about', component: Bar
  }
]
const router = createRouter({
  history: createWebHashHistory(),
  routes,
})
const app = createApp(App)
app.use(router)
app.mount('#app')
  • App.tsx
import { defineComponent } from 'vue';
import { RouterView } from 'vue-router';

export const App = defineComponent({
  setup() {
    return () => (
      
    )
  }
})

5. 使用 css module 和全局 css

使用 css module

1). 在当前目录下创建一个.module.scss 文件
2). 引入这个 css 文件通过变量名的形式
3). 通过 s.样式名来使用

  • Welcome.module.scss
.wrapper {
  color: red;
}
  • Welcome.tsx
import { defineComponent } from 'vue';
import s from './Welcome.module.scss';
export const Welcome = defineComponent({
  setup: (props, context) => {
    return () => (
      
aaa
) } });

因为我们用的是 sass 所以需要使用 pnpm i sass

使用全局 css

1). 新建一个.css 文件
2). 直接通过 import './***.css' 引入

6. 使用 slot 插槽

import { defineComponent } from 'vue';
import s from './First.module.scss';
export const First = defineComponent({
  setup: (props, {slots}) => {
    return () => (
      
{slots.icon?.()} {slots.title?.()}
{slots.buttons?.()}
) } })
  • demo
import { WelcomeLayout } from './WelcomeLayout';
export const First = defineComponent({
  setup: (props, context) => {
    const slots = {
      icon: () => icon,
      title: () => 'hi',
      buttons: () => <>
    }
    return () => (
     
    )
  }
})
或者
export const First = defineComponent({
  setup: (props, context) => {
    return () => (
     
      {{
        icon: () => icon,
        title: () => 'hi',
        buttons: () => <>
      }}
      
    )
  }
})

7. 使用多个 RouterView

router.tsx

{
    path: '/welcome',
    component: Welcome,
    children: [
      { path: '', redirect: '/welcome/1', },
      { path: '1', components: { main: First, footer: FirstActions }, },
      { path: '2', components: { main: Second, footer: SecondActions }, },
      { path: '3', components: { main: Third, footer: ThirdActions }, },
      { path: '4', components: { main: Forth, footer: ForthActions }, },
    ]
  }
  • demo
import { RouterView } from 'vue-router';
export const Welcome = defineComponent({
  setup: (props, context) => {
    return () => 

山竹记账

} })

路由动画

{({Component: Content, route: R}: { Component: VNode, route: RouteLocationNormalizedLoaded}) => ( {Content} )}
.slide_fade_enter_active, .slide_fade_leave_active { position: absolute; left: 0; top: 0; width: 100%; height: 100%; transition: all 0.5s ease-out; } .slide_fade_enter_from { transform: translateX(100vw); } .slide_fade_leave_to { transform: translateX(-100vw); }

8. 写一个svg vite 插件用来预加载所有的svg

问题:我们页面的svg在路由切换的时候有可能还没加载完成,会出现图片加载慢的问题
解决:
1). 安装 svgo 和 svgstore

pnpm i svgo svgstore

2). 创建 vite_plugins/svgstore.js

/* eslint-disable */
import path from 'path'
import fs from 'fs'
import store from 'svgstore' // 用于制作 SVG Sprites
import { optimize } from 'svgo' // 用于优化 SVG 文件

export const svgstore = (options = {}) => {
  const inputFolder = options.inputFolder || 'src/assets/icons';
  return {
    name: 'svgstore',
    // 解析 如果文件是 @svgstore 直接加载 svg_bundle.js
    // 引入的时候直接使用 import '@svgstore'
    resolveId(id) {
      if (id === '@svgstore') {
        return 'svg_bundle.js'
      }
    },
    load(id) {
      if (id === 'svg_bundle.js') {
        // 创建一个大的 svg
        const sprites = store(options);
        const iconsDir = path.resolve(inputFolder);
        // 遍历所有的svg,然后把每一个都添加到这个大的里
        for (const file of fs.readdirSync(iconsDir)) {
          const filepath = path.join(iconsDir, file);
          const svgid = path.parse(file).name
          let code = fs.readFileSync(filepath, { encoding: 'utf-8' });
          sprites.add(svgid, code)
        }
        // 优化大的 svg
        const { data: code } = optimize(sprites.toString({ inline: options.inline }), {
          plugins: [
            'cleanupAttrs', 'removeDoctype', 'removeComments', 'removeTitle', 'removeDesc', 
            'removeEmptyAttrs',
            { name: "removeAttrs", params: { attrs: "(data-name|data-xxx)" } }
          ]
        })
        // 把这个大的 svg 变成js文件
        return `const div = document.createElement('div')
div.innerHTML = \`${code}\`
const svg = div.getElementsByTagName('svg')[0]
if (svg) {
  svg.style.position = 'absolute'
  svg.style.width = 0
  svg.style.height = 0
  svg.style.overflow = 'hidden'
  svg.setAttribute("aria-hidden", "true")
}
// listen dom ready event
document.addEventListener('DOMContentLoaded', () => {
  if (document.body.firstChild) {
    document.body.insertBefore(div, document.body.firstChild)
  } else {
    document.body.appendChild(div)
  }
})`
      }
    }
  }
}

3). 在 vite.config.ts 里注册这个配置

import { svgstore } from './src/vite_plugins/svgstore';
export default defineConfig({
  plugins: [
   + svgstore(),
  ]
})

4). 在入口文件中引入我们的svgstore

  • main.ts
import '@svgstore';

5). 将我们的 标签换成 svg


    

9. hooks

  • useSwipe
import { computed, onMounted, onUnmounted, ref, Ref } from "vue"
type Point = {
  x: number;
  y: number;
}

export const useSwipe = (element: Ref) => {
  const start = ref(null)
  const end = ref(null)
  const swiping = ref(false)
  const distance = computed(() => {
    if (!start.value || !end.value) { return null }
    return {
      x: end.value.x - start.value.x,
      y: end.value.y - start.value.y,
    }
  })
  const direction = computed(() => {
    if (!distance.value) { return '' }
    const { x, y } = distance.value
    if (Math.abs(x) > Math.abs(y)) {
      return x > 0 ? 'right' : 'left'
    } else {
      return y > 0 ? 'down' : 'up'
    }
  })
  const onStart = (event: TouchEvent) => {
    swiping.value = true
    end.value = start.value = { x: event.touches[0].screenX, y: event.touches[0].screenY }
  }
  const onMove = (event: TouchEvent) => {
    if (!start.value) { return }
    end.value = { x: event.touches[0].screenX, y: event.touches[0].screenY, }
  }
  const onEnd = (event: TouchEvent) => {
    swiping.value = false
  }
  onMounted(() => {
    if (element.value) {
      element.value.addEventListener('touchstart', onStart)
      element.value.addEventListener('touchmove', onMove)
      element.value.addEventListener('touchend', onEnd)
    }
  })
  onUnmounted(() => {
    if (element.value) {
      element.value.removeEventListener('touchstart', onStart)
      element.value.removeEventListener('touchmove', onMove)
      element.value.removeEventListener('touchend', onEnd)
    }
  })
  return {
    swiping,
    direction,
    distance
  }
}

使用

export const Welcome = defineComponent({
  setup: (props, context) => {
    const main = ref(null)
    const { direction, swiping } = useSwipe(main)
    return () => (
      
) }

10. 自定义组件类型声明

  • 子组件
// 方法1
interface Props {
  onClick: (event: MouseEvent) => void;
  name: 'add' | 'chart';
}
export const Button = defineComponent({
  setUp: (props, context) => {
   // 使用 这种方式只有内置的属性才能访问到 onClick 是内置的所以能访问到
    console.log(props.onClick)
    // name 内部没有定义所以访问不到
    console.log(props.name)
  }
})

// 方法2(获取我们自己定义的 props)
export const Button = defineComponent({
  props: {
    name: {
      // String 是js PropType里面是 ts
      type: String as PropType<'add' | 'chart'>
    }
  }
  setUp: (props, context) => {
    console.log(props.name)
  }
})
  • 父组件
const onClick = () => {}

11. 打包静态资源

如果我们需要引入图片资源有两种方式
1). 把图片资源放到 public 目录里,直接通过 public 目录下的路径引入

  • public/images/logo.png

这样我们打包后 dist 目录下就会多一个 images 文件里面有我们的 logo.png

2). 我们自己创建的目录,比如我在 src/assets/icons/logo.png
那么我们可以通过 import 语法

import logo from "@/assets/icons/logo.png";

这样打包后就会生成一个 asset/logo.chunk值.png

12. proxy

使用 proxy 就是 将你本地的 localhost:3000/api 代理到对应的后端域名,
所以一定要保证我们是通过 localhost 来调这个接口的,如果使用axios的话,baseUrl 要写成 /

 server: {
      // Listening on all local IPs
      cors: true,
      proxy: {
        "/api": {
          target: "http://f2e-sit.ccc.com",
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ""),
        },
      },
    },


这样我们调 localhost:3000/api 就会代理到 http://f2e-sit.ccc.com

你可能感兴趣的:(搭建 vite + vue3 + tsx 项目)