vue3 + vite

时间:2025-03-05 07:01:02

端口控制 + 跨域

在文件中

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';

// /config/
export default defineConfig({
  plugins: [vue()],
  base: "./",//打包路径
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')//设置别名
    }
  },
  server: {
    open: true,//启动项目自动弹出浏览器
    port: 4000,//启动端口
    proxy: {
      '/api': {
        target: 'http://localhost:3001',	//实际请求地址
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
    }
  }
  // server: {
  //   port: 4000,//启动端口
  //   open: true,
  //   proxy: {
  //     // 跨域简单写法写法
  //     '/api': 'http://123.56.85.24:5000'//代理网址
  //   },
  //   cors: true
  // },
})

拦截器

import Axios from 'axios'
const axios = Axios.create({
  headers: {
    'Content-Type': 'application/json'
  },
  timeout: 60000, // 超时
  baseURL: '' // 请求接口地址,这里使用本项目地址,因为我们是前后端分离,后面需要在里面配置代理,实际请求得地址不是这个。
})

// 请求拦截
axios.interceptors.request.use(req => {
  if (req.method === 'get') {
    const url = req.url
    const t = new Date().getTime()
    if (url.indexOf('?') >= 0) {
      req.url = `${url}&t=${t}`
    } else {
      req.url = `${url}?t=${t}`
    }
  }
  return req
})

// 响应拦截
axios.interceptors.response.use(
  response => {
    return response.data
  },
  error => {
    // 响应失败统一处理
    const { response } = error
    if (response) {
      switch (response.status) {
        case 400:
          window.$vm.$message.error('请求无效')
          break
        case 401:
          window.$vm.$message.error({ message: '尚未登录请重新登录' })
          break
        case 403:
          window.$vm.$message.error('您没有权限这样做,请联系管理员')
          break
        case 404:
          window.$vm.$message.error('请求未找到')
          break
        case 500:
          window.$vm.$message.error('系统异常')
          break
        case 504:
          window.$vm.$message.error('请求超时,请稍后再试')
          break
        default:
          window.$vm.$message.error('系统异常')
          break
      }
    }
    return Promise.reject(error)
  }
)
export default axios

api

import request from '../utils/request' // 引入封装得axios

// 登录
export function cellphone (data) {
  return request({
    url: '/api/login/cellphone',
    method: 'POST',
    data:{
      phone:'19829215473',
      password:'lsh112233'
    }
  })
}

Element plus 引入

  1. npm install element-plus --save

  2. 按需导入(自动导入):
    npm install -D unplugin-vue-components unplugin-auto-import

  3. 在 或 中写入

import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default {
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
}

ref

import {ref,isRef,shallowRef,triggerRef,customRef} from "vue";
/**
 * ref()   响应式
 * isRef()    判断是否为ref
 * shallowRef()    浅层响应式    注意:与ref混用时,失效
 * triggerRef()    强制收集依赖,shallowRef与ref混用时失效就是由于,ref底层代码中包含triggerRef
 * customRef()    自定义一个ref
 *                  例:function MyRef<T>(value: T) {
                          return customRef((track, trigger) => {
                            return {
                              get() {
                                track();
                                return value;
                              },
                              set(newValue) {
                                value = newValue;
                                trigger();
                              },
                            };
                          });
                        }

                        const obj = ref<string>("大家好");
 */

直接使用ref

		<div ref="dom">哈哈哈</div>

		const dom = ref<HTMLDivElement>();
		(?.innerHTML);  // 注意:刚开始加载时间听不到的

reactive

<template>
  <div>
    <form>
      <input v-model="" type="text" />
      <br />
      <input v-model="" type="text" />
      <br />
      <button @="play">提交</button>
    </form>

    <ul>
      <li v-for="(item, index) in list" :key="index">{{ item }}</li>
    </ul>

    <button @click="addList">新增</button>
  </div>
</template>

<script lang="ts" setup>
import { reactive, readonly, shallowReactive } from "vue";

/**
 * ref 支持所有类型   reactive  只支持object类型
 * ref 取值 赋值 都需要加.value   reactive  不需要.value
 * reactive proxy 不能直接赋值    解决方法:方法一: 可以使用push + 解构的方式 ;方法二:可以添加一个对象,把数组作为一个属性去解决
 * readonly 只读的
 * shallowReactive 浅层响应式(只响应一层)  注意:会被reactive影响
 */
type M = {
  name: string;
  age: number;
};

const form = reactive<M>({
  name: "张三",
  age: 18,
});

const play = () => {
  (form);
};

const list = reactive<string[]>([]);

const addList = () => {
  let res: string[] = ["张三", "李四", "王麻子"];
  (...res);
};
</script>

<style scoped lang="scss"></style>

toRef toRefs toRaw

<!-- name -->
<template>
  <div>{{ user }}</div>
  <button @click="updateUser">修改</button>
</template>

<script lang="ts" setup>
import { reactive, toRef, ToRefs, toRaw } from "vue";

/**
 * toRef 只能修改响应式对象的值  非响应式视图毫无变化
 * toRefs 相当于toRef的集合
 * toRaw  使元素由响应式变为普通的
 */

type m = {
  name: string;
  age: number;
};

const user = reactive<m>({
  name: "李四",
  age: 18,
});

const aaa = toRef(user, "name");

// toRefs实现原码
const toRefs = <T extends object>(object: T) => {
  const map: any = {};

  for (let key in object) {
    map[key] = toRef(object, key);
  }

  return map;
};

// end

let { name, age } = toRefs(user);

const updateUser = () => {
  //  = "占山";

   = "大王";
};
</script>

<style scoped lang="scss"></style>

computed

<!-- name -->
<template>
  <div>
    <input type="text" v-model="name1" />
    <input type="text" v-model="name2" />

    <div>
      {{ name3 }}
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed, reactive, ref } from "vue";

/**
 *
 */

let name1 = ref<string>("");
let name2 = ref<string>("");

// 方法一
// const name3 = computed(() => {
//   return  + ;
// });

// 方法二
const name3 = computed({
  get() {
    return  + ;
  },
  set() {
     + ;
  },
});
</script>

<style scoped lang="scss"></style>

watch

<!-- name -->
<template>
  <div>
    <input type="text" v-model="name1" />
    <input type="text" v-model="name2" />
    <input type="text" v-model="" />
    {{ name3 }}
  </div>
</template>

<script lang="ts" setup>
import { computed, reactive, ref, watch } from "vue";

/**
 *
 */

interface bbb {
  ccc: String;
}

interface aaa {
  bbb: bbb;
}

interface text {
  aaa: aaa;
}

const name1 = ref<string>("");
const name2 = ref<string>("");
const name3 = ref<text>({
  aaa: {
    bbb: {
      ccc: "123",
    },
  },
});

// 情况一:监听单个
// watch(name1, (newValue, oldValue) => {
//   (newValue, oldValue);
// });

// 情况二:监听多个  --- 以数组的形式书写,同时newValue, oldValue返回值也是数组
// watch([name1, name2], (newValue, oldValue) => {
//   (newValue, oldValue);
// });

// 情况三:监听对象  --- 如果为ref则需要开启第三个属性 deep: true,
//                  --- 如果为reactive则不需要开启第三个属性
//                  --- 注意:监听到的对象都是新的返回值
// watch(
//   name3,
//   (newValue, oldValue) => {
//     (newValue, oldValue);
//   },
//   {
//     deep: true,
//     immediate: true, // 立即执行一次
//     flush: "post", // pre 默认值 组件更新前调用  sync 同步调用  post 组件更新后调用
//   }
// );

//  情况四:监听对象的某个属性,需要声明reactive,同时写成一个类似get的形式把要监听的属性返回
watch(
  () => ,
  (newValue, oldValue) => {
    (newValue, oldValue);
  }
);
</script>

<style scoped lang="scss"></style>

watchEffect – 高级侦听器

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

用法:
import { watchEffect } from ‘vue’

