Vue3.2 setup工作笔记

997 阅读27分钟

0、Vite搭建项目

B站学习视频

B站视频.# vue3 基础入门,读懂vue3官方API

B站视频.# Vue3小兔鲜电商项目实战

Vite官方中文文档

使用 NPM安装Vite: npm create vite@latest

image.png

vue渐进式

渐进式框架是指,我们可以根据自己的项目的需求,选择不同部件来构建一个完整的框架。 例如状态管理我可以使用pinia,也可以使用vuex。这种分层可选的灵活方式就是“渐进式”

简述MVC, MVVM的区别

MVVM是一种架构模式,实现了数据的双向绑定。无论用户更新View还是Model,另一个都能跟着自动更新。他是由Model, View, 和ViewModel三部分构成。

Model数据模型:用来修改数据【JSON、Array、String】;
View视图界面:就是用户看到的浏览器界面;
ViewModel绑定器:用来同步view和model的一个对象。

MVC模式,中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验

1、Vue指令(setup语法糖)

当使用 <script setup> 构建组件的时候,我们不再需要把外面定义的方法return出去。任何在 <script setup> 声明的顶层绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用:

<template>
    <Breadcrumb :breadcrumbState="breadcrumbState"/>
</template>

<script setup>
    import { reactive, ref, watch, onMounted } from 'vue'
    //这里我们引入了子组件 面包屑导航[子组件]
    import Breadcrumb from '../../components/Breadcrumb/index.vue' 
    const breadcrumbState = reactive({ tit1: "需求申请", tit2: "新建工单", })

    onMounted(() => {
        // 组件挂载完成后执行
    })
</script>

<style lang="less" scoped>
<!-- 只作用于当前组件中的元素 -->
</style>

Vue3中ref和reactive区别

在vue3中对数据进行响应式的声明,有ref和reactive/riˈæktɪv/俩种方式。ref可以对String、Array、Number类型进行声明,并用.value 赋予新的值,除此之外ref还可以获取dom元素。而reactive只能对一个对象进行响应式代理。

  • ref 声明数据类型:数字、字符串、数组
  • reactive 只能声明数据类型:对象
const count = ref(10) // ref 声明数据类型:数字、字符串、数组
const web = reactive({ // reactive 声明数据类型:对象
  title: '上证指数',
  year: 3018
})
const onClick = () => {
  count.value++ // ref 修改属性值,加个 .value
}
const onYear = () => {
  web.year = 3019 // reactive 修改属性值,
}
ref获取dom元素
// vue2 写法
<script>
export default {
    mounted(){
	console.info("获取dom元素", this.$refs['bar']);
    },
}
</script>
<template>
    <h3 ref="bar">《帝国崛起:华夏觉醒》</h3>
</template>
// vue3 写法
<script setup lang="ts">
import { ref } from 'vue'
const msg = ref(null)
function handleDom() {
    console.log('获取dom元素', msg.value)
}
</script>

<template>
    <h2 ref="msg">《帝国崛起:民族战线》</h2>
    <button @click="handleDom">获取dom元素</button>
</template>
id获取dom元素
// vue3 写法
<script setup lang="ts">
function handleDom() {
    console.log('获取dom元素', document.getElementById('msg'))
}
</script>

<template>
    <h2 id="msg">《帝国崛起:民族战线》</h2>
    <button @click="handleDom">获取dom元素</button>
</template>

v-if, v-show 的区别

在我们页面渲染的时候,如果这个组件的显示和隐藏是一次性决定,后面不会再修改的话用v-if。如果说我们的这个元素,需要经常的切换显示和隐藏的话v-show,因为v-show的显示隐藏是操作了css的属性display: none

1.v-if能在<template>/ˈtempleɪt/特恩噗累特上用,而v-show不行。

watch 侦听器

作用: 用于侦听一个或者多个数据的变化,一旦数据变化时执行回调函数进行其他操作
有三个额外参数:

  • immediate/ɪˈmiːdiət/ 依眯跌特 (立即执行)。当值设置为 true 时,那么被监控的对象在初始化时就会触发

  • deep (深度侦听)。当侦听对象的时候,明明数据修改了,却没有侦听的提示,这个时候,需要我们开启深度侦听。因为watch默认可以浅层侦听const state = ref(0),若是侦听的是对象属性const state = ref({ count: 0 })就不会触发回调执行。但deep底层是递归,影响性能不建议使用

  • once (单次侦听)。vue3.4版本新增功能,once:true

computed计算属性,watch侦听器 的区别

  1. computed/kəmˈpjuːtɪd/肯piu忒d是计算用的,当一个数据的值,需要通过某些逻辑,或多个数据计算而来。那么就需要用到computed。最典型的就是购物车结算时的总金额。

  2. watch是监听用的,如果是一条数据更改,影响多条数据时,就需要用watch。watch监听有两个可选属性,分别是immediate/ɪˈmiːdiət/衣咪跌特deep/diːp/帝泼,当immediate为true时是组件加载立即触发回调函数,deep是深度监听,当监听对象的时候,明明内部数据发生了修改,却没有监听提示时,我们需要开启深度监听。使用场景搜索框。

watch和watchEffect异同?

  • watch 和 watchEffect 都能监听响应式数据的变化,不同的是它们监听数据变化的方式不同。

  • watch 会明确监听某一个响应数据,而 watchEffect 则是隐式的监听回调函数中响应数据。

  • watch 在数据初始化时,不会执行回调函数的,watchEffect 则会立即执行回调函数。

vue3写法(watch)

监听单个属性
    <script setup lang="ts">
    // 1.导入watch
    import { ref, watch } from 'vue'
    const count = ref(0)
    const setCount = () => {
      count.value++
    }
    // 2.调用watch 侦听变化
    watch( count, (newVal, oldVal) => {
      console.log(`count发生变化,新值${newVal},老值${oldVal}`)
    }, {
      immediate: true // 立即执行
    })
    </script>
    <template>
      <div class="card">
        <button type="button" @click="setCount">count is {{ count }}</button>
      </div>
    </template>
监听多个属性

说明:同时侦听多个响应式数据的变化,不管哪个数据变化都需要执行回调

    <script setup lang="ts">
    // 1.导入watch
    import { ref, watch } from 'vue'
    const count = ref(0)
    const name = ref("柴西")
    const setCount = () => {
      count.value++
    }
    // 2.调用watch 侦听变化
    watch( [count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`count发生变化,新值${[newCount, newName]},老值${[oldCount, oldName]}`)
    })
    </script>
    <template>
      <div class="card">
        <button type="button" @click="setCount">count is {{ count }}</button>
      </div>
    </template>
deep (深度侦听)

默认机制:通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep选项

    <script setup lang="ts">
    // 1.导入watch
    import { ref, watch } from 'vue'
    const state = ref({count:0})

    const setCount = () => {
      state.value.count++
    }
    // 2.调用watch 侦听变化
    watch( state, () => {
      console.log(`默认浅层侦听,打印不到回调函数,需要deep`)
    }, {
      deep: true
    })
    </script>
    <template>
      <div class="card">
        <button type="button" @click="setCount">count is {{ state.count }}</button>
      </div>
    </template>
精准侦听
    <script setup lang="ts">
    // 1.导入watch
    import { ref, watch } from 'vue'
    const state = ref({
      name: '超carry的柴西',
      age: 18 // 只在`age`变化时,执行回调函数
    })
    const setCount = () => {
      state.value.age++
    }
    const setAge = () => { state.value.name = '小柴西' }
    // 2.调用watch 精准侦听变化
    watch( () => state.value.age, () => {
      console.log(`精准侦听,只有点击age,才能打印回调函数`)
    })
    </script>
    <template>
      <div class="card">
        <button type="button" @click="setCount">count is {{ state.age }}</button>
        <button type="button" @click="setAge">count is {{ state.name }}</button>
      </div>
    </template>

vue2写法(watch)

    watch: {
      // 监听器:当城市、日期、子导航、发生变化时,重新调用接口
      Map_cityName(newVal, oldVal) {
        console.log('JJJ1', newVal, oldVal)
      },
    },

nextTick的使用

概念

/neks'tɪk/ 耐克斯'忒克nextTick主要用于当数据动态变化后,dom还未及时更新的问题

在vue里面,我们的dom更新是异步执行的。它就相当于我们在修改数据的时候,视图层并不是立即更新,而是会监听数据的一个变化,并且缓存在同一个事件循环当中。只有等同一数据循环中的所有数据变化完成之后,才会进行统一的视图更新。我们经常会在dom元素还没更新的时候,就使用了某个元素,这样是拿不到变更后的dom的。为了确保我们能获取到数据循环之后的dom。所以我们设置了nextTick方法。这个api的用处是:在修改了数据以后,立即使用这个方法获取更新后的dom。

nextTick使用的场景

只有在这种document.getElementById('counter')的旧写法才会出现数据动态变化后,dom还未及时更新的问题

  1. created组件创建后的生命周期,想要获取dom时
  2. 获取列表更新后的高度
  3. 添加input框,并获取焦点

代码示例

// vue官网,vue2写法
<script>
import { nextTick } from 'vue'
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    async increment() {
      this.count++
      // DOM 还未更新
      console.log(document.getElementById('counter').textContent) // 0
      await nextTick()
      // DOM 此时已经更新
      console.log(document.getElementById('counter').textContent) // 1
    }
  }
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>
// vue3 写法
// 添加input框,并获取焦点
<script setup lang='ts'>
import { nextTick, ref } from 'vue'

const isShow = ref(false)
async function addInput() {
    isShow.value = true
    await nextTick(() => {
        document.getElementById('test')?.focus()
    })
}
</script>

<template>
    <input v-if="isShow" id='test' />
    <button @click="addInput">添加input,获取焦点</button>
</template>
// vue3 写法二
// 获取列表更新后的高度
<script setup lang='ts'>
import { nextTick, ref } from 'vue'

