Vue3从入门到痴呆

一、为什么学习 Vue3?

  • Vue3 运行性能大幅提升,速度是 Vue2 的 1.5 倍左右

  • Vue3 支持 tree shaking,可以进行按需编译,编译后的文件体积比 Vue2 更小

  • Vue3 组合式 API 使应用中的功能代码更聚合,使组件间公共逻辑的抽取更容易

  • Vue3 中提供了更加先进的功能,比如 teleport,suspense 等

  • Vue3 对 TypeScript 的支持更加友好,对大型前端应用的支持更加游刃有余

  • Vue 是目前国内前端使用者最多的框架,Vue 官方已经将 Vue3 作为默认版本使用

二、创建 Vue 项目

必须升级vue cli版本为5

npm install @vue/cli@5.0.4 -g


三、创建 Vue 项目

1. [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar): Vue3 语法支持
  1. TypeScript Vue Plugin (Volar)
  2. Prettier-Code formatter: 代码格式化
  3. ESLint: 代码质量检查

四、组合式 API 入口

1.setup 函数是一个新的组件选项,它被作为组合式 API 的入口
export default {
  setup () {}
}
2.setup 函数在任何生命周期函数之前执行,且函数内部 `this` 为 `undefined`,它不绑定组件实例对象
export default {
  setup() {
    console.log(this) // 1. undefined
  },
  beforeCreate() {
    console.log("before create") // 2. before create
  },
}
3.setup 函数的返回值必须是对象,对象中的属性会被添加到组件实例对象中,所以它们可以在其他选项和模板中使用
export default {
  setup() {
    let name = "张三"
    let age = 20
    return { name, age }
  },
  beforeCreate() {
    console.log(this.name);
  },
}
<template>{{ name }} | {{ age }}</template>
4.在 setup 方法中声明的变量虽然可以在模板中显示,但它不是响应式数据,就是说当数据发生变化后视图不会更新。
export default {
    setup() {
      let name = "张三"
      let age = 20
      const onClickHandler = () => {
        name = "李四"
        age = 30
      }
      return { name, age, onClickHandler }
    }
  }
 <template>
    {{ name }} | {{ age }} 
  	<button @click="onClickHandler">button</button>
  </template>

五、创建响应式数据 ref

1.ref 方法用于创建响应式数据,即数据的变化可以引起视图的更新。
//响应式数据ref 从vue中导入
import { ref } from "vue"
export default {
  setup() {
    const name = ref("张三")
    const age = ref(20)
    return { name, age }
  },
}
<template>{{ name }} | {{ age }}</template>
在 JavaScript 中通过 value 属性修改数据。(基本数据类型)
export default {
  setup() {
    const name = ref("张三")
    const age = ref(20)
    const onClickHandler = () => {
      console.log(ref('娄哥'),111)
      name.value = "李四"
      age.value = 30
    }
    return { name, age, onClickHandler }
  },
}

在这里插入图片描述

<template>
  {{ name }} | {{ age }} 
	<button @click="onClickHandler">button</button>
</template>
2.使用 ref 方法创建引用数据类型的响应式数据。
export default {
  setup() {
    const name = ref({ name: '张三', age: '18' });
    const person = () => {
      console.log(ref(name), 'name');
      name.value.name = '李四';
      name.value.age = 40;
    };
    return { name, person, a };
  },
};

在这里插入图片描述

 <div>
    <span>{{ name }}{{ age }}</span>
    <button @click="person">account</button>
 </div>

六、创建响应式数据 reactive

reactive用来创建响基于引用类型的响样式数据;对于基本数据类型不起作用
import { reactive } from 'vue';
export default {
  setup() {
    let name = reactive({ name: '张三', age: 18 });
    const onClickHandler = () => {
      console.log(name);
      name.name = '李四';
      name.age = 50;
    };
    return { name, onClickHandler };
  },
};
<template>
  <div>
    <span>{{ name }}</span>
    <button @click="onClickHandler">account</button>
  </div>
</template>
案例:在点击按钮后将 `newPerson` 中的值赋值给 `person`

这里的newPerson因为不是响样式数据,所以赋值需要for in一下

import { reactive } from 'vue';
export default {
  setup() {
    let person = reactive({ name: '张三', age: 30 });
    const newPerson = { name: '李四', age: 50 };
    const onClickHandler = () => {
      for (const key in newPerson) {
        person[key] = newPerson[key];
      }
    };
    return { person, onClickHandler };
  },
};
<template>
  <div>
    <span>{{ person }}</span>
    <button @click="onClickHandler">account</button>
  </div>
</template>

