vue3 支持 jsx
安装依赖
pnpm add @vitejs/plugin-vue-jsx vite.config.ts 中引用插件 import { defineConfig } from "vite" import vue from "@vitejs/plugin-vue" import vueJsx from "@vitejs/plugin-vue-jsx" // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), vueJsx({ transformOn: true, mergeProps: true, }) ], })
使用 jsx
import { defineComponent, ref } from "vue" import "./App.scss" const App = defineComponent({ setup() { const count = ref(0) const onClick = () => { count.value++ } return () => ( <> <div class='page'> {count.value} </div> <button onClick={onClick}>增加</button> </> ) }, }) export default App
CSS Module
任何以 .module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件
/* example.module.css */ .red { color: red; }
import classes from './example.module.css' document.getElementById('foo').className = classes.red
手机调试 H5
Chrome 远程调试手机页面
用 usb 连接手机和电脑
手机开启 usb 调试
手机打开要调试的页面 (ip地址 => http://192.x.x.1:端口号/)
手机浏览器输入 http://192.168.3.37:3000 访问我电脑上的网页
打开
edge://inspect
https://links.jianshu.com/go?to=edge%3A%2F%2Finspect
或者
chrome://inspect/#devices
https://links.jianshu.com/go?to=chrome%3A%2F%2Finspect%2F%23devices
页面, 等待一会, 找到对应页面
vue-router 一个路由多个视图
应用场景: 可以对其中路由做动画
// routes.ts export const routes: RouteRecordRaw[] = [ { path: "/", redirect: "/welcome" }, { path: "/welcome", component: Welcome, children: [ { path: "", redirect: "/welcome/one" }, // components 可以用对象的方式 { path: "one", components: { main: First, footer: FirstActions } }, { path: "two", components: { main: Second, footer: SecondActions } }, { path: "three", components: { main: Third, footer: ThirdActions } }, { path: "four", components: { main: Forth, footer: ForthActions } }, ], } ]
// 视图使用 import { defineComponent, h, Transition, VNode } from 'vue'; import { RouteLocationNormalizedLoaded, RouterView } from 'vue-router'; import s from './Welcome.module.scss' export const Welcome = defineComponent({ setup: (props, context) => { return () => <div class={s.wrapper}> <header> <h1>xx记账</h1> </header> <main class={s.main}> // 使用对应的 name <RouterView name="main"> </RouterView> </main> <footer> <RouterView name="footer" /> </footer> </div> } })
SvgSprite svg 雪碧图
useSwipe 滑动 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 (!end.value || !start.value) return null return { x: end.value?.x - start.value?.x, y: end.value?.y - start.value?.y, } }) const direction = computed(() => { if (!swiping) return null if (!distance.value) return null 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 = (e: TouchEvent) => { const { clientX, clientY } = e.touches[0] start.value = { x: clientX, y: clientY, } end.value = null swiping.value = true } const onMove = (e: TouchEvent) => { const { clientX, clientY } = e.touches[0] if (swiping.value) { end.value = { x: clientX, y: clientY, } } } const onEnd = (e: TouchEvent) => { swiping.value = false start.value = null end.value = null } onMounted(() => { if (!element.value) return null element.value.addEventListener("touchstart", onStart) element.value.addEventListener("touchmove", onMove) element.value.addEventListener("touchend", onEnd) }) onUnmounted(() => { if (!element.value) return null element.value.removeEventListener("touchstart", onStart) element.value.removeEventListener("touchmove", onMove) element.value.removeEventListener("touchend", onEnd) }) return { swiping, distance, direction, } }
组件使用插槽(slots) 和 属性 props
// slots 使用 import { defineComponent } from "vue" import s from "./button.module.scss" interface ButtonProps { onClick: (e: MouseEvent) => void } export const Button = defineComponent<ButtonProps>({ inheritAttrs: false, // 不让 vue 帮我自动继承属性 // 插槽 context.slots setup: (props, context) => { return () => <button class={s.button}>{context.slots.default?.()}</button> }, }) // 具名插槽 import { defineComponent, PropType } from "vue" import s from "NavBar.module.scss" export const NavBar = defineComponent({ setup: (props, context) => { const { slots } = context return () => ( <div class={s.navbar}> <span class={s.icon_wrapper}>{slots.icon?.()}</span> <span class={s.title_wrapper}>{slots.default?.()}</span> </div> ) }, })
// props 使用
import { defineComponent, PropType } from "vue" import s from "./Icon.module.scss" export type IconNames = "add" | "chart" | "clock" | "cloud" export const Icon = defineComponent({ // inheritAttrs: false, props: { name: { type: String as PropType<IconNames>, required: true, } }, setup: (props) => { const { name } = props return () => ( <svg class={s.icon}> <use xlinkHref={"#" + name}></use> </svg> ) }, })
表情 搜索 emoji full list
[表情](
Full Emoji List, v15.0 (unicode.org)
https://links.jianshu.com/go?to=https%3A%2F%2Funicode.org%2Femoji%2Fcharts%2Ffull-emoji-list.html
)
'\u{1F600}'
reactive & toRaw
// 创建响应式数据 const formData = reactive({ name: '', sign: '' }) const onSubmit = (e: Event) => { e.preventDefault() console.log("proxy", formData) // proxy console.log("proxy", toRaw(formData)) // json } // v-model 使用 <input v-model={formData.name} class={[s.formItem, s.input, s.error]}></input>