const arr = ref(['AA', 'BB', 'CC'])
async function addEE() {
    arr.value.push('EE')
    console.log('len数_arr.value.length', arr.value.length) // 4
    console.log("len数_nextTick()前", document.getElementById('test')?.children.length) // 3
    await nextTick()
    console.log("len数_nextTick()后", document.getElementById('test')?.children.length) // 4
}
</script>

<template>
    <ul id="test">
        <li v-for="(item, index) in arr" :key="index">{{ item }}</li>
    </ul>
    <button @click="addEE">添加EE</button>
</template>
watch 代码示例

watch: 监听器用于监听数据是否被修改,一旦修改就可以执行一些其他的操作

const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)
import { ref, reactive, watch } from 'vue'
watch(data, (newVal, oldVal) => {
    // ...
}, {immediate: true, deep: true})

注意:第一个参数是一个箭头函数

watch 的第三个参数:

  1. deep: 深度监听。当监听对象的时候,明明数据修改了,却没有监听的提示,这个时候,需要我们开启深度监控, { deep: true }
  2. immediate:作用就是设置是否立即执行监控,当值设置为 true 时,那么被监控的对象在初始化时就会触发一次 watch 方法。

这里改成俩个,

一、监听ref声明の变量,数组

const name = ref('握奇')
const age = ref(21)

// 监听单个普通类型
watch(name, (newVal, oldVal) => { 
  console.log(newVal)
})

// 监听多个普通类型,返回数组
watch([name, age], (newVal, oldVal) => { 
  console.log(newVal)
})

二、监听reactive声明の整个对象

但是当监听对象的时候,明明数据修改了,却没有监听的提示,这个时候,需要我们开启深度监控,{ deep: true }

const person = reactive({
  name: '握奇',
  infos: {
    age: 21,
    address: '上海'
  }
})

// 监听对象person时,vue3将强制开启deep深度监听
watch(person, (newVal, oldVal) => { 
  console.log(newVal)
})

watch(() => person, (newVal, oldVal) => { 
  console.log(newVal)
}, { deep: true })

两种实现方式效果相同,只要对象中有任何变化就会触发watch方法

三、监听对象中的属性

const person = reactive({
  name: '握奇',
  infos: {
    age: 21,
    address: '上海'
  }
})

// 只有当person对象中的name属性发生变化才会触发watch方法
watch(() => person.name, (newVal, oldVal) => { 
  console.log(newVal)
})

// 注意:监听对象的属性为复杂数据类型时,需要开启deep深度监听
watch(() => person.infos, (newVal, oldVal) => { 
  console.log(newVal)
}, { deep: true })

2、生命周期

概念

生命周期: 在人生这条单行线中,每一个年龄阶段需要做的一些事情,例如3-6上幼儿园、20-30的时候结婚、60岁的时候退休。每一个时间节点都要做固定的事情,把这些事情组合在一起就是生命周期。

vue3变化

Vue2の生命周期共分为8个阶段,创建前后,载入前后,更新前后,销毁前后
ue3の生命周期没有createdbeforeCreate。并且setup在最前执行

vue2vue3作用
最最最最最最最最前面执行--setup--
组件创建前beforeCreate--此时 data数据 和 methods方法 还未完成初始化,无法调用。也不能获得DOM节点。无任何软用
组件创建后created--此时 data数据 和 methods方法 已经完成初始化,可以调用。但是模板还没有编译,还不能获取到 DOM节点
组件加载前beforeMountonBeforeMount此时 数据已渲染出来,模板进行编译,会调用 render 函数生成虚拟DOM,但还是无法获取真实 DOM节点。可以访问接口数据
组件加载后mountedonMounted此时 数据和DOM都已被渲染出来,也就是我们页面可以显示了。一般我们的异步请求都写在这里。
组件更新前beforeUpdateonBeforeUpdate此时 data数据已更新,DOM节点已获取,但变化后的数据还未渲染到页面之上。view层没更新
组件更新后updatedonUpdated此时 data数据已更新,DOM节点已获取,但变化后的数据还未渲染到页面之上。view层已更新
组件卸载前beforeUnmountonBeforeUnmount组件还没有被销毁,还可以正常使用。可用于解除一些定时器或订阅的取消
组件卸载后unmountedonUnmount实例销毁之后执行,且 dom 完全销毁

写法区别

beforeMount(){
    // vue2 写法
}
onBeforeMount(() => {
    // vue3 写法,并且可以写多个
})

详细用法

实例代码:

// vue2 写法
<script>
import { useRouter } from "vue-router";
export default {
	data(){
		return { msg: '《帝国崛起:民族战线》' }
	},
	beforeCreate(){
		console.info("-----beforeCreate-----"); // [vue3 已删除] 此时 data数据 和 methods方法 还未完成初始化,无法调用。也不能获得DOM节点。无任何软用
		console.info("A1.组件创建前_data", this.msg);
		console.info("A1.组件创建前_dom", this.$refs['bar']);

	},
	created() {
		console.info("-----created-----"); // [vue3 已删除] 此时 data数据 和 methods方法 已经完成初始化,可以调用。但是模板还没有编译,还不能获取到 DOM节点
		console.info("A2.组件创建后_data", this.msg);
		console.info("A2.组件创建后_dom", this.$refs['bar']);
	},
	beforeMount(){
		console.info("-----beforeMount-----"); // 此时 数据已渲染出来,模板进行编译,会调用 render 函数生成虚拟DOM,但还是无法获取真实 DOM节点。
		console.info("B1.组件加载前_data", this.msg);
		console.info("B1.组件加载前_dom", this.$refs['bar']); // 可以访问各种数据、获取接口数据
	},
	mounted(){
		console.info("-----mounted-----"); // 此时 数据和DOM都已被渲染出来,也就是我们页面可以显示了。
		console.info("B2.组件加载后_data", this.msg);
		console.info("B2.组件加载后_dom", this.$refs['bar']); // 一般我们的异步请求都写在这里。
	},
	beforeUpdate() {
		console.info("-----beforeUpdate-----"); // 此时 data数据已更新,DOM节点已获取,但变化后的数据还未渲染到页面之上。view层没更新
		console.info("C1.组件更新前_data", this.msg); 
		console.info("C1.组件更新前_dom", this.$refs['bar']);
		debugger
	},
	updated() {
		console.info("-----updated-----"); // 此时 data数据已更新,DOM节点已获取,此时变化后的数据已经渲染到页面之上。view层已更新
		console.info("C2.组件更新后_data", this.msg);
		console.info("C2.组件更新前_dom", this.$refs['bar']);
	},
	beforeUnmount() {
		console.info("-----beforeUnmount-----");  // 此时 组件还没有被销毁,还可以正常使用。可用于解除一些定时器或订阅的取消
		console.info("D1.组件卸载前_data", this.msg);
		console.info("D1.组件卸载前_dom", this.$refs['bar']);
		debugger
	},
	unmounted() {
		console.info("-----unmounted-----"); // 组件 实例销毁之后执行,且 dom 完全销毁。
		console.info("D2.组件卸载后_data", this.msg);
		console.info("D2.组件卸载后_dom", this.$refs['bar']);
	},
	methods: {
		changeMsg() {
			// 点击按钮,组件更新
			this.msg = "同志们,向西太平洋,出发!";
		},
		toRouter() {
			// 跳转页面,组件销毁
			this.$router.push("/Lifecycle");
		}
	}
}
</script>
<template>
	<h3 ref="bar" @click="changeMsg">{{ msg }}</h3>
	<button @click="toRouter">跳转页面,执行组件卸载</button>
</template>

输出结果:

  1. beforeCreate[组件创建前]
  2. created[组件创建后]
  3. beforeMount[组件加载前]
  4. mounted[组件加载后]

image.png

  1. beforeUpdate ----> [组件更新前]

image.png

  1. updated ----> [组件更新后]

image.png

  1. beforeUnmount ----> [组件卸载前]
  2. unmounted ----> [组件卸载后]

image.png

父子组件生命周期执行顺序

B站视频.# 父子组件生命周期执行顺序【Vue面试题】

3、组件通信

vue2组件通信

父组件

<div id="demo">
  <input type="text" v-model="name">
    <!--子组件传递过来的参数由自定义事件绑定的方法接收-->
  <demo-child :nameFather="name" @newData="newName"></demo-child>
</div>

import DemoChild from '@/components/DemoChild'
export default {
    data(){
        return {
            name:'nihao'
        }
    },
    components:{
        DemoChild
    },
    methods:{
       //因此参数value是  '这是子组件传递过来的数据'
        newName(value){
            this.name=value
        }
    }
}

子组件

<template>
    <div id="demo_child">
        <p @click="change">DemoChild组件:{{nameFather}}</p>
    </div>
</template>

export default {
    //接收属性nameFather
    props:{
        nameFather: String
    },
    methods:{
        change(){
            //触发父组件中的自定义事件newData ,  this.$emit('需要触发的自定义事件' [,'需要传递的参数'])
            this.$emit('newData','这是子组件传递过来的数据')
        }
    }
}

vue3组件通信

# B站视频,小兔仙vue3组件通信

defineProps() 和 defineEmits()

父传子【defineProps】 ----> [子组件通过props,接收父组件的传参]
子传父【defineEmit】 ----> [子组件自定义事件,可以通过emit传递传参到父组件]

图片.png

defineExpose()

暴露子组件属性【defineExpose】 ----> [父组件通过Expose,获取子组件定义的一些属性或函数事件]

图片.png

顶底组件 provide 和 inject

顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信

provide/prəˈvaɪd/ 破肉ˈ外得,inject/ɪnˈdʒekt/ 因ˈ在克特

传递数据

图片.png

传递事件

图片.png

WebStorage存储

localStoragesessionStorage 都是浏览器提供的用于在客户端存储数据的机制,它们的主要区别如下:

(1)localStorage:数据是持久化存储的,除非被手动清除,否则数据会一直存在,即使关闭浏览器窗口、重启电脑等操作后数据依然保留。