setup()
{
	
    watchEffect(函数(){
    	依赖追踪:
    		同步调用:
    			追踪所有依赖
    			
    		异步调用:
    			只有在第一个 await 正常工作前访问到的 property 才会被追踪。
    			
    			如下:只会追踪作为依赖
    			const response = await fetch()
				 = await ()
    })	
   
   	(1)停止侦听
   	
   	  隐式停止:
   		当watchEffect在组件的setup()函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
   		
   	  显示停止:
   	  	const xx=watchEffect(...)
   	  	();
   	  	
   	(2)清除副作用
   		watchEffect传入参数
   		
		watchEffect((onInvalidate)=>{

	      onInvalidate(()=>{
		      执行时机:
				在副作用即将重新执行时
				如果在setup()或生命周期钩子函数中使用了 watchEffect, 则在卸载组件时
	      })
	
	    })
	    
	(3)异步副作用以及清楚
		副作用函数是一个异步函数时,清理函数必须要在Promise被resolve之前被注册,同时Vue依赖这个返回的Promise来自动处理Promise链上的潜在错误
		watchEffect(async () => {
			onInvalidate(() => {
			    /* ... */
			})
			 = await fetchData()
		})
	
	(4)副作用需要同步或在组件更新之前
		watchEffect(
		  () => {
				...
		  },
		  {
		    flush: 'sync',    'pre'在DOM更新前运行,'post'在DOM更新后运行,'sync'强制效果始终同步触发,默认为'pre'
		  }
		)
		
	(5)调试侦听器的行为(只能在开发模式下工作)
		watchEffect(
		  () => {
		    ...
		  },
		  {
		    onTrack(e){
		    	当一个 reactive对象属性或一个 ref 作为依赖被追踪时触发
		    	包含了值
		    	debugger
		    },
		    onTrigger(e) {
		        依赖项变更导致副作用被触发时
		        包含了值
		        debugger
		    }
		  }
		)

(2)watchPostEffect
和flush:‘post’ 效果一致,作为别名使用
(3)watchSyncEffect
和flush:‘sync’ 效果一致,作为别名使用

(4)卸载watchEffect时机
异步回调创建一个侦听器,那么它不会绑定到当前组件上,必须手动停止它,以防内存泄漏,同步则不用

需要手动卸载:
setTimeout(() => {
  watchEffect(() => {})
}, 100)

生命周期

注:当作为组件引入时,大体与vue2一致,但不需要在computed中声明

<!-- name -->
<template>
  <div></div>
</template>

<script lang="ts" setup>
import {
  ref,
  reactive,
  toRefs,
  onBeforeUnmount,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onUnmounted,
  onRenderTracked,
  onRenderTriggered,
} from "vue";

// beforCreated created setup语法糖是没有这两个生命周期的 set代替
("setup");

// 创建
onBeforeMount(() => {
  ("创建之前");
});

onMounted(() => {
  ("创建完成");
});

// 更新
onBeforeUpdate(() => {
  ("更新之前");
});

onUpdated(() => {
  ("更新完成");
});

// 销毁
onBeforeUnmount(() => {
  ("销毁之前");
});

onUnmounted(() => {
  ("销毁完成");
});

// 收集依赖
onRenderTracked((e) => {
  ();
});

onRenderTriggered((e) => {
  (e);
});
</script>

<style scoped lang="scss"></style>

父传子

父组件
	<!-- name -->
<template>
  <div>
    我是父组件
    <br />
    ——————————————————————————————————————————————————————
    <A :text="'123'"></A>
  </div>
</template>

<script lang="ts" setup>
import { computed, reactive, ref, watch, watchEffect } from "vue";

import A from "./components/";
/**
 *
 */
</script>

<style scoped lang="scss"></style>

子组件
<!-- name -->
<template>
  <div>
    我是子组件 <br />
    {{ text }}
  </div>
</template>

<script lang="ts" setup>
import {
  ref
} from "vue";

// 方法一  接受父组件传过来的值defineProps
 const content = defineProps({
   text: {
     type: String,
     default: "abc",
   },
});

// 方法二  ts特有的默认值withDefaults
const content = withDefaults(
  defineProps<{
    text: string;
  }>(),
  {
    text: () => "你好", // 默认值
  }
);

();
</script>

<style scoped lang="scss"></style>

子传父

父组件
<!-- name -->
<template>
  <div>
    我是父组件
    <br />
    ——————————————————————————————————————————————————————
    <A @on-click="name"></A>
  </div>
</template>

<script lang="ts" setup>
import { computed, reactive, ref, watch, watchEffect } from "vue";

import A from "./components/";
/**
 *
 */

const name = (name: string) => {
  (name);
};
</script>

<style scoped lang="scss"></style>

子组件
	<!-- name -->
<template>
  <div>
    我是子组件 <br />
    <button @click="send">传值</button>
  </div>
</template>

<script lang="ts" setup>
import {
  ref
} from "vue";
// 方法一 给父组件传值defineEmits
const emit = defineEmits(["on-click"]);

// 方法二 ts特有
const emit = defineEmits<{
  (e: "on-click", name: string): void;
}>();

const send = () => {
  emit("on-click", "子传父");
};
</script>

<style scoped lang="scss"></style>

子组件向父组件暴露一些属性

子组件
	<!-- name -->
<template>
  <div>我是子组件 <br /></div>
</template>

<script lang="ts" setup>
import {
  ref,
} from "vue";

defineExpose({
  name: "张三",
  open: (): void => {
    ("123");
  },
});
</script>

<style scoped lang="scss"></style>
父组件
<!-- name -->
<template>
  <div>
    我是父组件
    <br />
    ——————————————————————————————————————————————————————
    <A ref="waterFall"></A>
  </div>
</template>

<script lang="ts" setup>
import { computed, onMounted, reactive, ref, watch, watchEffect } from "vue";

import A from "./components/";
/**
 *
 */
const waterFall = ref<InstanceType<typeof A>>();
onMounted(() => {
  (?.name);
  ?.open();
});
</script>

<style scoped lang="scss"></style>

局部组件

  1. 在components中书写.vue文件
  2. 在对应页面引入
<template>
  <div>
    <A></A>
  </div>
</template>

<script lang="ts" setup>
import { computed, onMounted, reactive, ref, watch, watchEffect } from "vue";

import A from "./components/";
/**
 *
 */
</script>

全局组件

  1. 在components中书写.vue文件
  2. 在中声明
import { createApp } from 'vue'
import './'
import HelloWorldVue from './components/'  // 在中引入
import App from './'

const app = createApp(App)
('A', HelloWorldVue)  // 声明

('#app')
  1. 在对应页面引入
<template>
  <div>
    <A></A>
  </div>
</template>

可选链操作符

例:
	let a = {};
	();   // underfind
	();   // 报错

	(?.length);   // underfind      可选链操作符:如果后面的一层读不到的话会反一个underfind

动态组件

  1. 方法一
<template>
  <div style="display: flex; cursor: pointer">
    <div
      @click="play(item)"
      v-for="(item, index) in data"
      style="margin-left: 30px"
    >
      <div>{{  }}</div>
    </div>
  </div>
  <component :is="aaa"></component>
</template>

<script lang="ts" setup>
import {
  computed,
  onMounted,
  reactive,
  ref,
  watch,
  watchEffect,
  markRaw,
  shallowRef,
} from "vue";

import AVue from "./components/expame/";
import BVue from "./components/expame/";
import CVue from "./components/expame/";
/**
 *
 */

const aaa = shallowRef(AVue);

const data = reactive([
  {
    name: "A组件",
    com: markRaw(AVue),
  },
  {
    name: "B组件",
    com: markRaw(BVue),
  },
  {
    name: "C组件",
    com: markRaw(CVue),
  },
]);

const play = (item: object) => {
   = ;
};
</script>

<style scoped lang="scss"></style>

  1. 方法2
<!-- name -->
<template>
  <div style="display: flex; cursor: pointer">
    <div
      @click="play(item)"
      v-for="(item, index) in data"
      style="margin-left: 30px"
    >
      <div>{{  }}</div>
    </div>
  </div>
  <component :is="aaa"></component>
</template>

<script lang="ts">
import AVue from "./components/expame/";
import BVue from "./components/expame/";
import CVue from "./components/expame/";

export default {
  components: {
    AVue,
    BVue,
    CVue,
  },
};
</script>

<script lang="ts" setup>
import {
  computed,
  onMounted,
  reactive,
  ref,
  watch,
  watchEffect,
  markRaw,
  shallowRef,
} from "vue";

/**
 *
 */

const aaa = shallowRef("AVue");

const data = reactive([
  {
    name: "A组件",
    com: "AVue",
  },
  {
    name: "B组件",
    com: "BVue",
  },
  {
    name: "C组件",
    com: "CVue",
  },
]);

const play = (item: object) => {
   = ;
};
</script>

<style scoped lang="scss"></style>

slot 插槽

匿名插槽

// 父组件
<template>
  <div>
    <mySlot>
      <template v-slot>
        <div>匿名插槽</div>
      </template>
    </mySlot>
  </div>
</template>

<script lang="ts" setup>
import mySlot from "./components/mySlot/";
</script>

// 子组件
<template>
  <div>
    <slot></slot>
  </div>
</template>

具名插槽

// 父组件
<template>
  <div>
    <mySlot>
      <template v-slot:slot_1>
        <div>具名插槽</div>
      </template>
    </mySlot>
  </div>
</template>

<script lang="ts" setup>
import mySlot from "./components/mySlot/";
</script>

// 子组件
<template>
  <div>
    <header>
      <slot name="slot_1"></slot>
    </header>
  </div>
</template>

作用域插槽

// 父组件
<!-- name -->
<template>
  <div>
    <mySlot>
      <template v-slot="{ date }">
        <div>{{ date }}</div>
      </template>
    </mySlot>
  </div>
</template>

<script lang="ts" setup>
import mySlot from "./components/mySlot/";
</script>

// 子组件
<!-- name -->
<template>
  <div>
    <!-- 作用域插槽 -->
    <!-- 在子组件动态绑定参数 派发给父组件的slot去使用 -->
    <main>
      <div v-for="(item, index) in A" :key="index">
        <slot :date="item"></slot>
      </div>
    </main>
  </div>
</template>

<script lang="ts" setup>
import { reactive} from "vue";

type aaa = {
  name: string;
  age: number;
};
let A = reactive<aaa[]>([
  {
    name: "李四",
    age: 18,
  },
  {
    name: "张三",
    age: 19,
  },
  {
    name: "王五",
    age: 20,
  },
]);
</script>

异步组件(代码优化)

顶层 await
在setup语法糖里面 使用方法
< script setup > 中可以使用顶层 await。结果代码会被编译成 async setup()

	const { data } = await <Data>("./");

父组件引用子组件 通过defineAsyncComponent加载异步配合import 函数模式便可以分包

<script setup lang="ts">
import { reactive, ref, markRaw, toRaw, defineAsyncComponent } from 'vue'
 
const Dialog = defineAsyncComponent(() => import('../../components/Dialog/'))
 
//完整写法
 
const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () => import('./'),
 
  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间,默认为 200ms
  delay: 200,
 
  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

Suspense

  • Suspense:是 vue3 中新增的组件,类似于 keep-alive 不需要任何的引入,可以直接进行使用。自带两个 slot 分别为 default、fallback。顾名思义,当要加载的组件不满足状态时,Suspense 将回退到 fallback状态一直到加载的组件满足条件,才会进行渲染。
  • 最为常见的使用场景,配合 vue3 中 defineAsyncComponent 来加载异步组件时,进行 loading 的处理。
<!-- name -->
<template>
  <div>
    <Suspense>
      <template #default>
        <SyncVue></SyncVue>
      </template>

      <template #fallback>
        <skeleton></skeleton>
      </template>
    </Suspense>
  </div>
</template>

<!-- <script lang="ts">
import mySlot from "./components/mySlot/";
export default {};
</script> -->

<script lang="ts" setup>
// import Sync from "./components/expame/";

import {
  computed,
  onMounted,
  reactive,
  ref,
  watch,
  watchEffect,
  markRaw,
  shallowRef,
  defineAsyncComponent,
} from "vue";

import skeleton from "./components/expame/";

const SyncVue = defineAsyncComponent(
  () => import("./components/expame/")
);

/**
 * 引入
 * defineAsyncComponent 异步组件
 *
 * Suspense:是 vue3 中新增的组件,类似于 keep-alive 不需要任何的引入,可以直接进行使用。自带两个 slot 分别为 default、fallback。顾名思义,当要加载的组件不满足状态时,Suspense 将回退到 fallback状态一直到加载的组件满足条件,才会进行渲染。
 * 最为常见的使用场景,配合 vue3 中 defineAsyncComponent 来加载异步组件时,进行 loading 的处理。
 */
</script>

<style scoped lang="scss"></style>

Teleport传送组件

Teleport Vue 3.0新特性之一。

Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。

主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响

使用方法
通过to 属性 插入指定元素位置 to=“body” 便可以将Teleport 内容传送到指定位置

<Teleport to="body">
    <Loading></Loading>
</Teleport>

也可以自定义传送位置 支持 class id等 选择器

    <div ></div>
    <div class="modal"></div>
<template>
 
    <div class="dialog">
        <header class="header">
            <div>我是弹框</div>
            <el-icon>
                <CloseBold />
            </el-icon>
        </header>
        <main class="main">
            我是内容12321321321
        </main>
        <footer class="footer">
            <el-button size="small">取消</el-button>
            <el-button size="small" type="primary">确定</el-button>
        </footer>
    </div>
 
</template>
 
<script setup lang='ts'>
import { ref, reactive } from 'vue'
 
</script>
<style lang="less" scoped>
.dialog {
    width: 400px;
    height: 400px;
    background: #141414;
    display: flex;
    flex-direction: column;
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -200px;
    margin-top: -200px;
 
    .header {
        display: flex;
        color: #CFD3DC;
        border-bottom: 1px solid #636466;
        padding: 10px;
        justify-content: space-between;
    }
 
    .main {
        flex: 1;
        color: #CFD3DC;
        padding: 10px;
    }
 
    .footer {
        border-top: 1px solid #636466;
        padding: 10px;
        display: flex;
        justify-content: flex-end;
    }
}
</style>

多个使用场景

	<Teleport to=".modal1">
     <Loading></Loading>
</Teleport>
<Teleport to=".modal2">
     <Loading></Loading>
</Teleport>

动态控制teleport
使用disabled 设置为 true则 to属性不生效 false 则生效

    <teleport :disabled="true" to='body'>
      <A></A>
    </teleport>

内置组件keep-alive

有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。

开启keep-alive 生命周期的变化

初次进入时: onMounted> onActivated
退出后触发 deactivated
再次进入:
只会触发 onActivated
事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivated中

<!-- 基本 -->
<keep-alive>
  <component :is="view"></component>
</keep-alive>
 
<!-- 多个条件判断的子组件 -->
<keep-alive>
  <comp-a v-if="a > 1"></comp-a>
  <comp-b v-else></comp-b>
</keep-alive>
 
<!-- 和 `<transition>` 一起使用 -->
<transition>
  <keep-alive>
    <component :is="view"></component>
  </keep-alive>
</transition>

include 和 exclude

<keep-alive :include="" :exclude="" :max=""></keep-alive>

include 和 exclude 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

max

<keep-alive :max="10">
  <component :is="view"></component>
</keep-alive>

Transition

  1. 过度单个元素
<!-- name -->
<template>
  <div>
    <button @click="flag = !flag">变</button>
    <Transition name="fade">
      <div v-if="flag" class="box"></div>
    </Transition>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";

const flag = ref<boolean>(true);

/**
 *Vue 提供了两个内置组件,可以帮助你制作基于状态变化的过渡和动画:
  <Transition> 会在一个元素或组件进入和离开 DOM 时应用动画。本章节会介绍如何使用它。
  <TransitionGroup> 会在一个 v-for 列表中的元素或组件被插入,移动,或移除时应用动画。我们将在下一章节中介绍。
 */
</script>

<style scoped>
.box {
  width: 200px;
  height: 200px;
  background-color: red;
}

/* 进入之前 */
.fade-enter-from {
  width: 0;
  height: 0;
}
/* 过度曲线 */
.fade-enter-active {
  transition: all 1.5s ease;
}
/* 进入结束 */
.fade-enter-to {
  width: 200px;
  height: 200px;
}

/* 离开之前 */
.fade-leave-from {
  width: 200px;
  height: 200px;
}
/* 过度曲线 */
.fade-leave-active {
  transition: all 1.5s ease;
}
/* 进入结束 */
.fade-leave-to {
  width: 0;
  height: 0;
}
</style>

Transition自定义类名

<!-- name -->
<template>
  <div>
    <button @click="flag = !flag">变</button>
    <Transition name="fade" enter-from-class="e-from">
      <div v-if="flag" class="box"></div>
    </Transition>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";

const flag = ref<boolean>(true);

/**
 * 自定义类名:
 *  1.  enter-from-class="自定义类名"
 *  2.  enter-active-class="自定义类名"
 *  3.  enter-to-class="自定义类名"
 *  4.  leave-from-class="自定义类名"
 *  5.  leave-active-class="自定义类名"
 *  6.  leave-to-class="自定义类名"
 *
 */
</script>

<style scoped>
.box {
  width: 200px;
  height: 200px;
  background-color: red;
}
.e-from {
  width: 0;
  height: 0;
}
.fade-enter-active {
  transition: all 1.5s ease;
}
.fade-enter-to {
  width: 200px;
  height: 200px;
}

.fade-leave-from {
  width: 200px;
  height: 200px;
}
.fade-leave-active {
  transition: all 1.5s ease;
}
.fade-leave-to {
  width: 0;
  height: 0;
}
</style>

transtions结合

  1. 安装:npm install -S
<template>
  <div>
    <button @click="flag = !flag">变</button>
    <Transition
      name="fade"
      enter-from-class="animate__animated animate__fadeOut"
      enter-to-class="animate__animated animate__fadeOut"
    >
      <div v-if="flag" class="box"></div>
    </Transition>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import "";
const flag = ref<boolean>(true);

/**
 * 1. 引入   import "";
 * 2. 在Transtion中使用
 */
</script>

Transtion的duration属性

<template>
  <div>
    <button @click="flag = !flag">变</button>
    <Transition
      :duration="{ enter: '50', leave: '500' }"
      name="fade"
      enter-from-class="animate__animated animate__fadeOut"
      enter-to-class="animate__animated animate__fadeOut"
    >
      <div v-if="flag" class="box"></div>
    </Transition>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import "";
const flag = ref<boolean>(true);

/**
 * 1. :duration="500"   // 定义动画时长
 * 2. :duration="{ enter: '50', leave: '500' }"    // 单独定义动画时长
 */
</script>

Transtion声明周期

<!-- name -->
<template>
  <div>
    <button @click="flag = !flag">变</button>
    <Transition
      name="fade"
      @before-enter="enterFrom"
      @enter="enterActive"
      @after-enter="enterTo"
      @enter-cancelled="enterCancelled"
      @before-leave="leaveFrom"
      @leave="leaveActive"
      @after-leave="leaveTo"
      @leave-cancelled="leaveCancelled"
    >
      <div v-if="flag" class="box"></div>
    </Transition>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import "";
const flag = ref<boolean>(true);

/**
 * 1.@before-enter="对象名"   // 在元素被插入到 DOM 之前被调用  --  用这个来设置元素的 "enter-from" 状态
 * 2.@enter="对象名"   // 在元素被插入到 DOM 之后的下一帧被调用  --  用这个来开始进入动画
 * 3.@after-enter="对象名"   // 当进入过渡完成时调用。
 * 4.@enter-cancelled="对象名"   //   --  过度效果被打断
 *
 * 5.@before-leave="对象名"   // 在 leave 钩子之前调用  -- 大多数时候,你应该只会用到 leave 钩子
 * 6.@leave="对象名"   // 在离开过渡开始时调用  -- 用这个来开始离开动画
 * 7.@after-leave="对象名"   // 在离开过渡完成,且元素已从 DOM 中移除时调用
 * 8.@leave-cancelled="对象名"   // --  过度效果被打断
 */

const enterFrom = (el: Element) => {
  ("开始之前");
};

const enterActive = (el: Element, done: Function) => {
  ("过度曲线");
  // 调用回调函数 done 表示过渡结束
  // 如果与 CSS 结合使用,则这个回调是可选参数
  setTimeout(() => {
    done();
  }, 1000);
};
const enterTo = (el: Element) => {
  ("过度结束");
};

const enterCancelled = (el: Element) => {
  ("过度效果被打断");
};

const leaveFrom = (el: Element) => {
  ("结束之前");
};

const leaveActive = (el: Element, done: Function) => {
  ("过度曲线");
  // 调用回调函数 done 表示过渡结束
  // 如果与 CSS 结合使用,则这个回调是可选参数
  setTimeout(() => {
    done();
  }, 1000);
};

const leaveTo = (el: Element) => {
  ("过度结束");
};

const leaveCancelled = (el: Element) => {
  ("过度效果被打断");
};
</script>

<style scoped>
.box {
  width: 200px;
  height: 200px;
  background-color: red;
}
/* 进入之前 */
.fade-enter-from {
  width: 0;
  height: 0;
}
/* 过度曲线 */
.fade-enter-active {
  transition: all 1.5s ease;
}
/* 进入结束 */
.fade-enter-to {
  width: 200px;
  height: 200px;
}

/* 离开之前 */
.fade-leave-from {
  width: 200px;
  height: 200px;
}
/* 过度曲线 */
.fade-leave-active {
  transition: all 1.5s ease;
}
/* 进入结束 */
.fade-leave-to {
  width: 0;
  height: 0;
}
</style>

Transtion 结合动画库jsap使用

<template>
  <div>
    <button @click="flag = !flag">变</button>
    <Transition
      @before-enter="enterFrom"
      @enter="enterActive"
      @leave="leaveActive"
    >
      <div v-if="flag" class="box"></div>
    </Transition>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import gsap from "gsap";
const flag = ref<boolean>(true);

/**
 * 1. 下载  npm install jsap -S
 * 2. 引入  import gsap from "gsap";
 * 3. 使用  
 */

const enterFrom = (el: Element) => {
  // ("开始之前");
  (el, {
    height: 0,
    width: 0,
  });
};

const enterActive = (el: Element, done: ) => {
  (el, {
    height: 200,
    width: 200,
    onComplete: done,
  });
};

const leaveActive = (el: Element, done: ) => {
  (el, {
    height: 0,
    width: 0,
    onComplete: done,
  });
};
</script>

Transition 的 appear属性

<template>
  <div>
    <button @click="flag = !flag">变</button>
    <Transition
      appear
      appear-from-class="from"
      appear-active-class="active"
      appear-to-class="to"
    >
      <div v-if="flag" class="box"></div>
    </Transition>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
const flag = ref<boolean>(true);

/**
 * 1. 在进入时执行动画效果
 * 2. 可以结合使用
 */
</script>

<style scoped>
.box {
  width: 200px;
  height: 200px;
  background-color: red;
}

.from {
  width: 0;
  height: 0;
}
.active {
  transition: all 1.5s ease;
}
.to {
  width: 200px;
  height: 200px;
}
</style>

Transition-group过度列表

  1. 默认情况下,它不会渲染一个包裹元素,但是你可以通过 tag attribute 指定渲染一个元素。
  2. 过渡模式不可用,因为我们不再相互切换特有的元素。
  3. 内部元素总是需要提供唯一的 key attribute 值。
  4. CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
<transition-group>
     <div style="margin: 10px;" :key="item" v-for="item in list">{{ item }</div>
</transition-group>
const list = reactive<number[]>([1, 2, 4, 5, 6, 7, 8, 9])
const Push = () => {
    (123)
}
const Pop = () => {
    ()
}

< transition-group > 组件还有一个特殊之处。除了进入和离开,它还可以为定位的改变添加动画。只需了解新增的 v-move 类就可以使用这个新功能,它会应用在元素改变定位的过程中。像之前的类名一样,它的前缀可以通过 name attribute 来自定义,也可以通过 move-class attribute 手动设置

<template>
    <div>
        <button @click="shuffle">Shuffle</button>
        <transition-group class="wraps" name="mmm" tag="ul">
            <li class="cell" v-for="item in items" :key="">{{  }}</li>
        </transition-group>
    </div>
</template>
  
<script setup  lang='ts'>
import _ from 'lodash'
import { ref } from 'vue'
let items = ref((null, { length: 81 } as number[]).map((_, index) => {
    return {
        id: index,
        number: (index % 9) + 1
    }
}))
const shuffle = () => {
     = _.shuffle()
}
</script>
  
<style scoped lang="less">
.wraps {
    display: flex;
    flex-wrap: wrap;
    width: calc(25px * 10 + 9px);
    .cell {
        width: 25px;
        height: 25px;
        border: 1px solid #ccc;
        list-style-type: none;
        display: flex;
        justify-content: center;
        align-items: center;
    }
}
 
.mmm-move {
    transition: transform 0.8s ease;
}
</style>
<template>
  <div>
    <button @click="change">变</button>
    <transition-group move-class="mmm" class="wraps" tag="div">
      <!-- 当key值绑定index时动画不生效。原因是dom是动态的,每次都会重新渲染 -->
      <div class="items" v-for="(item, index) in arr" :key="">
        {{  }}
      </div>
    </transition-group>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref } from "vue";
import _ from "lodash";
let arr = ref(
  (null, { length: 81 } as number[]).map((_, index) => {
    return {
      id: index,
      num: (index % 9) + 1,
    };
  })
);
(arr);

// 改变排列
const change = () => {
  (arr);
   = _.shuffle();
  (arr);
};

/**
 * 1.平移动画 move-class
 */
</script>

<style>
.wraps {
  display: flex;
  flex-wrap: wrap;
  width: calc(25px * 9 + 18px);
}
.items {
  height: 25px;
  width: 25px;
  border: 1px solid #ccc;
}

.mmm {
  transition: all 1s;
}
</style>

Vue 也同样可以给数字 Svg 背景颜色等添加过度动画 今天演示数字变化

<template>
    <div>
        <input step="20" v-model="" type="number" />
        <div>{{ (0) }}</div>
    </div>
</template>
    
<script setup lang='ts'>
import { reactive, watch } from 'vue'
import gsap from 'gsap'
const num = reactive({
    tweenedNumber: 0,
    current:0
})
 
watch(()=>, (newVal) => {
// to(选择器,状态对象) 是指从原有状态 变化成参数里的状态对象
    (num, {
        duration: 1,
        tweenedNumber: newVal
    })
})
 
</script>
    
<style>
</style>

依赖注入Provide / Inject

provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。

  1. 祖先组件(声明provide)
<!-- name -->
<template>
  <div>
    <div>爷爷<button @click="playColor">变红</button></div>
    <div class="box"></div>
    <A></A>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, watch, provide } from "vue";
