前端基础-Vue 基础

166 阅读9分钟

Vue组件生命周期函数

每个 Vue 生命周期事件都会对应 2 个钩子函数,分别在事件开始前和事件结束后调用。
在Vue app中,可以应用 4 个主要事件,所有有 8 个主要钩子函数(main hooks)。

  • Creation - 组件创建时运行
  • Mounting - 当 DOM 挂载时运行
  • Updates - 响应数据修改时运行
  • Destruction - 元素销毁时运行

Vue2 生命周期函数

  • beforeCreate - 实例初始化后立即调用
  • Created - 实例处理完状态相关的选项后立即调用
  • beforeMount - 组件被挂载前调用
  • Mounted - 组件被挂载后调用
  • beforeUpdate - 响应数据被修改,并重现渲染前调用
  • Updated - 重新渲染后调用
  • beforeDestroy - 实例销毁前调用
  • Destroyed - 实例销毁后调用

Vue3 生命周期函数

相对于 Vue2 的改变如下

  • beforeCreate -> setup
  • Created -> setup
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • Destroyed -> onUnmounted

Vue3 组件的通讯方式

Vue3 组件的通讯方式有

  • props - 父传子
  • $emit - 子传父
  • expose/ref - 暴露属性或方法
  • $attrs - 获取传过来除 props 外的属性
  • v-model - 双向绑定
  • provide/inject - 使用依赖
  • Vuex - 状态管理模式

props

2 种接收方法

<!-- 第一种方法,声明要接受的数据 -->
<script>
export default {
  // props:['list'], //数组形式

  //对象形式,定义数据类型
  props: {
    // list: Array,
    list: {
      type: Array,
      default: ()=>{return []},
    },
  },
  
  //可以在setup函数中作为参数获取
  setup(props) {
    console.log(props); //传过来数据,必须要有上面的声明
  },
};
</script>
<!-- 第二种,纯Vue3写法 -->
<script setup>
const props = defineProps({
  // list: Array,
  list: {
    type: Array,
    default: () => {return []},
  },
});
console.log(props);
</script>

$emit

用于声明组件触发的自定义事件

//发送数据的组件
<script setup>
//声明事件名和要发送的数据
//写法一
this.$emit("getName","张三");
this.$emit("getSex",18);
//写法二
const emits = defineEmits(["getName", "getSex"]);
emits("getName", "张三");
emits("getAge", 18);
</script>
//接收数据的组件
<template>
 <MyInput @getName="emitsGetName" @getAge="emitsGetAge" /> //接收自定义事件
<template/>

<script setup>
const emitsGetName = (data) => {  //data是子组件传递过来的数据
  console.log(data);  //张三
};

const emitsGetAge = (data) => {
  console.log(data);
};
</script>

expose/ref

用于声明组件被父组件通过模板引用访问时暴露的公共属性

//发送数据的组件
<script setup>
defineExpose({
  name:'子组件的属性',
  func(){
    console.log('子组件的方法')
  }
})
</script>
//接收数据的组件
<template>
  <MyInput ref="comp" />  //发送数据的组件,使用ref接收
</template>

<script setup>
import MyInput from "./components/MyInput.vue";
import { ref, onMounted } from "vue";
const comp = ref(null);
onMounted(() => {
  console.log(comp.value.name);
  comp.value.func()
});
</script>

$attrs

主要用于接收在 props 中没定义,但父组件传过来的属性

//接收数据的组件
<script setup>
import { useAttrs } from "vue";
const props = defineProps({
  // list: Array,
  list: {
    type: Array,
    default: () => {
      return [];
    },
  },
});
console.log(props);
console.log(useAttrs().data); //props 中没定义,但父组件要传的属性
</script>
//发送数据的组件
<template>
  <MyList :list="list" data="props 中没定义,但父组件要传的属性"/>
</template>

v-model

双向绑定

//子组件
<template>
  <div class="input">
    <input
      type="text"
      :value="modelValue"
      @input="$emit('update:modelValue',$event.target.value)"
    />
  </div>
</template>

<script setup>
defineProps(["modelValue"]);
defineEmits(["update:modelValue"]);
</script>
//父组件
<MyInput v-model="data" />

<script setup>
import { ref } from "vue";
const data = ref("传入的数据");
</script>

