vue2~vue3

发布于 2022-06-25  115 次阅读


Vue2

vue学习代码

推荐观看

Vue 是一套用于构建用户界面的 渐进式框框架 可自底向上逐层应用

渐进式就是逐步实现新特性的意思。如实现模块化开发 路由 状态管理的新特性 其特点是综合了Angular(模块化)和React(虚拟DOM)的优点

image-20220610164317765

网络通讯:axios 前端通讯框架

页面跳转:vue-router

状态管理:vuex

Vue-UI:ICE

vue 特点

  • 采用 组件化 模式,提高代码复用率,并且更好维护
  • 声明式 编码 无需操作 DOM 提供开放效率

模板语法

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

<div id="app">
    <h1>{{ message }}</h1>
</div>

<script>
    var app = new Vue({
        el: '#app',
        data: {
            message: 'Hello Vue! '
        }
    })
</script>

数据绑定

v-bind 单向绑定

<a v-bind:href="url">点击</a>
<a :href="url">点击</a>

v-model 双向绑定

Vue中有2种数据绑定的方式:

​ 1.单向绑定(v-bind):数据只能从data流向页面。

​ 2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。

备注:

​ 1.双向绑定一般都应用在表单类元素上(如:input、select等)

​ 2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。

v-model:value 简写 v-model 因为 v-model 默认收集的就是 value 值

<input type="text" v-model="message">
<h1>{{ message }}</h1>

el 的两种写法

var app = new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue! ' + new Date().toLocaleString(),
        url: 'http://www.baidu.com'
    }
})
var app = new Vue({
    data: {
        message: 'Hello Vue! ' + new Date().toLocaleString(),
        url: 'http://www.baidu.com'
    }
})
// 挂载
app.$mount('#app')

data 的两种写法

对象式

var app = new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue! ' 
    }
})

函数式

var app = new Vue({
    el: '#app',
    data()  {
        message: 'Hello Vue! ' 
    }
})

生命周期

  • 在 Vue 实例初始化时,Vue 将调用 $mount() 方法
  • 在 Vue 实例挂载到 DOM 时,Vue 将调用 $mount() 方法
  • 在 Vue 实例被销毁时,Vue 将调用 $destroy() 方法

生命周期

常用生命周期钩子

  • mounted():发送 ajax 请求 启动定时器 初始化数据 创建子组件 绑定自定义事件 订阅信息等
  • beforeDestroy():销毁子组件 取消订阅信息 移除自定义事件 移除定时器

​ 销毁后借助vue开发着工具看不到任何信息·

​ 销毁后自定义事件失效 但原生DOM事件仍然可以触发

js 调式

debugger;

MVVM (Model-View-ViewModel)

是一种软件架构设计模式 是一种简化用户界面的事件驱动编程方式

MVVM 源自于经典的MVC 模式

MVVM 的核心是 ViewModel 层 负责转化Model 中的数据对象来让数据更加容易管理和使用

M : 模型(Model) 对应 data 中的数据

V : 试图(View) 模块

VM : 试图模型(ViewModel) Vue 实例对象

image-20220610181906620

数据代理

回顾:

Object.defineProperty

let number = 18;
let person = {
    name: '张三',
    sex: '男'
}
Object.defineProperty(person, 'age',{
    // value: 18,
    enumerable:true,  // 控制属性是否可以枚举, 默认为 false
    writable: true,   // 控制属性是否可以修改, 默认为 false
    configurable: true,  // 控制属性是否可以删除, 默认为 false

    // 当有人读取 person 的 gae 时 get 就会被调用,并且返回值就是 age 的值
    get (){
        return number
    },

    // 当有人写入 person 的 age 时 set 就会被调用,并且传入的值就是 age 的值
    set (value){
        console.log(value);
    }

})

数据代理:通过一个对象代理另一个对象中的属性的操作(读 写)

let obj1 = {x:100}
let obbj2 = {y:200}

