38个Vue、Nuxt 和 Vite 技巧、窍门和实践的合集

大家好,我是宝哥。

本周我会分享下面一系列的Vue教程,欢迎关注我。

  • Vue 如何处理异步组件加载错误

  • Vue、Nuxt 和 Vite 技巧、窍门和实践的集合(当前)

  • 我是如何发现没有在 Vue SPA 中将 EventListeners 放在挂载上的

  • 开源 Vueform 中的新 phone 元素

  • Vue DevTools Next v7.2 发布

  • 尤大谈 Vite & Vue 2024 状态

本文分享几个关于Vue、Nuxt 和 Vite 技巧。

01

从 Vue 3.4 开始,推荐的实现双向数据绑定的方法是使用 defineModel() 宏。在 Vue 中:

<!-- 使用 defineModel 之前 -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
  <input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" />
</template>

<!-- 使用 defineModel 之后 -->
<script setup>
const model = defineModel();
</script>
<template>
  <input v-model="model" />
</template>

02

在 Vue 代码库中,我经常看到的一个常见错误是误用 ref()。你不必将每个变量都用 ref() 包裹起来,只有在需要响应性时才这么做。

<script setup>
// 这里不需要用 ref() 包裹
// 在这里不需要响应性
const links = [
  { name: 'about', href: '/about' },
  { name: 'terms of service', href: '/tos' },
  { name: 'contact us', href: '/contact' }
]

// isActive 标志需要响应 UI 的变化
// 这就是为什么将 tabs 用 ref 包裹是一个好主意
const tabs = ref([
  { name: 'Privacy', url: '/privacy', isActive: true },
  { name: 'Permissions', url: '/permissions', isActive: false }
])
</script>

03

从 Vue 3.4 开始,你可以使用 v-bind 的同名简写。

<template>
  <!-- 现在你可以缩短这个: -->
  <img :id="id" :src="src" :alt="alt">
  <!-- 到这个: -->
  <img :id :src :alt>
</template>

04

在 Vue 中,当你使用大型数据结构并且不需要深度响应性时,可以使用 shallowRef 而不是 ref

const state = shallowRef({ count: 1 })
// 不会触发变化
state.value.count = 2
// 会触发变化
state.value = { count: 2 }

05

在 Vue 中,你可以对你的组件 emits 进行类型定义,以便拥有更好的错误处理和编辑器支持。

const emit = defineEmits<{
  change: [id: number]
  update: [value: string]
}>()

06

在 Vue 中,你可以在 <style> 中直接使用任何动态值,这要归功于 v-bind 指令。它是完全响应式的。

<style scoped>
button {
  background-color: v-bind(backgroundColor);
}
</style>

07

如果你在 Vue 中过度使用这种数据获取模式,感谢 Tanstack Query for Vue,你可以减少样板代码,并利用一些有用的特性,比如自动缓存、自动重新获取等。这是一个非常棒且强大的库!

// 在许多组件中过度使用这种数据获取模式?
const posts = ref([])
const isLoading = ref(false)
const isError = ref(false)

async function fetchPosts() {
  isLoading.value = true
  isError.value = false
  try {
    const response = await fetch('someurl')
    posts.value = await response.json()
  } catch(error) {
    isError.value = true
  } finally {
    isLoading.value = false
  }
}

onMounted(() => {
  fetchPosts()
})

// 感谢 Tanstack Query (Vue Query),你可以用几行代码替换它 ✅
const { data: posts, isLoading, isError } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts
})

async function fetchPosts() {
  const response = await fetch('someurl')
  const data = await response.json()
  return data
}

08

在 Vue 中,当你使用作用域样式但想让一个规则全局生效时,你可以使用 :global 伪类而不是创建另一个 <style>

<style scoped>
:global(.red) {
  color: red;
}
</style>

09

在 Nuxt 中,你可以使用 local font fallbacks 来减少 CLS,这要感谢强大的 Fontaine 库。

// 安装包
npm install -D @nuxtjs/fontaine

// 在 nuxt.config 中设置模块
export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
})

就是这样!

10

你可以为你的 props 设置默认值,即使你在使用 defineProps 进行类型声明时也是如此。这要感谢 withDefaults 宏。

<script setup lang="ts">
export interface Props {
  variant?: 'primary' | 'secondary'
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  variant: 'primary',
  disabled: false
})
</script>

11

在 Nuxt Content 中,你可以感谢实验性特性 ContentSearch 来搜索你的内容。