provide/inject

provide - 标记父组件中可以让后代组件访问的变量,无论多少层
inject - 子组件使用其访问变量

// Parent.vue
<script setup>
    import { provide } from "vue"
    provide("name", "沐华")
</script>

// Child.vue
<script setup>
    import { inject } from "vue"
    const name = inject("name")
    console.log(name) // 沐华
</script>

Vuex

// store/index.js
import { createStore } from "vuex"
export default createStore({
    state:{ count: 1 },
    getters:{
        getCount: state => state.count
    },
    mutations:{
        add(state){
            state.count++
        }
    }
})

// main.js
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store"
createApp(App).use(store).mount("#app")

// Page.vue
// 方法一 直接使用
<template>
    <div>{{ $store.state.count }}</div>
    <button @click="$store.commit('add')">按钮</button>
</template>

// 方法二 获取
<script setup>
    import { useStore, computed } from "vuex"
    const store = useStore()
    console.log(store.state.count) // 1

    const count = computed(()=>store.state.count) // 响应式,会随着vuex数据改变而改变
    console.log(count) // 1 
</script>

Vue 常见的优化方式

  • 路由懒加载 - import()
  • 异步组件 - defineAsyncComponent
  • 组件缓存 - keep-alive
  • 计算属性稳定性 - computed()
  • 浅响应 - shallowRef()/shallowReactive()

路由懒加载

当打包时,js文件会变得非常大,影响页面加载。我们可以将不同路由对应的组件分割成不同的代码块,在访问路时只加载相应的组件,这样就会更加高效。
使用 import()

//普通加载
import home from "../components/Home.vue";

//懒加载
const about = import("../components/About.vue")
const routes = [
  {
    path: "/",
    name: "Home",
    component: home,  
  },
  {
    path: "/about",
    name: "About",
    component: about,
    
    //或者直接使用
    // component: () => import("../components/About.vue"),  
  },
];

异步组件

将应用拆分成尽可能小的块,通过异步加载组件,在需要使用时才从服务器上加载相关组件,从而加快加载速度。
使用 defineAsyncComponent()

import { defineAsyncComponent } from "vue";

//像其他组件一样使用 AsnycComp
const AsnycComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    resolve(/*服务器获取的组件*/);
  });
});

//或者使用import() 按需引入
const AsyncComp = defineAsyncComponent(() =>
  import('./components/about.vue')
)

组件缓存

在组件切换时将状态保留在缓存中,避免重复渲染DOM,减少加载时间及性能消耗。
使用 keep-alive

<template>
  <router-link to="/" active-class="isActive">home</router-link>
  <router-link to="/about" active-class="isActive">about</router-link>
  <router-view v-slot="{ Component }">
      <keep-alive>
        <component :is="Component" />
      </keep-alive>
    </router-view>
</template>

计算属性

计算属性可以减少不必要的重复计算,从而提高性能。
使用 computed

import { computed, ref, watchEffect } from "vue";
const count = ref(0);

//判断每次值的变化,如果变化返回新的值,如果没变化,使用旧值
const coumputedObj = computed((oldValue) => {
  const newValue = { isEven: count.value % 2 === 0 };
  if (oldValue && oldValue.isEven === newValue.isEven) return oldValue;
  return newValue;
});

watchEffect(() => {
  console.log(coumputedObj.value.isEven);
});

count.value = 2; // 没变化 true
count.value = 4; // 没变化 true
count.value = 3; // 变化 false

浅响应

在类似大型列表的大小数据量中,深度响应会导致不小的性能负担,因为每个属性访问都会触发代理个跟踪。这时最好使用浅响应。

  • shallowReactive - 只处理对象外层属性的响应式
  • shallowRef - 只处理基本类型的响应式

shallowReactive

const hugeList = shallowReactive({
  //只有这一层有响应式
  name: "张三",
  age: 22,
  parent: {
    //以下都没有响应式
    fa: {
      realFa: "哈哈",
    },
  },
});

shallowRef

//响应式
const sum = shallowRef(0);

//非响应式
const sum1 = shallowRef({
  n: 0,
});

Vue 组件的多种用法

  • 动态组件 - component 组件
  • 异步组件 - defineAsyncComponent()
  • 组件缓存 - keep-alive 组件
  • 插槽 - slot 组件

