Vue3+Vant+Vue-Router4+Pinia 知识点笔记

797 阅读7分钟

1.Vue3篇

1.17 父组件传递给子组件的属性虽然不能修改,但是传递的属性值是引用值的话,可以修改引用值的某个属性

1.16 vue3组件属性值为数组时,赋初始值的方式:

  withDefaults(defineProps<{ data: Item[] }>(), {
    data: () => {
      return [] as Item[];
    },
  });

1.15 没拿到渲染数据前,模板显示{{}}的处理方法

[v-cloak] {
  display: none;
}

<div v-cloak>
  {{ message }}
</div>

1.14 defineProps,defineEmit,defineExpose这三个api,使用时都不用导入到vue文件

1.13 vue报这种错误 Failed to execute ‘insertBefore’ on ‘Node’ ,解决方法是给v-if 组件加上key

1.12 vue有时候报错信息是 xx资源加载不到,查看的时候,发现存在,实际这种错误是别的代码片段引起的,而非提示的代码文件,另外vue模板报渲染错误,一般大概率是取了属性不存在的值

1.11 Vue3 plugin写法, 要定义一个install方法,另外install方法有一个传入参数是应用全局对象。

loadSDK.ts

import { App, Plugin } from 'vue';
import { getEnv, WECHAT_ENV } from '@/utils';

const qyWxSDKUrl = 'https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.0.0.js';
const wxSDK = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js';

export const loadSDK: Plugin = {
  install(app: App, options: {}) {
    getEnv() === WECHAT_ENV.qyWechat ? loadJS(qyWxSDKUrl) : loadJS(wxSDK);
  },
};

function loadJS(jsUrl: string) {
  const script = document.createElement('script');
  // @ts-ignore
  script.crossorigin = 'anonymous';
  script.src = jsUrl;
  script.onerror = () => {
    console.log('qy-wx-sdk:loadError');
  };

  script.onload = async () => {
    console.log(import.meta.en);
    if (import.meta.env.DEV) 
      await apis.mockLogin({ userid: 'liudongjie' });
    }
  };
  document.body.appendChild(script);
}
import { createApp } from 'vue';
import App from '@/App.vue';
import { loadSDK } from './loadSDK';

const app = createApp(App);
app.use(loadSDK);

1.10 watch和watchEffect应用场景区别,watch监听单个变量, watchEffect监听多个变量,默认会执行一次。

