你不知道的vue3!

一、vue3语法

01.生命周期


步骤1:vue2与vue3的生命周期对比如下

步骤2:组合式api中,生命周期钩子需要导入函数,且可以多次定义

<script setup>
    onMounted(() => {
        console.log('第一次调用钩子')
    })

    onMounted(() => {
        console.log('第二次调用钩子')
    })
</script>

02.setup钩子


写法1:原生写法了解

<script>
    export default {
        setup() {
            // 数据
            const message = ref('this is message')
            // 处理函数
            const logMessage = () => {
                console.log(message.value)
            }
            // 必须要return后才能使用
            return {
                message, logMessage
            }
        }
    }
</script>

写法2:setup语法糖掌握

<script setup>
    // 数据
    const message = ref('this is message')
    // 处理函数
    const logMessage = () => {
        console.log(message.value)
    }
</script>

03.data


方法1:reactive函数(只能用于声明复杂数据类型)

// (1)按需导入reactive函数。
import { reactive } from "vue"

// (2)使用reactive函数,包装复杂数据类型。
const state = reactive({
    count: 100
})

// (3)Js中通过【state.count】访问,HTML中通过【state.count】访问。
const setCount = ()=> {
    state.count += 1 
}

方法2:ref函数(可以是任意数据类型,推荐!)

// (1)按需导入
import { ref } from 'vue'

// (2)用ref函数包装数据。(底层其实还是reactive)
const count = ref(0)

// (3)在Js中需要【count.value】才能获取值。 在HTML中可以直接用{{ count }}。
console.log(count.value);

// (4)修改数据,需要通过【count.value】
const setCount = () => {
    count.value++
}

04.computed


写法1:只读写法

// (1) 按需导入computed函数
import { ref, computed } from "vue";

// (2)computed是一个函数
const count = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])
const computed_count = computed(()=> count.value.filter(item => item > 2) })

写法2:getter、setter写法

// (1) 按需导入computed函数
import { ref, computed } from "vue";

// (2) 跟vue2有点像,提供setter、getter写法,只不过需要用computed函数包一下。
const computed_count = computed({
     get: () => {
       return count.value + 1;
     },
     set: (val) => {
       count.value = val - 1;
     }
})

05.watch


可以直接监听响应式对象,无需.value访问。

情况1:监听一个简单数据类型。

//  按需导入watch函数
import { ref, watch } from "vue";

//  测试数据
const count = ref(1)

//  (1) watch函数
//   参数1:响应式数据对象(无需.value)
//   参数2:回调函数
watch(count, (newValue, oldValue) => {
 	...
})

情况2:监听多个简单数据类型。

//  按需导入watch函数
import { ref, watch } from "vue";
//  测试数据
const count = ref(1);
const name = ref('思密达')

//  (1) watch函数
//   参数1:响应式对象组成的数组。
//   参数2:函数。
watch([count, name], ([newCount, newName],[oldCount, oldName]) => {
 	...
})

情况3:监听整个对象。

import { ref, watch } from "vue";

//  测试数据
const obj = ref({  
   age: 18,
   name: '张益达'
});

//  (1) watch函数
//   参数1:要监控的对象本身;
//   参数2:函调函数;
//   参数3:配置对象。
watch(obj, (newValue, oldValue) => {
 	console.log(newValue, oldValue);
}, {
 immediate: true,  // 表示页面一打开就执行一次watch
 deep: true 	   // 深度监视(默认是浅层监视,只能监视简单数据类型)
})

情况4:监听对象中的某个属性。

import { ref, watch } from "vue";

// (1) 定义响应式数据obj
const obj = ref({  
    age: 18,
    name: '张益达',
})

//  (1) watch函数
//   参数1:回调函数;
//   参数2:函数;
watch(
    () => obj.value.age,  // 固定写法,以回调函数的形式。
    (newValue, oldValue) => { ... }
)

06.获取DOM实例


使用ref对DOM节点进行标识,然后通过ref(null)函数来获取对应的DOM节点。

功能1:获取DOM节点实例

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

    // 调用ref函数,根据ref标识得到ref对象
    const h1Ref = ref(null)
</script>

<template>
	<h1 ref='h1Ref'>我是h1标签</h1>
</template>

功能2:获取组件实例使用演示如下:

A组件

<script setup>
//  2.定义响应式数据testMessage
const testMessage = ref('我真的很帅')

//  3. 将testMessage暴露出去(核心!)
defineExpose({
    testMessage
})
</script>

<template>
	<h1 ref='h1Ref'>我是h1标签</h1>
</template>

B组件

<script setup>
import A from '@/components/A.vue'

//  1.如果B组件想调用A组件内提供的testMessage数据,
//    需要在A组件中通过defineExpose({})暴露后才能被访问。
const ARef = ref(null)
console.log(ARef.testMessage.value)
</script>

<template>
	<A ref='ARef'>我是h1标签</A>
</template>

07.自定义指令


步骤1:了解钩子函数和组件的生命周期名称一致了。

步骤2:去main.js文件中注册指令

const app = createApp(App)

app.directive('指令名称', {
	// 钩子函数
})

08.给组件起名称


写法1:现在的写法vue3.3以上的版本可用!

<script setup>
    defineOptions({
        name: '组件名称'
    })
</script>

写法2:过去的写法

<script>
    export default {
        name: '组件名称'
    }
</script>

<script setup>
   
</script>

09.延迟执行JS


vue3的代码一般在setup中书写,没有this可用。

<script setup>
    // 导入nextTick函数
    import { nextTick, ref } from 'vue'
    // 准备数据
    let flag = ref(true)
    // Mounted生命周期
    onMounted(() => {
        // 延迟到DOM渲染后,才执行该代码
        nextTick(() => {
            flag.vlaue = false
        })
    })
</script>

10.插槽


11.指令修饰符


第一类:v-model修饰符

(1).lazy(输入框聚焦、失焦时触发)

(2).number(尝试将输入框的value值转为number类型)

(3).trim(去除输入框的value值首尾空格)

第二类:事件修饰符

(1).stop(阻止事件冒泡)

(2).prevent(防止触发默认事件)

(3).capture(将默认触发方式改为捕获触发)