Object.defineProperty(obj2, 'x',{
    get (){
        return obj1.x
    },

    set (value){
        obj.x = value   
    }
})

vue 数据代理

image-20220610232055546

vue2 vue3 响应式原理

vue2: Object.defineProperty

vue3: Proxy(代理)Reflect(反射)   

let person = {
            name: 'xiaotao',
            age: 18
        }

        // 模拟vue2中实现响应式
        let p = {}
        Object.defineProperty(p, 'name', {
            get() {  // 有人读取name时调用
                return person.name
            },
            set(newValue) {  // 有人修改name时调用
                console.log('有人修改name属性 我要去更新页面');
                person.name = newValue
            }
        })
        Object.defineProperty(p, 'age', {
            get() {  // 有人读取age时调用
                return person.age
            },
            set(newValue) {  // 有人修改age时调用
                console.log('有人修改age属性 我要去更新页面');
                person.age = newValue
            }
        })

        // 模拟vue3中实现响应式
        /* - 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
           - 通过Reflect(反射): 对源对象的属性进行操作。 */
        let proxy = new Proxy(person, {
            get(target, key) {
                console.log(`有人读取 ${key} 属性`);
                return Reflect.get(target, key)
            },
            set(target, key, value) {
                console.log(`有人修改或添加 ${key} 属性 我要去更新页面`);
                return Reflect.set(target, key, value)
            },
            deleteProperty(target, key) {
                console.log(`有人删除 ${key} 属性 我要去更新页面`);
                return Reflect.deleteProperty(target, key)
            }
        })

mixin 混入

作用: 将一段需要重复编写的方法 抽离出来 定义下成为一个全局可以使用的组件

方便代码复用

mixin.js

export const mixin = {
    methods: {
        showName() {
            alert(this.name);
        }
    }
}

全局混入

main.js

// 全局引入混合
import {mixin} from './mixin'

Vue.mixin(mixin)

new Vue({
  render: h => h(App),
}).$mount('#app')

局部混入

<template>
    <div>
        <h3 @click="showName">学生姓名:{{name}}</h3>
    </div>
</template>

<script>
    import { mixin } from '../mixin' 
   export default {
       name: 'Student',
       data() {
           return {
                name: '张三'
           }
       },
        mixins: [mixin]
   }
</script>

vue 插件 plugins

增强 vue 的使用

plugins.js

export default {
    install(Vue, x, y, z) {
        console.log(x, y, z);
        console.log('install', Vue);    
        // 全局过滤器
        Vue.filter('mySlice', function(value) {return value.slice(0, 4)});

        // 全局指令
        Vue.directive('fbind', {
            // 指令与元素成功绑定时
            bind(el, binding, vnode) {
                console.log('bind');
                el.value = binding.value;
            },
            // 指令所在元素被插入页面时
            inserted(el, binding, vnode) {
                console.log('inserted');
                el.focus();
            },
            // 指令所在模板被重新解析时
            update(el, binding, vnode) {
                console.log('update');
                el.value = binding.value;
            }
        });

        // 全局混合
        Vue.mixin({
            data() {return {a: 1, b: 2}},
        });

        // 给 Vue 原型添加一个方法
        Vue.prototype.hello = function() {
            console.log('hello');
        }
    }
}

main.js

// 引入自定义插件
import plugins from './plugins'
// 使用插件
Vue.use(plugins)

浏览器本地存储

localStorage

localStorage.setItem('data', JSON.stringify(data));

let data = localStorage.getItem('data');

localStorage.removeItem('msg');

localStorage.clear();

sessionStorage

sessionStorage.setItem('data', JSON.stringify(data));

let data = sessionStorage.getItem('data');

sessionStorage.removeItem('msg');

sessionStorage.clear();

Axios

是一个开源的可在浏览器端 和 NodeJS 的异步通信框架 主要作用就是实现AJAX 的异步通信