// 首先,你需要在 nuxt.config.ts 中启用这个选项
export default defineNuxtConfig({
  content: {
    experimental: {
      search: true
    }
  }
})

// 然后在你的组件中使用它
<script lang="ts" setup>
const search = ref('')
const results = searchContent(search)
</script>

<template>
  <main>
    <input v-model="search">
    <pre>{{ results }}</pre>
  </main>
</template>

12

在 Nuxt Content 中,你可以轻松生成上一篇和下一篇文章的链接。

<script setup lang="ts">
const route = useRoute()

const [prev, next] = await queryContent()
.only(['_path', 'title'])
.sort({ date: -1 })
.findSurround(route.path);
</script>

<template>
<NuxtLink v-if="prev" :to="prev._path">
  <span>previous</span>
</NuxtLink>
</template>

13

在 Vue 中,你可以轻松注册一个自定义指令,通过创建一个包含带有 'v-' 前缀的生命周期钩子的对象。

<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>

14

在 Vite 中,你可以创建自定义导入别名。这使得创建绝对导入路径变得更加容易。

// vite.config.ts
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: [
      {
        find: '@',
        replacement: fileURLToPath(new URL('./src', import.meta.url))
      },
    ]
  }
})

// tsconfig.ts
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"],
    }
  }
}

// 感谢绝对路径导入别名,
// 每个组件的导入语句看起来都是一样的。
import Button from '@/components/Button.vue'
import Dropdown from '@/components/Dropdown.vue'

// 通过使用相对导入,导入语句可以在文件之间变化
import Button from './Button.vue'
import Button from './../Button.vue'
import Dropdown from './components/Dropdown.vue'

15

在 Nuxt Content 中,创建所有页面的站点地图真的很容易。

// api/routes/sitemap.ts
import { SitemapStream, streamToPromise } from 'sitemap'
import { serverQueryContent } from '#content/server'

export default defineEventHandler(async (event) => {
  const docs = await serverQueryContent(event).find()
  const staticSites = [
    { _path: '/' },
    { _path: '/about' },
    { _path: '/open-source' }
  ]
  const sitemapElements = [...staticSites, ...docs]
  const sitemap = new SitemapStream({ hostname: import.meta.env.VITE_BASE_URL as string })
  for (const doc of sitemapElements) {
    sitemap.write({ url: doc._path, changefreq: 'monthly' })
  }
  sitemap.end()
  return streamToPromise(sitemap)
})

16

在 Nuxt Content 中,创建所有内容的 RSS 订阅真的很容易。

// api/routes/rss.ts
import RSS from 'rss'
import { serverQueryContent } from '#content/server'

export default defineEventHandler(async (event) => {
  const BASE_URL = 'https://your-domain.com'

  const feed = new RSS({
    title: 'Your title',
    site_url: BASE_URL,
    feed_url: `${BASE_URL}/rss.xml`
  })

  const docs = await serverQueryContent(event)
  .sort({ date: -1 })
  .where({ _partial: false })
  .find()

  for (const doc of docs) {
    feed.item({
      title: doc.title ?? '-',
      url: `${BASE_URL}${doc._path}`,
      date: doc.date,
      description: doc.description
    })
  }

  const feedString = feed.xml({ indent: true })
  setHeader(event, 'content-type', 'text/xml')
  return feedString
})

17

如果你想在作用域样式中有一个 CSS 选择器是“深层”的,即影响子组件,你可以使用 :deep() 伪类。

<style scoped>
.a :deep(.b) {
  /* ... */
}
</style>

18

默认情况下,作用域样式不影响 <slot/> 渲染的内容。要明确定位插槽内容,使用 :slotted 伪类。

<style scoped>
:slotted(div) {
  color: red;
}
</style>

19

在 Vue.js 中,默认情况下,当你从一个活动组件切换时,该组件实例将被卸载。但是如果你想要保留切换组件时的状态呢?你可以用内置的 <KeepAlive> 组件来包裹它,以保留和缓存状态。

<template>
<KeepAlive>
  <component :is="activeComponent" />
</KeepAlive>
</template>

20

在 Vue.js 中,你可以向你的子组件传递多个命名插槽。

<!-- 子组件 / Input.vue -->
<template>
  <div class="input-wrapper">
    <label>
      <slot name="label" />
    </label>
    <input />
    <div class="input-icon">
      <slot name="icon" />
    </div>
  </div>
</template>

<!-- 父组件 -->
<template>
  <Input>
    <template #label>
      Email
    </template>
    <template #icon>
      <EmailIcon />
    </template>
  </Input>
</template>

21