(4).self(只有当事件发生在目标元素自身时才会触发

(5).once(事件只触发一次)

第三类:键盘修饰符

(1).enter(回车键触发)

(2).tab(tab键触发)

(3).delete(通过该元素需要配合v-if使用,是指DOM节点显示、隐藏时触发

(4).esc(esc键触发)

 二、项目起步

01.搭建vue3环境

vue-cli是基于webpack构建vue2、3项目。 create-vue是基于vite构建vue3项目。

1.安装node环境(必看

这里使用nvm的方式安装node.js,要求:安装前必须卸载电脑上原有的node.js

步骤1:安装nvm

网盘地址:'https://pan.baidu.com/s/1uoxlk8CVNHV2KTCwIGbQMQ?pwd=yi5m'
提取码:'yi5m'

步骤2:配置nvm镜像

// 在命令行依次执行下面的两条命令

'nvm npm_mirror https://npmmirror.com/mirrors/npm/'
'nvm node_mirror https://npmmirror.com/mirrors/node/'

步骤3:nvm命令

// 查看当前安装和使用的 node 版本
'nvm list'

// 安装某个 node 版本
'nvm install v版本号'

// 切换 node 版本
'nvm use v版本号'

// 设置默认版本
'nvm alias v12.22.12'

步骤4:配置npm镜像

// 将npm源设置为淘宝镜像
'npm config set registry https://registry.npmmirror.com'

// 获取当前npm源
'npm config get registry'

2.创建vue3项目必看


步骤1:确保安装了node.js

// 查看node.js的版本
'node --version'

步骤2:npm命令创建vue3项目

// 1.初始化vue3项目结构
'npm init vue@latest'

// 2.紧接着输入项目的名称
'项目名称'

// 3.最后根据提示执行命令
 'npm install'

步骤3:启动vue3项目

'npm run dev'   

02.模块配置

 1.路由模块(router)


步骤1:安装路由模块

'npm i vue-router@4'

步骤2:创建路由实例,并导出

import { createRouter, createWebHistory } from 'vue-router'

//  (1) 创建路由实例对象
//      hash模式:createWebHashHistory(基地址)。	
//      history模式:createWebHistory(基地址)。
const router = createRouter({
    // 【import.meta.env.BASE_URL】是vite.config.js中的环境变量。
     history: createWebHistory(import.meta.env.BASE_URL),  
     routes: [
        {
            path: '/',
            redirect: '/home',
        },
        {
            path: '/home',
            component: () => import('@/views/home.vue')
        },
        {
            path: '/about',
            component: () => import('@/views/about.vue')
        }
      ]
})
    
//  (2) 导出路由实例
export default router

步骤3:在main.js中,导入路由实例对象,并挂载到vue

//  导入路由模块
import router from '@/router/index.js' 

//  注册
app.use(router)

步骤4:获取路由对象

在vue3中是通过内置方法来获取路由对象的,因为vue3不存在this指针。     

<script setup>
    import { useRote, useRouter } from 'vue-router'

    // 1.获取全部路由,相对于this.$router
    const router = useRouter()
    router.push('/path')

    // 2.获取本次路由,相对于this.$route
    const route = useRoute()
</script>		

步骤5:配置路由守卫

本质:在路由跳转前,可以进行路由拦截。

方法1:全局路由守卫默认全部拦截,不过可以用数组来指定哪些不需要拦截的。

//  1. 配置前置路由守卫
/*  (1)to:到哪里去,完整路由信息对象(路径,参数)      获取路径:to.path
	(2)from:从哪里来,完整路由信息对象(路径,参数)
	(3)next():默认直接放行,放行到to要去的路径。
		 next(路径):进行拦截,拦截到next里面配置的路径。
*/
const authUrls = ['/pay', '/myorder']
router.beforeEach((to, from, next) => {
 if (!authUrls.includes(to.path)) {
   next()
   return
 }
 //  如果是权限页面,需要判断token,如果存在就放行,如果不存在就跳到登录页面
 const token = store.getters.token
 	token ? next() : next('/login')
 })
    
//  2. 后置路由守卫    
router.afterEach(() => {
  nProgress.done()
})    

方法2:独享路由守卫给单个路由配置路由守卫

2.pinia模块


//  Pinia的优势

1. 去掉了mutaitons,只需写在actions即可。
   去掉了modules,每一个store都是一个独立的模块。 
   
2. 提供组合式API写法:普通数据就是state,普通函数就是actions,computed就是getters,确实有点牛逼。

3. 可以配合TypeScript,提供可靠的类型推荐。

4. pinia可以直接修改state,可以不通过mutations来修改。

步骤1:安装pinia

'npm i pinia'

步骤2:创建pinia实例(在store/index.js文件)

//  导入createPinia函数,创建出pinia实例
import { createPinia } from 'pinia';
//  导入pinia持久化插件
import persist from 'pinia-plugin-persistedstate'

const pinia = createPinia() 
pinia.use(persist)
export default pinia

步骤3:将pinia实例在main.js中注册

import App from './App.vue'
import { createApp } from 'vue'
//  导入pinia实例
import pinia from '@/store/index.js'

const app = createApp(App)
//  把pinia实例挂载到vue
app.use(pinia)

步骤4:一个文件就是一个pinia模块不区分核心模块和普通模块,无需单独配置modules

import { defineStore } from 'pinia';
import persist from 'pinia-plugin-persistedstate' // pinia持久化插件。

//  1. 组合式API写法。  优点:普通数据就是state,普通函数就是actions,computed就是getters,确实有点牛逼。
export const useUserStore = defineStore('user', () => {
    // (1) 维护数据count
    let count = ref(0)
    const setCount = (value) => count.value += value
    const getCount = () => count.value
    const doubleCount = computed(() => count.value * 2)

    // (2) 维护数据token
    const token = ref('123abc456bdc')
    const setToken = (value) => token.value += value
    const getToken = () => token.value

    // (3) 记得最后要导出这些state、actions、getters。
    return { 
        count, doubleCount, setCount, getCount,
        token, setToken, getToken 
    }
},
{
    // 2. 开启pinia持久化插件
    // 写法1:使用默认配置。
	persist: true	
    
    // 写法2:自定义配置。
    persist: {
    	// 默认是localStorage
    	storage: sessionStorage,	
    	// 默认持久化全部,可指定好哪些state、getters、actions要被持久化
    	paths: ['count', 'token', 'setToken', 'getCount'],	
         // 默认是以仓库的唯一标识'user'作为键名,值就是全部被持久化的state、getters、actions。                                
         key: 'hm-user'	
    }
})

步骤5:pinia持久化插件安装

安装插件:npm i pinia-plugin-persistedstate --save-dev

步骤6:使用pinia

<script setup>
//  导入store的counter模块
import { useUserStore } from '@/store/counter'

//  (1) 不要对state、getters进行解构,会丢失响应式。
const userStore = useUserStore()	
//  (2) 如果真的需要解构,需要特殊处理后才能恢复响应式:需要使用storeToRefs()函数包一下。 
const { count } = storeToRefs(userStore) 

//  (3) 如果解构store中的函数,则不用任何处理,因为函数不需要响应式。
const { addCount, minusCount } = userStore	  
</script> 
    
<template>
 <div>
   <div>我是爷爷</div>
   <span>{{ userStore.count }} - {{ userStore.msg }} - {{ userStore.double }}</span>
   <button @click="userStore.addCount">+</button>
 </div>
</template>    

3.组件库(eLement-Plus)

官方文档:https://element-plus.org/zh-CN/


组件库安装:npm i element-plus --save

步骤1:安装组件库

'npm i element-plus --save'

步骤2:导入组件库到项目中

方法1:全部导入(便捷,但不节省空间)

import { createApp } from 'vue'
import App from './App.vue'

// 导入Element-Plus的组件和样式
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App)

app.use(ElementPlus).mount('#app')

方法2:按需导入(每次都要手动import,但是节约空间)

步骤2.1:首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件

'npm install -D unplugin-vue-components unplugin-auto-import'

步骤2.2:去vite.config.js中配置刚刚安装的两个插件

步骤2.3:可能并没有完全搞定,可能还需要在main.js导入组件库的css样式。

import 'element-plus/dist/index.css'

4.axios模块


 步骤1:发送get、post请求

GET请求的数据放params,POST请求的数据放data。

写法1:完整写法

axios({
    method: 'get',
    url: 'https://www.baidu.com',
    // POST请求的数据放这
    data: {
          loginId: 'abc',
          loginPwd: '123123',
          nickname: '棒棒鸡'
    },
    // GET请求的数据放这
    params: {
         loginId: 'abc',
         loginPwd: '123123',
    }
    headers: {
        ...
    },
    timeout: 1000,
    // 一般都是json类型数据
    responseType: 'json'
})

写法2:简洁写法

//  1.发送get请求
await axios.get("https://www.baidu.com", {
  params: { 
    loginId: "abc"
  },
})

//  2.发送post请求
axios.post("https://www.biadu.com", {
  loginId: 'abc',
  loginPwd: '123123',
  nickname: '棒棒鸡'
})

步骤2:配置请求、响应拦截器

需要用到axios官方提供的 axios.create() 方法来创建实例对象,然后配置请求响应拦截器。

import axios from 'axios'

//  1.创建axios实例
const instance = axios.create({
    baseURL: '',
    timeout: 10000
})
 
 
//  2.添加请求拦截器
instance.interceptors.request.use(function (config) {
  const token = localStorage.getItem('token');
  if(token){
    config.headers.authorization = token;
  }
  return config; // 返回处理后的配置
})
 
//  3.添加响应拦截器
instance.interceptors.response.use(function (resp) {
  return resp.data; 
}, function (error) {
  alert(error.message); // 弹出错误消息
})
 

// 4.统一get、post的传参方式
const http = {
  get(url:any, params:any, config:any) {
    return new Promise((resolve, reject) => {
      instance.get(url, {params, ...config}).then((res) => {
        resolve(res)
      }).catch((error) => {
        reject(error)
      })
    })
  },

  post(url:any, params:any, config:any) {
    return new Promise((resolve, reject) => {
      instance.post(url, params, config).then((res) => {
        resolve(res)
      }).catch((error) => {
        reject(error)
      })
    })
  }
}

// 导出
export default http

步骤3:axios实现原理(可选)

function MyAxios (config) {
   return new Promise((resolve, reject) => {
	    // (1)获取原生XMLHttpRequest,发起ajax请求。
       let xhr = new XMLHttpRequest();

       // (2)判断有没有携带params请求参数,拼接到url后面去。
       if (config.params) {
           //(传入的是params对象,我们需要转成uname='张三'&age=18的形式,调用URLSearchParams对象.toString()方法)
           const queryString = new URLSearchParams(config.params).toString()
           config.url += `?${queryString}`
       }

       // (3)判断是GET还是POST请求,如果不传默认使用GET请求。
       xhr.open(config.method || 'GET', config.url);

       // (4)绑定loadend事件,根据响应状态码,来决定是执行reject()函数,还是resolve()函数。
       xhr.addEventListener('loadend', ()=> {
           if(xhr.status>=200 && xhr.status<300) { 
               // 成功,回调resolve()方法
               resolve(JSON.parse(xhr.response)) 
           } else {  
               // 失败,回调reject()方法
               reject(new Error(xhr.response))
           }
       })

       // (5)发起请求之前,先判断在请求体里面有没有数据,如果有就必须告知请求体数据的类型。
       if (config.data) {
           xhr.setRequestHeader('content-Type', "application/json");
           xhr.send(JSON.stringify(data));
       } else {
           xhr.send();
       }
}        

************************************************************

// 使用时和axios一样使用即可。                       
   myAxios({
       method: 'POST'
       url: "http://localhost:8080/",
       params: {
           pname: "辽宁省",
           cname: "大连市"
       }
   }).then((resp) => {

   }).catch((error) => {

   });        

03.开发的问题解决方案

 1.vite中配置源码路径@


 问题描述:一般来说创建的vue项目是没有配置@为源码路径的,需要我们手动配置

步骤:在vite.config.js文件中,配置alias选项 

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
const path = require('path');

export default defineConfig({
  plugins: [vue()],
  resolve: {
    // 配置路径别名
    alias: {
      '@': path.resolve(__dirname, './src'),
    }
  }
})

2.安装自动引入插件

问题描述: 每个.vue文件中,都需要手动导入依赖,例如:import { ref, onMounted } from 'vue' 这种每个文件都需要做的事情,能不能有一种方法帮我们自动引入

步骤1:安装自动引入插件

`npm i unplugin-auto-import -D`

步骤2:去vite.config.js中配置,在plugins中配置AutoImport

// (1)导入插件
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
    plugins: [
        vue(),

        // (2) 注册插件
        AutoImport({
            imports: ['vue', 'vue-router']
        })
    ]
})

3.解决require is not defined

需要解决的问题: 在vite环境下,就可能出现require is not defined的报错

步骤1:安装插件

yarn add -D vite-plugin-require-transform
or 
npm i vite-plugin-require-transform --save-dev

步骤2:去vite.config.js中配置,配置plugins

import { defineConfig } from 'vite' 
import requireTransform from 'vite-plugin-require-transform'; 

plugins: [
    requireTransform({
      fileRegex: /.js$|.vue$/
    })
 ],

如果还是解决不了,就去vite.config.js中修改如下配置


// 不用require('path')引入,而是使用import导入
import path from 'path'

4.解决跨域问题


跨域:跨域是由于浏览器的同源策略导致的,其实后端是成功的响应了数据,但是浏览器为了安全策略,没有把数据交给前端。同源策略是指:协议+主机名+端口号 三者一样才满足同源策略,否则为异源。

代理解决跨域原理:前端请求到node代理的服务器,通过node代理服务器来向后端发起请求,就可以成功的发起请求并获取数据。因为这是服务器与服务器之间的通信,不存在同源策略的影响,同源策略只在浏览器中起作用。

总结:前端只能解决开发环境下的跨域问题,无法真正的解决跨域,还得靠后端解决。

方法1:配置代理解决跨域仅开发阶段

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 导入自动import插件
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  // 插件
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue', 'vue-router']
    })
  ],
  // 代理
  server: {
    proxy: {
      '/api': 'http://testapi.xuexiluxian.cn'
    }
  },
  // 配置@符号
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

 方法2:解决生产阶段跨域问题需要后端解决好跨域问题