功能:

  • 从浏览器创建 XMLHttpRequests
  • 从node.js 创建 http 请求
  • 支持 Promise API [js 链式编程]
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转化 JSON 数据
  • 客户端支持防御 XSPF (跨站请求伪造)

为什么使用?

由于 Vue.js 是一个视图层框架 并且严格遵守 SoC(关注度分离原则) 所以 Vue 不包含 AJAX 的通信功能 为了解决通信问题

作者单独开发 vue-resource , 2.0 后停止更新 推荐使用 Axios 框架

{
  "name": "狂神说Java",
  "url": "https://blog.kuangstudy.com",
  "page": 1,
  "isNonProfit": true,
  "address": {
    "street": "含光门",
    "city": "陕西西安",
    "country": "中国"
  },
  "links": [
    {
      "name": "bilibili",
      "url": "https://space.bilibili.com/95256449"
    },
    {
      "name": "狂神说Java",
      "url": "https://blog.kuangstudy.com"
    },
    {
      "name": "百度",
      "url": "https://www.baidu.com/"
    }
  ]
}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <div>{{info.name}}</div>
            <div>{{info.address.country}}</div>
            <a v-bind:href="info.url">点我</a>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
        <script type="text/javascript">
            var vm = new Vue({
                el:"#app",
                data() {
                    return{
                        info: {
                            name: null,
                            address: {
                                country: null,
                                city: null,
                                street: null
                            },
                            url: null
                        }
                    }
                },
                mounted() {  //钩子函数  链式编程
                    axios
                        .get('data.json')
                        .then(response => (this.info = response.data));
                }
            })
        </script>
    </body>
</html>

计算属性

1.定义:要用的属性不存在,要通过已有属性计算得来。

2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。

3.get函数什么时候执行?

(1).初次读取时会执行一次。

​ (2).当依赖的数据发生改变时会被再次调用。

4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便

5.备注:

​ 1.计算属性最终会出现在vm上,直接读取使用即可。

​ 2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

let vm = new Vue({
        el: '#app',
        data: {
        fastName: 'xiao',
        lastName: 'tao'
        },
        computed: {
        fullName() {
        return this.fastName + ' - ' + this.lastName
        }
        }
 })

第一个 Vue-cli 项目

vue-cli 是官方提供的一个脚手架 用于快速生成一个vue 的项目模板

主要的功能:

  • 统一的目录结构
  • 本地调试
  • 热部署
  • 单元测试
  • 集成打包上线

需要的环境:

确认nodejs 是否安装成功

cmd

node -v # 查看是否能打印出版本号即可
npm -v  # 查看是否能打印出版本号即可

npm : 就是一个软件包管理工具 就和linux 下的apt 软件安装差不多

安装 Node.js 淘宝镜像加速器 (cnpm)

npm install cnpm -g   # -g 就是全局安装

# 或使用如下
npm install --registry=https://registry.npm.taobao.org

安装 vue-cli

cnpm install vue-cli -g

测试是否安装成功

查看可以基于哪些模板创建vue 通常选择 webpack

vue list  

创建第一个 vue-cli

cmd 去到一个文件夹 如 D:vue

vue init webpack myvue   # myvue 项目名
? Project name myvue
? Project description A Vue.js project
? Author xiaotao
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? No
? Set up unit tests Noc
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) no

   vue-cli · Generated "myvue".

# Project initialization finished!
# ========================

To get started:

  cd myvue
  npm install (or if using yarn: yarn)
  npm run dev

Documentation can be found at https://vuejs-templates.github.io/webpack

初始化并运行:

cd myvue
npm install
npm run dev

打包成功访问

脚手架创建vue项目

vue create  项目名称

WebPack

安装打包工具

npm install webpack -g

npm install webpack-cli -g
# 删除
npm uninstall -g webpack
npm uninstall -g webpack-cli

测试安装成功

webpack -v 
webpack-cli -v