七、计算属性 computed

计算属性是指基于现有状态派生(演变)出新的状态,现有状态发生变化,派生状态重新计算,在 Vue3 中通过 computed 方法创建计算属性。(简单点就是根据依赖关系进行缓存的计算,当计算属性引用的响应式属性发生改变时才会重新计算,如果引用的属性没有改变,则调用上一次缓存值。)

export default {
  setup() {
    let num1 = ref(10);

    let sum = computed(() => {
      return num1.value * 2;
    });
    return { sum };
  },
};
<template>
  <div>{{ sum }}</div>
</template>
案例:在搜索框中输入名字,在现有名字中查找,找到后列出名称列表。
export default {
  setup() {
    const names = ref([
      '林俊杰',
      '孙燕姿',
      '周杰伦',
      '张惠妹',
      '刘若英',
      '林宥嘉',
      '刘德华',
      '张韶涵',
      '周笔畅',
      '孙楠',
    ]);
    const search = ref('');
    const filterNames = computed(() =>
      names.value.filter((name) => name.includes(search.value))
    );
    return { search, filterNames };
  },
};
<template>
  <input type="text" v-model="search" />
  <ul>
    <li v-for="name in filterNames">{{ name }}</li>
  </ul>
</template>

八、监听状态 watch

watch 方法用于监听响应式数据的变化。

1.使用 watch 方法监听基于 ref 创建的响应式数据 (基本数据类型)。
export default {
  setup() {
    const text = ref('');
    watch(text, (newVal, oldVal) => {
      console.log(newVal, 'newVal');
      console.log(oldVal, 'oldVal');
    });
    return { text };
  },
};

在这里插入图片描述

<input type="text" v-model="text" />
2.使用 watch 监听响应式数据内部的具体属性 (基本数据类型)
export default {
  setup() {
    const person = ref({ name: '张三' });
    watch(
      () => person.value.name,
      (newVal, oldVal) => {
        console.log(newVal);
        console.log(oldVal);
      }
    );
    return { person };
  },
};
<template>
  <div>{{ person.name }}</div> //输出结果 张三
</template>
3.使用 watch 监听响应式数据内部的具体属性 (引用数据类型)
import { ref, watch } from 'vue';
export default {
  setup() {
    const person = ref({ name: { title: '啊娄' } });
    const onClickHandler = () => {
      person.value.name.title = '娄哥哥';
    };
    watch(
      () => person.value.name.title,
      (newVal, oldVal) => {
        console.log(newVal, 'newVal');
        console.log(oldVal, 'oldVal');
      }
    );
    return { person, onClickHandler };
  },
};

在这里插入图片描述

<template>
  <p>{{ person.name.title }}</p>
  <button @click="onClickHandler">title</button>
</template>
4.使用 watch 监听基于 reactive 创建的响应式数据。
import { reactive, watch } from 'vue';
export default {
  setup() {
    const person = reactive({ name: '张三' });
    const onClickHandler = () => {
      person.name = '李四';
    };
    watch(
      () => person.name,
      (newVal, oldVal) => {
        console.log(newVal, 'newVal');
        console.log(oldVal);
      }
    );
    return { person, onClickHandler };
  },
};

在这里插入图片描述

<template>
  <p>{{ person.name }}</p>
  <button @click="onClickHandler">title</button>
</template>
5.使用 watch 监听多个值的变化
import { ref, watch } from 'vue';
export default {
  setup() {
    const firstName = ref('');
    const lastName = ref('');
    watch([firstName, lastName], (newVal, oldVal) => {
      console.log(newVal, oldVal);
    });
    return { firstName, lastName };
  },
};
<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>
6.使 watch 监听数据在初始时执行一次
import { ref, watch } from 'vue';
export default {
  setup() {
    const firstName = ref('');
    const lastName = ref('');
    watch(
      [firstName, lastName],
      (newVal, oldVal) => {
        console.log(newVal, oldVal);
      },
      {
        immediate: true,
        deep: true, //深度监听只有引用数据类型才需要开启,vue3内部会强制开启
      }
    );
    return { firstName, lastName };
  },
};

在这里插入图片描述

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

九、 监听状态 watchEffect

watchEffect 和 watch 一样,都是用于监听响应式数据的变化。

1.watchEffect 只关心数据的最新值,不关心旧值是什么,而且 watchEffect 默认会在初始时执行一次。	
import { ref, watchEffect } from 'vue';
export default {
  setup() {
    const firstName = ref('');
    const lastName = ref('');
    watchEffect(() => {
      console.log(firstName.value);
      console.log(lastName.value);
    });
    return { firstName, lastName };
  },
};