根据环境不同,从而请求不同的基地址。

(1)获取环境变量:process.env.VUE_APP_变量名

(2)获取环境:process.env.NODE_ENV

const instance = axios.create({
    baseURL: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_URL : '',
    timeout: 2000
})

5.配置环境变量

//  1. 在哪配置环境变量
在项目根目录下,配置两个文件(这两个文件与src目录同级):
(1) .env.development // 开发环境会被运行
(2) .env.production  // 生产环境会被运行


//  2.怎么定义环境变量?
必须以'VUE_APP_'开头,来声明环境变量。 
例如:声明一个baseURL: 'VUE_APP_BASE_URL = https://www.baidu.com'


//  3.怎么读取环境变量?
(1) 访问环境变量:'process.env.VUE_APP_变量名'。
(2) 判断是否开发环境:'process.env.NODE_ENV'。// production、development

04.业务功能

1.登录功能


1.账号密码登录

2.短信登录(滑块验证)
// 步骤如下:

1.用户输入手机号,点击发送验证码
(1)前端要进行正则校验手机号
(2)如果合法就弹出滑块组件。

2.展示滑块组件(插件):插件本身做的事
(1)发请求:
     前端给后端传递:验证码 + 随机数uuid + 当前时间戳,
     后端给前端响应:背景图 + 小滑块 + 加密的校验码(根据:验证码 + 随机数uuid + 当前时间戳)