(2)sessionStorage:数据仅在当前会话期间有效。当浏览器窗口或标签页关闭时,存储的数据将被清除。

//保存数据有3种方法:
sessionStorage.setItem("key","value");
//或
sessionStorage.key="value";
//或
sessionStorage["key"]="value";

//读取数据的3种方法,将数据赋值给变量v
var v=sessionStorage.getItem("key");
//或
var v=sessionStorage.key;
//或
var v=sessionStorage["key"];

//移除
sessionStorage.removeItem("key");
//删除所有数据
sessionStorage.clear()

4、状态管理

Json 假数据渲染

image.png

Pinia 安装 & 配置

Pinia 是 Vue 的专属的最新状态管理库 ,是 Vuex 状态管理工具的替代品

Pinia 是 Vue 的存储库(官方插件),它允许您跨组件/页面共享状态,不用再通过父组件向子组件传值。

npm install pinia

安装完成后我们需要将pinia挂载到Vue应用中。修改main.js,引入pinia提供的createPinia方法,创建根存储。

// main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia"; // 复制这里

const pinia = createPinia(); // 复制这里
const app = createApp(App);
app.use(pinia); // 复制这里
app.mount("#app");

定义store

store共有3种内容:{ 变量:state 计算属性:getter 函数:action }

// 文件路径:/src/store/user.ts

import { defineStore } from 'pinia'
// 组件名 xxxStore 在父组件引入时会用到. xxx 没啥用 必须写并且不能重名
export const xxxStore = defineStore( 'xxx', {
    // 变量 state
    state: () => {
        return {
            age: 25,
            fruitList: ['菠萝', '草莓', '樱桃']
        } 
    },
    // 计算属性 getters
    getters: {

    },
    // 函数 actions
    actions: {
        ageClick() {
            this.age++
        }
    }
})

这个例子后期要更加详细

image.png

定义store - vue2

image.png

pinia-vue2示例代码

<template>
    <h3>年龄: {{ age }}</h3>
    <h3>年龄: {{ gettersAge }}</h3>
    <button @click="addAge">加一岁</button>
</template>

<script setup>
import { storeToRefs } from 'pinia';
import { useXxxStore } from '../pinia/index.js';
// 变量、计算属性需要写在 storeToRefs() , 函数则不需要
const { age, gettersAge } = storeToRefs(useXxxStore())
const { addAge } = useXxxStore()
</script>
import { defineStore } from 'pinia'
/**
 * defineStore() 该函数接收两个参数:
 * name:一个字符串,必传项,该store的唯一id
 * options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
 */
// vue2 の写法
export const useXxxStore = defineStore( 'Xxx', {
    // 变量 state
    state: () => {
        // return 防止数据污染
        return {
            age: 25,
        } 
    },
    // 计算属性 getters
    getters: {
        gettersAge(state) {
            return state.age + 5
        }
    },
    // 函数 actions
    actions: {
        addAge() {
            this.age++
        }
    }
})
定义store - vue3 setup

image.png

pinia-vue3示例代码

<template>
    <h3>年龄: {{ xxxStore.age }}</h3>
    <h3>年龄: {{ xxxStore.gettersAge }}</h3>
    <button @click="xxxStore.addAge">加一岁</button>
</template>

<script setup>
import { useXxxStore } from '../pinia/index.js';
const xxxStore = useXxxStore()


</script>

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// vue3 setupの写法
/**
 * ref() 就是 state 属性
 * computed() 就是 getters
 * function() 就是 actions
 */
export const useXxxStore = defineStore( 'Xxx', () => {
    const age = ref(25)
    const gettersAge = computed(() => {
        return age.value + 5
    })
    function addAge(params) {
        age.value++
    }
    return { age, gettersAge, addAge }
})

State基本使用

State批量修改数据。示例代码

image.png

Getters基本使用

getter中调用其它getter
getter传参

Actions基本使用

async \ await 访问接口
import { defineStore } from 'pinia'
import request from '@/utils/request'

// Axios. API商店
export const apiStore = defineStore('apiStore', {
    state: () => {
        return {
            dataSelectAll: [], // API商店 列表数据
            dataSelectAllByUser: [], // 我的订阅 列表数据
            dataRuDetail: [], // 查看服务示例 列表数据
            WaySorting: [
                { value: '1', label: '订阅次数升序' },
                { value: '2', label: '订阅次数降序' },
                { value: '3', label: '浏览次数升序' },
                { value: '4', label: '浏览次数降序' },
            ], // 排序方式
            ApiType: [
                { value: '', label: '全部' },
                { value: '1', label: '使用中' },
                { value: '0', label: '待审核' },
                { value: '-1', label: '已过期' },
            ], // API状态
            saveInfoSuccess: false,
            ruDetailSuccess: false,
            sqlContent: '', // json编辑器里の数据

        }
    },
    actions: {
        /**
         * Axios. API商店
         * @param {number} page  // 当前页
         * @param {number} limit  // 每页几条
         * @param {string} apiName  // API名称
         * @param {string} orderType  // 排序方式
         * @param {string} startTime  // 服务有效期-开始
         * @param {string} endTime  // 服务有效期-结束
         * 
         */
        async runSelectAll(params) {
            const { data } = await request.post(`/api/apirubaseinfo/selectAll`, params )
            console.log('API商店', data)
            this.dataSelectAll = data
        },
        /**
         *  Axios. 我的订阅
         * @param {number} page  // 当前页
         * @param {number} limit  // 每页几条
         * @param {string} apiName  // API名称
         * @param {string} apiStatus // API状态
         * @param {string} startTime  // 服务有效期-开始
         * @param {string} endTime  // 服务有效期-结束
         */
        async runSelectAllByUser(params) {
            const { data } = await request.post(`/api/apirubaseinfo/selectAllByUser`, params )
            this.dataSelectAllByUser = data
        },
        /**
         * Axios. API商店 订阅
         * @param {string} apiId  // api Id
         * @param {string} apiName  // API名称
         * @param {string} apiValidPeriod  // 有效期
         * @param {string} assigneeId  // 第一级审批人 Id [暂时还没有,传空]
         * @param {string} remark  // 描述
         * @param {string} validPeriod  // 密钥有效期
         */
        async runSaveInfo(params) {
            const { data, message } = await request.post(`/api/apisubscriptionrecord/saveInfo`, {
                "apiId": params.apiId,
                "apiName": params.apiName,
                "apiValidPeriod": params.validPeriod,
                "assigneeId": '',
                "remark": params.serviceDesc,
                "validPeriod": params.keyValidPeriod,
            })
            // this.runSelectAll()
            this.saveInfoSuccess = message
            // console.log('API商店 订阅', data, message)
        },
        /**
         * 查看服务示例
         * @param {string} apiId  // api Id
         */
        async runRuDetail(params) {
            const { data, success } = await request.post(`/api/apihibaseinfo/ruDetail`, {
                apiId: params.apiId
            })
            this.dataRuDetail = data
            this.sqlContent = JSON.stringify(data.returnExample, null, 2)
            console.log('查看服务示例', data, data.returnExample )
        },
        

    }
})
多个store的使用

image.png


vueX 安装 & 配置

B站视频,Vuex从入门到实战

image.png 2.将所有的状态都写在 store文件里,main.js全局引用这个store文件 image.png

image.png

pina 和 vuex的比较

image.png

4、vue3-Router4路由

因为vue是单页应用不会有那么多html, 所有要使用路由做页面的跳转

router4 安装 & 配置

使用Vue3 安装对应的router4版本

使用Vue2 安装对应的router3版本

// 安装路由 vue-router
yarn add vue-router
// # 或 
npm install vue-router
// 可以在package.json文件中查看版本

1.在main.ts中导入

image.png

代码示例

// main.ts 挂载
import { createApp } from 'vue'
import App from './App.vue'
import vueRouter from './router'

// 注意use要在mount之前
createApp(App).use(vueRouter).mount('#app')

2.在app.vue 添加路由占位符router-view

image.png

3.新建router文件夹,创建index.js 该截图是 history路由模式

image.png

代码示例

import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router';

const routes:Array<RouteRecordRaw> = [ // RouteRecordRaw 类型, 接收参数:path 和 component。此二为必传项
    {
        path: '/',
        name: '生命周期',
        component: () => import('../components/HelloWorld.vue') 
    },
    {
        path: '/NextTick',
        component: () => import('../components/NextTick.vue') 
    },
]

const router = createRouter({
    history: createWebHistory(), // 路由的模式啊
    routes // 存放 router 信息的
})

export default router

hash & history 概念

vue2vue3
historycreateWebHistory
hashcreateWebHashHistory
abstactcreateMemoryHistory

hash —— 在hash模式下 url带了一个很丑的 # ,前端路由修改的是#中的信息,而浏览器请求时不会将 # 后面的数据发送到后台,对后端完全没有影响,因此改变 hash 不会重新加载页面。

history —— 丢掉了丑陋的#,但是它也有个问题:不怕前进,不怕后退,就怕刷新,f5,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。如果服务器中没有相应的响应或者资源,则会刷新出来404页面。

路由跳转 & 传参

使用push路由跳转,使用query路由传参

// 路由跳转,传递动态参数
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()

const toPage = () => {
  /**
   * push 路由跳转时,携带历史记录
   * replace 路由跳转时,没有历史记录
   */
  router.push({
    path: '/details',
    query: {
        msg: '《帝国崛起:民族战线》'
    }
  })
}
</script>

<template>
  <button @click="toPage">详情页</button>
</template>

接受参数,使用 useRoute 的 query

<script setup>
import { useRoute } from 'vue-router';
const route = useRoute()
</script>

<template>
 <div>{{ route.query?.msg }}</div>
</template>

路由嵌套

image.png

路由-前置导航守卫

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

