目录

一、watch监听及深度监听

二、directive自定义指令详解+实例

三、1.vue父子组件:数据双向绑定

一、数据双向绑定.sync(支持多个双向绑定值)

三、2.父子组件间方法的调用

1.this.$parent

三、3.兄弟组件间传值

三、4.vue子组件调用多层父组件的方法1——递归

三、4.vue子组件调用多层父组件的方法2-桥梁

四、Message消息提示每次只弹出一个 + 设置全局message弹框的格式

问题描述

Message消息提示每次只弹出一个 + 设置全局message弹框的格式

Message距离窗口顶部的偏移量

五、什么是重定向

六、为什么要进行重定向?什么时候需要重定向?

七、HTTP协议详解之响应篇

八、export与export default区别

九、路由传参-使用encodeURI加密参数

十、base64格式的加密与解密

十一、vite

1.介绍

2.速度 

3. 支持框架

4.Vite与webpack对比

5.Vite原理 

 6.总结

7.支持的浏览器 

 8.vite 中使用css预处理器等

9. vite 打包将 js 和 css 文件夹分离

 10.vite 根目录vite.config.js配置文件读取env变量

11.vite 项目index.html中使用env环境变量

12.引用静态资源和 public 目录

13.打包生产环境移除 console 和 debugger

14.vue-router4.x路由 404 匹配

15.vite 启动后关于defineEmits的警告

16.vue3 和echarts5动态更新数据报错

17.Vue3.x 使用

18.vue3 中的 vue-router4.x 下的 keep-alive 写法

19.vue-router4 监听路由变化 onBeforeRouteUpdate

20. nginx 线上部署,刷新后页面 404

21. 参考资料

十二、单文件组件

1.动态组件:is

2.顶层的绑定会被暴露给模板

3.使用组件

4.组件传值(defineProps 、defineEmits) 

1.使用ts

 2.非ts

5.provide和inject

浅析

6.computed函数-计算属性与监视

 7.watch函数

十三、ref,toRef,toRefs,reactive,onMounted的区别与用法

ref vs toRef

 toRef vs toRefs

ref vs reactive

十四、快速生成vue3代码模板

1、如何创建模板

2、如何使用模板

十五、命令升级 

十六、mixins

1.mixins的使用方法和注意点

mixins基础概况

一、定义公共的mixins文件:如mixin.vue

二、在你页面内调用:需要import这个mixins文件 ,然后通过mixins:['文件名']来使用就可以了

三、下面来说说mixins的特点:

十七、Object.defineProperty方法(详解)-Vue数据双向绑定原理

1.说明: 

2.当是数据属性时:

3.当是访问器属性时: 

4.定义多个属性 

十八、插入排序

十九、a++和++a的区别

二十、vue3为什么不能用this


一、watch监听及深度监听

watch:{
     //最初绑定的时候是不会执行的
     a(val, oldVal){//普通的watch监听
         console.log("a: "+val, oldVal);
     },
     //最初绑定的时候就执行
     b:{
         //深度监听,可监听到对象、数组的变化
         handler(val, oldVal){
             console.log("b.c: "+val.c, oldVal.c);
         },
         //代表在wacth里声明了b这个方法之后立即先去执行handler方法
         immediate: true,
         //deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器
         deep:true, //true 深度监听
     }
 }

注意对象的写法奥~ 

watch: {
      'node.indeterminate'(val) {
        this.handleSelectChange(this.node.checked, val);
      },

      'node.checked'(val) {
        this.handleSelectChange(val, this.node.indeterminate);
      },

      'node.expanded'(val) {
        this.$nextTick(() => this.expanded = val);
        if (val) {
          this.childNodeRendered = true;
        }
      }
    },

二、directive自定义指令详解+实例

说明

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

实例1

拖拽:

Drag.js:

export default function(el){
  let oDiv=el;
  oDiv.onmousedown=function(e){
    let l=e.clientX-oDiv.offsetLeft;
    let t=e.clientY-oDiv.offsetTop;
    document.onmousemove=function(e){
      oDiv.style.left=e.clientX-l+'px';
      oDiv.style.top=e.clientY-t+'px';
    };
    oDiv.onmouseup=function(){
      document.onmousemove=null;
      oDiv.onmouseup=null;
    }
  }
}

Vue引用:

<div v-drag>我可以拖拽</div>

import drag from 'drag.js'
Vue.directive('drag',drag)

实例2

当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

如果想注册局部指令,组件中也接受一个 directives 的选项:

directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

然后你可以在模板中任何元素上使用新的 v-focus property,如下:

<input v-focus>

三、1.vue父子组件:数据双向绑定

一、数据双向绑定.sync(支持多个双向绑定值)

大致说明:

  • 绑定值加上 .sync 
  • $emit 去触发 update:prop名,实现修改父组件的变量值实现双向数据流 
  • :startDate.sync="startDate"  等价于  :startDate="startDate"  @updata:startDate="startDate= $event"   (这里的$event就是子组件$emit传递的参数)

例:

父组件