动态组件

适用于多个组件来回切换的场景
component 组件

    <button @click="curComp = comp[0]">组件1</button>
    <button @click="curComp = comp[1]">组件2</button>
    <keep-alive>
    <component :is="curComp" /> // :is是当前显示的哪个组件
    </keep-alive>
  
    import home from "./components/Home.vue";
    import about from "./components/About.vue";
    import { ref } from "vue";

    const comp = ref([home, about]);
    const curComp = ref(comp[0]);

异步组件

在用到该组件时才会加载,用于优化性能
defineAsyncComponent()

从服务器加载

defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    resolve(/*从服务器获取的组件*/);
  });
});

从本地加载 配合 import()

defineAsyncComponent(() => {
  import('./component/Mycomp.vue')
});

组件缓存

组件缓存,用于缓存组件的状态,当切换时不会重新加载
keep-alive 组件

  <keep-alive>
    <component :is="curComp" />
  </keep-alive>

插槽

为子组件传入一下模板片段,在子组件固定位置中渲染这些片段
slot 组件

//父组件使用
  <home>
    <template v-slot:header> 插槽1 </template>
    <template v-slot:main> 插槽2 </template>
    <template v-slot:footer> 插槽3 </template>
  </home>
  
//home.vue
  <template>
  <div></div>
  <header>
    <!-- 如果没有传值,里面就是默认值 -->
    <slot name="header">默认值</slot>  
  </header>
  <main>
    <slot name="main"></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</template>

nextTick

用于在修改数据时,获取最新的 DOM
大概原理是理由事件循环机制,进行异步操作,在DOM异步渲染结束后,才执行里面的回调函数,这样就可以获取最新的 DOM

  <ul>
    <li v-for="(item, key) in list" :key="key">{{ item.name }}</li>
  </ul>
  <button @click="add">添加</button>
  
  const list = ref([
  {
    name: "张三",
    age: 10,
  },
  {
    name: "李四",
    age: 12,
  },
  {
    name: "王五",
    age: 15,
  },
]);
const add = () => {
  list.value.push({
    name: "新增",
  });
  // 添加前的长度
  console.log(document.querySelectorAll("li").length);

  //添加后的长度
  nextTick(() => {
    console.log(document.querySelectorAll("li").length);
  });
};

Vue3 的 refreactive

ref

ref 的作用是将一个原始数据类型转换成一个响应式的数据类型,在这个数据改变时,用到这个数据的地方就会改变,原始数据类型共七个:NumberStringBigIntBooleanSymbolNullUndefined

const data = ref("你好")

//模板内容 当 data 的值改变时,这里也会更新
<p>{{ data }}</p> 

reactive

reactive 的作用是接收一个对象/副本的参数,返回一个响应式副本,在对象的属性改变时,用到这个对象的地方就会更新

const list = reactive([
  {
    name: "张三",
    age: 10,
  },
  {
    name: "李四",
    age: 12,
  },
  {
    name: "王五",
    age: 15,
  },
]);

//模板内容 当 list 的值改变时,这里也会更新
 <ul>
   <li v-for="(item, key) in list" :key="key">{{ item.name }}</li>
 </ul>

Vue2 和 Vue3 的区别

  • 双向绑定数据的原理不同
  • 是否支持碎片
  • API 类型
  • 定义数据变量和方法不同
  • 父子传参方法不同
  • 生命周期的钩子函数不同

双向绑定数据的原理不同

vue2:通过 OObject.defineProperty() 它会遍历每个属性,都给加上get和set方法,进行数据劫持,结合发布订阅模式,达到双向绑定。
vue3:使用 ES6 的 proxy 代理实现。

是否支持碎片

vue2template中必须有根标签,不支持碎片。
vue3:可以没有跟标签,支持碎片。

//vue2
<template>
  <div>
      <p>template 下必须有根标签</p>
  </div>
</template>

//vue3
<template>
  <p>template 下可以没有根标签</p>
</template>

API 类型

vue2选项式 API,vue2 中将代码分割成不同部分,比如:数据放在 data 里、方法放在methods,还有 computed、watch之类的。
vue3组合式 API,vue3 中将代码都放在一块,可以用我们自己的方法来分割,这样使得更简洁,也便于维护。