在这里插入图片描述

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

十、toRef 方法

在讲解toRef方法前,我们先来看一个js基础输出语句

let person = { name: "张三" };
let name = person.name;
person.name = "李四";
console.log(name); // 张三,这里涉及到的是堆栈问题;

toRef 方法用于将响应式数据内部的普通数据转换为响应式数据,并且转换后的数据和原始数据存在引用关系,存在引用关系意味着当原始数据发生变化后,toRef 转换后的数据也会跟着变化。

import { toRef, ref } from 'vue';
export default {
  setup() {
    let person = ref({ name: '张三' });
    let onClickHandler = () => {
      person.value.name = '李四';
    };//person.name 引用的是简单数据类型,但是使用ref创建了响样式数据
    return {
      name: person.value.name,//name这里获取到的只是普通数据张三
      person,
      onClickHandler,
    };
  },
};

在这里插入图片描述

<template>
  <p>{{ name }}</p>
  <p>{{ person.name }}</p>
  <button @click="onClickHandler">account</button>
</template>
解决办法:使用toRef
import { toRef, ref } from 'vue';
export default {
  setup() {
    let person = ref({ name: '张三' });
    let onClickHandler = () => {
      person.value.name = '李四';
    };
    return {
      name: toRef(person.value,"name"),//观察结果点击后输出的都是李四
      person,
      onClickHandler,
    };
  },
};

在这里插入图片描述

需求: 将模板中的 `person.brand.name` 简化成 `brandName`
import { toRef, ref } from 'vue';
export default {
  setup() {
    const person = ref({ brand: { name: '奥迪' } });
    const onClickHandler = () => {
      person.value.brand.name = '娄哥';
    };
    return {
      person,
      onClickHandler,
      brandName: toRef(person.value.brand, 'name'),//输出结果 点击之后都变为娄哥
    };
  },
};
<template>
  <p>{{ person.brand.name }}</p>
  <p>{{ brandName }}</p>
  <button @click="onClickHandler">account</button>
</template>

十一、toRefs 函数

通过 toRef 方法一次只能转换一个数据,通过 toRefs 方法可以实现批量数据转换。
toRefs 方法接收引用数据类型的响应式数据,它可以将数据中的第一层属性全部转换为响应式数据, 返回值是一个对象, 对象中存储了所有转换之后的响应式数据。

import { toRefs, reactive } from 'vue';
export default {
  setup() {
    const person = reactive({
      name: '张三',
      age: 20,
      brand: { title: '奥迪', year: 2 },
    });
    return { ...toRefs(person) };
  },
};
<template>
  <p>{{ name }}</p>
  <p>{{ age }}</p>
  <p>{{ brand.title }}{{ brand.year }}</p>
</template>
对引用数据类型内部的数据进行转换
import { toRefs, reactive } from 'vue';
export default {
  setup() {
    const person = reactive({
      name: '张三',
      age: 20,
      brand: { title: '奥迪', year: 2 },
    });
    return { ...toRefs(person), ...toRefs(person.brand) };
  },
};
<template>
  <p>{{ name }}</p>
  <p>{{ age }}</p>
  <p>{{ title }}{{ year }}</p>
</template>

十二、组件通讯props

1.父组件通过 props 向子组件传递数据

父组件

