多重 RouterView 与 Transition
WelcomeLayout 组件功能迁移到 Welcome 组件,重构 Welcome 组件
// src/views/Welcome.tsx
import { defineComponent, h, Transition, VNode } from 'vue';
import { RouteLocationNormalizedLoaded, RouterView } from 'vue-router';
import s from './Welcome.module.scss'
import logo from '../assets/icons/mangosteen.svg'
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">
{({ Component: X, 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}>
{X}
</Transition>
}
</RouterView>
</main>
<footer>
<RouterView name="footer" />
</footer>
</div>
}
})
多重 RouterView
用法:
// src/config/routes.tsx
export const routes: RouteRecordRaw[] = [
{ path: '/', redirect: '/welcome' },
{
path: '/welcome',
component: Welcome,
children: [
{ path: '', redirect: '/welcome/1', },
{ path: '1', components: { main: First, footer: FirstActions }, }, // 使用 components 配置
]
}
]
// src/views/Welcome.tsx
<main class={s.main}>
<RouterView name="main" /> // 如果没有指定 name,则为默认 default
</main>
<footer>
<RouterView name="footer" />
</footer>
动画
自定义过渡 class
向 <Transition>
传递以下的 props 来指定自定义的过渡 class:
- enter-active-class
- enter-active-class
- enter-to-class
- leave-from-class
- leave-active-class
- leave-to-class
传入的这些 class 会覆盖相应阶段的默认 class 名
<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}>{X}</Transition>
是怎么知道 RouterView 插槽的参数的
({ Component: X, route: R }: { Component: VNode, route: RouteLocationNormalizedLoaded }) =>
<Transition >
{X}
</Transition>
根据 IDE 提示(TS 的好处)查看 RouterView 定义
export declare const RouterView: new () => {
$props: AllowedComponentProps & ComponentCustomProps & VNodeProps & RouterViewProps;
$slots: {
default: (arg: {
Component: VNode;
route: RouteLocationNormalizedLoaded;
}) => VNode[];
};
};
学习方法重于学习结果
自制 Vite SVG Sprites 插件
新建 src/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',
resolveId(id) {
// 传入模块请求时被调用,如果请求的是 @svgstore,则返回 svg_bundle.js,svg_bundle.js 是一个虚拟文件,实际并不存在,直接请求不存在的路径 IDE 会报错
if (id === '@svgstore') {
return 'svg_bundle.js'
}
},
load(id) {
// 加载函数,返回自定义内容
if (id === 'svg_bundle.js') {
const sprites = store(options)
const iconsDir = path.resolve(inputFolder)
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)
}
const { data: code } = optimize(sprites.toString({ inline: options.inline }), {
plugins: [
'cleanupAttrs', 'removeDoctype', 'removeComments', 'removeTitle', 'removeDesc', 'removeEmptyAttrs',
{
name: 'removeAttrs',
params: {
attrs: '(data-name|data-xxx)'
}
}
]
})
return `const div = document.createElement('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)
}
})
`
}
}
}
}
tsconfig.node.json
"include": [
"vite.config.ts",
+ "src/vite_plugins/**/*",
],
vite.config.ts
// @ts-nocheck
+ import { svgstore } from './src/vite_plugins/svgstore'
export default defineConfig({
plugins: [
vue(),
vueJsx({
transformOn: true,
mergeProps: true
}),
+ svgstore()
]
})
封装 useSwipe Hook
这里就不写项目代码示例了,文档很容易看懂的 ~
约定和最佳实践
命名
组合式函数约定用驼峰命名法命名,并以“use”作为开头。
输入参数
即便不依赖于 ref 或 getter 的响应性,组合式函数也可以接收它们作为参数。如果你正在编写一个可能被其他开发者使用的组合式函数,最好处理一下输入参数是 ref 或 getter 而非原始值的情况。可以利用 toValue() 工具函数来实现
返回值
推荐的约定是组合式函数始终返回一个包含多个 ref 的普通的非响应式对象,这样该对象在组件中被解构为 ref 之后仍可以保持响应性
副作用
确保在组件挂载后才调用的生命周期钩子中执行 DOM 相关的副作用,例如:onMounted()
确保在 onUnmounted() 时清理副作用
使用限制
组合式函数只能在 <script setup>
或 setup() 钩子中被调用。在这些上下文中,它们也只能被同步调用。在某些情况下,你也可以在像 onMounted() 这样的生命周期钩子中调用它们