vue&vue-router知识点

97 阅读2分钟

Node报错

报错:NodeJS.Timer,ts报错Cannot find namespace 'NodeJS'.
解决方法:在tsconfig.app.json中增加types:["node"]

如何访问路由

访问当前路由:useRoute()
访问router对象:useRouter

import { useRouter, useRoute } from 'vue-router'
export default {
  setup() {
    const router = useRouter()
    const route = useRoute()
    function pushWithQuery(query) {
      router.push({
        name: 'search',
        query: {
          ...route.query,
          ...query,
        },
      })
    }
  },
}

动态路由

使用场景:看上去是不同的页面,但实际上有着同样的页面组件,只是数据不一样。比如不同景区的详情页
语法:动态字段【路径参数】用:开头
对外暴露:$route.params.xxx 注意点:可设置多个路径参数,都会映射到$route.params

const User = {
  template: '<div>User</div>',
}
const routes = [
  { path: '/users/:id', component: User },
]

效果:/u/jo/u/ju不同的url会被映射到同一个路由

动态路由对于页面渲染的影响

当动态路由切换的时候,只有url会变化,但组件实例则会被复用,这会导致组件的生命周期钩子函数不会被调用。

解决方案

方案1:watch监听指定属性,比如治理监听路由参数的变化

watch( 
    () => $route.params, 
    (toParams, previousParams) => { // 对路由变化做出响应... } 
);

方案2:导航守卫

import {onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

export default {
  setup() {
    const userData = ref()
    onBeforeRouteUpdate(async (to, from) => {
      //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
      if (to.params.id !== from.params.id) {
        userData.value = await fetchUser(to.params.id)
      }
    })
  },
}

匹配任意路径的路由

方法:使用正则

const routes = [
  // 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
  // 将匹配以 `/user-` 开头的所有内容,并将其放在 `$route.params.afterUser` 下
  { path: '/user-:afterUser(.*)', component: UserGeneric },
]

vue的watch

作用:根据状态变化执行一些effect。
语法:watch(监听对象,回调函数)

// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.includes('?')) {
    loading.value = true
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    } finally {
      loading.value = false
    }
  }
})

监听对象可以是一个ref或者computed,一个响应式对象,一个getter函数或者多个数据源组成的数组,但不能直接监听响应式对象的属性值

const obj = reactive({ count: 0 })
// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
  console.log(`Count is: ${count}`)
})

此时可以用一个getter函数作为数据源

watch(
  () => obj.count,
  (count) => {
    console.log(`Count is: ${count}`)
  }
)

普通监听器只监听响应式对象或引用本身的变化,而不深入监听对象内部的属性变化。 比如下面这段代码,watc监听的是obj本身,只有当obj的引用被替换为另外一个对象,才会触发回调函数。所以这里并不会执行console

//不会触发回调函数
const obj = ref({ count: 0 })
watch(obj, (newValue, oldValue) => {
  console.log('普通监听器:', newValue, oldValue)
})
const handleClick=()=>{
 obj.value.count++
}
//会触发回调函数
const  obj = ref({ count: 0 })

watch(obj, (newValue, oldValue) => {
  console.log('普通监听器:', newValue, oldValue)
})
const handleClick=()=>{
  obj.value={count:obj.value.count+1}
}

可以通过deep:true将第一段中的watch强制转换为转成深层侦听器。

watch默认懒执行,只有当数据源变化才会执行回调函数,但如果希望创建的时候就执行一次可以使用immediate:true来强制执行

vue的watchEffect

作用:自动跟踪回调函数中的响应式依赖

//watch实现
watch(
  todoId,
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    )
    data.value = await response.json()
  },
  { immediate: true }
)
//watchEffect简化版
watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

适用场景:需要同时监听多个数据源,这个函数可以让开发者不需要手动维护依赖

动态加载组件Component

component组件不是真正的组件,和<slot>以及<template>一样有类似组件的特性,可以使用模板语法,但会在编译期间被编译掉

<component is="xxx"/>
interface DynamicComponentProps {
  is: string | Component
}

v-slot指令

插槽

组件内部的slot元素是一个插槽出口,表明父元素的插槽内容渲染在哪里,即子组件只负责渲染子组件外层的标签和样式,而插槽的内容则由父组件提供。

image.png

<button class="fancy-btn">
  <slot></slot> <!-- 插槽出口 -->
</button>

具名插槽

在某个组件有多个插槽出口,可以通过设置name属性给各个插槽分配唯一的id来确定每个地方渲染什么,如果没有设置则默认name为default

  <header>
    <slot name="header"></slot>
  </header>

在调用该组件的时候,用v-slot:插槽名称或者缩写#插槽名称指令传递插槽名称,并且只能使用<template>元素

<BaseLayout>
  <template v-slot:header>
    <!-- header 插槽的内容放这里 -->
  </template>
   <template #default>
    <!-- header 插槽的内容放这里 -->
  </template>
</BaseLayout>

条件插槽

只有在父组件传入指定插槽内容的时候才渲染该插槽,在子组件内用$slotsv-if结合实现
场景:比如期望对插槽内容统一添加某些样式,但如果没有传入该插槽内容则不希望渲染这些样式

<div v-if="$slots.header" class="card-header">
  <slot name="header" />
</div>

作用域插槽

目前缺陷:插槽内容无法访问子组件的数据
场景:插槽的内容需要同时使用父组件域内和子组件域内的数据
解决方法:让子组件在渲染时将一部分数据提供给插槽。即向一个插槽的出口上<slot>传递 attributes:,父组件v-slot:插槽名称="headerProps"

image.png

//App.vue
<template>
  <FancyList api-url="url" :per-page="10">
    <template #item="{item}">
      <div class="item">
        <p>{{ item.body }}</p>
        <p class="meta">by {{ item.username }} | {{ item.likes }} likes</p>
      </div>
    </template>
  </FancyList>
</template>
<!-- FancyList.vue -->
<template>
  <ul>
   <template v-if="!items.length">
      <li>Loading...</li>
    </template>
    <template v-else>
      <li v-for="(item, index) in items" :key="index">
        <slot name="item" :item="item"/>
      </li>
    </template>
  </ul>
</template>

【注意】插槽 prop 是一个对象,而不是单个值。因此要解构这个对象以便使用传递的值