const routes = [
    {
        path: '/',
        component: () => import('../components/Login.vue') 
    },
    {
        path: '/Home',
        component: () => import('../components/Home.vue'),
        children: [
            {
                path: '/Aaa',
                component: () => import('../components/Aaa.vue') 
            },
            {
                path: '/Bbb',
                component: () => import('../components/Bbb.vue') 
            },
        ]
    },
]

const router = createRouter({
    history: createWebHistory(), // 路由的模式啊
    routes // 存放 router 信息的
})
// 白名单
const whileList = ['/', '/Ccc']
// 路由-前置导航
router.beforeEach(( to, form, next) => {
    /**
     * 如果你跳转的地址,在我的白名单里,或者你有token,我允许你跳
     * 否则返回 '/' 登录页
     */
    if (whileList.includes(to.path) || localStorage.getItem('token')) {
        next()
    } else {
        next('/')
    }
})
export default router

路由重定向

{
    name: '路由重定向',
    path: '/',
    component: Home,
    redirect: '/Home', // redirect 就是路由重定向
},

5、Axios二次封装

axios安装 && 配置

npm i axios
// package.json查看版本 "axios": "^1.3.3",
// 无需在main文件中全局配置,按需引用即可 import axios from 'axios'

图片.png

requset.js文件 封装axios,写一些拦截器,请求头,超时时间 图片.png

基础请求get、post

<script setup>
    import axios from 'axios'
    function getHandle() {
        /**
         * params 传递到服务器端的数据,拼接在url地址的后面
         * headers 表示请求头 里面可以写 Authorization:token
         */
        // get请求
        axios.get(`https://api.vvhan.com/api/hotlist?type=${'bili'}`)
        axios.get('https://api.vvhan.com/api/hotlist', {
            params: {
                type: 'bili'
            },
            headers: {}
        })
        // post请求
        axios.post('https://api.vvhan.com/api/hotlist', {
            type: 'bili' // post 请求体写法
        }, { 
            params: {}, // post请求 也可以将入参拼接在url地址的后面
            headers: {}
        })
    }
</script>
<template>
    <button @click="getHandle">get请求</button>
</template>
// 测试post接口
axios.post('http://8.130.106.81:58084/bonchqdec/radarImport/selectRadarKpiList')
  .then(function (response) {
    console.log(response);
  })

axios请求封装

src下创建utils/request.js文件

image.png

示例代码

import axios from 'axios';

const service = axios.create({
    baseURL: "https://api.vvhan.com",
    timeout: 5000,
    // headers: headers
})


export default service

全局拦截

import axios from 'axios';

const service = axios.create({
    baseURL: "https://api.vvhan.com",
    timeout: 5000,
    // headers: headers
})
//请求拦截器: 在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
service.interceptors.request.use(config => {
    // 在发送请求之前做一些事情
    if (localStorage.getItem('token')) {
        config.headers['Authorization'] = localStorage.getItem('token')
    }
    return config;
},error => {
    // 处理请求错误
    return Promise.reject(error);
});

//响应拦截器:包含两个函数(一个是成功返回的函数,一个是失败的返回的函数)
service.interceptors.response.use(response => {
    // 在发送响应之前做一些事情
    if (response.status !== 200) {
        console.log('你接口又报错倆')
    }
    return response;
},error => {
    // 做一些响应错误的事情
    return Promise.reject(error);
});

export default service

token & cookie

token

token的意思是令牌,是用户第一次登录时,服务器生成的一段加密字符串,然后返回给客户端(浏览器),后面客户端每次向服务端请求资源的时候,只需要带上token,不用再带着用户名和密码去请求。为什么要带token呢,是因为用户登录成功后,后续需要反复到服务端去获取数据,服务器对每一次请求,都要去验证是哪位用户发送的请求,这样返回请求数据库,会对数据库造成压力。当后续请求都带上token后,服务器直接解密token,就可以知道用户的相关信息,省去了查询数据库的操作,减轻数据库的压力,这就是token的作用。一般成功登录第一次后,会把token存在sessionStorage中, 并在请求拦截器统一封装,让每一个请求都带上token。

cookie

withCredentials/krəˈdenʃ(ə)lz/位置ˈ克抡笨搜滋: true, // 允许携带cookie

const service = axios.create({
  baseURL: 'http://172.16.82.15:8081/approveService',
  withCredentials: true, // 允许携带cookie
  timeout: 50000 // 请求超时时间
})

image.png

6、api文档工具

Apifox

image.png

7、[JS]数组方法

map 和 forEach区别?

共同点

  • 只能循环数组,并遍历数组中的每一项。
  • 支持3个参数,
    • 数组中的当前项 item,
    • 当前项的下标索引 index,
    • 原始数组 array

区别

  • forEach() 没有返回值,会改变原数组
  • map()方法返回一个新数组,原数组不变

map

图片.png

图片.png

forEach

图片.png

for..in 和 for..of 区别?

B站视频,# for in 和 for of的区别??

日常工作中主要是处理,循环数组、循环对象、循环数组对象

区别
for..in遍历得到数组中的下标索引 index可枚举的数据:数组、字符串、对象
for..of遍历得到数组中的每一项 item可迭代的数据:数组、字符串、Set、Map

示例代码

/**
 * 循环数组
 * for..in循环出来的是数组中的下标索引 index。
 * for..of循环出来的是数组中的每一项 item。
 */
const arr = ['a', 'b', 'c']
for (const key in arr) {
    // for..in循环出来的是数组中的下标索引 index
    console.log('数组[for..in]', key) // 0,1,2
}
for (const val of arr) {
    // for..of循环出来的是数组中的每一项 item。
    console.log('数组[for..of]', val) // a,b,c
}
/**
 * 循环对象
 * for..in循环用于遍历对象的属性名、和属性值
 * for..of报错,不能循环对象
 */
const obj = {
    name: '张三',
    age: 18,
}
for (const key in obj) {
    // for..in 循环用于遍历对象的属性名、和属性值
    console.log('对象[for..in]', key) // name, age
    console.log('对象[for..in]', obj[key]) // '张三', 18
}
for (const val of obj) {
    // for..of 循环对象会报错
    console.log('对象[for..of]', val) // 报错了!
}

replace()替换

replace() 替换字符串的某元素,并返回替换后的字符串

var str = "《帝国崛起:民族战线》"
str.replace(/民族战线/, '12')  // '《帝国崛起:12》'

split()截取

把字符串分割为字符串数组

var time = "2023-02-28T11:25:00.075Z"
time.split('T')[0] // '2023-02-28' 
time.split('T')[1] // '11:25:00.075Z'

8、[JS]八股文面试题

数据类型

JS的8个数据类型

【前端面试JS】JS的8个数据类型重点讲解es11新增的bigint类型

基本数据类型(7个):number、string、boolean、null、undefined、symbol、bigInt
引用数据类型(1个):object,里面包含有Math对象,数组对象、正则对象、函数(Function)

  • 基本数据类型(7个)

    • 数字 number
    • 字符串 string
    • 布尔 boolean
    • 对象为空不存在的,转为数值时为0 null
    • 变量已经声明,但是没有赋值 undefined
    • ES6新增的 symbol
    • ES11新增的 bigInt
  • 引用数据类型(1个)

    • 对象数据类型 object,object里面又包含如下对象等

      • 普通对象 {}
      • 数组对象 []
      • 正则对象 /^$/
      • 日期对象 new Date
      • 数学函数对象 Math
      • 函数对象 Function

图片.png

JS数据类型4种判断方法

【前端面试JS】判断JS数据类型4种方法每种方法优缺点总结和检测原理

  1. 使用typeof操作符:typeof操作符可以返回某个值的数据类型
typeof 42;  // 返回 "number"
typeof 'hello';  // 返回 "string"
typeof true;  // 返回 "boolean"
typeof {};  // 返回 "object"
typeof function(){};  // 返回 "function"
typeof undefined;  // 返回 "undefined"
  1. 使用Object.prototype.toString方法:通过调用Object.prototype.toString方法,可以得到一个对象的具体类型。
Object.prototype.toString.call(42);  // 返回 "[object Number]"
Object.prototype.toString.call('hello');  // 返回 "[object String]"
  1. 使用instanceof操作符:instanceof操作符可以检测一个对象是否属于某个特定的类型
42 instanceof Number;  // 返回 false
'hello' instanceof String;  // 返回 false
  1. 使用constructor /kənˈstrʌktər/肯死抓克特儿属性:constructor属性指向创建该对象的构造函数。通过判断对象的constructor属性,可以得到对象的具体类型
(42).constructor;  // 返回 Number
'hello'.constructor;  // 返回 String

图片.png

判断一个对象为空的方法

B站视频.震惊对象为空的方法

  1. JSON.stringify(),这个方法可以将json对象,转换为json字符串。我们只需要判断序列化后的对象,是否等于字符号花括号即可 '{}'【推荐】

图片.png

  1. Object.keys(),这个方法会把对象中的属性名取出来,以数组形式返回,我们只需要判断这个数组的长度是否为0,如果为0,说明该对象中一个属性都没有,也就是说他是个空对象。

图片.png

  1. Object.getOwnPropertyNames(),判断方法同上

图片.png

  1. Reflect.ownKeys(), /rɪˈflekt//əʊn/ 瑞福来克特,欧恩Key ,判断方法同上【推荐】

图片.png

  1. for..in遍历对象

变量、作用域、函数提升

# 纯干货 js变量和函数提升动画详解 作用域以及var和let区别

JS作用域

全局作用域 任何地方都可以访问的变量

// 全局作用域:任何地方都可以访问的变量
var a = "全局变量"
function Mona () {
  console.log(a)
}

函数作用域 只在当前函数体内可以访问

// 函数作用域:只在当前函数体内可以访问
function Mona () {
  var a = "函数体内变量"
  console.log(a)
}

块级作用域 通常是指一对大括号包裹的代码片段if、for、while

{
  let a = 22
  const b = 33
}

JS的三种声明方式,let、const、var的区别