import A from "./components/";

const color = ref<string>("pink");

const playColor = () => {
   = "red";
};

provide("color", color);

/**
 *
 */
</script>

<style scoped>
.box {
  width: 100px;
  height: 100px;
  background-color: v-bind(color);
}
</style>

  1. 后代组件(调用inject)
<!-- 组件A -->
<template>
  <div>
    <div>爸爸</div>
    <div class="box"></div>
    <B></B>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, watch, inject } from "vue";
import B from "./";
import type { Ref } from "vue";
const color = inject<Ref<string>>("color");
/**
 *
 */
</script>

<style scoped>
.box {
  width: 100px;
  height: 100px;
  background-color: v-bind(color);
}
</style>

<!-- 组件B -->
<template>
  <div>
    <div>孩子</div>
    <div class="box"></div>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, watch, inject } from "vue";
import type { Ref } from "vue";
const color = inject<Ref<string>>("color");
/**
 *
 */
</script>

<style scoped>
.box {
  width: 100px;
  height: 100px;
  background-color: v-bind(color);
}
</style>

后代修改祖先传的值

可以添加readonly使后代组件无法修改祖先传的值:provide(“color”, readonly(color));

  1. 祖先组件(声明provide)
<!-- name -->
<template>
  <div>
    <div>爷爷<button @click="playColor">变红</button></div>
    <div class="box"></div>
    <A></A>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, watch, provide } from "vue";