配置

创建webpack.config.js 配置文件

  • entry :入口文件 指定webpack用哪个文件作为项目的入口

  • output:输出 指定webpack把处理完成的文件放置到指定路径

  • module:模块 用于处理各种类型的文件

  • resolve:设置路径指向

  • piugins:插件 如:热更新 代码重用

  • watch:监听 用于设置文件改动后直接打包

  • 创建一个项目

    2021-10-06_163955

  • 创建一个名为modules 的目录 用于放置js模块

  • 在modules下创建模块文件 如 hello.js 用于编写js模块相关代码

    // 暴露一方法
    exports.sayHei = function(){
    document.write('

    ES6 规范

    ') };
  • 在modules下创建一个main.js 的入口文件 用于打包设置entry属性

    var hello = require('./hello');  // 类似于java 的导包 导入 hello.js
    hello.sayHei();
  • 在项目目录下创建webpack.config.js 配置文件

module.exports = {
    entry:  './modules/main.js', // 入口
    output: {
        filename: './js/bundle.js'    //输出到哪
    }
}
  • 使用 webpack 命令打包

    webpack  
    webpack --watch   # 类似于 idea 的热部署  实时更新

打包成功后:

  • 在项目目录下创建index.html 引入打包后的js

    运行

传值

父传子

使用 props 获取父传过来的值

data() {
        return {
            todo: [
                {
                    id: '001',
                    name: '吃饭',
                    status: true
                }
            ]
       }
 }
<template>
    <user-list :todo='todo' />
</template>

<div>
    {{ todo }}
</div>
 props: ['todo']

子传父

先在父类定义一个接收数据的方法 子类调用该方法传值给父类

<user-header :receive='receive' />
methods: {
        receive(x) {
            console.log('收到子组件的值',x)
        }
}

props: ['receive'],
// 子组件传值给父组件
this.receive(todo)

父传孙

data() {
        return {
            todo: [
                {
                    id: '001',
                    name: '吃饭',
                    status: true
                }
            ]
       }
 }
<template>
    <user-list :todo='todo' />
</template>

<template>
    <user-itme :todo='todo' />
</template>
props: ['todo']

 props: ['todo']
<div>
    {{ todo }}
</div>

自定义事件实现 子传父

方式一

<student @xiaotao="getStudentName" />
getStudentName(name) {
    console.log('学生姓名: '+ name);
}

<button @click="sendStudentName">把学生姓名给App</button>
sendStudentName() {
    // $emit 触发自定义事件
    // 触发 Student 组件实例上的 xiaotao 事件
    this.$emit('xiaotao', this.name);
}

方式二

<student ref='student'/>
mounted() {
    this.$refs.student.$on('xiaotao', this.getStudentName);
}

解绑事件

<button @click="unbind">解绑 xiaotao 事件</button>
unbind() {
    // $off 解绑自定义事件
    // 解绑 Student 组件实例上的 xiaotao 事件
    this.$off('xiaotao');
    // 解绑多个事件
    // this.$off(['xiaotao', 'xiaotao2']);
    // 解绑所有
    this.$();
}

给实例绑定原生事件

<student  @click.native="show" />
show() {
    console.log('点击了');
}

全局事件总线

任意组件间通信

image-20220615145854240

配置全局事件总线

new Vue({
      beforeCreate() {
        Vue.prototype.$bus = this    // 配置全局事件总线
      }
})

取值组件

mounted() {
    this.$bus.$on('xiaotao', (name) => {
            console.log(name);
    });
},
    // 销毁自定义事件
    beforeDestroy() {
               this.$bus.$off('xiaotao');
    }

传值组件

sendStudentName() {
        this.$bus.$emit('xiaotao', this.name);
}

信息订阅与发布

任意组件间通信

  • 订阅消息: 消息名
  • 发布消息: 消息内容

使用第三方库

pubsub.js

安装

npm i pubsub.js

引入

import PubSub from 'pubsub-js'

取数据

mounted() {
    this.pudid = PubSub.subscribe('xiaotao', (msg, data) => {
        console.log('有人发布了 '+ msg +' 消息, 消息回调执行了: '+data);
    });
},
    beforeDestroy() {
        PubSub.unsubscribe(this.pudid);
    }

提供数据

PubSub.publish('xiaotao', this.name);

$nextTick()

DOM 节点更新后执行

过渡与动画

Animate.css

Animate.css | A cross-browser library of CSS animations.

安装

npm install animate.css --save

导入

    import 'animate.css';
<transition-group 
                  name="animate__animated animate__bounce"
                  enter-active-class="animate__swing"
                  leave-active-class="animate__backOutUp"
                  appear>
    <h1 v-show="!isShow" key="1">hello</h1>    
    <h1 v-show="isShow" key="2">vue</h1>    
</transition-group>

vue axios

npm i axios
<template>
  <div>
      <button @click="getDept">获取部门信息</button>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: "App",
  methods: {
    getDept() {
      axios.get('http://localhost:5000/').then(res => {
        console.log(res.data)
      }, err => {
        console.log(err)
      })
    }
  },
};
</script>

存在问题 跨域

image-20220616125631803

解决方式:

后端: cors nainx

前端:

代理服务器 vue-cli

配置 vue.config.js

devServer: {
    port: 8080,
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''       // 去掉路径中的api
        }
      }
    }
  }