(1)用于块级作用域: let和const具有块级作用域,var声明的变量有于函数作用域或全局作用域。

(2)重复定义变量: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。

(3)先声明后赋值: 在变量声明时,var 和 let 可以先声明后赋值,默认初始值是undefined。const声明变量必须设置初始值,赋值后不可更改。

区别varletconst
能否用于块级作用域×✔️✔️
能否重复定义变量✔️××
能否先声明后赋值✔️✔️×

变量提升

console.log(a) // undefined
var a = 1 
// 在执行代码时,Js会将变量的声明,提升到当前作用域顶部,并且是为赋值状态,所以会输出undefined
// 按照如下代码执行顺序
var a
console.log(a) // undefined
a = 1 

函数提升

console.log(Mona) 
var Mona = 1
console.log(Mona)
function Mona () {
  return '666'
}
console.log(Mona)
// 在同一作用域,如果函数名、变量名相同。变量声明会被函数声名覆盖
// 按照如下代码执行顺序
function Mona () {
  return '666'
}
console.log(Mona) // 输出 function Mona()
Mona = 1
console.log(Mona) // 输出 1
console.log(Mona) // 输出 1

for 循环机制,含setTimeout

for 循环机制

for (let i = 0; i < 5; i++) {
    // 循环体
    // console.log(i) // 1、2、3、4
}
/**
 * 执行的顺序如下:
 * 第一步 : i=0  初始化值
 * 第二步 : i<5 进行条件判断,如果为真,则继续执行
 * 第三步 : 执行循环体的内容
 * 第四步 : i++ 变量i自增
 * 第五步 : 回到第二步,条件判断为真,则执行循环体内容,再到i++一直循环,
 * 直到第二步的判断条件为假,则退出该循环
 */
for(条件1;条件2;条件3){  
     循环体4  
} 
**执行的循环: 1243 243 243 ....直到条件2为假则退出循环**

面试题

var a = 10
function A() {
    console.log('1', a) // undefined
    var a = 20
    console.log('2', a) // 20
    for (var a = 1; a < 5; a++) {
        setTimeout(() => {
            console.log('3', a) // 4个 5。setTimeout为异步最后执行
        }, 10);  
    }
}
A()
console.log('4', a) // 10

image.png

答案解析

for (var i = 0; i < 10; i++){
    setTimeout(() => {
        console.log(i);
    }, 1000)
}

image.png

setTimeout是异步执行,10ms后往任务队列里面添加一个任务,只有主线上的全部执行完,才会执行任务队列里的任务,当主线执行完成后,i是10,所以此时再去执行任务队列里的任务时,i全部是10了。 对于打印10次是:每一次for循环的时候,settimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里面,等待执行,for循环了4次,就放了4次,当主线程执行完成后,才进入任务队列里面执行。

for (let i = 0; i < 10; i++){
  setTimeout(() => {
    console.log(i);
  }, 1000)
}

image.png

因为for循环头部的let不仅将i绑定到for循环快中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过 var 定义的变量是无法传入到这个函数执行域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function就能使用 i 这个变量了;这个匿名函数的参数作用域 和 for参数的作用域 不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的。


JS中构造函数、回调函数、箭头函数

回调函数

# 3分钟搞懂回调函数 每个开发者都具备的基础能力

回调函数就是普通函数,当函数作为参数传递给另一个函数,作为参数传递的函数就改名叫回调函数了。

图片.png

汉堡制作函数 v3.0 -- 多种汉堡[回调函数示例]

// 汉堡制作函数 v1.0 -- 一种汉堡
function Hamburger_v1 () {
  console.log('1.烤面包')
  console.log('准备牛肉饼 + 酱料配菜')
  console.log('食材叠加,组装汉堡🍔')
}
Hamburger_v1()

// 汉堡制作函数 v2.0 -- 多种汉堡
function Hamburger_v2 (type) {
  console.log('2.烤面包')
  
  if(type === '牛肉堡') {
    console.log('准备牛肉饼🥩 + 酱料配菜')
  } else if (type === '鸡腿堡') {
    console.log('准备鸡腿肉🍗 + 酱料配菜')
  } else if (type === '龙虾堡') {
    console.log('加入6只小龙虾🦐 + 酱料配菜')
  }
  
  console.log('食材叠加,组装汉堡🍔')
}
Hamburger_v2('牛肉堡')
Hamburger_v2('鸡腿堡')
Hamburger_v2('龙虾堡')

// 汉堡制作函数 v3.0 -- 多种汉堡[回调函数]
function beef() {
  console.log('准备牛肉饼🥩 + 酱料配菜')
}
function chicken() {
  console.log('准备鸡腿肉🍗 + 酱料配菜')
}
function prawn() {
  console.log('加入6只小龙虾🦐 + 酱料配菜')
}
function Hamburger_v3 (type) {
  console.log('3.烤面包')
  type()
  console.log('食材叠加,组装汉堡🍔')  
}
Hamburger_v3(beef)
Hamburger_v3(chicken)
Hamburger_v3(prawn)

JS中this相关问题

js的this指向

熟练掌握JS函数相关体系,包括但不限于构造函数、回调函数、箭头函数等等

箭头函数与普通函数的区别

语法简洁性:

  1. 箭头函数的语法更为简洁,可以在一些情况下省略大括号、return 关键字和参数括号
  2. 普通函数的语法相对繁琐,需要使用 function 关键字、大括号和参数括号

this 绑定:

  1. 箭头函数的 this 绑定是词法作用域的,它会捕获当前上下文中的 this 值,无法通过 call、apply 或bind 改变。
  2. 普通函数的 this 绑定是动态的,取决于函数的调用方式和上下文。

arguments 对象:

  1. 箭头函数没有自己的 arguments 对象,它会继承外层作用域的 arguments 对象(如果有的话)
  2. 普通函数具有自己的 arguments 对象,其中包含了函数调用时传递的参数。

构造函数:

  1. 无法通过 new 关键字实例化对象箭头函数不能用作构造函数
  2. 普通函数可以用作构造函数,可以通过 new 关键字实例化对象

  1. 写法不同,箭头函数使用箭头定义,写法简洁。 普通函数使用function定义。
  2. 箭头函数都是匿名函数,而普通函数既可以是匿名函数,也可以是具名函数。
  3. 箭头函数不能作为构造函数来使用,普通函数可以用作构造函数,以此来创建一个对象的实例。
  4. this指向不同,箭头函数没有this,在声明的时候,捕获上下文的this供自己使用,一旦确定不会再变化。在普通函数中,this指向调用自己的对象,如果用在构造函数,this指向创建的对象实例。普通函数可以使用call,apply
  5. 箭头函数没有arguments(实参列表,类数组对象),每一个普通函数在调用后都有一个arguments对象,用来存储实际传递的参数。
  6. 箭头函数没有原型,而普通函数有。

继承

原型&原型链

在JS中,每一个函数都自带一个 prototype 属性,我们就把 函数名.prototype 称为原型。也叫“显式原型”。当我们打印 函数名.prototype 时,打印出来的这个对象,称作原型对象

    /**
     * 什么是原型?
     * 在JS中,每一个函数都自带一个 prototype 属性,我们就把 函数名.prototype 称为原型。也叫“显式原型”
     * 当我们打印 函数名.prototype 时,打印出来的这个对象,称作原型对象
     * 
     * 原型对象 默认又有俩个属性:
     * 1.constructor(构造器): 指向构造函数本身
     * 2.__Proto__(隐式原型):指向其上一级的原型
     * 
     * [[prototype]]其实就是__proto__ 因为各大浏览器厂家取名不同
     */

    //  1.构造函数
    function MM(name) {
        this.name = name
    }

    // 2.实例化 - 创建对象
    const mm = new MM('哈妮克孜')

    // 3.打印
    console.log(mm) // 实例化对象mm
    console.log(MM.prototype) // 原型对象
    console.log(MM.prototype.constructor === MM) // true =>  指向函数本身
    console.log(MM.prototype === mm.__proto__) // true => 
    // __proto__和prototype不太一样,
    // __proto__是对象拥有的隐式原型,prototype是函数拥有的显式原型

JS中闭包、防抖截流

闭包【概念题】

3W1H,这个东西是什么。在哪里用,为什么这么用,以及怎么去用

闭包是指能够访问另一个函数作用域中变量的一个函数,简单来讲,在我们JS当中只有函数内部的子函数才能访问局部变量,所以闭包可以理解为,定义在一个函数内部中的函数,就是闭包。

使用闭包的主要目的,实现JS中的封装,因为js没有强类型语言的三大基本特征,封装继承多态。所以想要在js中封装就可以使用闭包,设置一些私有的方法跟变量。

优点:避免全局变量的污染,因为在全局作用域去定义一个变量,就会出现变量污染的问题,使用闭包就不会了、

缺点:闭包会常驻在我们的内存中,会增大内存使用量,使用不当就会出现内存泄漏

闭包的特征:1.函数嵌套函数。2.函数内部可以引用外部函数中的这个参数跟变量。3.参数和变量不会被js的垃圾回收机制回收。

闭包主要用于防抖截流

内存泄漏

函数在形成闭包的这个过程中,就会产生内存泄漏。只需要在使用完这个函数,把函数内的变量销毁掉,就会避免内存泄漏

防抖和节流

是性能优化

防抖[回城]: 用户在短时间内多次触发事件时,只取最后一次触发结果。60秒短信

节流[金身]: 用户在短时间内多次触发事件时,将次数压缩一次,取决于定时器设定的时间