import A from "./components/";

const color = ref<string>("pink");

const playColor = () => {
   = "red";
};

provide("color", color);

/**
 *
 */
</script>

<style scoped>
.box {
  width: 100px;
  height: 100px;
  background-color: v-bind(color);
}
</style>

  1. 后代组件
<!-- 后代 -->
<template>
  <div>
    <div>孩子</div>
    <div class="box"></div>
    <button @click="playColor">变回来</button>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, watch, inject } from "vue";
import type { Ref } from "vue";
const color = inject<Ref<string>>("color");

const playColor = () => {
  // 添加感叹号的原因,color有string和number两种类型,进行类型断言
  // 另一种解决方法是:const color = inject<Ref<string>>("color",ref("red"));
  color!.value = "pink";
};
/**
 *
 */
</script>

<style scoped>
.box {
  width: 100px;
  height: 100px;
  background-color: v-bind(color);
}
</style>

兄弟组件传参

方法一:借用父组件中转

父组件

<!-- name -->
<template>
  <div>
    <A @bbb="Q1"></A>
    <B :Q2="qqq"></B>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref } from "vue";
import A from "./components/";
import B from "./components/";

const qqq = ref<string>("");

const Q1 = (e: string) => {
   = e;
};

/**
 *
 */
</script>

<style scoped>
.box {
}
</style>