(2)前端拿到数据后,要干的活:
     把背景图、小滑块渲染到页面,
     把后端给的'加密校验码'存储起来(存储到一个变量或者存储到本地都行)
(3)用户交互行为:
     鼠标按下:没有跟后端交互。
     鼠标滑动:没有跟后端交互。
     鼠标松开:与后端交互,看(4)
(4)发请求:
     前端给后端传递:小滑块的位置 + 之前后端给的加密校验码
     后端给前端响应:提示成功或者失败 + 第二次加密的校验码(根据:滑块位置 + 第一次加密校验码)
         
3.前端调用短信验证码API
(1)发请求:
    前端给后端传递:手机号 + 之前后端第二次加密的加密校验码
    后端给前端响应:成功就发短信,失败就提示错误就行了    

3.微信扫码登录

步骤1:需要哪些参数,可以找微信开放平台。

微信开放平台:微信开放平台

步骤2:思路分析

(1)后端已经把所需的参数提供给前端了。(参数看下图)

(2)前端调API接口,拿到全部参数,并动态的创建一个script标签来加载微信二维码,
         通过new WxLogin({...}),然后把后端给的参数一一传进去,就会自动生成一个二维码

(3)注意一下回调地址的填写:前端要写一个回调地址,后端也要写一个回调地址,
     微信开放平台也有一个回调地址,必须三者同时一致,登录成功后才能进行页面重定向。

 步骤3:代码实现

// 代码实现:
<script setup lang="ts">
import { onMounted } from 'vue'
import { getLoginParamsApi } from '@/api/login.js'

// 总结:加载JS脚本、发请求拿到密钥、创建WxLogin对象并传入密钥,生成微信二维码图片。
onMounted(() => {
  const scriptDom = document.createElement('script')
  scriptDom.src = 'http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
  document.body.appendChild(scriptDom)
  // JS加载完成后要做的事:
  scriptDom.onload = async () => {
    // 发请求拿参数
    const resp = await getLoginParamsApi()
    const { appid, redirectUri, scope, state } = resp.data
    console.log(resp)

    // 填入参数,生成微信二维码
    let obj = new WxLogin({
      self_redirect: false,
      id: 'wxLogin',
      appid,
      scope, // 网页直接写死即可:'snsapi_login'
      redirect_uri: decodeURIComponent(redirectUri),
      state
    })
  }
})
</script>

<template>
  <div class="login-form">
    <div id="wxLogin" class="qrcode">二维码</div>
  </div>
</template>

<style lang="scss" scoped>
// 扫码登录
.login-form {
  .qrcode {
    text-align: center;
  }
}
</style>

步骤4:自定义微信二维码样式

// 自己写一个css,转换程MD5,然后传入到参数href中。

.impowerBox .qrcode {width: 200px;}
.impowerBox .title {display: none;}
.impowerBox .info {width: 200px;}
.status_icon {display: none}
.impowerBox .status {text-align: center;} 

2.


05.常用组件

1.消息提示组件

1.创建组件

(1)index.js 



import { createApp, ref, watch } from 'vue'
import ToastComponent from './index.vue'

// 消息队列
const messageArr = ref([])


// 弹出消息提示框
const showMessage = (options) => {

    // 动态生成一个消息提示框
    const app = createApp(ToastComponent, options)
    const EL = document.createDocumentFragment() // 虚拟节点
    const VM = app.mount(EL)   // 真实的节点

    // 从组件接收一些东西进来用
    const { setIsShowMessage, setNewTop, top, height } = VM

    // 然后插入到body后面
    document.body.appendChild(EL)

    // 加入到消息队列。
    messageArr.value.push(VM)

    // 监视messageArr,刚调用方法时要执行一次,为了为弹框设置一次高度。
    watch(messageArr, () => {
        // 动态计算当前消息提示框的位置。当前VM的位置= (当前位置的index + 1) * top + 当前位置index * height
        const currentIndex = messageArr.value.findIndex(item => item === VM)
        const newTop = (currentIndex + 1) * top + currentIndex * height
        setNewTop(newTop)
    }, { immediate: true })

    // 几秒后,消息提示框消失、从消息队列中移除、删除DOM节点
    setTimeout(() => {
        setIsShowMessage(false)
        messageArr.value = messageArr.value.filter(item => item !== VM)
        VM.$el.addEventListener('transitionend', () => {
            app.unmount()
        })
    }, options.duration);

}


export default showMessage

(2)index.vue 


<script setup>
// 导入
import { ref, toRefs, onBeforeMount, onMounted, computed } from 'vue'

// 外界拿数据
const props = defineProps({
  type: {
    type: String,
    default: 'info'
  },
  content: {
    type: String,
    default: '这是一个消息提示内容'
  }
})

// 定义映射数据
const types = {
  success: 'success',
  error: 'error',
  warn: 'warn',
  info: 'info'
}
const resultType = computed(() => {
  console.log(types[props.type]);
  return types[props.type]
})

// 1.控制显示(默认不显示)
const isShowMessage = ref(false)
let timer = null
const setIsShowMessage = (flag) => {
  isShowMessage.value = flag
}

// 2.设置默认值
let top = ref(20) // 默认高度
const height = 40 // 弹框原始高度
// 设置top
const setNewTop = (newTop) => {
  top.value = newTop
}

// 导出
defineExpose({
  setIsShowMessage,
  setNewTop,
  height,
  top: 20
})
</script>


<template>
  <Transition name="slide-fade">
    <div v-show="isShowMessage" class='message-container' :class="[resultType]"
      :style="{top: top + 'px'}">
      {{ content }}
    </div>
  </Transition>