定义数据变量和方法不同

vue2:定义的数据放在 data 中,方法反正 methods 中
vue3:定义的数据和方法都放在 setup 中

//vue2
<script>
export default {
  data() {
    return {
      data: "0",
      data1: "1",
    };
  },
  methods: {
    func() {},
    func1() {},
  },
};
</script>

//vue3
<script setup>
data1 = ref("0");
data2 = ref("1");

const func = ()=>{}
const func1 = ()=>{}
</script>

父子传参方法不同

vue2

  • 父传子 props 需要先声明传过来的 props,才能获取
  • 子传父 emit需要使用this.emit 需要使用 this.emit 传入事件名和参数才能调用函数

vue3

  • 父传子 props 可以在 setup 第一个参数获取
  • 子传父 emit 可以在 setup 第二个参数用分解对象直接使用

生命周期的钩子函数不同

vue2:八个钩子函数
vue3:去掉了 beforeCreate 和 created 用 setup 代替

Vuex 的五个属性

  • state - 存储数据
  • getters - 储存对stae中数据做处理后的结果
  • mutations - 提交更新数据的方法,只能是同步操作
  • actions - 和mutations类似,但是提交的 mutations,可以包含异步操作
  • muduels - vuex模块化,使每个模块都有自己的 state、getter、mutations、actions
export default createStore({
  //储存数据
  state: {
    count: 1,
  },
  
  //储存获取数据的方法
  getters: {
    getCOunt: state => state.count,
  },

  //储存更新数据的方法,同步
  mutations: {
    add(state) {
      state.count++;
    },
  },

  //异步
  actions: {
    add(state) {
      setTimeout(() => {
        state.count++;
      });
    },
  },

  //模块化
  modules: {},
});

使用:

import { useStore } from "vuex";

const store = useStore();

//获取数据
console.log(store.state.count);

const handleClick = () => {
  // 触发mutations,用于同步修改state的信息
  store.commit("updateInfo", "nihao");

  // 触发actions,用于异步修改state的信息
  store.dispatch("updateInfo", "hi");
};

Vue 路由守卫

路由守卫分类

  • 全局守卫
    • beforeEach(to, from, next) - 进入路由前触发
    • beforeResolve(to, from, next) - 路由解析前触发
    • afterEach(to, from, next) - 进入路由后触发
  • 路由守卫
    • beforeEnter(to, from, next) - 进入特定路由前触发
  • 组件守卫
    • beforeRouteEnter(to, from, next) - 进入组件前触发
    • beforeRouteUpdate(to, from, next) - 组件更新前触发
    • beforeRouteLeave(from, next) - 离开组件前触发

路由守卫的回调参数

  • to - 要进入的目标路由对象
  • from - 要离开的路由对象
  • next - 进行管道中的下一个钩子,有 next 参数的钩子必须使用 next() 来 resolve() 这个钩子,不然会卡在这里,不会进行下一个钩子

全局守卫

//全局前置守卫
router.beforeEach((to, from, next) => {
  //身份验证
  const user = store.state.user;
  if (user || to.path === "login") {
    next();
  } else {
    next("login");
  }
});

//全局解析守卫
router.beforeResolve((to, from, next) => {
  //在解析前执行的逻辑
  next();
});

//全局后置守卫
router.afterEach((to, from, next) => {
  //在导航完成后执行的逻辑
  next();
});

路由守卫

const routes = [
  {
    path: "/",
    name: "Home",
    component: home,
  },
  {
    path: "/about",
    name: "About",
    component: about,
    beforeEnter: () => {
      //在进入该路由前执行的逻辑
      console.log("进入about");
    },
  },
];

组件守卫

<script setup>
import {
  onBeforeRouteUpdate,
  onBeforeRouteLeave,
} from "vue-router";

//组件中使用
//onBeforeRouteEnter 不能使用

onBeforeRouteUpdate((to,from,next) => {
  //组件更新前执行的逻辑
  console.log("组件更新");
  next();
});

onBeforeRouteLeave((to,from,next) => {
  //离开组件前执行的逻辑
  console.log("离开组件");
  next();
});
</script>

Vue 数据响应式的原理