传参组件

<!-- name -->
<template>
  <div></div>
</template>

<script lang="ts" setup>
import { ref, reactive, toRefs } from "vue";
const emit = defineEmits(["bbb"]);
const aaa = () => {
  emit("bbb", "我来自A组件");
};

aaa();
</script>

<style scoped lang="less"></style>

接参组件

<!-- name -->
<template>
  <div>{{ qqq.Q2 }}</div>
</template>

<script lang="ts" setup>
import { ref, reactive, toRefs } from "vue";

const qqq = withDefaults(
  defineProps<{
    Q2: string;
  }>(),
  {
    Q2: () => "",
  }
);
</script>

<style scoped lang="less"></style>

方法二:使用Event Bus

原码实现:

type BusClass = {
  emit: (name: string) => void
  on: (name: string, callback: Function) => void
}
type PramsKey = string | number | symbol

type List = {
  [key: PramsKey]: Array<Function>
}

class Bus implements BusClass {
  list: List
  constructor() {
     = {}
  }
  emit(name: string, ...args: Array<any>) {
    let eventName: Array<Function> = [name]
    (fn => {
      (this, args)
    })
  }

  on(name: string, callback: Function) {
    // 如果之前有就直接用,如果没有就创建一个空的数组
    let fn: Array<Function> = [name] || []
    (callback)
    [name] = fn
  }
}

export default new Bus()

父组件

<!-- name -->
<template>
  <div>
    <A></A>
    <B></B>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref } from "vue";
import A from "./components/";
import B from "./components/";

/**
 *
 */
</script>

<style scoped>
.box {
}
</style>

兄弟组件

<!-- 发送-->
<template>
  <div>
    <button @click="play">发送</button>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive, toRefs } from "vue";
import Bus from "../Bus";

let bb = "我来自A组件";
// 因为在 setup 语法糖中的代码最先执行, 所以监听端会在DOM创建之前就执行。但是事件发布端, 需要和点击事件绑定, 所以在 emit 可以调用回调函数。
const play = () => {
  ("aa", bb);
};
</script>

<style scoped lang="less"></style>

<!-- 接收-->
<template>
  <div>{{ bb }}</div>
</template>

<script lang="ts" setup>
import { ref, reactive, toRefs } from "vue";
import Bus from "../Bus";

const bb = ref("");

("aa", (e: string) => {
   = e;
});
</script>

<style scoped lang="less"></style>

方法二:Mitt(对于event Bus的封装)

在vue3中 o n , on, onoff 和 $once 实例方法已被移除,组件实例不再实现事件触发接口,因此大家熟悉的EventBus便无法使用了。然而我们习惯了使用EventBus,对于这种情况我们可以使用Mitt库(其实就是我们视频中讲的发布订阅模式的设计)

1.安装

npm install mitt -S

初始化
全局总线,vue 入口文件 中挂载全局属性

import { createApp } from 'vue'
import App from './'
import mitt from 'mitt'
 
const Mit = mitt()
 
//TypeScript注册
// 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module "vue" {
    export interface ComponentCustomProperties {
        $Bus: typeof Mit
    }
}
 
const app = createApp(App)
 
//Vue3挂载全局API
.$Bus = Mit
 
('#app')

3使用方法通过emit派发, on 方法添加事件,off 方法移除,clear 清空所有

A组件派发(emit)

<template>
    <div>
        <h1>我是A</h1>
        <button @click="emit1">emit1</button>
        <button @click="emit2">emit2</button>
    </div>
</template>
 
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance();
const emit1 = () => {
    instance?.proxy?.$('on-num', 100)
}
const emit2 = () => {
    instance?.proxy?.$('*****', 500)
}
</script>
 
<style>
</style>

B组件监听(on)

<template>
    <div>
        <h1>我是B</h1>
    </div>
</template>
 
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
instance?.proxy?.$('on-num', (num) => {
    (num,'===========>B')
})
</script>
 
<style>
</style>

监听所有事件( on(“*”) )

instance?.proxy?.$('*',(type,num)=>{
    (type,num,'===========>B')
})

移除监听事件(off)

const Fn = (num: any) => {
    (num, '===========>B')
}
instance?.proxy?.$('on-num',Fn)//listen
instance?.proxy?.$('on-num',Fn)//unListen