区别共同点区别应用场景
防抖 debounce在事件频繁被触发时只执行最后一次input搜索框、scroll滚动条,减少http请求
节流 throttle减少事件执行次数有规律的执行60秒短信
防抖
// 输入框内容变化时的回调
function inputSearch_onChange(params) {
  // 获取输入框 value
  debounce(function () {
    console.log('打印', params.target.value)
    // ...执行 Axios 调取数据
  })
}
// 防抖
let timer = null
function debounce(fn) {
  if (timer) clearTimeout(timer) // 规定时间内若定时器存在则清除
  timer = setTimeout(() => {
    fn()
  }, 1000);
}
节流
function inputSearch_onSearch(params) {
  
  throttle(function () {
    console.log('点击搜索按钮', params)
  })
}
// 节流
let flag = true
function throttle(fn) {
  if(flag) {
    setTimeout(() => {
      console.log('触发点击')
      fn() // 调用接口
      flag = true // 在定时器执行后 移除if阻断
    }, 1000);
  } 
  flag = false // 在执行一次后 if阻断定时器继续执行
}

图片预加载和懒加载

当页面中有很多图片的时候,图片加载就需要一定的时间,不仅会影响渲染速度,还浪费服务器性能和带宽。而懒加载就是优先加载可视区的内容,其他内容等进入了可视区域再进行加载。我们要实现懒加载需要做到2个步骤,1.如何加载图片,2.如何判断进入可视区域。先说加载图片,我们知道图片都是根据图片标签上的src属性进行加载的,所以在图片进入可视区域前,我们先不给src属性赋值,或者给一个很小的loading图地址,等到图片进入可视区域后再给src附上真正的地址。再来看图片是否进入了可视区域。

方法一.IntersectionObserver

有个方法叫做交叉观察器,intersectionObserver。/ˌɪntəˈsekʃn//əbˈzɜːvə(r)/.音特塞克伸,额波蜇窝,这个api可以自动观察一个元素是否可见或者两个元素是否相交,一般用于实现图片懒加载、内容无限滚动等功能

image.png

代码示例

// 自定义指令 v-lazy
export default {
    mounted(el) {
        console.log('el', el)
        const imgSrc = el.src // 复制图片src地址
        el.src = '' // 默认将src清空

        // 交叉观察者 API
        const lazyLoad = new IntersectionObserver((entries) => {
            // 当元素出现在可视区域, 和离开可视区域被触发
            console.log('entries', entries[0].isIntersecting)
            if (entries[0].isIntersecting) {
                // isIntersecting 为 true 说明进入可视区域
                el.src = imgSrc // 进入可视区域 还原sec地址,实现懒加载
                // 停止观察
                lazyLoad.unobserve()
            }
        });
        lazyLoad.observe(el)
    }

}
方法二. vue-lazyload

安装vue-lazyload插件,在main.js  中全局引入,然后在图片元素src属性名称写成v-lazy即可


简述vue2, vue3的区别

# B站视频,搞懂Vue2和Vue3

1.双向数据绑定的原理【核心】

vue2 首先,Vue通过Object.defineProperty()/ˈprɑːpərti/ 普绕ˈ破忒方法对数据进行劫持,监听数据的变化,并通过getter和setter方法对数据进行读写。当数据发生变化时,Vue会通知所有订阅者进行更新,使得数据和视图完成双向数据绑定。并且Object.defineProperty()方法会遍历所有对象属性,无论是否用到,它都会运行一遍,徒增内存消耗。

补充:订阅者是Vue中的一个概念,每一个挂载到视图上的组件,都可以被看成一个订阅者。当数据发生变化时,Vue会通知所有的订阅者进行更新。

vue3 通过ES6的Proxy代理的方式/ˈprɒːksi/ 普绕ˈ克西依,拦截对象中的属性变化,通过Reflect/rɪˈflekt/ 瑞ˈ福来克特反射函数进行读写、添加、删除等操作。vue3可以用ES module按需引入/ˈmɑːdʒuːl/猫灸,减少内存消耗,优化用户体验。

2.根标签

vue2 vue2中的template/ˈtempleɪt/ 泰母ˈ泼累特模板,在最外层必须要有一个根标签,根节点

vue3 但在vue3有个fragement的/ˈfræɡmənt/ 福ˈ赖哥闷特虚拟标签。支持放置多个根标签,根节点

3.API类型不一样

vue2 选项式API,数据放在data里、方法放在method里/ˈmeθədz/咩色滋。watch和computed也要单独存放。需要多个对象来描述组件的逻辑。

vue3 组合式API,有一个setup函数,定义变量用ref和reactive,而方法可以直接写到setup函数中。可以将一个功能的所有代码放到一块,代码简洁,便于维护。

补充:ref 声明数据类型:数字、字符串、数组。reactive 只能声明数据类型:对象

4.生命周期

vue3的生命周期相对于vue2的少了beforeCreate和created,而多了setup函数来替代。 Vue3的生命周期前面多了一个ˈonˈ关键字。 并且Vue3中可以重复写同一个生命周期函数,例如写上好几份onMounted(()=>{})
补充:beforeCreate组件创建前、created组件创建后

5.组件通信

vue2

  1. 父传子[props]: 子组件通过props,接收父组件的传参
  2. 子传父[$emit]: 子组件自定义事件,可以通过emit传递传参到父组件,父组件监听事件来接收参数

vue3

Vue3的组件通信前面多了一个ˈdefineˈ关键字。分别是defineProps() 和 defineEmits()。
还新增了跨层组件通信,顶层组件向任意的底层组件传递数据和方法。顶层组件通过provide/prəˈvaɪd/ 破肉ˈ外得函数提供数据,底层组件通过inject/ɪnˈdʒekt/ 因ˈ在克特函数获取数据。

6.vuex 和pinia的区别

vue2 vuex是一个公共的状态管理工具,通常用于管理一些公共的状态。还可以用于给俩个毫不相关组件,进行值的传递。vuex有五个核心

  1. state 储存公共状态,或者说是公共的数据
  2. getters,处理state数据后的一些结果,类似于computed
  3. mutations 用于更改state数据
  4. actions 处理一些异步任务,就是接口请求
  5. modules 项目过大时,要分模块去开发,每个模块有自己独立的状态管理,解释:可能你A模块要用到state里的name数据,B模块也要用到name数据,我们要把他用在不同的模块中。

使用方法:组件中使用Vuex的state数据用this.store.$state、使用mutations的一个方法,用this.store.$commit,用actions的一个方法,用this.store.$dispatch。开发过程中还会使用辅助函数,比方说mapState、mapMutations、mapActions。来快速操作state、getters、mutations以及actions中的方法和属性

vue3 pinia最重要的是,搭配 TypeScript 一起使用

pinia 没有 mutations,而actions的使用不同,在actions中可以处理同步也可以处理异步,getters的使用是一致的。

pinia 没有总出口全是模块化,需要定义模块名称,当多个模块需要协作的时候需要引入多个模块,vuex是有总入口的,在使用模块化的时候不需要引入多个模块。

pinia 在修改状态的时候不需要通过其他api,vuex需要通过commit,dispatch去修改所以在语法上比vuex更容易理解和使用,灵活

7.构建工具vite、webpack

vue2 webpack

  1. 构建方式:webpack需要分析整个项目依赖,进行多次文件扫描和转译。尤其在大型项目中,构建会慢一点。
  2. 开发模式:webpack的是HMR热模块替换,来实现开发,配置复杂。
  3. 插件系统:webpack时间久远,拥有庞大的生态插件系统。
  4. 编译方式:webpack使用了有多种加载器来处理多种不同类型的资源。
  5. 总结:webpack灵活强大,适用于大型复杂需要自定义配置的项目,构建速度慢,配置复杂,插件生态庞大

vue3 Vite

  1. 构建方式:Vite特点是轻量级,速度快,极速构建。它是利用ES模块中的一些特性去构建一些正在编译的文件,而不是整个项目,这样使得在开发环境下vite的构建是你写到哪里,它就构建到哪里。
  2. 开发模式:Vite也支持HMR热模块替换,无需任何配置,默认支持。
  3. 插件系统:vite插件少。
  4. 编译方式:vite使用原生浏览器导入来处理模块不需要大规模编译打包。
  5. 总结:Vite适合快速搭建小应用场景,速度快,0配置启动,原声ES模块支持,插件少

8.diff算法

vue2 vue2中的diff算法是遍历每一个虚拟节点,进行虚拟节点的对比。并返回一个patch对象,用来存储俩个节点不同的地方。用patch去记录一个消息去更新dom。缺点是对于不更新的元素,有一定的性能消耗。

vue3 vue3给每一个虚拟节点添加一个动态标识patchflag/派吃ˈ福赖哥/,去比较标识发生变化的节点,进行一个视图更新,标识没有变化的元素,在渲染的时候就直接复用

深浅拷贝

深浅拷贝只适用于对象。

  1. 浅拷贝: 假设B复制了A,当修改A时,B也跟着变。这说明只拷贝了指针,AB实际上还是共用的一份数据。

  2. 深拷贝: 假设B复制了A,当修改A时,A变,B没有变。复制对象不受原对象的影响,因为不仅拷贝了指针,还拷贝了内存。各自内容互相独立。

浅拷贝实现

把变量A直接赋值给变量B就是浅拷贝

<script type="text/javascript">
    const obj1 = {
        name: '张三',
        age: 18,
        address: { city: '北京' },
        nobby: [ '台球', '篮球']
    }
    const obj2 = obj1 //  浅拷贝
    obj2.address.city = '上海'

    console.log('obj1', obj1)
    console.log('obj2', obj2)
</script>

image.png

深拷贝实现

4种方法

(1) JSON.parse(JSON.stringify(obj))

js内置的JSON序列化和反序列化方法 缺点: 不能存放函数,不支持循环引用、不支持时间对象和正则

<script type="text/javascript">
    const obj1 = {
        name: '张三',
        age: 18,
        address: { city: '北京' },
        fn: function name(params) {
            
        },
        nobby: [ '台球', '篮球'],
    }
    // const obj2 = obj1 //  浅拷贝
    const obj2 = deepClone(obj1) // 深拷贝
    obj2.address.city = '上海'

    console.log('obj1', obj1)
    console.log('obj2', obj2)
    // 方法一:JSON序列化
    function deepClone(params) {
        return JSON.parse(JSON.stringify(params))
    }
