实战篇笔记-山竹记账前端(四)


多重 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 插件

插件 API

新建 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() 这样的生命周期钩子中调用它们


文章作者: April-cl
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 April-cl !
  目录