清空所有监听(clear)

instance?.proxy?.$() 

TSX

  1. 安装 (装不上,下来找原因)
npm install @vitejs/plugin-vue-jsx -D

自动引入插件

  1. 安装
npm i -D unplugin-auto-import
  1. 配置 文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

import { resolve } from 'path'

import AutoImport from 'unplugin-auto-import/vite'   // 引入

// /config/
export default defineConfig({
  plugins: [vue(),
  // 声明
  AutoImport({
    imports: ['vue'],  // 语言
    dts: 'src/'   // 配置信息位置 
  })],
  resolve: {
    alias: {
      "@": resolve(__dirname, 'src'), // 路径别名
    },
    extensions: ['.js', '.json', '.ts'] // 使用路径别名时想要省略的后缀名,可以自己 增减
  }
})

演示

<template>
  <button @click="play">测试</button>
  <div>{{ cs }}</div>
</template>

<script lang="ts" setup>
const play = () => {
   = !;
};

const cs = ref<boolean>(false);
</script>

v-model

其实是一个语法糖,由props与emit组合而成

1.默认值的改变

prop:value -> modelValue;
事件:input -> update:modelValue;
v-bind 的 .sync 修饰符和组件的 model 选项已移除
新增 支持多个v-model
新增 支持自定义 修饰符 Modifiers

单v-model

父子组件传值

<!-- 父组件-->
<template>
  <div>父组件</div>
  <div>
    <button @click="play">开关 -- {{ aaa }}</button>
  </div>
  <VModel v-model="aaa"></VModel>
</template>

<script lang="ts" setup>
import VModel from "@/components/";

const aaa = ref<boolean>(true);

const play = () => {
   = !;
};
</script>

<style scoped></style>

<!-- 子组件-->
<template>
  <div>我是子组件</div>
  <div><button @click="play">子组件开关</button></div>
  <div v-if="modelValue">
    <input type="text" />
  </div>
</template>

<script lang="ts" setup>
defineProps({
  modelValue: {
    type: Boolean,
    default: true,
  },
});

const emit = defineEmits(["update:modelValue"]);

const play = () => {
  emit("update:modelValue", false);
};
</script>

<style scoped lang="less"></style>

多个v-model

<template>
  <div>父组件</div>
  <div>
    <button @click="play">开关 -- {{ aaa }} -- {{ bbb }}</button>
  </div>
  <VModel v-model="aaa" v-model:two="bbb"></VModel>
</template>

<script lang="ts" setup>
import VModel from "@/components/";

const aaa = ref<boolean>(true);
const bbb = ref<string>("hello world!");

const play = () => {
   = !;
};
</script>

<style scoped></style>


<template>
  <div>我是子组件</div>
  <div><button @click="play">子组件开关</button></div>
  <div v-if="modelValue">
    <input type="text" @input="change" :value="two" />
  </div>
</template>

<script lang="ts" setup>
defineProps({
  modelValue: {
    type: Boolean,
    default: true,
  },
  two: {
    type: String,
    default: "",
  },
});

// 声明
// const;
const emit = defineEmits(["update:modelValue", "update:two"]);
// end

const play = () => {
  emit("update:modelValue", false);
};

// 传给父组件
const change = (e: Event) => {
  let target =  as HTMLInputElement;
  emit("update:two", );
};
// end
</script>

<style scoped lang="less"></style>


自定义修饰符

添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件。在下面的示例中,我们创建了一个组件,其中包含默认为空对象的 modelModifiers prop

<script setup lang='ts'>
 
type Props = {
    modelValue: boolean,
    title?: string,
    modelModifiers?: {
        default: () => {}
    }
    titleModifiers?: {
        default: () => {}
    }
 
}
 
const propData = defineProps<Props>()
 
const emit = defineEmits(['update:modelValue', 'update:title'])
 
const close = () => {
    ();
 
    emit('update:modelValue', false)
    emit('update:title', '我要改变')
}

directive-自定义指令(属于破坏性更新)

Vue中有v-if,v-for,v-bind,v-show,v-model 等等一系列方便快捷的指令 今天一起来了解一下vue里提供的自定义指令

Vue3指令的钩子函数

created 元素初始化的时候
beforeMount 指令绑定到元素后调用 只调用一次
mounted 元素插入父级dom调用
beforeUpdate 元素被更新之前调用
update 这个周期方法被移除 改用updated
beforeUnmount 在元素被移除前调用
unmounted 指令被移除后调用 只调用一次

  • 页面加载触发:created beforeMount Mounted
  • 出现触发:created beforeMount Mounted
  • 关闭触发:beforeUnmount unmounted
  • 修改触发:beforeUpdate updated

注:子组件最外层只能有一个div

  mounted(...arr: Array<any>) {
    ("初始化完成");
    (arr);
    /**
     * arr
     *  第一个值指的是当前绑定指令元素
     *  第二个值:dir ==> 传的东西都放在里面
     *  第三个值:vNode ==> 当前组件的虚拟Dom
     *  第四个值:prevVNode ==> 上一个组件的虚拟Dom
     */
  },
<template>
  <div>父组件</div>
  <div>
    <button @click="on_off = !on_off">开关</button>
    <button @click="text = 'hello world!'">修改</button>
  </div>
  <!-- 
    { background: 'red' }   // 传值
    aaa                     // 自定义参数
    bbb                     // 自定义修饰符
    注:传递的值在每一个钩子函数中都可以看到
   -->
  <A v-moveDirective:="{ background: 'red' }"></A>
</template>

<script lang="ts" setup>
import { Directive, DirectiveBinding } from "vue";

import A from "./components/";

type Dir = { background: string;};

const text = ref<string>("Directive");

const on_off = ref<boolean>(true);

// 指令生命周期
const vMoveDirective: Directive = {
  created() {("初始化");},
  
  beforeMount() {("初始化一次前");},

  mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
    ("初始化完成");
    (el);
    ();
     = ;
  },
  beforeUpdate() {("修改前");},
  
  updated() {("修改完成");},
  
  beforeUnmount() {("关闭前");},
  
  unmounted() {("关闭");},
};
// end
</script>

函数简写

只在 mounted 和 updated 时触发

<template>
   <div>
      <input v-model="value" type="text" />
      <A v-move="{ background: value }"></A>
   </div>
</template>
   
<script setup lang='ts'>
import A from './components/'
import { ref, Directive, DirectiveBinding } from 'vue'
let value = ref<string>('')
type Dir = {
   background: string
}
const vMove: Directive = (el, binding: DirectiveBinding<Dir>) => {
    = 
}
</script>
练习

思路:监听鼠标按下的事件,当鼠标按下后监听鼠标移动,并动态改变元素位置,最后鼠标松开关闭事件

<!-- name -->
<template>
  <div style="position: relative; height: 100vh; width: 100vw">
    <div
      v-move
      style="
        position: absolute;
        width: 100px;
        height: 100px;
        background-color: pink;
      "
    >
      父组件
    </div>
  </div>
</template>

<script lang="ts" setup>
import { Directive, DirectiveBinding } from "vue";

// addEventListener() 方法用于向指定元素添加事件句柄。

// MouseEvent接口指用户与指针设备(如鼠标)交互时发生的事件。
// mousedown在元素上按下任意鼠标按键。
// mouseup在元素上释放任意鼠标按键。
// mousemove指针在元素内移动时持续触发。
// click在元素上按下并释放任意鼠标按键。

const vMove: Directive<any, void> = (
  el: HTMLElement,
  bingding: DirectiveBinding
) => {
  let box: HTMLElement = el as HTMLDivElement;

  const mouseDown = (e: MouseEvent) => {
    let X =  - ;
    let Y =  - ;
    const move = (e: MouseEvent) => {
       =  - X + "px";
       =  - Y + "px";
    };
    ("mousemove", move);
    ("mouseup", () => {
      ("mousemove", move);
    });
  };

  ("mousedown", mouseDown);
};
</script>

<style scoped></style>

自定义的hook

Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数
Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数
Vue3 hook 库Get Started | VueUse : /guide/

案例

import { onMounted } from 'vue'
 
 
type Options = {
    el: string
}
 