</template>


<style lang='scss' scoped>
.message-container {
  position: fixed;
  z-index: 9999;
  left: 50%;
  transform: translate(-50%);
  width: 360px;
  height: 40px;
  background-color: #edf2fc;
  border-radius: 15px;
  text-align: center;
  line-height: 40px;
  color: #91949a;
  transition: all 0.3s;

  // 不同样式
  &.success {
    color: #67c23a;
    background-color: #f0f9eb;
  }
  &.error {
    color: #f56c6c;
    background-color: #fef3f3;
  }
  &.warn {
    color: #e6a9a2;
    background-color: #fdf6ec;
  }
  &.info {
    color: #91949a;
    background-color: #edf2fc;
  }
}

.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(-50%) translateY(-20px);
  opacity: 0;
}
</style>

 2.使用说明文档 

// 步骤1:导入核心函数showMessage
import showMessage from '@/components/Message/index.vue'

// 步骤2:绑定好点击事件
const show = () => {
    showMessage({
        type: 'success', // 类型可以有:success、error、info、warn
        content: '这是一条提示消息',
        duration: 3000
    })
}

2.滑动校验组件

步骤1:创建滑动校验组件index.vue 

<template>
  <div class="slider-container" :style="{ width: w + 'px' }">
    <div class="slider-canvas" :style="{ width: w + 'px', height: h + 'px' }">
      <!-- 大图 -->
      <canvas :width="w" :height="h" ref="canvas" />
      <!-- 小图 -->
      <canvas class="slider-block" :width="l" :height="smallHeight" ref="block" />
      <!-- 结果遮罩层 -->
      <div
        :class="`result-mask ${resultMask.class}`"
        :style="{ height: `${resultMask.height}px` }"
      />
      <div class="btn" @click="refresh"></div>
    </div>
    <!-- 拖动的滑块内容 -->
    <div class="slider-square">
      <div class="box">
        <div
          class="slider-square-icon"
          @mousedown="sliderDown"
          :style="{ left: slideInfo.sliderLeft }"
        ></div>
        <span>{{ slideInfo.sliderText }}</span>
      </div>
    </div>
  </div>
</template>
  
<script>
import { ref, reactive, onMounted, watch, onDeactivated } from 'vue'

export default {
  props: {
    w: {
      // 大图的宽度
      type: Number,
      default: 320,
    },
    h: {
      // 大图的高度
      type: Number,
      default: 160,
    },
    l: {
      // 小图的宽度/高度(正方形的宽度,实际宽度需要加上圆弧的宽度)
      type: Number,
      default: 40,
    },
    sliderText: {
      // 滑块文字
      type: String,
      default: "拖动滑块向右>>",
    },
    bigImg: {
      // 大图源
      type: String,
      default: "",
    },
    smImg: {
      // 小图图源
      type: String,
      default: "",
    },
    r: {
      // 小图的拼图半径
      type: Number,
      default: 0,
    },
    accuracy: {
      // 拖动误差范围
      type: Number,
      default: 5,
    },
    posY: {
      // Y坐标
      type: Number,
      default: 0,
    },
    posX: {
      // X坐标
      type: Number,
      default: 0,
    },
    smallHeight: {
      // 小图的宽度/高度(正方形的宽度,实际宽度需要加上圆弧的宽度)
      type: Number,
      default: 40,
    }
  },
  setup (props, { emit }) {
    const canvas = ref(null); // 大图ref
    const canvasCtx = ref(null); // 大图canvas绘制容器
    const block = ref(null); // 小图ref
    const blockCtx = ref(null); // 小图canvas绘制容器
    const { w, h, l, r, sliderText, posY, posX, accuracy, smallHeight } = props;
    const slideInfo = reactive({
      sliderLeft: 0, // 可拖动滑块的left
      block_x: 0, // 拼图最终要滑动的位置x
      block_y: 0, // 拼图位置的y
      sw: l, // 拼图的实际宽度 小图宽度 + 2*半径 + 3(border)
      sliderText, // 滑块提示文字
      bigImg: "",
      smImg: "",
    });
    const origin = reactive({
      // 鼠标按下初始位置
      orginX: 0,
      originY: 0,
    });
    const resultMask = reactive({
      // 结果提示背景色
      class: "",
      height: 0,
    });

    // 初始化canvas dom
    const initDom = () => {
      canvasCtx.value = canvas.value.getContext("2d");
      blockCtx.value = block.value.getContext("2d");
    };
    // 初始化图片
    const initImg = () => {
      const bigImg = createImg(() => {
        canvasCtx.value.drawImage(bigImg, 0, 0, w, h);
      }, props.bigImg);
      const smImg = createImg(() => {
        blockCtx.value.drawImage(smImg, 0, 0, l, smallHeight);
      }, props.smImg);
    };

    const createImg = (onload, url) => {
      const img = document.createElement("img");
      img.crossOrigin = "Anonymous";
      img.onload = onload;
      img.onerror = () => {
        img.src = url;
      };
      img.src = url;
      return img;
    };

    onMounted(() => {
      initDom();
      initImg();
      bindEvents();
    });

    const slider = ref(false);

    // 鼠标按下
    const sliderDown = (e) => {
      slider.value = true;
      slideInfo.sliderText = "";
      origin.orginX = e.pageX;
      origin.originY = e.pageY;
    };

    // 鼠标移动
    const sliderMove = (e) => {
      if (slider.value) {
        const moveX = e.pageX - origin.orginX;
        const moveY = e.pageY - origin.originY;
        if (moveX < 0 || moveX + l > w) return false;
        slideInfo.sliderLeft = moveX + "px";
        let blockLeft = ((w - slideInfo.sw) / (w - 40)) * moveX;
        block.value.style.left = blockLeft + "px";
      }
    };

    // 鼠标松开
    const sliderUp = (e) => {
      if (!slider.value) return false;
      slider.value = false;
      resultMask.height = h / 2;
      const left = parseInt(block.value.style.left);
      emit("sliderJudge", left);
    };

    // 将鼠标移动,鼠标松开事件绑定在dom上,如果鼠标移出滑块范围,还可以继续移动
    const bindEvents = () => {
      document.addEventListener("mousemove", sliderMove);
      document.addEventListener("mouseup", sliderUp);
    };

    // 调用接口获取图片,异步,监听图片地址变动
    watch(
      () => props.bigImg,
      (newVal) => {
        reset(); // 重置图片状态
        initImg(); // 重新加载图片
      }
    );

    //刷新图片
    const refresh = () => {
      emit("refresh");
    }

    // 重置/刷新图片
    const reset = () => {
      canvasCtx.value.clearRect(0, 0, w, h);
      blockCtx.value.clearRect(0, 0, l, smallHeight);
      slideInfo.sliderText = sliderText;
      resultMask.height = 0;
      resultMask.class = "";
      slideInfo.sliderLeft = 0;
      block.value.style.left = 0;
    };

    // 组建销毁时,移除鼠标事件
    onDeactivated(() => {
      document.removeEventListener("mousemove", sliderMove);
      document.removeEventListener("mouseup", sliderUp);
    });

    return {
      canvas,
      canvasCtx,
      blockCtx,
      block,
      slideInfo,
      resultMask,
      refresh,
      sliderDown,
      sliderMove,
      sliderUp,
      reset,
      initDom,
      initImg
    };
  }
}
</script>
  
