收藏Typescriptvue

搭建 vite + vue3 + tsx 项目

2022-05-04  本文已影响0人  sweetBoy_9126

锁死 npm 版本号

npm config set save-prefix=''

1. 创建项目

以下命令二选一

pnpm create vite@2.9.0 mangosteen-fe-1 -- --template vue-ts
npm create vite@2.9.0 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

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 写法

<script setup lang="ts">
import { ref } from 'vue';
const count = ref(0)
const onClick = () => {
  count.value += 1
}
</script>

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

import { defineComponent, ref } from 'vue';

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

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). 使用

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')
import { defineComponent } from 'vue';
import { RouterView } from 'vue-router';

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

5. 使用 css module 和全局 css

使用 css module

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

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

因为我们用的是 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 () => (
      <div class={s.wrapper}>
        <div class={s.card}>
          {slots.icon?.()}
          {slots.title?.()}
        </div>
        <div class={s.actions}>
          {slots.buttons?.()}
        </div>
      </div>
    )
  }
})
import { WelcomeLayout } from './WelcomeLayout';
export const First = defineComponent({
  setup: (props, context) => {
    const slots = {
      icon: () => <span>icon</span>,
      title: () => 'hi',
      buttons: () => <><button>+1</button></>
    }
    return () => (
     <WelcomeLayout v-slots={slots} />
    )
  }
})
或者
export const First = defineComponent({
  setup: (props, context) => {
    return () => (
     <WelcomeLayout>
      {{
        icon: () => <span>icon</span>,
        title: () => 'hi',
        buttons: () => <><button>+1</button></>
      }}
      </WelcomeLayout>
    )
  }
})

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 }, },
    ]
  }
import { RouterView } from 'vue-router';
export const Welcome = defineComponent({
  setup: (props, context) => {
    return () => <div class={s.wrapper}>
      <header>
        <img src={logo} />
        <h1>山竹记账</h1>
      </header>
      <main class={s.main}><RouterView name="main" /></main>
      <footer>
        <RouterView name="footer" />
      </footer>
    </div>
  }
})

路由动画

<main class={s.main}>
        <RouterView name="main">
          {({Component: Content, route: R}: { Component: VNode, route: RouteLocationNormalizedLoaded}) => (
            <Transition
              enterFromClass={s.slide_fade_enter_from}
              enterActiveClass={s.slide_fade_enter_active}
              leaveToClass={s.slide_fade_leave_to}
              leaveActiveClass={s.slide_fade_leave_active}
            >
              {Content}
            </Transition>
          )}
        </RouterView>
      </main>

.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

import '@svgstore';

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

<svg>
    <use xlinkHref='#chart'></use>
</svg>

9. hooks

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

export const useSwipe = (element: Ref<HTMLElement | null>) => {
  const start = ref<Point | null>(null)
  const end = ref<Point | null>(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<HTMLElement | null>(null)
    const { direction, swiping } = useSwipe(main)
    return () => (
      <main ref={main/>
    )
}

10. 自定义组件类型声明

// 方法1
interface Props {
  onClick: (event: MouseEvent) => void;
  name: 'add' | 'chart';
}
export const Button = defineComponent<Props>({
  setUp: (props, context) => {
   // 使用<Props> 这种方式只有内置的属性才能访问到 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 = () => {}
<Button onClick={onClick} name={'lifa'}>按钮</Button>

11. 打包静态资源

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

<img src="/images/logo.png" />

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

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

import logo from "@/assets/icons/logo.png";
<img src={logo}

这样打包后就会生成一个 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
上一篇下一篇

猜你喜欢

热点阅读