type Return = {
    Baseurl: string | null
}
export default function (option: Options): Promise<Return> {
 
    return new Promise((resolve) => {
        onMounted(() => {
            const file: HTMLImageElement = () as HTMLImageElement;
             = ():void => {
                resolve({
                    Baseurl: toBase64(file)
                })
            }
 
        })
 
 
        const toBase64 = (el: HTMLImageElement): string => {
            const canvas: HTMLCanvasElement = ('canvas')
            const ctx = ('2d') as CanvasRenderingContext2D
             = 
             = 
            (el, 0, 0, ,)
            ();
            
            return ('image/png')
 
        }
    })
 
 
}

自定义全局函数和变量

由于Vue3 没有Prototype 属性 使用 代替 然后去定义变量和函数

在 下写入,如:

.$env = '123'
.$env2 = {
  name<T>(str: T) {
    return '我叫' + str
  }
}


type Filter = {
  name<T>(str: T): string
}

// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
declare module 'vue' {
  export interface ComponentCustomProperties {
    $env: string
    $env2: Filter
  }
}

在setup中使用

方式一:
import { getCurrentInstance, ComponentInternalInstance } from 'vue';
const { appContext } = <ComponentInternalInstance>getCurrentInstance()
(.$env);
 
推荐 方式二:
 
import {ref,reactive,getCurrentInstance} from 'vue'
const app = getCurrentInstance()
(app?.proxy?.$('李四'))

自定义插件

插件
插件是自包含的代码,通常向 Vue 添加全局级功能。你如果是一个对象需要有install方法Vue会帮你自动注入到install 方法 你如果是function 就直接当install 方法去使用

使用插件
在使用 createApp() 初始化 Vue 应用程序后,你可以通过调用 use() 方法将插件添加到你的应用程序中。

实现一个Loading插件

components/

<template>
  <div
    v-if="isShow"
    style="width: 100vw; height: 100vh; background-color: #666; position: fixed;top: 0;"
  >
    loading
  </div>
</template>
    
<script setup lang='ts'>
import { ref } from 'vue';
const isShow = ref(false)//定位loading 的开关
 
const show = () => {
     = true
}
const hide = () => {
     = false
}
//对外暴露 当前组件的属性和方法
defineExpose({
    isShow,
    show,
    hide
})
</script>

components/

import {  createVNode, render, VNode, App } from 'vue';
import Loading from './'
 
export default {
    install(app: App) {
        //createVNode vue提供的底层方法 可以给我们组件创建一个虚拟DOM 也就是Vnode
        const vnode: VNode = createVNode(Loading)
        //render 把我们的Vnode 生成真实DOM 并且挂载到指定节点
        render(vnode, )
        // Vue 提供的全局配置 可以自定义
        .$loading = {
            show: () => ?.exposed?.show(),
            hide: () => ?.exposed?.hide()
        }
 
    }
}

import Loading from './components/loading'
 
 
let app = createApp(App)
 
(Loading)
 
 
type Lod = {
    show: () => void,
    hide: () => void
}
//编写ts loading 声明文件放置报错 和 智能提示
declare module '@vue/runtime-core' {
    export interface ComponentCustomProperties {
        $loading: Lod
    }
}
 
 
('#app')

使用

<template>
 
  <div></div>
 
</template>
 
<script setup lang='ts'>
import { ref,reactive,getCurrentInstance} from 'vue'
const  instance = getCurrentInstance()  
instance?.proxy?.$()
setTimeout(()=>{
  instance?.proxy?.$()
},5000)
 
 
// (instance)
</script>
<style>
*{
  padding: 0;
  margin: 0;
}
</style>

样式穿透 /deep/ 和 :deep

vue3推荐:deep

插槽选择器

A 组件定义一个插槽

<template>
    <div>
        我是插槽
        <slot></slot>
    </div>
</template>
<style scoped>
  :slotted(.a) {                 // 修改了文件下的样式
    color:red
}
</style>

在 引入

<template>
    <div>
        <A>
            <div class="a">私人定制div</div>
        </A>
    </div>
</template>
 
<script setup>
import A from "@/components/"
</script>

全局选择器

<style lang="less" scoped>
:global(div){
    color:red
}
</style>

css Style完整新特性

动态 CSS

单文件组件的

<template>
    <div class="div">
       小满是个弟弟
    </div>
</template>
 
<script lang="ts" setup>
import { ref } from 'vue'
const red = ref<string>('red')
</script>
 
<style lang="less" scoped>
.div{
   color:v-bind(red)
}
 
</style>

如果是对象 v-bind 请加引号

 <template>
    <div class="div">
        小满是个弟弟
    </div>
</template>
 
<script lang="ts" setup>
import { ref } from "vue"
const red = ref({
    color:'pink'
})
</script>
 
<style lang="less" scoped>
.div {
    color: v-bind('');
}

</style>

module

自定义注入名称(多个可以用数组)

你可以通过给 module attribute 一个值来自定义注入的类对象的 property 键

<template>
    <div :class="[,]">
        小满是个弟弟
    </div>
</template>
 
<style module="zs">
.red {
    color: red;
    font-size: 20px;
}
.border{
    border: 1px solid #ccc;
}
</style>

与组合式 API 一同使用

注入的类可以通过 useCssModule API 在 setup() 和

<template>
    <div :class="[,]">
        小满是个弟弟
    </div>
</template>
 
 
<script setup lang="ts">
import { useCssModule } from 'vue'
const css = useCssModule('zs')
</script>
 
<style module="zs">
.red {
    color: red;
    font-size: 20px;
}
.border{
    border: 1px solid #ccc;
}
</style>

使用场景一般用于TSX 和 render 函数 居多

Vue3集成Tailwind CSS

Tailwind CSS中文官网地址:/
安装学习地址:/jrQvZ

Event Loop

JS 执行机制
在我们学js 的时候都知道js 是单线程的如果是多线程的话会引发一个问题在同一时间同时操作DOM 一个增加一个删除JS就不知道到底要干嘛了,所以这个语言是单线程的但是随着HTML5到来js也支持了多线程webWorker 但是也是不允许操作DOM

单线程就意味着所有的任务都需要排队,后面的任务需要等前面的任务执行完才能执行,如果前面的任务耗时过长,后面的任务就需要一直等,一些从用户角度上不需要等待的任务就会一直等待,这个从体验角度上来讲是不可接受的,所以JS中就出现了异步的概念。

同步任务
代码从上到下按顺序执行

异步任务
1.宏任务
script(整体代码)、setTimeout、setInterval、UI交互事件、postMessage、Ajax

2.微任务
catch finally、MutaionObserver、( 环境)

运行机制

所有的同步任务都是在主进程执行的形成一个执行栈,主线程之外,还存在一个"任务队列",异步任务执行队列中先执行宏任务,然后清空当次宏任务中的所有微任务,然后进行下一个tick如此形成循环。

next

nextTick 就是创建一个异步任务,那么它自然要等到同步任务执行完成后才执行。

<template>
   <div ref="xiaoman">
      {{ text }}
   </div>
   <button @click="change">change div</button>
</template>
   
<script setup lang='ts'>
import { ref,nextTick } from 'vue';
 
const text = ref('小满开飞机')
const xiaoman = ref<HTMLElement>()
 
const change = async () => {
    = '小满不开飞机'
   (?.innerText) //小满开飞机
   await nextTick();
   (?.innerText) //小满不开飞机
}
</script>

unocss原子化

参考:/AkrHY

函数式编程,h函数

参考:/tD9Nc

Vue开发桌面程序Electron

ps:打包后白屏,无法解决。爆炸…
开发桌面程序

创建一个vite 项目
npm init vite@latest

安装electron
npm install electron -D
npm install vite-plugin-electron -D

根目录新建 electron / 

修改 配置文件
引入electron插件配置main entry对应electron的文件

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import electron from 'vite-plugin-electron'

// /config/
export default defineConfig({
  plugins: [vue(), electron({
    // 这里需要调整main对象,vite-plugin-electron 0.10.4中使用main会报错。
    // main: {
    //   entry: "electron/"
    // }
    entry: "electron/"
  })],
})

编写代码 electron /

import { app, BrowserWindow } from 'electron'
import path from 'path'
//app 控制应用程序的事件生命周期。
//BrowserWindow 创建并控制浏览器窗口。
 
let win: BrowserWindow | null;
//定义全局变量获取 窗口实例
 