<style lang="scss" scoped>
.slider-container {
  .slider-canvas {
    position: relative;

    .slider-block {
      position: absolute;
      top: 0;
      left: 0;
    }

    .btn {
      position: absolute;
      right: 0;
      top: 0;
      width: 34px;
      height: 34px;
      cursor: pointer;
      background-image: url('../../assets/images/light.png');
      background-position: 34px 35px;
      z-index: 999;
    }

    @keyframes result-show {
      0% {
        opacity: 0;
      }

      100% {
        opacity: 0.7;
      }
    }

    .result-mask {
      position: absolute;
      left: 0;
      bottom: 0;
      width: 100%;
      opacity: 0.7;

      &.success {
        background-color: #52ccba;
        animation: result-show 1s;
      }

      &.fail {
        background-color: #f57a7a;
        animation: result-show 1s;
      }
    }
  }
}

.slider-square {
  background-color: #52a352;
  height: 40px;
  text-align: center;
  line-height: 40px;
  border: 1px solid #ddd;
  position: relative;
  margin-top: 12px;

  .slider-square-icon {
    position: absolute;
    top: 0;
    left: 0;
    height: 39px;
    width: 39px;
    background-color: #ee9016;
    box-shadow: blanchedalmond 0px 0px 1px 2px;
    cursor: pointer;

    &:hover {
      background-color: rgb(224, 147, 30);
    }
  }
  span {
    color: #fff;
  }
}
</style>
  

 步骤2:该组件使用说明

// 步骤1:导入组件
import SlideSplit from '@/components/SlideSplit/index.vue'

// 步骤2:template声明组件
<template>
  <SlideSplit v-if="isShowSlideSplit" :w="slideInfo.bigWidth" :h="slideInfo.bigHeight"
    :l="slideInfo.smallWidth" :bigImg="slideInfo.bigImageBase64" :smImg="slideInfo.smallImageBase64"
    :r="slideInfo.smallWidth" :accuracy="slideInfo.accuracy" :smallHeight="slideInfo.smallHeight"
    @refresh="handlerRequest" @sliderJudge="onJudge"></SlideSplit>
</template>

// 步骤3:js中准备数据
const slideInfo = ref({
  bigWidth: 0,
  bigHeight: 0,
  bigImageBase64: '',
  smallImageBase64: '',
  smallWidth: 0,
  smallHeight: 0,
  accuracy: 8,
  bigImage: null, // 为了ts类型不报错,下面属性均无效。
  smallImage: null,
  posX: null,
  posY: null
})

3.虚拟滚动组件(等高)

步骤1:创建虚拟列表组件index.vue

<script setup>
// 导入
import { ref, toRefs, onBeforeMount, onMounted, computed, onUnmounted } from 'vue'

// 1.总数据、每条数据的高度
const props = defineProps({
  items: { // 总数据
    type: Array,
    defualt: () => []
  },
  itemSize: { // 每条数据的高度
    type: Number,
    defualt: '60'
  }
})

// 2.内层容器总高度
const totalHeight = computed(() => {
  props.items.length * props.itemSize
})

// 3.对10w+条数据处理后的数据。我希望格式:{item: 原始数据,position:该数据所在的位置}
const pool = ref([])
const container = ref(null)
let start = ref(0)
let end = ref(0)
const scrollTop = ref(0)  //滚动的高度
const setScrollTop = () => {
  // (1) 滚动条滚动的位置
  scrollTop.value = container.value.scrollTop
  // (2) 计算首尾位置
  start.value = Math.floor(scrollTop.value / props.itemSize)
  end.value = Math.ceil((scrollTop.value + container.value.clientHeight) / props.itemSize)
  // (3) 优化:首尾多显示十个,防止出现白屏
  start.value -= 10
  end.value += 10
  if (start.value < 0) { start.value = 0 }
  // (4) 给渲染池赋值
  pool.value = props.items.slice(start.value, end.value).map((item, index) => ({
    item: item,
    position: (start.value * props.itemSize) + index * props.itemSize
  }))
}

// 4.页面加载
onMounted(() => {
  setScrollTop()
  container.value.addEventListener('scroll', () => {
    setScrollTop()
  })
})
</script>


<template>
  <!-- 外层容器 -->
  <div class='recycle-scroller-container' ref="container">
    <!-- 内层超长容器 -->
    <ul class="recycle-scroller-wrapper" :style="{height: `${totalHeight}px`}">
      <li class="recycle-scroller-item" v-for="item in pool" :key="item"
        :style="{height: `${itemSize}px`, transform: `translateY(${item.position}px)`}">
        <slot :item="item.item"></slot>
      </li>
    </ul>
  </div>
</template>


<style lang='scss' scoped>
// 最外层容器
.recycle-scroller-container {
  overflow: auto;
  width: 100%;
  height: 100%;
  border: 1px solid black;
  margin: 0 auto;
  scrollbar-width: none; // 在火狐中,隐藏滚动条样式
  -ms-overflow-style: none; // 在ie10+、Edge中隐藏滚动条样式
  // 在webkit内核,隐藏滚动条样式
  &::-webkit-scrollbar {
    display: none;
  }

  // 内层超长容器
  .recycle-scroller-wrapper {
    position: relative;

    .recycle-scroller-item {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      border-bottom: 1px solid black;
    }
  }
}
</style>

步骤2:组件的使用说明

<script setup lang="ts">
import { onMounted, ref } from 'vue'
// (1) 导入组件
import listItem from '@/components/scroll/listItem.vue'
import Scroll from '@/components/scroll/index.vue'

// (2) 测试生成一万条数据
const items = ref([])
for (let i = 1; i <= 10000; i++) {
  let id = i
  let name = `征服者${i}`
  const obj = {
    id: i,
    name: name
  }
  items.value.push(obj)
}
</script>


<template>
    <!-- (3) 虚拟列表组件的宽高由外层容器main-container决定 -->
    <!-- (4) 需要传入数据、虚拟列表每一个item的高度值 -->
    <!-- (5) 虚拟组件的每一个item的内容可以用插槽定制 -->
  <div class="main-container">
    <Scroll :items="items" :itemSize="200" v-slot="{item}">
      <listItem :item="item" />
    </Scroll>
  </div>
</template>


<style lang="scss" scoped>
// 虚拟组件的大小,由外层容器main-container决定(内部设置了width、height为100%)
.main-container {
  width: 800px;
  height: 600px;
  margin: 100px auto;
}
</style>
 