感谢实验性的组件 'Suspense',你可以在组件树中协调异步依赖。它可以在等待组件树中多个嵌套的异步依赖被解决时渲染一个加载状态。

<template>
  <Suspense>
    <!-- 带有嵌套异步依赖的组件 -->
    <Dashboard />
    <!-- 通过 #fallback 插槽的加载状态 -->
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</template>

22

Nuxt Island 是一个特别内置的组件,它允许你完全在服务器上渲染组件,这意味着没有将任何客户端 JavaScript 提供给浏览器。

// 这个特性仍然是实验性的,所以你必须在 nuxt.config 中启用它
export default defineNuxtConfig({
  experimental: {
    componentIslands: true
  }
})

假设你有一个 JS 丰富的组件,但你不需要该库的代码在你的生产包中。一个例子可能是使用一个像 moment.js 这样的庞大的日期操作库。我们只想格式化一些数据并显示给用户结果。这是服务器组件的完美用例。你在服务器上运行 JS 并返回没有 JS 的 HTML 到浏览器。

<!-- components/Hello.vue -->
<template>
  <div>
    <h1>Hello</h1>
    {{ date }}
  </div>
</template>

<script setup lang="ts">
import moment from 'moment'
const date = moment().format('MMMM Do YYYY, h:mm:ss a')
</script>

23

你所要做的就是把你的组件移动到 /components/islands 目录,然后调用组件。

<!-- app.vue -->
<template>
  <NuxtIsland name="Hello" />
</template>

24

在 Vue 中,你可以将组件模板的一部分“传送”到组件 DOM 层次结构之外的 DOM 节点中。为此,请使用内置的 Teleport 组件,并定位你想要传送模板部分的特定 DOM 元素。

<template>
  <Teleport to="body">
    <div v-if="open" class="modal">
      <p>Hello from the modal!</p>
      <button @click="open = false">Close</button>
    </div>
  </Teleport>
</template>

25

在 Vue 中,你可以在浏览器开发工具的性能/时间轴面板中启用性能跟踪。这只在开发模式下工作。

const app = createApp(App)
app.config.performance = true
app.mount('#app')

26

在 Vue 中,你可以使用内置的 <Component> 组件动态渲染组件。

<script setup>
import UserSettings from './Foo.vue'
import UserNotifications from './Bar.vue'

const activeComponent = ref(UserSettings)
</script>

<template>
  <component :is="activeComponent" />
</template>

27

感谢 Nuxt 中的 callOnce 实用工具,你可以在服务器端渲染(SSR)或客户端渲染(CSR)期间执行指定的函数或代码块一次。

<script setup lang="ts">
const websiteConfig = useState('config')

await callOnce(async () => {
  console.log('This will only be logged once')
  websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')
})
</script>

28

在 Vue 中,当你将布尔类型作为 prop 并显式赋予 true 值时,可以使用以下简写。

<template>
  <!-- 你可以使用这个 -->
  <BlogPost is-published />
  <!-- 而不是这个 -->
  <BlogPost :is-published="true" />
</template>

29

默认情况下,v-model 在每个输入事件后同步输入和数据。你可以添加 lazy 修饰符,改为在 change 事件后进行同步。

<!-- 在 "change" 而不是 "input" 后同步 -->
<input v-model.lazy="msg" />

30

如果你想让用户输入自动转换为数字类型,可以在你的 v-model 管理的输入中添加 number 修饰符。

<input v-model.number="age" />

31

如果你想让输入中的空白自动被修剪,可以在你的 v-model 管理的输入中添加 trim 修饰符。

<input v-model.trim="msg" />

32

你可以利用本地隧道将你的 Nuxt 开发服务器暴露到互联网上。你所要做的就是用 --tunnel 标志运行 Nuxt 开发服务器。

npx nuxt dev --tunnel

33

你可以用自签名证书在 HTTPS 协议上运行你的 Nuxt 开发服务器。

npx nuxt dev --https

34

使用 <script setup> 的组件默认是封闭的。要明确地在 <script setup> 组件中暴露属性,请使用 defineExpose 编译器宏。

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

35

在 Nuxt 中,有时你可能想刷新 'useCookie' 组合式返回的 cookie 值。你可以使用 Nuxt 3.10 中提供的 refreshCookie 实用函数。

<script setup lang="ts">
const tokenCookie = useCookie('token')

const login = async (username, password) => {
  const token = await $fetch('/api/token', { ... })
  // 在响应上设置 `token` cookie
  refreshCookie('token')
}

const loggedIn = computed(() => !!tokenCookie.value)
</script>