<template>
  <div>
    <!-- 此处只需在平时常用的单向传值上加上.sync修饰符 -->
    <HelloWorld :startDate.sync="startDate" :endDate.sync="endDate" />
  </div>
</template>

<script>
  import dataPicker from '@/components/common/data_picker'

export default {
   components: {
      dataPicker
    },
    data(){
        return{
            startDate: new Date(new Date() - 24 * 60 * 60 * 1000 * 30), //开始时间
            endDate: new Date(), //结束时间
        }
    },
}
</script>

子组件

<template>
  <div style="display: inline-block;">
    <el-date-picker type="datetime" v-model="startDate" placeholder="开始时间"
      style="width: 200px"></el-date-picker>
    <el-date-picker type="datetime" v-model="endDate" placeholder="结束时间"
      style="width: 200px"></el-date-picker>
  </div>
</template>
<script>
  export default {
    props: ['startDate', 'endDate'],
    watch: {
      startDate(newVal, oldVal) {
        this.$emit('update:startDate', newVal);
      },
      endDate(newVal, oldVal) {
        this.$emit('update:endDate', newVal);
      },
    },
  }

</script>

三、2.父子组件间方法的调用

1.this.$parent

说明:

  • 在子组件中想要向父组件传值,我们通常使用this.$emit()方法
  • 在子组件中想要调用到父组件的方法和属性,使用this.$parent,例如:
// 在子组件中调用父组件的method1方法
this.$parent.method1()
// 获取父组件属性值
this.$parent.prop

注意: 

  •  这个方法有个前提条件: 父组件在应用子组件的时候,位置不能随意放,例如不能放在element UI组件的插槽里, 通常要放在根元素div里

三、3.兄弟组件间传值

一、$emit  $on

  • 传递数据方–发射事件
    • 通过事件传递$emit(方法名,传递的数据)
  • 接收数据方–监听事件
    • 在mounted()中触发$on(方法名,callback(接收数据的数据))
// 建立一个空的Vue实例,将通信事件挂载在该实例上
// emptyVue.js
import Vue from 'vue'
export default new Vue()
// 兄弟组件a:childa.vue
<template>
  <div>
    <span>A组件->{{msg}}</span>
    <input type="button" value="把a组件数据传给b" @click ="send">
  </div>
</template>
<script>
import vmson from "./emptyVue"
export default {
  data(){
    return {
      msg:"我是a组件的数据"
    }
  },
  methods:{
    send:function(){
      vmson.$emit("aevent",this.msg)
    }
  }
}
</script>
// 兄弟组件b:childb.vue
<template>
   <div>
    <span>b组件,a传的的数据为->{{msg}}</span>
  </div>
</template>
<script>
import vmson from "./emptyVue"
export default {
 data(){
    return {
      msg:""
    }
  },
  mounted(){
    vmson.$on("aevent",(val)=>{//监听事件aevent,回调函数要使用箭头函数;
      console.log(val);//打印结果:我是a组件的数据
      this.msg = val;
    })
  }
}
</script>
// 父组件:parent.vue
<template>
  <div>
    <childa><childa>
    <childb></childb>
  </div>
</template>
<script>
import childa from "./childa";
import childb from "./childb";
export default {
  data(){
    return{
      msg:""
    }
  },
  components:{
    childa,
    childb
  },
}
</script>

三、4.vue子组件调用多层父组件的方法1——递归

父子组件传值多层的传值,provide和inject。

注意奥:

  • 父组件放置provide,子组件放置inject

父组件:

export default {
    provide() {
        return {
            fatherMethod: this.popupOpen, // popupOpen是父级的一个事件名
        };
    },
    methods: {
        popupOpen(params) {
            console.log(params)
        }
    }
}


子组件:

export default {
    inject: ["fatherMethod"],
    methods: {
        init() {
            this.fatherMethod(params); // params是可以选择传的参数
        }
    }
}

三、4.vue子组件调用多层父组件的方法2-桥梁

使用方法非常简单只需要在路过的组件上v-bind="$attrs"  v-on="$listeners"

我们来三个组件来演示父组件向孙组件传数据,孙数据向父组件传数据。

1.孙组件

this.$emit("updategrand",this.msg)

2.父组件(起着一个桥梁的作用)

 <Box2 v-bind="$attrs" v-on="$listeners"></Box2>

3.祖父组件 

 <Box1 :title="msg" @updategrand="fm"></Box1>

 fm(arg){
   this.msg=arg
 }

四、Message消息提示每次只弹出一个 + 设置全局message弹框的格式

问题描述

 - Element UI的Message消息提示是点击一次触发一次的,如果一直点,然后就会出现打开多个的情况。
 - 在后台弹消息的时候,也会同时出现
 - 设置全局message弹框的格式
 - Message距离窗口顶部的偏移量
 

Message消息提示每次只弹出一个 + 设置全局message弹框的格式

 在main.js平级建立文件夹,其中新建message.js


message.js:


/**防止重复点击重复弹出message弹框 */
import {
  Message
} from 'element-ui';
let messageInstance = null;
const resetMessage = (options) => {
  if (messageInstance) {
    messageInstance.close()
  }
  messageInstance = Message(options)
};
/**设置message弹框的格式 */
['error', 'success', 'info', 'warning'].forEach((item) => {
  resetMessage[item] = (msg) => {
    return Message({
      showClose: true,
      duration:2000,
      type: item,
      message: msg
    })
  }
})
export const message = resetMessage
 

main.js:


import {message} from './request/message.js';
Vue.prototype.$message = message;
```

Message距离窗口顶部的偏移量

public.css:

```
.el-message {
  top: 48%!important
}
```

注意
因为使用了new DonMessage()的原因,所以导致this.$message(options)的方式无法使用。所以推荐使用this.$message.success('成功提示')或者this.$message.success(options)的方式进行调用。具体参数可以查看官方文档。

五、什么是重定向

就是地址A跳转到地址B啦。百度百科的解释:重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向)。

六、为什么要进行重定向?什么时候需要重定向?


1)网站调整(如改变网页目录结构);
2)网页被移到一个新地址;
3)网页扩展名改变(如应用需要把.php改成.Html或.shtml)。
这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。

七、HTTP协议详解之响应篇


状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息–表示请求已接收,继续处理
2xx:成功–表示请求已被成功接收、理解、接受
3xx:重定向–要完成请求必须进行更进一步的操作
4xx:客户端错误–请求有语法错误或请求无法实现
5xx:服务器端错误–服务器未能实现合法的请求
常见状态代码、状态描述、说明:
200 OK //客户端请求成功
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
eg:HT
TP/1.1 200 OK (CRLF)

八、export与export default区别

1.export default 和export都可以用于导出常量,函数,文件,模块等;

2.可以在模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用。

3.在一个文件或者模块中,export,import可以有多个,但是export default只能有一个

4.通过export方式导出,在导入的时候需要加{},export default不需要在导入的时候加{}

使用export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名。

九、路由传参-使用encodeURI加密参数

在路由切换时页面需要使用地址栏传参,但地址栏会暴露参数的值,然后想到了encodeURI加密参数

比如参数是一个对象obj

obj:{
	id: 1,
	name: 'Tom'
}

那么需要将参数转换为JSON字符串,在使用encodeURI加密,需要注意的是通过路由跳转的时候会自动解密一次,所以需要加两次密。

  • JSON.stringify是日常开发中经常用到的 JSON 对象中的一个方法。
  • JSON 对象包含两个方法:一是用于解析成 JSON 对象的 parse();
  • 二是用于将对象转换为 JSON 字符串方法的 stringify()
  • encodeURI 加密函数
  • decodeURI 解密函数
 let param = {
     	id: 1,
		name: 'Tom'
      }
      param = encodeURI(JSON.stringify(param))
      param = encodeURI(param) // 第二次加密
      this.$router.push({path: `/record-index-city/${param}`,})

解密方式是使用decodeURI

 let param = this.$route.params.param
 param = JSON.parse(decodeURI(param))

加密后效果:

在这里插入图片描述

十、base64格式的加密与解密

window.atob() 与window.btoa()可以实现对base64格式的数据进行解码和编码,其兼容性是主流浏览器,IE10及以上。

window.atob(encodedStr)只可以解码用btoa方法编码的字符串。

window.btoa():将ascii字符串或二进制数据转换成一个base64编码过的字符串,但该方法不能直接作用于Unicode字符串

<script>
      var str = 'RUNOOB'
      var enc = window.btoa(str) //编码成base64的
      var dec = window.atob(enc) //把base64解码
      var res = '编码字符串为: ' + enc + '<br>' + '解码后字符串为: ' + dec
      console.log(res)
</script>


当遇到中文时,需要先对中文转码否则会乱码。

var str = btoa(encodeURIComponent("中文汉字"));
//还可以解码回来
decodeURIComponent(atob(enc)) =>  中文汉字

十一、vite

  • 升级 vite
$ npm i vite @vitejs/plugin-vue --save-dev

1.介绍

2.速度 

Vite 使用了 原生 ES 模块,期间没有涉及模块编译过程,节约了不少时间。 

3. 支持框架

vite 支持的框架有 6 种

  • vanilla:Vanilla JS 是一个快速、轻量级、跨平台的JavaScript框架。Vanilla JS 是世界上最轻量的JavaScript框架(没有之一) —— 其实这玩意就是原生 JS。
  • vue/react:不过多介绍。
  • preact:React 的轻量级替代方案。
  • lit:Lit 是一个简单的库,用于构建快速、轻量级的 Web 组件。(看了一眼语法,感觉还挺好玩的。)
  • svelte:一个不使用 Virtual DOM 的库 —— 真酷。这个库的作者和 Rollup 的作者是同一人。

4.Vite与webpack对比

1、给一个入口文件,通过路口文件找依赖,然后就找到很多模块出来,模块中有js,css和图片总之包含了一大堆文件
2、然后webpack打包,通过loader,plugin等等进行打包;
3、打包完过后就变成非常少量的打包之后的文件,可能是一个也可能是多个,但肯定不是原始的模块文件;
4、打包完后,在开发阶段还要启动一个开发服务器,然后配置服务器,然后启动开发服务器,
5、最终通过访问开发服务器,他就会把打包后的结果给我们
 
这些过程在vue的开发过程运行项目时的打包其实是在内存中完成的,所以经过这一系列的方式会
使得在开发阶段每次修改代码都会运行打包,如果依赖太多运行就会很慢

5.Vite原理 

 

1、vite在开发阶段没有打包过程,他是直接启动一个服务器,启动后就啥事也没有做
2、请求一个模块到开发服务器;
3、开发服务器编译模块,根据页面用所需要的依赖去加载文件
4、加载完成后,开发服务器把编译的结果返回给页面
 
这使得提高了我们在开发阶段运行的效率

 6.总结

  • 在vue运行项目时,其内部会执行webpack打包命令,打包完后把项目放入开发服务器中然后启动开发服务器,请求服务器是直接给予打包结果。而vite是直接启动开发服务器,请求那个模块再对该模块进行实时编译。由于现代浏览器本身就支持ES Moudule,会自动向依赖的Module发出请求。
  • vite成分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。由于vite在启动的时候不需要打包,也就意味着不需要分析模块的依赖,不需要编译,因此启动速度非常块!
  • 当浏览器请求某个模块时,在根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂,模块越多vite的优势越明显。
  • 在HMR方面,当改动一个模块后,仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的
  • 相关依赖模块全部编译一次,效率更高。
  • 当需要打包到生产环境时,vite使用传统的rollup进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的时ES Module,因此在代码中不可以使用CommonJS

7.支持的浏览器 

通过修改配置最低支持到 es2015,也就是 ES6。处理成 es5:

// vite.config.js
import { defineConfig } from "vite";
// https://github.com/vitejs/vite/tree/main/packages/plugin-legacy
import legacy from "@vitejs/plugin-legacy";

export default defineConfig({
  plugins: [
    legacy({
      targets: ["ie >= 11"],
      additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
    }),
  ],
  build: {
    target: "es2015", // 默认 "modules"
  },
});

 8.vite 中使用css预处理器等

  1. vite 中使用 less/scss/sass/stylus 等 css 预处理器

    vitejs.cn/guide/featu… 直接进行安装,不用像 webpack 那样安装 loader 和配置

npm install -D less
复制代码
<style lang="less">
/* use less */
</style>
复制代码
  1. vite 中使用 postcss

    vitejs.cn/config/#css… vite 中已集成了 postcss,无需再单独安装

  2. vite 中使用 autoprefixer

import autoprefixer from "autoprefixer";

export default defineConfig({
  css: {
    postcss: {
      plugins: [autoprefixer],
    },
  },
});
复制代码

9. vite 打包将 js 和 css 文件夹分离

默认打包后,js 和 css 是全部混在一起的,对强迫症很难受

github.com/vitejs/vite…

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        chunkFileNames: "static/js/[name]-[hash].js",
        entryFileNames: "static/js/[name]-[hash].js",
        assetFileNames: "static/[ext]/[name]-[hash].[ext]",
      },
    },
  },
});
复制代码

 10.vite 根目录vite.config.js配置文件读取env变量

在配置文件中直接使用import.meta.env.xxx这样读取是报错的,不过有了上面的异步配置,读取env变量就方便了,我们可以直接使用fs直接获取

  • .env文件
# PROXY_URL
VITE_PROXY_URL=http://xxxxxx/
# DBPATH_ENV
DBPATH_ENV=XXXX
  • vite.config.js文件
const fs = require("fs");
const path = require("path");
const { promisify } = require("util");
const fsState = promisify(fs.stat);
const readFile = promisify(fs.readFile);

// 定义一个函数,读取.env文件中的内容
async function getEnvConfig(vite_env_key) {
  const envFilePath = path.resolve(__dirname, "./.env");
  const [notExistEnvFile, existEnvFile] = await fsState(envFilePath)
    .then((data) => [null, data])
    .catch((e) => [e, null]);

  if (notExistEnvFile || existEnvFile.size <= 0) return;

  const envContent = await readFile(envFilePath, "utf8");

  if (!envContent) return;

  const envContentArr = envContent
    .split("\n")
    .filter((str) => !str.startsWith("#")) // 过滤掉注释行
    .filter(Boolean);

  const resultKey = envContentArr.find((item) => item.includes(vite_env_key));

  return resultKey ? resultKey.split("=")[1] : null;
}

const resolveConf = async () => {
  // 读取 .env 文件中的VITE_PROXY_URL的值
  const PROXY_URL = await getEnvConfig("VITE_PROXY_URL");

  return {
    server: {
      host: "0.0.0.0",
      port: 3000,
      open: true,
      proxy: {
        "/api": {
          target: PROXY_URL,
          changeOrigin: true,
          // rewrite: (path) => path.replace(/^\/api/, ""),
        },
      },
    },
  };
};

export default defineConfig(resolveConf());

11.vite 项目index.html中使用env环境变量

如何像webpack项目的html-webpack-plugin那样使用<%= htmlWebpackPlugin.options.title %>,这种方式动态注入变量,vite 中可以使用vite-plugin-html来完成。

  1. 安装
npm i vite-plugin-html -D
复制代码
  1. 使用(比如不同环境下动态注入高德地图的key)
  • vite.config.js配置文件
import { minifyHtml, injectHtml } from "vite-plugin-html";

const AMAP_KEY = "xxx";

export default defineConfig({
  plugins: [
    injectHtml({
      data: {
        title: "hello",
        // 高德地图
        injectAMapScript: `<script src="https://webapi.amap.com/maps?v=1.4.15&key=${AMAP_KEY}"></script>`,
      },
    }),
  ],
});
  • index.html中使用 ejs 模板引擎动态注入
<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%- title %></title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
    <%- injectAMapScript %>
  </body>
</html>

12.引用静态资源和 public 目录

在 vue 文件中templatestyle中以相对路径和绝对路径两种方式引用静态资源

<template>
  <!-- 相对路径 -->
  <img src="./assets/img/test.png" />
  <!-- 绝对路径 -->
  <img src="/src/assets/img/test.png" />
</template>

<style scoped>
#app {
  background-image: url("./assets/img/test.png");
}
</style>
  • 放在public目录下的文件应使用绝对路径引用,例如public/icon.png应该使用/icon.png。
  • public中的资源不应该被JavaScript文件引用。
<img src="/logo.png" />

13.打包生产环境移除 console 和 debugger

export default defineConfig({
  build: {
    // 生产环境移除console
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
  },
});

14.vue-router4.x路由 404 匹配

捕获所有路由或 404 Not found 路由

import { createRouter, createWebHistory } from "vue-router";
const Result = () => import("@/views/Result");

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      // 注意这里,404页面匹配规则和以前不相同,要采用这种配置方式才行
      path: "/:pathMatch(.*)*",
      component: Result,
      name: "Result",
    },
  ],
});

export default router;

15.vite 启动后关于defineEmits的警告

defineEmits is a compiler macro and no longer needs to be importe

删掉defineEmits的引用即可

- import { reactive, defineEmits } from 'vue';
+ import { reactive } from 'vue';

// 直接使用defineEmits
const emit = defineEmits(['test1', 'test2']);

emit('test1', 'hello1');

16.vue3 和echarts5动态更新数据报错

  • 造成报错的原因是 vue3 中使用 proxy 的方式监听响应式,charts 实例会被在 vue 内部转换成响应式对象
  • 在初始化的时候可以使用markRaw指定为非响应式即可
<template>
  <div ref="lineChartDomRef"></div>
</template>

<script setup>
import { markRaw, ref, onMounted } from "vue";
import * as echarts from "echarts";

const lineChartInsRef = ref();
const lineChartDomRef = ref();

onMounted(() => {
  // 初始化时使用markRaw,后面使用lineChartInsRef.value实例更新时,就不会报错了
  const option = {
    // ...
  };
  lineChartInsRef.value = markRaw(echarts.init(lineChartDomRef.value));
  lineChartInsRef.value.setOption(option);

  window.addEventListener("resize", () => {
    lineChartInsRef.value.resize();
  });
});
</script>

17.Vue3.x 使用<script setup>如何设置组件name名称

实可以写两个 script 标签,下面两个可以并存

<script>
import { defineComponent } from "vue";

export default defineComponent({
  name: "Test",
});
</script>

<script setup>
// ...
</script>

18.vue3 中的 vue-router4.x 下的 keep-alive 写法

之前这样写,会报警告

<keep-alive>
  <RouterView />
</keep-alive>

根据提示,得这样写:

<template>
  <router-view v-slot="{ Component }">
    <!-- 缓存name名称为aaa和bbb的组件 -->
    <keep-alive :include="['aaa', 'bbb']">
      <component :is="Component" />
    </keep-alive>
  </router-view>
</template>

19.vue-router4 监听路由变化 onBeforeRouteUpdate

import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';

onBeforeRouteUpdate((to, from, next) => {
  if (from.name === 'MachineList') {
    ...
  }
  next();
});
复制代码

20. nginx 线上部署,刷新后页面 404

修改 nginx 配置(服务器配置)

location / {
    root   /usr/share/nginx/dist; # 服务默认启动目录
    index  index.html index.htm; # 默认访问文件
+    try_files $uri /index.html; # 防止浏览器刷新后,页面404
    client_max_body_size 100m;
}

21. 参考资料

  1. vue3 文档
  2. vite 文档
  3. vue-router4 文档

十二、单文件组件<script setup>

1.动态组件:is

由于组件被引用为变量而不是作为字符串键来注册的,在 <script setup> 中要使用动态组件的时候,就应该使用动态的 :is 来绑定:

<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

<template>
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
</template>
  • 请注意组件是如何在三元表达式中被当做变量使用的。
  • 请注意组件引入子组件是不需要components的。

2.顶层的绑定会被暴露给模板

当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用:

<script setup>
// 变量
const msg = 'Hello!'

// 函数
function log() {
  console.log(msg)
}
</script>

<template>
  <div @click="log">{{ msg }}</div>
</template>

import 导入的内容也会以同样的方式暴露。意味着可以在模板表达式中直接使用导入的 helper 函数,并不需要通过 methods 选项来暴露它:

<script setup>
import { capitalize } from './helpers'
</script>

<template>
  <div>{{ capitalize('hello') }}</div>
</template>

注意:子组件的方法要被父组件调用时,子组件的方法需要被暴露出来。

defineExpose({ addItemShow,addItemShow2 })//将子组件方法暴露出来,让父组件调用 

3.使用组件

<script setup> 范围里的值也能被直接作为自定义组件的标签名使用:

<script setup>
import MyComponent from './MyComponent.vue'
</script>

<template>
  <MyComponent />
</template>

将 MyComponent 看做被一个变量所引用。如果你使用过 JSX,在这里的使用它的心智模型是一样的。其 kebab-case 格式的 <my-component> 同样能在模板中使用。不过,我们强烈建议使用 PascalCase 格式以保持一致性。同时也有助于区分原生的自定义元素。

4.组件传值(defineProps 、defineEmits 

script setup> 中必须使用 defineProps 和 defineEmits API 来声明 props 和 emits ,它们具备完整的类型推断并且在 <script setup> 中是直接可用的

1.使用ts

父组件

<template>
  <div>
    <Child
      :msg.sync="'传递的值'"  // 传值
      @change="change"  // 调用子组件方法
    />
  </div>
</template>

<script setup lang="ts">
const change = (val: number) => {
  console.log(val)
}
</script>

子组件

// 1.接收父组件传值
const props = defineProps<{
  foo: string
  bar?: number
}>()

// 2.1不设置默认值
interface Props {
  msg?: string
  labels?: string[]
}
// 2.2想要设置默认值,通过 withDefaults 编译器宏
const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

console.log(props.msg)  // hello


// 抛出事件
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

const click = () => {
  emit('change', 1)  
  emit('update', 'abc')
}


 2.非ts

//不设置默认值
const props = defineProps({
  foo: String
})

//设置默认值
const props = defineProps({
  name: {
    type: String,
    default: ""
  }
});

const emit = defineEmits(['change', 'delete'])

5.provide和inject

浅析

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

 定义说明:这对选项是一起使用的。以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

通俗的说就是:组件得引入层次过多,我们的子孙组件想要获取祖先组件得资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱。这个就是这对选项要干的事情。

provide:是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。

inject:一个字符串数组,或者是一个对象。属性值可以是一个对象,包含from和default默认值。

例子:

 provide :父组件提供给子组件的数据或方法

<template>
    <child/>
</template>

<script setup>
import { provide } from 'vue'
 
//提供
provide("getList",getList)
 
function getList() {
    console.log(123456)
}
</script>

inject:子组件获取父组件提供的数据或方法

<script setup>
import { reactive, inject } from 'vue'
 
//获取
const getList = inject('getList')
 
//调用
getList()
 
</script>

6.computed函数-计算属性与监视

let obj = reactive({
      name: 'haha',
      age: 18
    })
    let fullName = computed({
      get () {
        return obj.age + 1
      },
      set (value) {
        obj.age = value
      }
    })

 7.watch函数

watch接收三个参数 监听的对象,监听的回调和监视的配置参数

watch('被监听的对象',()=>{},{immediate:'立即监听',deep:'深度监听'})
    let age = ref(18)
    let name = ref('小明')
    let obj = reactive({
      money: 100
    })

    
    //情况一:监视ref所定义的一个响应式数据
      watch(age, (newValue, oldValue) => {
        console.log('age', newValue, oldValue)
      },{immediate:true})

     //情况二:监视ref所定义的多个响应式数据
      watch([age, name], (newValue, oldValue) => {
      console.log('age-name', newValue, oldValue)//也是数组形式返回
    })

    // 情况三:监视reactive所定义的一个响应式数据的全部属性
      watch(obj, (newValue, oldValue) => {
        // 如果监听的是正规响应式对象的话
                // 1.注意:此处无法正确的获取oldValue
                // 2.注意:强制开启了深度监视(deep配置无效)
    })

    // 情况四:监视reactive所定义的一个响应式数据中的某个属性
       watch(() => obj.money, (newValue, oldValue) => {
        // [()=>person.name,()=>person.age]多参数时候也需要数组
        // 监听的参数需要以函数的形式返回才可以监听到
        //当监听的参数是深层对象,需要配置deep为true
    })

十三、ref,toRef,toRefs,reactive,onMounted的区别与用法

ref vs toRef

  • 如果利用ref函数将某个对象中的属性变成响应式数据,对其进行修改是不会影响原始数据的。
  • 利用toRef进行上方操作,对数据的修改是响应式的

 toRef vs toRefs

  • toRef:创建一个ref对象,其value值指向另一个对象中的某个属性
  • toRef语法:const name = toRef(object , 'property')
  • toRefs:功能与toRef一致,但可以批量创建多个ref对象
  • toRefs语法:toRefs(object)
  • 他们的作用都是将响应式对象中的某个属性单独提供给外部使用
在setup()中可以用return { ...toRefs(object)}的方式,将整个响应式对象object的所有属性提供给外部使用。

ref vs reactive

  • reactive用于创建引用类型的响应式对象  const refA = reactive({ name: "LISA", age: "36" })
  • ref用于创建基础数据类型的响应式变量(采用复制的方式,修改响应式数据不会影响原始数据,数据发生改变,界面就会自动更新)   const refA = ref(0)
  • ref 通过Object.defineProperty()getset来实现响应式(数据劫持)。
  • reactive 通过使用 Proxy 来实现响应式(数据劫持), 并通过 Reflect 操作源对象内部的数据。

例子: 

<script setup>
 import {
        ref,
        reactive,
        toRefs,
        onMounted,
    } from 'vue';

 const addPeople1 = ref()

 // reactive 数据双向绑定
 const data = reactive({
        dataCount: 0, //共几条
        pageSize: 5, // 每页显示多少条
        curPage: 1, //第几页
 });
 onMounted(() => {
    getAllDatas()
    });
</script>

十四、快速生成vue3代码模板

1、如何创建模板

  1. 首先在VScode编辑器中打开,【文件】–>【首选项】–>【用户片段】–>【新代码片段】–> 取名vue.json 确定
  2. 把下列代码放进去

{
  // Place your snippets for vue here. Each snippet is defined under a snippet name and has a prefix, body and
  // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
  // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
  // same ids are connected.
  // Example:
  // "Print to console": {
  //  "prefix": "log",
  //  "body": [
  //    "console.log('$1');",
  //    "$2"
  //  ],
  //  "description": "Log output to console"
  // }
  "Print to console": {
    "prefix": "vue3",
    "body": [
      "<template>",
      "  <div $1></div>",
      "</template>",
      "",
      "<script setup>",
      "import { ref, reactive, toRefs, onBeforeMount, onMounted, watchEffect, computed } from 'vue';",
      "import { useStore } from 'vuex';",
      "import { useRoute, useRouter } from 'vue-router';",
      "/**",
      "* 仓库",
      "*/",
      "const store = useStore();",
      "/**",
      "* 路由对象",
      "*/",
      "const route = useRoute();",
      "/**",
      "* 路由实例",
      "*/",
      "const router = useRouter();",
      "//console.log('1-开始创建组件-setup')",
      "/**",
      "* 数据部分",
      "*/",
      "const data = reactive({})",
      "onBeforeMount(() => {",
      "  //console.log('2.组件挂载页面之前执行----onBeforeMount')",
      "})",
      "onMounted(() => {",
      "  //console.log('3.-组件挂载到页面之后执行-------onMounted')",
      "})",
      "watchEffect(()=>{",
      "})",
      "// 使用toRefs解构",
      "// let { } = { ...toRefs(data) } ",
      "defineExpose({",
      "  ...toRefs(data)",
      "})",
      "",
      "</script>",
      "<style scoped lang='less'>",
      "</style>"
    ],
    "description": "Log output to console"
  }
}

2、如何使用模板

  1. 新建vue页面,在文章中输入vue3
  2. 点击回车即可

十五、命令升级 

npm升级

 npm install -g npm

十六、mixins

1.mixins的使用方法和注意点

mixins基础概况

混入 (mixins): 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。

具体操作,so easy:

一、定义公共的mixins文件:如mixin.vue

<template>
 
</template>
<script>
    export default {
        name: 'mixin-test',
        components: {},
        props: {},
        data () {
            return {
                mixinData: 'mixin中的变量'
            }
        },
        methods: {
            mixinFunction () {
                return '我是mixins里面的公共方法'
            },
        },
        mounted () {
        },    }
</script>

二、在你页面内调用:需要import这个mixins文件 ,然后通过mixins:['文件名']来使用就可以了

<template>
    <div>
        <div @click="handleMixin">调用mixin方法</div>
    </div>
</template>
<script>
    import MixinItem from './mixin'
    export default {
        name: 'mixin-test',
        props: {},
        mixins: [MixinItem],
        data () {
            return {}
        },
        methods: {
            handleMixin () {
                console.log('mixin-data=========', this.mixinData)
                let mixfun = this.mixinFunction()
                console.log('mixin-fun====>>>', mixfun)
            },
        },
    }
</script>

三、下面来说说mixins的特点:

 1、方法和参数在各组件中不共享

       比如混入对象中有一个 cont:1 的变量,在组件A中改变cont值为5,这时候在组件B中获取这个值,拿到的还是1,还是混入对象里的初始值,数据不共享

2、值为对象的选项,如methods,components等,选项会被合并,键冲突的组件会覆盖混入对象的,比如混入对象里有个方法A,组件里也有方法A,这时候在组件里调用的话,执行的是组件里的A方法

3、值为函数的选项,如created,mounted等,就会被合并调用,混合对象里的钩子函数在组件里的钩子函数之前调用,同一个钩子函数里,会先执行混入对象的东西,再执行本组件的

十七、Object.defineProperty方法(详解)-Vue数据双向绑定原理

1.说明: 

  • 首先Object.defineProperty是一个方法
  • 这个方法接收三个参数: 1.属性所在的对象 2.属性的名字 3.一个描述符对象(数据属性;访问器属性)
  • 数据属性:

    1.configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为false。

    2.enumerable:表示能否通过for in循环访问属性,默认值为false

    3.writable:表示能否修改属性的值。默认值为false。

    4.value:包含这个属性的数据值。默认值为undefined。

  • 访问器属性:

      1.get:在读取属性时调用的函数,默认值是undefined 2..set:在写入属性的时候调用的函数,默认值是undefined

2.当是数据属性时:

var p1 ={
    name:"lisi",
}

//通过这个方法设置好configurable 这个属性,delete就不能把name属性给删除掉了。
Object.defineProperty(p1,"name",{
    configurable : false,//表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为false。
})
console.log(p1); //{ name: 'lisi' }
delete p1.name;
console.log(p1); //{ name: 'lisi' } 

//然后我们给p1这个对象新加了一个age属性,并且设置成只读的。这样我们就无法修改这个age属性了。
Object.defineProperty(p1,"age",{
    writable :false,//表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为false。
    value : 15,
})
console.log(p1.age); //15
p1.age = 20;
console.log(p1.age); //15 

//在通过这个方法给enumerable设置为false,这样对象就不能通过迭代器遍历出age这个属性的值了。
Object.defineProperty(p1,"age",{
    enumerable:false,//表示能否通过for in循环访问属性,默认值为false
})
for(var i in p1){
    console.log(p1[i]);
} // lisi 

3.当是访问器属性时: 

var book = {
    _year : 2004,
    edition : 1
}

Object.defineProperty(book,"year",{
    get: function(){
        return this._year
    },
    set: function(newYear){
        if(newYear > 2004){
            this._year = newYear;
            this.edition += newYear - 2004
        }
    }
})

book.year = 2005;
console.log(book.edition); // 2
console.log(book._year); //2005

由于get方法返回_year的值,set方法通过计算来确定正确的版本。
因此把year的值设置为2005会导致edition的值变为2 

4.定义多个属性 

var student = {};
Object.defineProperties(student,{
    name:{
        writable:false,
        value:"lisi"
    },
    age : {
        writable:true,
        value : 16,
    },
    sex:{
        get(){
            return '男';
        },
        set(v){
            p1.sex = v
        }
    }
})
student.sex = "女";
console.log(student.sex); //男
console.log(p1.sex); // 女

附上一个模拟Vue中v-module数据双向绑定原理的代码: 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #myInput {
        width: 400px;
        height: 50px;
        font-size: 40px;
        color: red;
      }
      #contain {
        margin-top: 20px;
        width: 400px;
        height: 200px;
        border: 1px solid salmon;
      }
    </style>
  </head>
  <body>
    <input id="myInput" type="text" />
    <div id="contain"></div>
    <script>
      var text;
      window.data = {};
      var oIn = document.getElementById("myInput");
      var oDiv = document.getElementById("contain");

      oIn.addEventListener("input", function (e) {
        text = e.target.value;
        console.log(text);
        window.data.value = text;
      });
      Object.defineProperty(window.data, "value", {
        get() {
          return "";
        },
        set(v) {
          oDiv.innerHTML = v;
        },
      });
    </script>
  </body>
</html>

十八、插入排序

let arr = [7, 8, 9, 1, 2, 3, 4, 5, 6];
sort(arr);
function sort(arr) {
  arr.forEach((item,index)=>{
    //从第二个开始
    while (index > 0 && item < arr[index - 1]) {
      arr[index] = arr[index - 1];
      index--;
    }
    arr[index] = item;
  })
  console.log(arr)
}

十九、a++和++a的区别

a++称为“后增量”,++a称为“前增量”。
两者在使用中有一些区别,比如:

1、
let a = 1;
const b = a++;  // b = 1

2、
let a = 1
const b = ++a;  // b = 2

举个例子: 

二十、vue3为什么不能用this

由于setup在生命周期beforeCreated及created之前执行,此时vue对象还未创建,因此无法使用this。

如果一定要使用的话,需要从vue中引入getCurrentInstance

在mian.ts里:

// 引入 echarts
import * as echarts from "echarts"

// 全局挂载 echarts
app.config.globalProperties.$ECharts = echarts

在页面中使用: 

import { onMounted,ComponentInternalInstance } from "vue"

类型可以为任意类型
const { proxy } = getCurrentInstance();

onMounted(() => {
  // 获取挂载的组件实例
  const echarts = proxy.$ECharts
})

Logo

前往低代码交流专区

更多推荐