// watch监听对象某个属性值的写法
watch(()=>props.A,(newVal,oldVal)=>{ console.log(newVal,oldVal})

// watchEffect监听多个值的写法
watchEffect(()=>{
   console.log(props.A,props.B);
})

1.9 Vue3中如何优雅地在控制台打印Proxy对象,在Chrome调试控制台,设置中--勾选启用自定义格式设置工具

1.8 Vue 设置style,template,script标签顶层缩进两个空格的设置方法,在eslint的配置文件中定义规则

{
 "prettier/prettier": ["error", { vueIndentScriptAndStyle: true }],
}

1.7 Vue3全局变量或方法类型定义

// symbols.ts
import { InjectionKey } from 'vue';
interface Product {
  name: string;
  price: number;
}
export const ProductKey: InjectionKey<Product> = Symbol('Product');

定义注入常量

import { provide } from 'vue';
import { ProductKey } from '@/symbols';
provide(ProductKey, {
  name: 'Amazing T-Shirt',
  price: 100,
});

获取注入常量

import { inject } from 'vue';
import { ProductKey } from '@/symbols';
const product = inject(ProductKey); // typed as Product or undefined
console.log(product);

1.5 vue3中,不要在reactive中将属性值定义成对象,否则在视图部分,引用的时候会写得很长。

1.4 在template部分写style样式,保存代码时,代码自动格式化在style引号中结尾加分号引起eslint告警的解决方案,用模板字符串。

1.3 子组件接收父组件的属性,3.2.25以后,属性可以解构,不会丧失响应式,t另外oRefs解构的变量,不能赋默认值。

1.2 @click事件回调,如果函数存在参数的话,书写时要用箭头函数包起来,否则第一个参数是dom事件参数event

1.1 import导入内容必须放在const变量定义之前,否则会提示找不到模块名或对应的模块声明

2.Vant篇

2.8 van-popup组件样式初始化


<Popup :show="visible" class="dialog" position="bottom"></Popup>


<style lang="less">
.van-popup.van-popup--bottom.dialog {
   display: flex;
   align-items: center;
   justify-content: center;
   height: 100vh;
   background-color: transparent;
}
</style>

antd-mobile的Modal组件弹窗样式的初始化设置

<Modal
 visible={this.state.visible}
 className="modal-custom"
 transparent
 maskClosable={false}
 title=""
 footer={[]}
>
</Modal>
       
<style lang="less">
.modal-custom.am-modal.am-modal-transparent {
 width: 6rem;
 background: #fff;
 border-radius: 0.16rem;
 padding: 0.4rem 0.5rem;
 .am-modal-content .am-modal-body {
   margin: 0;
   padding: 0;
 }
} 
</style>

2.7 在Dialog.Confirm组件之前使用Toast组件,会造成Dialog.Confirm被遮住, Dialog.Confirm组件上的按钮无法点击。

2.6 Vant Picker选项是对象数组的处理方法,值要用value-key定义,这样就能获取到选择项完整的对象属性,而不仅是选择的展示名称和value.

     <van-picker
          title="请选择所在地区"
          show-toolbar
          :columns="columns"
          value-key="name"
          @confirm="onConfirm"
          @cancel="show = false"
     />

2.5 Vant  父组件修改子组件样式的方法

加上scoped声明之后,父组件的样式就不会在子组件中生效,用:deep(需要修改stylelint的校验规则,stylelint才对:deep这种语法不报错),否则深层的样式无法修改。要给Vant组件加一个自定义样式名,否则会引起全局命名污染。

<style lang="less" scoped>
// 新增一个的样式名,如my-popup
:deep(.my-popup){
  // 定义样式属性,覆盖van-popup样式值
}
</style>

2.4 Vant iPhone手机底部和顶部安全设置

  <!-- 开启顶部安全区适配 -->
  <van-nav-bar safe-area-inset-top />

  <!-- 开启底部安全区适配 -->
  <van-number-keyboard safe-area-inset-bottom />

2.3 van-field表单项类型定义

// 表单项ref的类型定义是
import type {FieldInstance} from 'vant';

// 校验器书写规则
:rules=[{validator:(value, rule) => boolean | string | Promise}],

2.2 Vant的PullRefresh组件是顶部下拉刷新,没有底部上拉刷新,需要自己封装一个

2.1  <van-dialog /> 有个大bug,弹窗关不了。

3.Vue-Router篇

3.7 router的跳转的路由必须有name属性,否则跳转会报错, 另外前进后退的api,与react-router名称不一样,前进是 router.forward(),后退是router.back()

3.6  页面动态路由缓存设置和keep-alive功能

<template>
  <RouterView v-if="initReady" v-slot="{ Component }">

     <template  v-if="Component">
        <!-- 通过name匹配keepAlive -->
        <KeepAlive :include="keepAliveComponents">
          <component :is="Component" :key="route.name" />
        </KeepAlive>
      </template>
  </RouterView>
</template>
<script setup>
import { computed } from 'vue';
import { useKeepAliveStore } from '@shared/store';
const keepAliveStore = useKeepAliveStore();
const keepAliveComponents = computed(() => keepAliveStore.list);
</script>
import { App } from 'vue';
import type { Router } from 'vue-router';
import { createPinia } from 'pinia';
import { useKeepAliveStore } from '@shared/store';

const redirect = location.href;

export function createTaskAuthRouterGuard(router: Router, app: App) {
  app.use(createPinia());
  const keepAliveStore = useKeepAliveStore();


  router.beforeEach((to, from, next) => {

    // 进入任务下发页--缓存
    if (to.name === 'task/taskPublish') {
      keepAliveStore.add('task/taskPublish');
    }

    // 离开任务下发页,前往的不是任务确认页面,清空缓存
    if (from.name === 'task/taskPublish' && to.name !== 'task/taskStaffList') {
      keepAliveStore.remove('task/taskPublish');
    }


    // 未进行任务权限认证
    return next();
    
  });
}
import { defineStore } from 'pinia';
interface KeepAliveState {
  /** 需要缓存的路由组件名称列表 */
  list: string[];
}

export const useKeepAliveStore = defineStore({
  id: 'keep-alive',
  state: (): KeepAliveState => ({
    list: [],
  }),
  actions: {
    add(name: string | string[]) {
      if (typeof name === 'string') {
        !this.list.includes(name) && this.list.push(name);
      } else {
        name.map((v) => {
          v && !this.list.includes(v) && this.list.push(v);
        });
      }
    },
    remove(name: string | string[]) {
      if (typeof name === 'string') {
        this.list = this.list.filter((v) => {
          return v !== name;
        });
      } else {
        this.list = this.list.filter((v) => {
          return !name.includes(v);
        });
      }
    },
    clear() {
      this.list = [];
    },
  },
});

3.5 Vue-Router4 + History模式 Nginx配置

root和alias的区别,参见此文

location /admin {    
    index  index.html;
    alias   D:/admin/dist;
    try_files $uri $uri/ /index.html;
} 

3.4 Vue-Router+History 模式  Apache 服务器配置

RewriteEngine On
# 如果请求的文件或目录存在,则不重写,直接访问
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]