三、打包优化

01.vue配置分包(异步组件)


在vue中,只有把组件配置成异步组件,该组件才会被打包成单独的JS文件。

场景1:一些首屏页面看不到的组件,可以做成异步组件。只有当用户滑到组件的位置时,

            才加载该组件,可以提升首屏加载速度。

//  思路
(1) 首先,组件不要直接import导入。
(2) 而是在js中异步加载组件:const componentC = defineAsyncComponent(() => import('@/components/componentC.vue'))
(3) 在template中:用'v-if'控制不能让组件渲染出来。
(4) 然后,当滑动到异步组件的位置时,由于使用vueuse中的函数,其会自动让状态'targetIsVisible'
    变成true,此时v-if就为true了,异步组件就可以加载出来了。
	
**************************************************************************

<template>
  <div>
    <componentA></componentA>
    <componentB></componentB>
    <!-- 1. 一开始不要让C组件显示,等页面滚动到C组件的位置时,targetIsVisible变量就会变成true -->
    <!-- 2. 用Suspense包裹异步组件,数据加载出来后会显示#default的内容,还没加载出来的话显示#fallback的内容。 -->
    <div ref="target">
      <Suspense v-if="targetIsVisible">
        <template #default>
          <componentC></componentC>
        </template>
        <template #fallback> 加载中... </template>
      </Suspense>
    </div>
  </div>
</template>


<script setup>