修改请求

axios.get('http://localhost:8080/api')

image-20220616140333501

vue-resource

跟 axios 类似 是 vue 的一个插件库

npm i vue-resource

main.js

import vueResource from 'vue-resource'

Vue.use(vueResource)

插槽 slot

默认插槽

<template>
  <div class="container">
      <Category title="美食">
          <img src="https://avatars.githubusercontent.com/u/89018496?v=4" alt="" srcset="">
      </Category>
      <Category title="游戏">
          <ul>
            <li v-for="item in games" :key="item.id">{{item.name}}</li>
          </ul>
      </Category>
      <Category title="电影"></Category>
  </div>
</template>
<div class="category">
        <h3>{{title}}分类</h3>
        <slot>默认值如果组件不传值就会显示</slot>
    </div>

image-20220616153515346

具名插槽

<template>
  <div class="container">
      <Category title="美食">
          <img slot="center" src="https://avatars.githubusercontent.com/u/89018496?v=4" alt="" srcset="">
          <a slot="footer" href="https://xiaotao.cloud">更多美食</a>
      </Category>
      <Category title="游戏">
          <ul slot="center">
            <li v-for="item in games" :key="item.id">{{item.name}}</li>
          </ul>
          <template v-slot:footer>
            <a slot="footer" href="https://xiaotao.cloud">更多游戏</a>
            <h4>欢迎使用!</h4>
          </template>
      </Category>
      <Category title="电影">

      </Category>
  </div>
</template>
<div class="category">
        <h3>{{title}}分类</h3>
        <slot name="center">默认值如果组件不传值就会显示</slot>
        <slot name="footer">默认值如果组件不传值就会显示2</slot>
    </div>

image-20220616154807683

作用域插槽

数据在子类 都是要在父类遍历

<template>
  <div class="container">
    <Category title="游戏">
      <template scope="{games}">
        <ul>
          <li v-for="item in games" :key="item.id">{{ item.name }}</li>
        </ul>
      </template>
    </Category>
    <Category title="游戏">
      <template scope="{games}">
        <ol>
          <li v-for="item in games" :key="item.id">{{ item.name }}</li>
        </ol>
      </template>
    </Category>
    <Category title="游戏">
      <template scope="{games}">
        <h4 v-for="item in games" :key="item.id">{{ item.name }}</h4>
      </template>
    </Category>
  </div>
</template>
<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <slot :games="games">默认值如果组件不传值就会显示</slot>
  </div>