import { toRefs, ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default {
  setup() {
    const msg = ref('我是你的小可爱噢!');
    return { msg };
  },
  components: { HelloWorld },
};
<template>
  <div>
    <HelloWorld :msg="msg"></HelloWorld>
  </div>
</template>

子组件

import { computed } from 'vue';
export default {
  props: ['msg'],
  setup(props) {
    // 当父组件更新 props 时 setup 函数是不会重新执行的
    // 所以在 setup 函数中使用 props 时需要用到 computed 或者 watch 来响应 props 的变化
    // 注意: 直接在模板中使用 props 数据是没有这个问题的
    const helloMsg = computed(() => {
      return props.msg;
    });
    return { helloMsg };
  },
};
<template>
  <div>{{ msg }}</div>
  <p>{{ helloMsg }}</p>
</template>
2.子组件通过自定义事件向父组件传递数据

父组件

import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default {
  setup() {
    const msg = ref('我是你的小可爱噢!');
    const onClickHandler = (data) => {
      // console.log(data, 'data'); data就是setup第二个参数context中的emit的...args传递过来的
      msg.value = data;
    };
    return { msg, onClickHandler };
  },
  components: { HelloWorld },
};
<template>
  <div>
    <HelloWorld :msg="msg" @onClickHandler="onClickHandler"></HelloWorld>
  </div>//onClickHandler($event)这里是自定义事件 
</template>

子组件

import { computed } from 'vue';
export default {
  props: ['msg'],
  emits: ['onClickHandler'],
  setup(props, { emit }) {
    // console.log(context, '333333');
    // console.log(context.emit, '22222');
    const helloMsg = computed(() => props.msg);
    const onClickHandler = () => {
      emit('onClickHandler', '艾瑞利亚');//输出结果 点击后都变为艾瑞利亚
    };
    return { helloMsg, onClickHandler };
  },
};
<template>
  <div>{{ msg }}</div>
  <p>{{ helloMsg }}</p>
  <button @click="onClickHandler">account</button>
</template>

十三、组件生命周期

onMounted、onUpdated、onUnmounted 组件生命周期函数的执行时机

`setup`: 组件初次挂载前、重新挂载前都会执行。
`onMounted` 组件挂载完成后执行
`onUpdated` 组件数据更新后执行
`onUnmounted` 组件卸载后执行

父组件

import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default {
  components: { HelloWorld },
  name: 'App',
  setup() {
    const show = ref(true);
    return { show };
  },
};
<template>
  <button @click="show = !show">toggle</button>
  <HelloWorld v-if="show"></HelloWorld>
</template>

子组件

import { ref, onMounted, onUpdated, onUnmounted } from 'vue';
export default {
  name: 'ChildComponent',
  setup() {
    console.log('setup');
    let timer = null;
    // 组件挂载完成之后开启定时器
    onMounted(() => {
      timer = setInterval(() => {
        console.log('timer...');
      }, 1000);
    });
    // 组件卸载完成之后清除定时器
    onUnmounted(() => {
      clearInterval(timer);
      console.log(timer, '卸载完成');
    });
    const count = ref(0);
    const onClickHandler = () => {
      count.value = count.value + 1;
    };
    // 组件更新之后在控制台中输出 onUpdated
    onUpdated(() => {
      console.log('onUpdated');
    });
    return { count, onClickHandler };
  },
};
<template>{{ count }} <button @click="onClickHandler">button</button></template>

十四、与服务端通信

需求:向服务器端发送请求获取列表数据渲染列表数据, 没有数据要显示暂无数据, 如果请求报错展示错误信息, 加载过程显示loading.

import axios from 'axios';
import { ref } from 'vue';

export default {
  name: 'App',
  setup() {
    let data = ref(null);
    let error = ref(null);
    let loading = ref(false);
    async function getSever() {
      loading.value = true;
      try {
        await axios({
          url: 'https://jsonplaceholder.typicode.com/posts',
        }).then((res) => {
          data.value = res.data;
        });
      } catch (err) {
        console.log(err, '12');
        error.value = err;
      }
      loading.value = false;
    }
    getSever();
    return { data, error, loading };
  },
};
<template>
  <div v-if="loading">loading...</div>
  <div v-else-if="error">{{ error }}</div>
  <div v-else-if="data && data.length > 0">
    <ul>
      <li v-for="item in data" :key="item">{{ item.title }}</li>
    </ul>
  </div>
  <div v-else>暂无数据</div>
</template>

十五、获取 DOM 对象

1.使用 ref 获取单个 DOM 对象
import { ref, onMounted } from 'vue';
export default {
  name: 'App',
  setup() {
    const myRef = ref(null);
    onMounted(() => {
      console.log(myRef.value);
    });
    return { myRef };
  },
};
<template>
  <div ref="myRef">艾欧尼亚昂扬不灭</div>
</template>
2.使用 ref 获取一组 DOM 对象
import { ref, onMounted, onUpdated, nextTick } from 'vue';
export default {
  setup() {
    const list = ref(['a', 'b', 'c']);
    const elms = ref([]);
    console.log(elms, 'elms');
    const onClickHandler = () => list.value.push('d');
    onMounted(() => console.log(elms.value));
    onUpdated(() => console.log(elms.value));
    nextTick(() => {
      console.log(elms.value, 'elms.value');
    });
    return { list, elms, onClickHandler };
  },
};
<template>
  <ul>
    <li
      v-for="(item, index) in list"
      :key="index"
      :ref="(el) => (elms[index] = el)"
    >
      {{ item }}
    </li>
  </ul>
  <button @click="onClickHandler">button</button>
</template>

十六、provide、inject 函数

在父子组件传递数据时,通常使用的是 props 和 emit,父传子时,使用的是 props,如果是父组件传孙组件时,就需要先传给子组件,子组件再传给孙组件,如果多个子组件或多个孙组件使用时,就需要传很多次,会很麻烦。

解决:通过 provide、inject 函数的配合使用,可以实现跨组件传递数据(组件与组件存在嵌套关系)
父组件

import { ref, provide, readonly, onMounted } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default {
  components: {
    HelloWorld,
  },
  setup() {
    let person = ref({ name: '张三' });
    const onClickHandler = () => {
      person.value.name = '李四';
    };
    provide('person', readonly(person));
    provide('onClickHandler', onClickHandler);
  },
};
<template>
  <HelloWorld></HelloWorld>
</template>

子组件

import Home from '@/components/Home.vue';
export default {
  name: 'HelloWorld',
  setup() {},
  components: {
    Home,
  },
};
<template>
  <Home></Home>
</template>

孙组件

import { inject } from 'vue';
export default {
  name: 'Home',
  setup() {
    const person = inject('person');
    const onClickHandler = inject('onClickHandler');
    return { person, onClickHandler };
  },
};
<template>
  <div>{{ person.name }}</div>
  <button @click="onClickHandler">account</button>
</template> //输出结果 点击按钮后张三变李四

十七、teleport 组件(传送门Teleport)

1.teleport 组件可以将指定组件渲染到应用外部的其他位置。
2.使用Teleport 组件,可以让我们在组件的逻辑位置写模板代码,可以使用组件的data或者props状态,然后在组件的范围之外渲染它
3.比如弹框组件,它可能在任意组件中使用,但它不属于任意组件,所以不能在使用它的组件中渲染它,我们需要将它渲染到指定位置。

1.需求:包含全屏模式的组件(单个组件)
<template>
  <div>
    <button @click="modelOpen = true">点击打开弹窗</button>
    <teleport to="body">
      <div v-if="modelOpen" class="model">
        <div class="model-body">
          这是一个模态框
          <button @click="modelOpen = false">关闭弹窗</button>
        </div>
      </div>
    </teleport>
  </div>
</template>
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "ModelButton",
  setup() {
    const modelOpen = ref(false);
    return {
      modelOpen,
    };
  },
});
.model {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
}