// 导入函数
import { defineAsyncComponent, ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'

// 导入组件, 其中componentC组件需要异步加载
import componentA from '@/components/componentA.vue'
import componentB from '@/components/componentB.vue'
const componentC = defineAsyncComponent(() => import('@/components/componentC.vue'))


// 3. 调用vueuse的useIntersectionObserver()方法
const target = ref(null)
const targetIsVisible = ref(false)
const { stop } = useIntersectionObserver(
  target,
  ([{ isIntersecting }]) => {
    if (isIntersecting) {
      targetIsVisible.value = isIntersecting
    }
  }
)

</script>

场景2:若网络请求的数据还没拿到,可以显示一些提示消息,让用户稍等


<template>
  <div>
    <!-- 1. 需要用Suspense包裹异步组件,数据加载出来后会显示#default的内容,还没加载出来的话显示#fallback的内容。 -->
    <Suspense>
      <template #default>
        <componentA></componentA>
      </template>
      <template #fallback> 加载中... </template>
    </Suspense>

    <componentB></componentB>
    <componentC></componentC>
  </div>
</template>

 
<script setup>

// 导入函数
import { defineAsyncComponent, ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'

// 导入组件, 其中componentC组件需要异步加载
const componentA = defineAsyncComponent(() => import('@/components/componentA.vue'))
import componentB from '@/components/componentB.vue'
import componentC from '@/components/componentC.vue'


// 2. 调用vueuse的useIntersectionObserver()方法
const target = ref(null)
const targetIsVisible = ref(false)
const { stop } = useIntersectionObserver(
  target,
  ([{ isIntersecting }]) => {
    if (isIntersecting) {
      targetIsVisible.value = isIntersecting
      stop()
    }
  }
)
</script>

02.动态组件(类似tab栏切换)


<template>
  <ul>
    <li v-for="(item, index) in tabList" :key="item.name" @click="changeCom(index)">
      {{ item.name }}
    </li>
  </ul>
  <!-- 动态组件 -->
  <keep-alive>
    <component :is="currentComponent.com"></component>
  </keep-alive>
</template>


<script setup>
import { ref, markRaw } from "vue"
// 导入组件
import componentA from '@/components/componentA.vue'
import componentB from '@/components/componentB.vue'
import componentC from '@/components/componentC.vue'


// 准备数据,markRaw只是为了让它不要浪费响应式,我们不需要使用响应式。
let tabList = ref([
  { name: 'A小伙子', com: markRaw(componentA) },
  { name: 'B小伙子', com: markRaw(componentB) },
  { name: 'C小伙子', com: markRaw(componentC) }
])

// 选中当前组件
const currentComponent = ref({
  com: componentA
})

// 改变组件
const changeCom = (index) => {
  currentComponent.value.com = tabList.value[index].com
  console.log(currentComponent.value.com, index);
}

</script>

03.按需加载路由


问题描述:vue是单页面应用程序,所有页面打包到一个JS文件中。必然会导致首屏加载过慢的问题。

解决方案:把路由组件改为按需引入,只有跳转到该路由时,才会加载该路由对应的组件

04.页面白屏问题

问题描述:当页面打开时,HTML还没加载完就会同时加载JS,由于JS加载需要一段时间,从而导致有那么一秒钟左右页面处于白屏状态

解决方案:使用defer属性,<script defer></script>。就会等HTML解析完才加载JS。

四.项目上线

五.面试题

1.HTML

2.CSS

3.JS

4.Vue

1.vue2和vue3的区别


(1)组件注册不同:vue3不需要进行组件的注册,只需导入组件即可使用。

(2)template不同:vue3支持定义多个根节点,而vue2只能有一个根节点。

(3)语法不同:vue3使用组合式API,vue2是选项式API。

(4)生命周期不同

vue2vue3
setup
beforeCreate
created
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted

(5)vue3新出了Suspense内置组件:Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。

<tempalte>
  <suspense>
    <template #default>
      <List />
    </template>
    <template #fallback>
      <div>
        Loading...      
     </div>
    </template>
  </suspense>
</template>

vue3新出了Teleport内置组件:Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗。

<button @click="dialogVisible = true">显示弹窗</button>
<teleport to="body">
  <div class="dialog" v-if="dialogVisible">
    我是弹窗,我直接移动到了body标签下  </div>
</teleport>

(6)响应式原理不同:vue2基于Object.defineProperty来对数据进行劫持,而vue3使用的是ES6提供的Proxy对象。(这是因为:在vue2的响应式无法对数组或者对象进行增删改进行监听,所以vue2只能单独的提供了this.$set()方法进行处理,但是vue3使用Proxy,就无需进行特殊处理。)

(7)diff算法不同

(8)TypeScript的支持不同:vue3支持TypeScript的语法,vue2对TypeScript的支持非常差。

2.v-if和v-show的区别


v-if是控制DOM节点是否要渲染,而v-show是在DOM节点渲染的情况下,通过display来控制DOM节点的显示与隐藏,本质上v-show的开销更大,v-if的稳定性不好。

总结:v-if适用于不频繁切换的场景,而v-show适用于频繁切换的场景

3.v-for和v-if的优先级


Vue2中v-for的优先级会更高,所以会先执行循环,再进行v-if判断,所以这样就会导致无论需不需展示这个元素,都会先遍历整个列表

但是在Vue 3中,v-for和v-if的指令不能再同时使用在同一个元素上。这是因为Vue 3引入了一项优化,即在编译阶段会根据v-if的条件进行静态分析,如果条件为假,则不会生成对应的虚拟节点,从而减少了不必要的渲染开销。而如果v-for和v-if同时存在,会导致静态分析变得困难,因此Vue 3禁止了这种用法。

总结:vue3不能将v-if和v-for写在同一个节点

4.vue3的内置组件有哪些


新增了三个组件。

(1)vue3新出了Suspense内置组件:Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。

<tempalte>
  <suspense>
    <template #default>
      <List />
    </template>
    <template #fallback>
      <div>
        Loading...      
     </div>
    </template>
  </suspense>
</template>

(2)vue3新出了Teleport内置组件:Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗。

<button @click="dialogVisible = true">显示弹窗</button>
<teleport to="body">
  <div class="dialog" v-if="dialogVisible">
    我是弹窗,我直接移动到了body标签下  </div>
</teleport>

(3)vue3新出了Fragment :组件可以没有根标签,内部会将多个标签包含在一个 fragment 的虚拟元素中。这个不需要自己手写,vue内部帮我们做了。

5.vue的事件修饰符有哪些,所在位置


第一类:v-model修饰符

(1).lazy(输入框聚焦、失焦时触发)

(2).number(尝试将输入框的value值转为number类型)

(3).trim(去除输入框的value值首尾空格)

第二类:事件修饰符

(1).stop(阻止事件冒泡)

(2).prevent(防止触发默认事件)

(3).capture(将默认触发方式改为捕获触发)

(4).self(只有当事件发生在目标元素自身时才会触发

(5).once(事件只触发一次)

第三类:键盘修饰符

(1).enter(回车键触发)

(2).tab(tab键触发)

(3).delete(通过该元素需要配合v-if使用,是指DOM节点显示、隐藏时触发

(4).esc(esc键触发)

6.vue双向数据绑定原理


在vue2:vue2数据双向绑定是由数据劫持 + 发布-订阅的模式实现的,通过object.defineProperty()来劫持对象属性的getter和setter操作,在数据变动时发布消息给订阅者,触发响应的监听回调。

在vue3:vue3使用的Proxy去劫持数据 当我们的数据发生变动时,发布消息给订阅者,触发相应的监听回调

7.v-for中的key值的作用


key值要设置一个唯一性的id,是为了确保渲染DOM的时候不会出错。因为创建DOM节点是比较消耗性能的操作,vue为了避免创建新的DOM节点,当vue发现一个节点的key值相同时,就不会创建新的DOM节点。
例如:

<template>
  <div class="root">
    <div class="item" v-for="(item, index) in list" :key="index">
      <h2>{{ item.text }}</h2>
    </div>
    <button @click="replaceData">替换数据</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      list: [
        {text: '33333'},
        {text: '66666'},
        {text: '99999'},
      ]
    }
  },
  methods: {
    replaceData() {
      this.list = [
        {text: '22222'},
        {text: '44444'},
        {text: '88888'},
      ]
    }
  }
}
</script>
<style lang="scss">
.item{ background: orange;}
</style>

                                初始化的样式

正常情况下:我们点击"替换数据"按钮此时会变成这样,很正常对吧。

特殊情况:当手动删除'66666'这个元素的DOM节点时,然后点击"替换数据"按钮,此时会变成这样子,额。。。我的44444跑哪去了? 这是因为vue发现这一批key和上一批key是一样的,就没有重新创建DOM节点,只是把内容给替换了而已。

8.mutations和actions的区别


如果方法体中涉及到异步操作,该方法需要转发给actions进行处理;
如果方法体中涉及的都是同步操作,该方法可以提交给mutations进行处理。

9.vue3新增的状态管理工具


pinia

10.css的预处理器有哪些,在vue中如何使用


css预处理器有:less、scss

以less为例:先安装less、less-loader:npm i less less-loader

                     然后在styles标签中声明lang='less':<style lang="less">

11.vue的路由传参和接参


传参:

第一种:可以通过路径传参,把参数以"?参数1=值1&参数2=值2"的形式,放在路径后面。

//  1.查询参数传参(两种写法)
(1)this.$router.push('/home?username=长官&age=18')
(2)this.$router.push({
        path: '/home'
        query: {
            name: '张三',
            age: 18
        }
     })

//  2.动态路由传参
this.$router.push({
    path: '/home',
    params: {
        username: '思密达'
    }
})       

第二种:给路由起个名称name,通过路由的name来进行跳转,参数要看情况放。

// 1.查询参数传参
this.$router.push({
    name: '路由名称',
    query: {
        username: '思密达',
        age: 18
    }
})

// 2.动态路由传参
this.$router.push({
    name: '路由名称',
    params: {
        username: '思密达'
    }
})

接收参数:

// 1.获取查询参数传参
this.$route.query.参数名

// 2.获取动态路由传参
this.$route.params.参数名

12.watch和computed的区别

computed:是对响应式数据处理后, 返回的新的响应式数据。(带有缓存功能)

watch:是对响应式数据进行监听,可以监听普通数据类型,也可以监听对象。

13.vite的优点和特点

优点:

(1)构建速度快:运行时动态的加载依赖,避免传统构建工具的静态打包和编译。

(2)更高效的热更新:实时更新代码并预览修改效果,无需重新编译整个项目。

(3)配置简单

缺点:

(1)由于是比较新的技术,可能导致一些老的浏览器不兼容。

14.vuex的五个状态分别是什么

分别是state、mutations、actions、getters、modules。

15.vue2和vue3中v-for循环有什么区别

(1)key的不同:vue2需要手动配置key,vue3默认情况下会自动推导出唯一的key值。

(2)v-if和v-for的优先级:在vue2中v-for优先级高,在vue3中v-if优先级高。

16.自定义组件的过程

在components或者views目录下,创建一个xxx.vue文件,然后在文件内定义内容

<script setup>
// 定义组件名称
defineOptions({
  name: 'TestVue'
})
</script>

<template>
  <div class="test-container"></div>
</template>


<style lang="less" scoped>
</style>

使用组件时,vue2需要注册后使用,vue3可以直接使用组件。

17.localSorage和session的区别

localStorage:
(1)不管存进去是什么类型,读取出来的永远是字符串
(2)存储大小问题:localStorage的存储容量根据不同浏览器的不同,结果就不一样,一般为4M。

18.如果localStorage超过最大容量怎么办

方法1:压缩字符串,仍然存进localStorage

缺点:多引用一个库。

优点:压缩。

参考文章:2. lz-string【JavaScript 字符串压缩库】 - 简书

方法2:使用其他存储方式,如indexedDB,我们不是直接用indexed DB,而是用封装的库localForage

缺点:兼容性不太好,操作繁琐。

优点:存进去的是什么类型,读出来的就是什么类型。并且可以是异步的。存储量远远大于localStorage。

参考文章:https://www.cnblogs.com/Jishuyang/p/17761981.html

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值