const createWindow = () => {
    win = new BrowserWindow({
        //
        webPreferences: {
            devTools: true,
            contextIsolation: false,
            nodeIntegration: true
            //允许html页面上的javascipt代码访问nodejs 环境api代码的能力(与node集成的意思)
        }
    })
    if () {
        ((__dirname, "../"));
    } else {
//VITE_DEV_SERVER_HOST 如果是undefined 换成  VITE_DEV_SERVER_HOSTNAME
       (`${['VITE_DEV_SERVER_URL']}`);
    }
}
//isPackage 不好使换下面的
  //  if(.NODE_ENV != 'development'){
      //  ((__dirname, "../"));
  //  }else{
        //(`${['VITE_DEV_SERVER_URL']}`);}`)
   // }
//在Electron完成初始化时被触发
().then(createWindow)

配置package json 增加main 字段 type 去掉

{
  "name": "my_vite",
  "private": true,
  "version": "0.0.0",
  "main": "dist-electron/",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build && electron-builder",
    "preview": "vite preview"
  },
  "dependencies": {
    "cross-env": "^7.0.3",
    "vue": "^3.2.45"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.0.0",
    "electron": "^22.0.0",
    "electron-builder": "^23.6.0",
    "typescript": "^4.9.3",
    "vite": "^4.0.0",
    "vite-plugin-electron": "^0.10.4",
    "vue-tsc": "^1.0.11"
  },
  "build": {
    "appId": "",
    "productName": "electron",
    "asar": true,
    "copyright": "Copyright © 2022 electron",
    "directories": {
      "output": "release/"
    },
    "files": [
      "dist-electron"
    ],
    "mac": {
      "artifactName": "${productName}_${version}.${ext}",
      "target": [
        "dmg"
      ]
    },
    "win": {
      "target": [
        {
          "target": "nsis",
          "arch": [
            "x64"
          ]
        }
      ],
      "artifactName": "${productName}_${version}.${ext}"
    },
    "nsis": {
      "oneClick": false,
      "perMachine": false,
      "allowToChangeInstallationDirectory": true,
      "deleteAppDataOnUninstall": false
    },
    "publish": [
      {
        "provider": "generic",
        "url": "http://127.0.0.1:8080"
      }
    ],
    "releaseInfo": {
      "releaseNotes": "版本更新的具体内容"
    }
  }
}

npm run dev

打包Electron

需要安装electron-builder

npm install electron-builder -D

package json 配置 build 修改npm run build命令

"build": "vue-tsc --noEmit && vite build  &&  electron-builder",
  "build": {
    "appId": "",
    "productName": "electron",
    "asar": true,
    "copyright": "Copyright © 2022 electron",
    "directories": {
      "output": "release/"
    },
    "files": [
      "dist"
    ],
    "mac": {
      "artifactName": "${productName}_${version}.${ext}",
      "target": [
        "dmg"
      ]
    },
    "win": {
      "target": [
        {
          "target": "nsis",
          "arch": [
            "x64"
          ]
        }
      ],
      "artifactName": "${productName}_${version}.${ext}"
    },
    "nsis": {
      "oneClick": false,
      "perMachine": false,
      "allowToChangeInstallationDirectory": true,
      "deleteAppDataOnUninstall": false
    },
    "publish": [
      {
        "provider": "generic",
        "url": "http://127.0.0.1:8080"
      }
    ],
    "releaseInfo": {
      "releaseNotes": "版本更新的具体内容"
    }
  }
npm run build

Vue响应性语法糖

注:将来可能有用
参考:/EP0wW

环境变量

  1. 查看项目的环境变量
// 查看环境变量
();

/**
 * BASE_URL: "/"     路由应用前缀
 *
 * DEV: true    // 是否开发环境
 *
 * MODE: "development"     // 当前运行环境
 *
 * PROD: false     // 生否生产环境
 *
 * SSR: false      // 是否服务端渲染
 */
  1. 配置开发环境:
    在根目录下创建.文件
    注意:.env后可以是任意名称,只是通常情况下都叫development
// 在.里写入
VITE_HTTP=
// 在的dev里添加:
// --mode 配置开发环境的文件名
// 现在已经默认读取了
  "scripts": {
    "dev": "vite  --mode development",
    "build": "vue-tsc && vite build",
    "preview": "vite preview"
  },
  1. 配置生产环境:
    在根目录下创建.文件
    注意:.env后可以是任意名称,只是通常情况下都叫production
// 在.里写入
VITE_HTTP=

// 默认读取production。不需要配置

  1. 查看打包文件
    打开dist文件夹:
安装:npm i http-serve -g
输入:http-server -p 9002
  1. 在vite中获取环境变量
// 引入
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'

// /config/
export default ({ mode }: any) => {
  (loadEnv(mode, ));

  return defineConfig({
    plugins: [vue()],
  })
} 

webpack 构建 Vue3项目

为什么要手写webpack 不用cli (脑子有病)并不是 其实是为了加深我们对webpack 的了解方便以后灵活运用webpack 的技术

参考:/Alf9Q

Vue3 性能优化

性能优化

我们可以使用谷歌浏览器自带的DevTools 进行性能分析 LightHouse

参数介绍

选项代表意思:
FCP (First Contentful Paint):首次内容绘制的时间,浏览器第一次绘制DOM相关的内容,也是用户第一次看到页面内容的时间。
Speed Index: 页面各个可见部分的显示平均时间,当我们的页面上存在轮播图或者需要从后端获取内容加载时,这个数据会被影响到。
LCP (Largest Contentful Paint):最大内容绘制时间,页面最大的元素绘制完成的时间。
TTI(Time to Interactive):从页面开始渲染到用户可以与页面进行交互的时间,内容必须渲染完毕,交互元素绑定的事件已经注册完成。
TBT(Total Blocking Time):记录了首次内容绘制到用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。 CLS(Cumulative Layout Shift):计算布局偏移值得分,会比较两次渲染帧的内容偏移情况,可能导致用户想点击A按钮,但下一帧中,A按钮被挤到旁边,导致用户实际点击了B按钮。

代码分析

由于我们使用的是vite vite打包是基于rollup 的我们可以使用 rollup 的插件

npm install rollup-plugin-visualizer

配置 记得设置open 不然无效

import { visualizer } from 'rollup-plugin-visualizer';
plugins: [vue(), vueJsx(),visualizer({
      open:true
 })],

然后进行npm run build打包

Vite 配置优化

build:{
       chunkSizeWarningLimit:2000,
       cssCodeSplit:true, //css 拆分
       sourcemap:false, //不生成sourcemap
       minify:false, //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。
       assetsInlineLimit:5000 //小于该值 图片将打包成Base64 
},

PWA离线存储技术

npm install vite-plugin-pwa -D

修改

import { VitePWA } from 'vite-plugin-pwa' 
plugins: [vue(),VitePWA(
	      workbox:{
          cacheId:"XIaoman",//缓存名称
          runtimeCaching:[
            {
              urlPattern:/.*\.js.*/, //缓存文件
              handler:"StaleWhileRevalidate", //重新验证时失效
              options:{
                cacheName:"XiaoMan-js", //缓存js,名称
                expiration:{
                  maxEntries:30, //缓存文件数量 LRU算法
                  maxAgeSeconds:30 * 24 * 60 * 60 //缓存有效期
 
                }
              }
            }
          ]
      },
)],

PWA 技术的出现就是让web网页无限接近于Native 应用

可以添加到主屏幕,利用manifest实现
可以实现离线缓存,利用service worker实现
可以发送通知,利用service worker实现

进行 npm run build 打包会生成

其他性能优化

  1. 图片懒加载
import lazyPlugin from 'vue3-lazy'

<img v-lazy="" >
  1. 虚拟列表
    当后台一次返回大量列表数据时,可以使用虚拟列表。
    其实际原理是,在可视化区域展示Dom
    element-plus已集成
  2. 多线程 使用 new Worker 创建

worker脚本与主进程的脚本必须遵守同源限制。他们所在的路径协议、域名、端口号三者需要相同

const myWorker1 = new Worker("./");

都使用postMessage发送消息

(arrayBuffer, [arrayBuffer]);

都使用onmessage接收消息

 = function (e) {
 // xxx这里是worker脚本的内容
};

关闭

();    

VueUse 库已经集成了 webWorker

  1. 防抖节流

同样VueUse 也是集成了

Vue3 Web Components

Web Components 提供了基于原生支持的、对视图层的封装能力,可以让单个组件相关的 javaScript、css、html模板运行在以html标签为界限的局部环境中,不会影响到全局,组件间也不会相互影响 。 再简单来说:就是提供了我们自定义标签的能力,并且提供了标签内完整的生命周期 。

参考:/qq1195566313/article/details/127328300