</template>

image-20220616160849519

vuex

解决多组件共享数据问题

全局事件总线

image-20220616161502608

vuex

image-20220616161647691

使用

  • 多个组件依赖同一状态
  • 来自不同组件的行为需要变更同一状态

image-20220616165035633

安装

vue2 要使用 vuex@3

vue3 要使用 vuex@4

npm i vuex

image-20220616173524626

// 该文件用于创建 vuex 最核心的 store,并将其绑定到 Vue 实例上。

import Vue from 'vue'
// 引入 Vuex
import Vuex from 'vuex'
// 使用
Vue.use(Vuex)

// 用于响应组件中的动作
const actions = {}

// 用于操作数据
const mutations = {}

//用于储存数据
const state = {}

// 创建 并 暴露 store 实例
export default new Vuex.Store({
    state,
    actions,
    mutations
})

main.js

import store from './store'

new Vue({
  render: h => h(App),
  store,   
  beforeCreate() {
    Vue.prototype.$bus = this    // 配置全局事件总线
  }
}).$mount('#app')

然后就有:

image-20220616171307772

路由

vue-router

image-20220617093453310

是 vue的一个插件库, 专门用来实现 SPA 应用的

SPA 应用

  • 单页面 WEB 应用(Single page web application)
  • 整个应用只有 一个完整的页面
  • 点击页面中的导航链接, 只会 局部刷新
  • 数据需要通过 ajax 请求

路由

  1. 一个路由就是一组映射关系(key-value)
  2. key 为路径 value 可能是 function 或 component

安装

npm i vue-router@3                             # vue2
npm i vue-router@4                             # vue3
import VueRouter from 'vue-router'
Vue.use(VueRouter)

image-20220617105451461

具体看 github 提供的代码有详细讲解

Vue3

1.Vue3简介

2.Vue3带来了什么

1.性能的提升

  • 打包大小减少41%

  • 初次渲染快55%, 更新渲染快133%

  • 内存减少54%

    ......

2.源码的升级

  • 使用Proxy代替defineProperty实现响应式

  • 重写虚拟DOM的实现和Tree-Shaking

    ......

3.拥抱TypeScript

  • Vue3可以更好的支持TypeScript

4.新的特性

  1. Composition API(组合API)
    • setup配置
    • ref与reactive
    • watch与watchEffect
    • provide与inject
    • ......
  2. 新的内置组件
    • Fragment
    • Teleport
    • Suspense
  3. 其他改变
    • 新的生命周期钩子
    • data 选项应始终被声明为一个函数
    • 移除keyCode支持作为 v-on 的修饰符
    • ......

Vue3 中可以没有根标签

一、创建Vue3.0工程

1.使用 vue-cli 创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve

2.使用 vite 创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

  • 什么是vite?—— 新一代前端构建工具。
  • 优势如下:
    • 开发环境中,无需打包操作,可快速的冷启动。
    • 轻量快速的热重载(HMR)。
    • 真正的按需编译,不再等待整个应用编译完成。
  • 传统构建 与 vite构建对比图

imgimg

## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev

二、常用 Composition API

官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

1.拉开序幕的setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。
  2. setup是所有**Composition API(组合API)**“ 表演的舞台 ”
  3. 组件中所用到的:数据、方法等等,均要配置在setup中。
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
    2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
  5. 注意点:
    1. 尽量不要与Vue2.x配置混用
      • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
      • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
      • 如果有重名, setup优先。
    2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

2.ref函数

  • 作用: 定义一个响应式的数据

  • 语法:

    const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 备注:

    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

3.reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”。
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

4.Vue3.0中的响应式原理

vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

    Object.defineProperty(data, 'count', {
        get () {}, 
        set () {}
    })
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。

Vue3.0的响应式

5.reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