# 开发环境下文件找不到的跳转规则
RewriteCond %{REQUEST_URI}  ^/h5_dev/   
RewriteRule ^ /h5_dev/index.html [L]

# 测试环境下文件找不到的跳转规则
RewriteCond %{REQUEST_URI}  ^/h5_test4/   
RewriteRule ^ /h5_test4/index.html [L]


# 生产环境下文件找不到的跳转规则
# 如果请求基础路径为h5的资源不存在, 跳转到/h5/index.html
RewriteCond %{REQUEST_URI}  ^/h5/   
RewriteRule ^ /h5/index.html [L]

# 如果输入的网址是服务器上的一个目录,此目录下没有DirectoryIndex指令中配置的索引文件
# 那么服务器会返回一个格式化后的目录列表,并列出该目录下的所有文件
Options -Indexes

3.3 router.beforeEach中的next参数,如果没传递路由参数,就是执行跳转,如果传递了路由,仅是改变路由,但不跳转。

router.beforeEach((to, from, next) => {
  
  // 仅仅改变跳转目的路由,但不跳转
   next({
        path: '/Login',
        query: {
          redirect: to.fullPath
        }
    });

  // 这种情况下才会跳转
  next();
})

参见 Vue-router beforeEach登录鉴权  

3.2  在url地址栏隐藏传递参数,用params传参写法

router.push({
    name:'taskUrge',
    params:{
      test:1,
    }
})

3.1 路由报错,一般不是路由配置出了问题,是路由中配置的页面出了问题。

4.  使用Pinia时大概率会遇到的报错 

Uncaught Error: [🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?
    const pinia = createPinia()
    app.use(pinia)
This will fail in production.

原因分析:调用store之前,Pinia还没有初始化。

场景1:在vue-router的beforeEach中调用store,不好的做法是在beforeEach外调用,好的做法是在beforeEach中调用

不好的做法

import { createRouter } from 'vue-router'
const router = createRouter({
  // ...
})

// 依赖导入顺序,可能Pinia还没安装好
const store = useStore()

router.beforeEach((to, from, next) => {
  // we wanted to use the store here
  if (store.isLoggedIn) next()
  else next('/login')
})

好的做法:

router.beforeEach((to) => {
  // 路由开始导航时,Pinia肯定已经安装好了
  const store = useStore()

  if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})

场景2:在store中调用另一个store仓库。

不好的做法: 在defineStore之前调用store, 被调用的store的实例可能还未生成。

import { defineStore } from 'pinia'
import { useUserStore } from '~/store/user'

const userStore = useUserStore()

export const useThemeStore = defineStore('theme', {
  state: () => ({
    name: 'theme-name'
  }),
  actions: {
    setTheme() {
      this.name += userStore.name
    }
  }
})

好的做法--等defineStore执行完之后,在actions中调用别的store

import { defineStore } from 'pinia'
import { useUserStore } from '~/store/user'

export const useThemeStore = defineStore('theme', {
  state: () => ({
    name: 'theme-name'
  }),
  actions: {
    const userStore = useUserStore()

    setTheme() {
      this.name += userStore.name
    }
  }
})