项目启动

# 项目启动

使用vite创建项目

npm init vite

.
├── README.md
├── index.html           // 入口文件
├── package.json
├── public               // 资源文件
│   └── favicon.ico
├── src                  // 源码
│   ├── App.vue          // 单文件组件
│   ├── assets
│   │   └── logo.png
│   ├── components   
│   │   └── HelloWorld.vue
│   └── main.js          // 入口
└── vite.config.js      // vite工程化配置文件

安装依赖库

npm install vue-router vuex

# 规范

├── src
│   ├── api            数据请求
│   ├── assets         静态资源
│   ├── components     组件
│   ├── pages          页面
│   ├── router         路由配置
│   ├── store          vuex数据
│   └── utils          工具函数

# 配置路由

router/index.js

import { createRouter, createWebHashHistory } from "vue-router";
import Home from "../pages/home.vue";
import About from "../pages/about.vue";

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/about",
    name: "About",
    component: About,
  },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;

App.vue

<template>
  <div>
    <router-link to="/">首页</router-link> | 
    <router-link to="/about">关于</router-link>
  </div>
  <router-view></router-view>
</template>

main.js

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";

createApp(App).use(router).mount("#app");

# 新的代码组织方式

Composition API + <script setup>

  • 使用 ref 函数,可以让数据变成响应式的数据,使用时需要是.value取值

  • 在 Composition API 的语法中,所有的功能都是通过全局引入的方式使用的,并且通过 <script setup> 的功能,我们定义的变量、函数和引入的组件,都不需要额外的生命周期,就可以直接在模板中使用

  • 通过把功能拆分成函数和文件的方式,掌握到 Composition API 组织代码的方式,我们可以任意拆分组件的功能,抽离出独立的工具函数,大大提高了代码的可维护性。

  • style 标签的特殊属性,通过标记 scoped 可以让样式只在当前的组件内部生效,还可以通过 v-bind 函数来使用 JavaScript 中的变量去渲染样式,如果这个变量是响应式数据,就可以很方便地实现样式的切换。

# Vue响应式机制

响应式原理是什么呢?Vue 中用过三种响应式解决方案,分别是 defineProperty、Proxy 和 value setter。

定义个一个对象 obj,使用 defineProperty 代理了 count 属性。这样我们就对 obj 对象的 value 属性实现了拦截,读取 count 属性的时候执行 get 函数,修改 count 属性的时候执行 set 函数,并在 set 函数内部重新计算了 double。

let getDouble = n => n*2

let obj = {}
let count = 1
let double = getDouble(count)

Object.defineProperty(obj,'count',{
    get(){
        return count
    },
    set(val){
        count = val
        double = getDouble(val)
    }
})
console.log(double)  // 打印2
obj.count = 2
console.log(double) // 打印4  有种自动变化的感觉

但 defineProperty API 作为 Vue 2 实现响应式的原理,它的语法中也有一些缺陷。比如在下面代码中,我们删除 obj.count 属性,set 函数就不会执行,double 还是之前的数值。这也是为什么在 Vue 2 中,我们需要 $delete 一个专门的函数去删除数据。

delete obj.count
console.log(double) // doube还是4

Vue 3 的响应式机制是基于 Proxy 实现的,Proxy 的重要意义在于它解决了 Vue 2 响应式的缺陷。我们看下面的代码,在其中我们通过 new Proxy 代理了 obj 这个对象,然后通过 get、set 和 deleteProperty 函数代理了对象的读取、修改和删除操作,从而实现了响应式的功能。


let proxy = new Proxy(obj,{
    get : function (target,prop) {
        return target[prop]
    },
    set : function (target,prop,value) {
        target[prop] = value;
        if(prop==='count'){
            double = getDouble(value)
        }
    },
    deleteProperty(target,prop){
        delete target[prop]
        if(prop==='count'){
            double = NaN
        }
    }
})
console.log(obj.count,double)
proxy.count = 2
console.log(obj.count,double) 
delete proxy.count
// 删除属性后,我们打印log时,输出的结果就会是 undefined NaN
console.log(obj.count,double) 

Proxy 是针对对象来监听,而不是针对某个具体属性,所以不仅可以代理那些定义时不存在的属性,还可以代理更丰富的数据结构,比如 Map、Set 等,并且我们也能通过 deleteProperty 实现对删除操作的代理。

Vue 3 的 reactive 函数可以把一个对象变成响应式数据,而 reactive 就是基于 Proxy 实现的。我们还可以通过 watchEffect,在 obj.count 修改之后,执行数据的打印。


import {reactive,computed,watchEffect} from 'vue'

let obj = reactive({
    count:1
})
let double = computed(()=>obj.count*2)
obj.count = 2

watchEffect(()=>{
    console.log('数据被修改了',obj.count,double.value)
})