6.setup的两个注意点

  • setup执行的时机
    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
    • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
    • slots: 收到的插槽内容, 相当于 this.$slots
    • emit: 分发自定义事件的函数, 相当于 this.$emit

7.计算属性与监视

1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    import {computed} from 'vue'
    
    setup(){
      ...
    //计算属性——简写
      let fullName = computed(()=>{
          return person.firstName + '-' + person.lastName
      })
      //计算属性——完整
      let fullName = computed({
          get(){
              return person.firstName + '-' + person.lastName
          },
          set(value){
              const nameArr = value.split('-')
              person.firstName = nameArr[0]
              person.lastName = nameArr[1]
          }
      })
    }

2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。
    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
    console.log('sum或msg变化了',newValue,oldValue)
    }) 
    
    /* 情况三:监视reactive定义的响应式数据
            若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
            若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
    */
    watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效
    
    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
      console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效

3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
      const x1 = sum.value
      const x2 = person.age
      console.log('watchEffect配置的回调执行了')
    })

8.生命周期

vue2.x的生命周期lifecycle_2

vue3.0的生命周期lifecycle_2

1

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
    • beforeDestroy改名为 beforeUnmount
    • destroyed改名为 unmounted
  • Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
    • beforeCreate ===> setup()
    • created ===> setup()
    • beforeMount ===>onBeforeMount
    • mounted===>onMounted
    • beforeUpdate===>onBeforeUpdate
    • updated ===>onUpdated
    • beforeUnmount ==>onBeforeUnmount
    • unmounted ===>onUnmounted

9.自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
  • 类似于vue2.x中的mixin。
  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

10.toRef

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
  • 语法:const name = toRef(person,'name')
  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

三、其它 Composition API

1.shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
  • 什么时候使用?
    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

2.readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景: 不希望数据(尤其是这个数据是来自与其他组件时)被修改时。

3.toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

4.customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

  • 实现防抖效果:

    
    
    

5.provide 与 inject

img

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
       ......
       let car = reactive({name:'奔驰',price:'40万'})
       provide('car',car)
       ......
      }
    2. 后代组件中:

      setup(props,context){
       ......
       const car = inject('car')
       return {car}
       ......
      }

6.响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

四、Composition API 的优势

1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

img

img

2.Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

img

img

五、新的组件

1.Fragment

  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用

2.Teleport

  • 什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to="移动位置">
    <div v-if="isShow" class="mask">
        <div class="dialog">
            <h3>我是一个弹窗</h3>
            <button @click="isShow = false">关闭弹窗</button>
        </div>
    </div>
</teleport>

3.Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件
    import {defineAsyncComponent} from 'vue'
    const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
    • 使用Suspense包裹组件,并配置好defaultfallback

六、其他

1.全局API的转移

  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。
    //注册全局组件
    Vue.component('MyButton', {
      data: () => ({
        count: 0
      }),
      template: ''
    })
    
    //注册全局指令
    Vue.directive('focus', {
      inserted: el => el.focus()
    }
  • Vue3.0中对这些API做出了调整:

    • 将全局的API,即:Vue.xxx调整到应用实例(app)上

    | 2.x 全局 API(Vue) | 3.x 实例 API (app) | | ------------------------- | ------------------------------------------- | | Vue.config.xxxx | app.config.xxxx | | Vue.config.productionTip | 移除 | | Vue.component | app.component | | Vue.directive | app.directive | | Vue.mixin | app.mixin | | Vue.use | app.use | | Vue.prototype | app.config.globalProperties |

2.其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法
    .v-enter,
    .v-leave-to {
      opacity: 0;
    }
    .v-leave,
    .v-enter-to {
      opacity: 1;
    }
    • Vue3.x写法
    .v-enter-from,
    .v-leave-to {
      opacity: 0;
    }
    
    .v-leave-from,
    .v-enter-to {
      opacity: 1;
    }
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件
    • 子组件中声明自定义事件
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

  • ......


本当の声を響かせてよ