.model-body {
  width: 300px;
  height: 250px;
  background: #fff;
}
2.需求:包含全屏模式的组件(多个组件)
//app.vue
<template>
  <teleport to="#modal">
    <HelloWorld></HelloWorld>
  </teleport>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
  components: {
    HelloWorld,
  },
};
//HelloWorld
<template>
  <div class="wrapper">
    <div class="content">
      <a class="close" href="javascript:">关闭</a>
    </div>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
};
</script>
<style scoped>
.wrapper {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.4);
}
.content {
  width: 660px;
  height: 400px;
  background: white;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
.close {
  position: absolute;
  right: 10px;
  top: 10px;
  color: #999;
  text-decoration: none;
}
</style>
//index.html
<div id="modal"></div>

十八、Suspense 组件

Suspense 用于确保组件中的 setup 函数调用和模板渲染之间的执行顺序。先执行 setup 后渲染模板。当组件中的 setup 被写成异步函数的形式, 代码执行的顺序就变成了先渲染模板后执行 setup 函数了。

//父组件
import { defineAsyncComponent } from "vue";
export default {
  components: {
    HelloWorld: defineAsyncComponent(() =>
      import("./components/HelloWorld.vue")
    ),
  },
};
<template>
  <Suspense>
    <template #default>
      <HelloWorld></HelloWorld>
    </template>
    <template #fallback> loading... </template>//添加等待提示效果
  </Suspense>
</template>
//子组件
import axios from "axios";
export default {
  name: "HelloWorld",
  async setup() {
    const data = await axios({
      url: "https://jsonplaceholder.typicode.com/posts",
    });
    return { data: data.data };
  },
};
<template>
  <div>
    <ul>
      <li v-for="item in data" :key="item">{{ item.title }}</li>
    </ul>
  </div>
</template>

十九、过渡动画transition

Vue 提供了 transition 组件供我们执行过渡动画, 我们只需要使用 transition 组件包裹你要执行动画的元素即可。执行过渡动画的前提条件是元素具有创建与销毁的操作。

import { ref } from "vue";
export default {
  setup() {
    const show = ref(false);
    return { show };
  },
};
<template>
  <div>
    <transition name="slide-fade">
      <h2 v-if="show">我是无敌的无敌的小可爱</h2>
    </transition>
    <button @click="show = !show">account</button>
  </div>
</template>
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

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

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}

具体的各种效果查看官方文档即可,https://vuejs.org/guide/built-ins/transition.html#css-based-transitions

总结

这里对文章进行总结:以上就是今天要讲的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值