36

在 Vue 中,当你的组件模板有一些类,并且你还在父组件中向这个组件添加了一些类,这些类将会合并在一起。

父组件:

<template>
  <Table class="py-2"></Table>
</template>

子组件 Table.vue:

<template>
  <table class="border-solid border-2 border-sky-500">
    <!-- ... -->
  </table>
</template>

来自父组件和子组件的类将会合并在一起:

<template>
  <table class="border-solid border-2 border-sky-500 py-2">
    <!-- ... -->
  </table>
</template>

37

在 Vite 中,你可以使用 Lightning CSS 作为你的默认转换器和压缩器。

import { browserslistToTargets } from 'lightningcss'
export default {
  css: {
    transformer: 'lightningcss',
    lightningcss: {
      targets: browserslistToTargets(browserlist('>= 0.25%'))
    }
  },
  build: {
    cssMinify: 'lightningcss'
  }
}

38

在 Vue 中,控制台记录响应式项目并不总是直观的。打开自定义格式化程序,享受任何响应式项目的格式化控制台输出。

你可以在 Chrome (Chromium) DevTools 中通过选择选项 "Console -> Enable custom formatters." 来启用自定义格式化程序。

Nuxt 以其出色的 DevTools 而闻名。你想要类似的东西用于 Vue 吗?

下面这个就是了!🥳

https://devtools-next.vuejs.org/


往期文章推荐


最后,如果你觉得宝哥的分享还算实在,就给我点个赞,关注一波。分享出去,也许你的转发能给别人带来一点启发。

关注下方宝哥微信,进宝哥前端开发11群,

获取我公众号整理的所有资料,

包括前端电子书,面试资料,简历模板和副业资料等!

5bd242e99e8399eae5e4fece3db7e540.png

添加好友备注【加群】拉你进技术交流群

公众号前端开发博客 专注 前端开发技术,分享 前端开发资源WEB前沿资讯,如果喜欢我的分享,给 宝哥 点一个 或者 分享 都是对我的支持

关注公众号后,在首页:

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF

  • 回复「Vue脑图」获取 Vue 相关脑图

  • 回复「思维图」获取 JavaScript 相关思维图

  • 回复「简历」获取简历制作建议

  • 回复「简历模板」获取精选的简历模板

  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。

  • 回复「知识点」下载高清JavaScript知识点图谱

  • 回复「读书」下载成长的相关电子书

以上,如果本文对你有所启发,欢迎点“c1fd8a25cd36205ce0f6339bd98a6287.gif在看、点赞”支持下吧! 

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的示例: ```javascript // App.vue <template> <div> <router-link to="/">Home</router-link> <router-link to="/about">About</router-link> <router-link to="/dynamic">Dynamic Route</router-link> <router-view></router-view> </div> </template> <script> import { defineComponent } from 'vue'; export default defineComponent({ name: 'App', }); </script> ``` ```javascript // router/index.js import { createRouter, createWebHistory } from 'vue-router'; const routes = [ { path: '/', name: 'Home', component: () => import('@/views/Home.vue'), }, { path: '/about', name: 'About', component: () => import('@/views/About.vue'), }, ]; const router = createRouter({ history: createWebHistory(), routes, }); export default router; ``` ```javascript // views/Dynamic.vue <template> <div> <h1>Dynamic Route Component</h1> <ul> <li v-for="component in components" :key="component.name"> <router-link :to="component.path">{{ component.name }}</router-link> </li> </ul> <router-view></router-view> </div> </template> <script> import { defineComponent } from 'vue'; export default defineComponent({ name: 'Dynamic', data() { return { components: [ { name: 'Dynamic Component 1', path: '/dynamic/component1', component: () => import('@/views/DynamicComponent1.vue'), }, { name: 'Dynamic Component 2', path: '/dynamic/component2', component: () => import('@/views/DynamicComponent2.vue'), }, ], }; }, }); </script> ``` ```javascript // views/DynamicComponent1.vue <template> <div> <h1>Dynamic Component 1</h1> </div> </template> ``` ```javascript // views/DynamicComponent2.vue <template> <div> <h1>Dynamic Component 2</h1> </div> </template> ``` 在这个示例中,我们创建了一个动态路由,可以添加和删除新的组件。在 `Dynamic.vue` 组件中,我们使用 `v-for` 渲染了一个包含动态组件的列表,并使用 `router-link` 组件将其链接到对应的路由路径。 当我们点击链接时,路由器将根据路径加载相应的组件并在 `router-view` 中呈现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值