</script>

image.png

(2) structuredClone()推荐

B站视频.# ES11深拷贝有新方法了-structuredClone

structuredClone /ˈstrʌktʃərd//kloʊn/ 斯抓克特儿得,克隆恩

优点:支持循环引用、支持时间对象和正则

缺点: 不能存放函数

图片.png

(3) 递归

优点:对于任何类型的对象都有效,包括循环引用。

缺点:对于大型对象可能会消耗大量内存,并可能导致堆栈溢出。

<script type="text/javascript">
    const obj1 = {
        name: '张三',
        age: 18,
        address: { city: '北京' },
        fn: function name(obj) {
            
        },
        nobby: [ '台球', '篮球'],
    }
    // const obj2 = obj1 //  浅拷贝
    const obj2 = deepClone(obj1) // 深拷贝

    obj2.age = 20
    obj2.address.city = '上海'

    console.log('obj1', obj1)
    console.log('obj2', obj2)
    // 方法二:递归
    function deepClone(obj) {
        if (typeof(obj) !== 'object' || typeof(obj) === null) {
            // 如果他不是一个对象,或者压根儿就是一个null。我们也没有必要处理它了
            return obj
        }
        // instanceof 判断基本数据类型的方法
        let res = obj instanceof Array ? [] : {}
        // for in
        for (const key in obj) {
            if (obj.hasOwnProperty(key)) {
                res[key] = deepClone(obj[key])
            }
        }
        return res
    }
</script>

image.png

(4) lodash.cloneDeep

优点:支持更多类型的对象和库,例如,支持 Proxy 对象。

缺点:会引入依赖导致项目体积增大。

const _ = require('lodash');
function deepClone(obj) {
    return _.cloneDeep(obj);
}

图片.png

跨域、同源策略、HTTP协议