在 Vue 3 中还有另一个响应式实现的逻辑,就是利用对象的 getter 和 setter 函数来进行监听,这种响应式的实现方式,只能拦截某一个属性的修改,这也是 Vue 3 中 ref 这个 API 的实现。在下面的代码中,我们拦截了 count 的 value 属性,并且拦截了 set 操作,也能实现类似的功能。


let getDouble = n => n * 2
let _value = 1
double = getDouble(_value)

let count = {
  get value() {
    return _value
  },
  set value(val) {
    _value = val
    double = getDouble(_value)
  }
}
console.log(count.value, double) // 1 2
count.value = 2
console.log(count.value, double) // 2 4
实现原理 实际场景 优势 劣势 实际应用
defineProperty Vue2响应式 兼容性 数组和属性删除等拦截不了 Vue2
Proxy Vue3 reactive 基于Proxy实现真正的拦截 无法兼容IE11 Vue3 复杂数据结构
value setter Vue3 ref 实现简单 只拦截了value属性 Vue3 简单数据结构

# Vueuse 工具库

我们可以把日常开发中用到的数据,无论是浏览器的本地存储,还是网络数据,都封装成响应式数据,统一使用响应式数据开发的模式。这样,我们开发项目的时候,只需要修改对应的数据就可以了。

npm install @vueuse/core

VueUse 提供了一大批工具函数,包括全屏、网络请求、动画等,都可以使用响应式风格的接口去使用,并且同时兼容 Vue 2 和 Vue 3,开箱即用。

# 组件化

组件的开发由于要考虑代码的复用性,会比通常的业务开发要求更高,需要有更好的可维护性和稳定性的要求。

  • 通用型组件就是各大组件库的组件风格,包括按钮、表单、弹窗等通用功能。
  • 业务型组件包含业务的交互逻辑,包括购物车、登录注册等,会和我们不同的业务强绑定。

# 评级分数组件

# props属性

一个组件库首先要实现的,就是通过 props 属性渲染内容

使用 defineProps 来规范传递数据的格式,这里规定了组件会接收外部传来的 value 属性,并且只能是数字,然后根据 value 的值计算出评分的星星

<template>
  <div>
    {{rate}}
  </div>
</template>

<script setup>
import { defineProps,computed } from 'vue';
let props = defineProps({
  value: Number
})
let rate = computed(()=>"★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))
</script>

使用这个父组件,传入value的值

<template>
  <Rate :value="score"></Rate>
</template>

<script setup>
import {ref} from 'vue'
import Rate from './components/Rate1.vue'
let score = ref(3)
</script>

# 组件事件

我们需要让组件的星星可点击,并且让点击后的评分值能够传递到父组件。

在 Vue 中,我们使用 emit 来对外传递事件,这样父元素就可以监听 Rate 组件内部的变化。

对于这个通知机制,我们使用 defineEmits 定义的方式来实现,这也是 Vue 组件中重要的数据交换机制。emits 配合 props,这样我们在使用一个组件的时候,就实现了给组件传递数据,并且我们也能够监听组件内部数据的变化。

image.png

对于自定义组件来说,v-model 是传递属性和接收组件事件两个写法的简写

最后我们通过规范 props 和 emit 的名字,实现了直接在自定义的组件之上使用 v-model。

默认情况下,组件上的 v-model 使用 modelValue 作为 prop,同时使用 update:modelValue 作为事件

首先我们把属性名修改成 modelValue,然后如果我们想在前端页面进行点击评级的操作,我们只需要通过 update:modelValue 这个 emit 事件发出通知即可。

let props = defineProps({
    modelValue: Number,
    theme:{type:String,default:'orange'}
})
let emits = defineEmits(['update:modelValue'])
function onRate(num) {
  emits("update:modelValue", num);
}

使用 Rate 这个组件,也就是直接使用 v-model 绑定 score 变量。这样,就可以实现 value 和 onRate 两个属性的效果。

 <template>
<h1>你的评分是 {{score}}</h1>
<Rate v-model="score"></Rate>
</template>

# 插槽

在 Vue 中直接使用 slot 组件来显示组件的子元素,也就是所谓的插槽

除了文本,也可以传递其他组件或者 html 标签 。

# 动画

Vue 3 中提供了一些动画的封装,使用内置的 transition 组件来控制组件的动画。

<transition name="fade">
  <h1 v-if="showTitle">你好 Vue 3</h1>
</transition>

image.png

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s linear;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

# 列表动画

清单列表并不是一个单独的标签,而是 v-for 渲染的列表元素

在 Vue 中,我们把这种需求称之为列表过渡。因为 transition 组件会把子元素作为一个整体同时去过渡,所以我们需要一个新的内置组件 transition-group。在 v-for 渲染列表的场景之下,我们使用 transition-group 组件去包裹元素,通过 tag 属性去指定渲染一个元素。

# 页面切换动画

<router-view v-slot="{ Component }">
  <transition  name="route" mode="out-in">
    <component :is="Component" />
  </transition>
</router-view>

# JS动画

复杂的动画需要使用js来完成

上次更新: 2022/4/25 17:08:51