什么是跨域、同源策略、HTTP协议

  1. HTTP协议:包括浏览器(客户端)、服务器(服务端)两个实体。当我们想访问某篇博文时。客户端会发送请求(例如 https://www.bilibili.com)给服务器,服务器收到API会解析他们,并返回浏览器相应的数据资源。

  2. 同源策略:是为了防止恶意脚本窃取数据。浏览器制定了同源策略(即协议、域名和端口号)https 协议www.bilibili.com域名8080 端口号

  3. 跨域:协议、域名和端口号,三者之间任意一个不同就是跨域。

http和https到底有什么区别?

HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

解决跨域

#B站视频,前端跨域解决方案

(1) 本地代理

本地代理,它是在浏览器和服务器中间,添加了一个代理的中转站。每次发起请求和返回数据时都要通过这个中转站。

浏览器 -》代理 -》中转站

vue.config.js文件里面设置过这样的一段代理代码,他不仅将我们的服务启动起来,还做了一个代理转发的功能。他就是请求通过这个server转到了后端地址,跳过了浏览器的同源策略,这样本地运行就不会跨域了。

【Vite本地代理截图, + 官网配置地址】

图片.png

【webpack本地代理截图, + B站配置视频】

图片.png

(2) JSONP

(前端+后端) 不受同源策略影响的标签:

<script src="http://1ocalhost:4000/getJSONP?fn=setJSONP"></script>

JSONP方法,只支持get请求,不支持post请求

(3) CORS(跨域资源分享)

后端设置响应头中的Access-Control-Alow-Origin字段,该属性表示哪些域名可以访问资源,如果设置为星*表示所有网站都可以访问资源

res.header('Access-Control-Allow-Origin'"*")
// res.header("Access-Control-Allow-Origin',"http://localhost:3000')
(4) Nginx反向代理

需要下载软件,然后配置server里的响应头Access-Control-Alow-Origin/ˈækses/哎克赛斯,/kənˈtrəʊl/ 肯臭

cookie、session、jwt、token`记录管理状态


异步处理进化史【Promise】

B站视频这个讲的更好 # ES6 Promise的用法,# ES7 async/await异步处理同步化

B站视频# 同步与异步 promise,async/await精讲,极速入门

同步与异步小概念

图片.png

JS从一出生就是一门单线程语言,所有程序都需要排队执行。起初我认为这是极好的,直到有一天,当遇到定时器或者网络请求的时候经常被卡住。我还需要暂停下手头的工作,等待这些卡住的任务完成,以至于体验极差。为了解决这个问题,我将任务分成了同步和异步俩个类型。

所有的编程语言都有同步和异步的概念,他们是两种不同的编程模型。

  1. 同步:所有的代码,一行一行的排队执行。不需要等待的就是同步任务。例如:let a = 1

图片.png

  1. 异步:当碰到setTimeout、ajax网络请求,这种需要等待的任务时。跳过等待继续执行后续的代码

图片.png

宏任务和微任务

异步操作执行的机制,涉及到事件循环的微任务和宏任务
JS的执行机制是自上而下,先去执行同步代码,然后是微任务,然后去渲染dom,最后去执行宏任务

微任务:需要连贯执行:Promise、async、await
宏任务:不需要连贯执行:setTimeout、setInterval、Ajax、DOM事件

1.0版本 ajax

2.0版本 Promice

通过同步的方式执行异步任务

但凡是出现.then()就是内置封装了Promice。我们常用的axios就是封装了的

promise是解决异步的方法,本质上是一个构造函数,可以用它实例化一个对象。对象身上有 resolve、reject、all,原型上有then、catch方法。

promise对象有三种状态:

  • pending (初识状态/进行中) 、
  • resolved或fulfilled (成功)
  • rejected (失败)

3.0版本 awite 和synce 异步处理同步化

ES7的新规范,这两个命令是成对出现的,如果使用await没有在函数中使用async命令,那就会报错,如果直接使用async没有使用await不会报错,只是返回的函数是个promise,可以,但是没有意义,所以这两个一起使用才会发挥出它们本身重要的作用。

在vue中,await是等待的意思,await关键字只能放在async函数里,await配合async一起使用,相当于把异步函数变成了同步,await会等待后面表达式的返回结果之后才执行下一步。

nextTick的使用

概念

/neks'tɪk/ 耐克斯'忒克 “nextTick”是 Vue.js 框架中的一个方法。主要用于当数据动态变化后,DOM 还未及时更新的问题。

在Vue里面,我们的 DOM 更新是异步执行的。所以我们在工作当中,经常遇到数据动态变化后,视图层并未立即更新。这意味着你的代码可能在 DOM 更新之前执行了。这时你可以使用 nextTick 方法。它允许你在 DOM 更新之后执行一个回调函数。在回调函数里写一些修改数据的内容。

nextTick使用的场景

我知道nextTick,也知道created。但上次面试官问我怎么在created获取dom?我怎么就想不到呢

  1. 生命周期“created”,组件创建后,此时 data数据 和 methods方法 已经完成初始化,还不能获取到 DOM节点,可以使用 nextTick 方法获取DOM
  2. Element UI库,里面有一个弹框组件,在设置弹框的visible为true(弹框显示)的时候,获取弹框里的某个dom元素是拿不到的,用了nextTick后就能获取到了
代码示例
// vue官网,vue2写法
<script>
import { nextTick } from 'vue'
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    async increment() {
      this.count++
      // DOM 还未更新
      console.log(document.getElementById('counter').textContent) // 0
      await nextTick()
      // DOM 此时已经更新
      console.log(document.getElementById('counter').textContent) // 1
    }
  }
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>
// vue3 写法
// 添加input框,并获取焦点
<script setup lang='ts'>
import { nextTick, ref } from 'vue'

const isShow = ref(false)
async function addInput() {
    isShow.value = true
    await nextTick(() => {
        document.getElementById('test')?.focus()
    })
}
</script>

<template>
    <input v-if="isShow" id='test' />
    <button @click="addInput">添加input,获取焦点</button>
</template>
// vue3 写法二
// 获取列表更新后的高度
<script setup lang='ts'>
import { nextTick, ref } from 'vue'

const arr = ref(['AA', 'BB', 'CC'])
async function addEE() {
    arr.value.push('EE')
    console.log('len数_arr.value.length', arr.value.length) // 4
    console.log("len数_nextTick()前", document.getElementById('test')?.children.length) // 3
    await nextTick()
    console.log("len数_nextTick()后", document.getElementById('test')?.children.length) // 4
}
</script>

<template>
    <ul id="test">
        <li v-for="(item, index) in arr" :key="index">{{ item }}</li>
    </ul>
    <button @click="addEE">添加EE</button>
</template>

虚拟DOM && diff算法

虚拟DOM

在没有vue之前,我们需要用document.getElementById('')方法,获取dom元素并对其操作来更新视图。而vue时代之所以能通过,更改数据来更新视图。就是用js模拟了我们的dom结构,并计算出我们的一个变更,最后再把虚拟DOM节点更新为真正的DOM节点。相比于真实的元素节点,虚拟dom可以减少操作dom次数,从而提高性能

diff算法

当页面的内容发生了修改,会生成出新旧两个虚拟dom树,用diff算法,遍历新旧虚拟树,同一层级 比较 标签名、key值,如果两个值都一样,则认为是同一个节点。以此类推比较下一层级。发现有不同,则进行更新属性。找出差异最小化更新视图。diff算法本质上就是两个JS对象的差异


9.搭建 vue3项目遇到的问题

// 升级新安装最新版本的 `@vue/cli`
yarn global add @vue/cli
// # 或 npm install -g @vue/cli

// 创建项目 hello-vue3
vue create hello-vue3

1.新建项目时,报错如下:

error eslint-plugin-vue@8.5.0: The engine "node" is incompatible with this module. Expected version "^12.22.0 || ^14.17.0 || >=16.0.0". Got "12.19.0"

解决方案:

yarn config set ignore-engines true

2.git提交代码时,不提交node_modules文件

// 新建文件 .gitignore
.DS_Store
node_modules/

image.png

4.安装 Ant Design Vue 和less

// 安装 less 要安装俩个插件 less、less-loader
yarn add less
yarn add less-loader@5.0.0
// 安装 ant-design-vue
yarn add ant-design-vue

image.png

1.在main中导入 image.png

import { createApp } from 'vue'
import App from './App.vue'
import vueRouter from './router'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'

// 注意use要在mount之前
createApp(App).use(vueRouter).use(Antd).mount('#app')

5.安装axios和vue-request

0.安装

yarn add axios
yarn add vue-request
# or
npm install vue-request

image.png

1.最基础用法

image.png

axios工作中使用

axios不需要在main中导入,创建api文件包,

image.png

import Axios from 'axios'

const baseURL = 'http://172.16.15.94:9898/goSchool/'

const biubiu = Axios.create({
    baseURL: baseURL, // 请求接口
    timeout: 150000, // 请求超时时间 15秒
    // headers: headers,
})
// request 请求拦截器[前台在向后台发送请求时,要做的事]
biubiu.interceptors.request.use(

)
// response 响应拦截器[后台返回数据时,要做的事]
biubiu.interceptors.response.use(
    // 1.根据返回的状态码,404、跳转页面
)

export default biubiu
vue.config.js 解决跨域
module.exports = {
    devServer: {
        proxy: {
            '/api': { //   /api 表示拦截以 /api开头的请求路径
                target: 'http://172.16.15.94:9898/goSchool', // 跨域的域名
                changeOrigin: true, // 是否开去跨域
                pathRewrite: {
                    '^/api': '' // 重写路由
                }
            }
        }
    }
}

image.png

image.png

6.通过Cookie控制登录状态

通过Cookie或Session控制Vue页面的登录状态

Cookie存在浏览器端,Session存在服务器 image.png

document.cookie查看是否有cookie image.png

项目中写代码遇到的问题

0.css动态样式

:class='[homeif ? "header cur" : "header"]'

1.全局化配置,插件显示英文的问题

image.png

<template>
  <a-locale-provider :locale="locale">
  <router-view></router-view>
  </a-locale-provider>
</template>

<script>
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN';
export default {
  name: 'App',
  components: {
  },
  data() {
    return {
      locale: zhCN,
    }
  }

}

2.表单提交后,清空

不忘记,:model=“formState”。也要写 image.png

<template>
  <a-form ref="formRef" :model="formState" >
    <a-form-item label="Activity name" name="name2">
      <a-input v-model:value="formState.name2" />
    </a-form-item>
    <a-form-item>
      <a-button @click="resetForm">Reset</a-button>
    </a-form-item>
  </a-form>
</template>
<script>
import { defineComponent, reactive, ref, toRaw } from 'vue';
export default defineComponent({
  setup() {
    const formRef = ref();
    const formState = reactive({
      name2: '',
    });

    const resetForm = () => {
      formRef.value.resetFields();
    };

    return {
      formRef,
      formState,
      resetForm,
    };
  },
});
</script>

3.复制邀请码

<!-- 复制邀请码 -->
<template #inviteCode="{ record }">
    <a-checkable-tag @change="clickInviteCode(record.inviteCode)">{{record.inviteCode}}</a-checkable-tag>
    <input id="copy_content" type="text" value="" style="position: absolute;top: 0;left: 0;opacity: 0;z-index: -10;"/>
</template>

// 复制邀请码
function clickInviteCode(params) {
    //获取点击的值
    var clickContent = params;         
    //获取要赋值的input的元素
    var inputElement =  document.getElementById("copy_content");
    //给input框赋值
    inputElement.value = clickContent;
    //选中input框的内容
    inputElement.select();
    // 执行浏览器复制命令
    document.execCommand('Copy')
    //提示已复制
    notification["success"]({
        message: '提示',
        description: "邀请码已复制",
    });
}

4.css媒体查询 @media

<style lang="less" scoped>
@media screen and ( max-width: 1680px) {
    .formWrap {
        padding: 0 80px !important;
    }
}
@media screen and ( max-width: 1440px) {
    .formWrap {
        padding: 0 0px !important;
    }
}
</style>

5.夏清の遍历

image.png

addtreeIcon = (tree) => {
    if (tree === null || tree.length === 0) {
        return
    }
    for (let index = 0; index < tree.length; index++) {
        const ele = tree[index];
        ele.value = ele.key
        if (ele.isLeaf === true) {
            if (ele.use === true) {
                ele.icon = <CheckCircleTwoTone twoToneColor="#52c41a" />
            } else {
                ele.icon = <CloseCircleTwoTone twoToneColor="#eb2f96" />
            }
        }
        this.addtreeIcon(ele.children);
    }
}

6.获取上传文件的本地路径blob并展示

<html>
<head>
    <meta charset="UTF-8">
    <title>Video Demo</title>
</head>
<body>
    <input type="file" name="file" id="upload" />
    <video id="videoA" controls="true" width="100" height="200" src=""></video>
    <script>
        const upload = document.querySelector('#upload');
        const videoA = document.querySelector('#videoA');
        upload.onchange = function(){
            const file = upload.files[0];
            // 生成一个 blob:null/e539e787-f27a-4f66-940a-0f9ee4d14f42 的指向本地的路径格式
            const src = URL.createObjectURL(file);
            videoA.src = src;
        }
    </script>
</body>
</html>

js获取本地服务器路径 该路径可以在浏览器中播放 它大概长这样:blob:null/e539e787-f27a-4f66-940a-0f9ee4d14f42

// 本地服务器路径 
console.log(URL.createObjectURL(file.raw)); 
// 本地电脑路径 
console.log(document.getElementsByClassName("el-upload__input")[0].value);

7.JS截取-后面或前面的字符串

var str = "我是中国人,你是外国人" 
console.log(str.split(',')[1]) // 后面 你是外国人 
console.log(str.split(',')[0]) // 前面 我是中国人

8.js 去重总结

  1. 利用 Set 结构 以上去方式对 NaN 和 undefined 类型去重也是有效的,是因为 NaN 和 undefined 都可以被存储在 Set 中, NaN 之间被视为相同的值(尽管在 js 中:NaN !== NaN)。
const arr = [
  1,
  2,
  2,
  "abc",
  "abc",
  true,
  true,
  false,
  false,
  undefined,
  undefined,
  NaN,
  NaN,
];
const set = new Set(arr);
const list = Array.from(set);
console.log(list); // [1, 2, 'abc', true, false, undefined, NaN]

2.通过遍历 遍历是最常用的,网上说的多种去重,很多仅仅是使用不同的数组 api 而已,如 includes , indexOf 等

const arr = [
  1,
  2,
  2,
  "abc",
  "abc",
  true,
  true,
  false,
  false,
  undefined,
  undefined,
  NaN,
  NaN,
];

let list = [];

arr.forEach((item) => {
  const flag = list.some((sub) => sub === item);
  if (!flag) list.push(item);
});

console.log(list); // [1, 2, 'abc', true, false, undefined, NaN, NaN]

9.AntD Vue —下拉选择器(红玉写法)

image.png

<a-select
  style="width: 240px"
  v-model:value="formStateQQ.resplanTypeitem"
  placeholder="请选择套餐资源类型"
  :options="resplanTypeitemoptions"
  @change="resplanTypeitemChange"
>
-----
const getOption = () => {
      API.getoptionsList().then((res) => {
        if (res.data.code === 1) {
          const options = res.data.data;
          for (let i = 0; i < options.length; i++) {
            let ele = options[i];
            state.resplanTypeitemoptions.push({label:ele.name,value:ele.code});
          }
        }
      });
    };

8.# Ant Design Vue - 手动清空表格复选框勾选状态(复选框打勾残留)

例如,当勾选数据后,点击【设置办案人】操作完成后,一般业务需求情况下需要清空复选框勾选(打勾)的残留,不能用户设置完相关操作后,你的复选框依然处于选中状态,

你必须执行完操作后,手动清空复选框勾选状态,如下图所示: 9b5bf0ab47e7446b89b9f7f3c4e9bc9b.gif

<!--
你只需要关注 rowSelection / rowKey 配置
selectedRowKeys: 复选框勾选后在你本地data存储的值(后续使用)
onSelectChange: 每次复选框发生变化时调用该方法(用于更新本地data存储的值)
-->
<template>
    <div>
        <button @click="resetSelect()">清空复选框(业务完成后)</button>
        <a-table
            :columns="XXX"
            :data-source="XXX"
            :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
            :rowKey="(record,index) => {return record.id}"
        />
    </div>
</template>
export default {
  data () {
    return {
        selectedRowKeys: [],//复选框数据
    }
},
  
  methods: {
  
  	/**
     * 复选框变化时触发
     * @description 表格复选框逻辑(赋值本地data数据)
     * @param {Object} selectedRowKeys - 选中行的id
     * @param {Object} selectedRows - 整row行数据
     * @return void
     */
    onSelectChange: function(selectedRowKeys, selectedRows) {
      // 用哪个根据实际业务需求
      // console.log(selectedRowKeys, selectedRows)
      
      // 赋值同步本地data接受数据(我这里只取id即可)
      this.selectedRowKeys = selectedRowKeys
	},

	/**
     * 清空表格复选框
     * @description 业务完成后调用此方法!
     * @return void
     */
    resetSelect: function() {
      this.selectedRowKeys = []
    },
    
  }
}
————————————————
版权声明:本文为CSDN博主「王佳斌」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44198965/article/details/122130106

10.vue3 富文本编辑器 wangEditor

// 安装 editor
yarn add @wangeditor/editor
// # 或者 npm install @wangeditor/editor --save

// 安装 Vue3 组件
yarn add @wangeditor/editor-for-vue@next
// # 或者 npm install @wangeditor/editor-for-vue@next --save

wangEditor使用文档Vue3的

image.png

Vue3项目示例

image.png

11.vue3实现区域打印

vue3实现区域打印功能(vue3-print-nb)