# 创建项目

# 使用vue-cli创建

// 查看vue-cli版本,确保版本在4.5.0以上
vue --version

// 安装或升级你的vue-cli版本
npm install -g @vue/cli

// 创建
vue create vue_test

# 使用vite创建

npm create vite
yarn create vite

// 使用vite4,可以兼容node16
npm create vite@4 

// 或者 会多了end.d.ts用于配置环境变量
npm create vue@latest

# scss安装

  • 在项目终端中输入面的命令
npm install sass -d
  • 在src/assets文件夹下新建,scss文件夹(文件名称随意),在文件夹下新建main.scss文件
  • 在vite.config.js文件中添加配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
 
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData:'@import "./src/assets/sass/main.scss";'
      }
    }
  }
})

# 安装路由

  • 在终端输入选择的命令
npm install vue-router@next
  • 在src下新建ruter文件夹。在ruter文件夹下新建俩个ts文件(index.ts、routes.ts),进行路由配置
// index.ts
import { createRouter, createWebHistory } from 'vue-router'
// 导入路由页面的配置
import routes from './routes';
 
// 路由参数配置
const router = createRouter({
    // 使用hash(createWebHashHistory)模式,(createWebHistory是HTML5历史模式,支持SEO)
    history: createWebHistory(),
    routes,
    scrollBehavior(to, from, savedPosition) {
        // 始终滚动到顶部
        return { top: 0 };
    }
})
 
// 全局前置守卫,这里可以加入用户登录判断
router.beforeEach((to, from, next) => {
    // 继续前进 next()
    // 返回 false 以取消导航
    next()
})
 
// 全局后置钩子,这里可以加入改变页面标题等操作
router.afterEach((to, from) => {
    const _title = to.meta.title
    if (_title) {
        window.document.title = _title
    }
})
export default router
 
//routes.ts
const routes = [
    {
        path: "/",
        redirect:"/home"
    },
    {
        path:'/home',
        name:"home",
        component:()=> import("../views/home.vue")
    }
]
export default routes
  • main.ts中引入使用




 



import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 引入使用
import router from './router'

createApp(app).use(router).mount('#app')

# 安装axios

  • 在终端输入选择的命令
npm install axios

# 项目相关命令

"scripts": {
  "dev": "vite --host", // 显示IP地址
  "build": "vue-tsc && vite build",
  "preview": "vite preview"
}

# 更换Vue模板支持工具

# Vue模板支持的VSCode扩展是Vetur,Vue3建议换成更加友好的Vue-Official(前期是Volar)

# 生命周期

// 引入需要的
import { onMounted, onUpdated, onUnmounted } from 'vue';  
export default {
  setup () {
    onMounted(() => {
	  console.log('mounted!')
	})
	onUpdated(() => {
	  console.log('updated!')
	})
	onUnmounted(() => {
	  console.log('unmounted!')
	})
  }
}
Vue2                  Vue3
beforeCreate()     -> 使用 setup()
created()          -> 使用 setup()
beforeMount()      -> onBeforeMount()    组件挂载到节点上之前执行的函数
mounted()          -> onMounted()        组件挂载完成后执行的函数
beforeUpdate()     -> onBeforeUpdate()   组件更新之前执行的函数
updated()          -> onUpdated()        组件更新完成之后执行的函数
beforeDestroy()    -> onBeforeUnmount()  组件卸载之前执行的函数
destroyed()        -> onUnmounted()      组件卸载完成后执行的函数
ativated()         -> onActivated()      <keep-alive>中的组件,会多出两个周期函数,被激活时执行
deactivated()      -> onDeactivated()    比如从 A 组件,切换到 B 组件,A 组件消失时执行
errorCaptured()    -> onErrorCaptured()  当捕获一个来自子孙组件的异常时激活钩子函数

注意

  • setup()的执行要优先于beforeCreate()
  • setup()中不能使用this,this是undefine
  • 父子组件中,先执行子组件的方法,再执行父组件的方法
  • setup()可以和生命周期函数同时使用,并且生命周期函数中可以调用setup()中的属性和方法,但是setup()不可以调用生命周期函数中的

# 常用方法

# setup方法

export default {
  // props:值为对象,包含组件外部传递过来,且组件内部声明接收了的属性
  // context:上下文对象
  //     attrs:值为对象,包含组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
  //     slots:收到的插槽内容,相当于this.$slots
  //     emit:分发自定义事件的函数,相当于this.$emit
  setup (props, context) {
    let name = '三'
	let age = 18
	
	function sayHello(){
		alert(`我叫${name},我${age}岁了,你好啊!`)
	}
	return{
	  name,
	  age,
	  sayHello
	}
	
	//返回一个渲染函数
	// return()=>h('h1','尚硅谷')
  }
}

# ref的使用

<template>
  <div>
    <p>{{num1}}</p>
	<button @click="num1++">按钮1</button>
	
	<p>{{num2}}</p>
	<button @click="hdClick1">按钮2</button>
	
	<p>{{objRef.num}}</p>
	<button @click="hdClick2">按钮2</button>
	
	<p ref="op">这是p标签</p>
	
	<!--父组件可使用ref获取子组件暴露的属性-->
	<Person ref="person"></Person>
  </div>
</template>

<script lang="ts">
import { ref, Ref, nextTick, defineExpose } from 'vue'; 
export default {
  setup () {
	// 1、直接这样定义的不是响应式数据
    let num1 = 20;
	
	// 2、普通类型
	let num2 = ref(20);
	const hdClick1 = () =>{
		// 上面template中依旧使用{{num}},因为vue在编译模板的时候会自动用.value获取
		num2.value++;
	}
	
	// 3、对象类型
	let obj = { num:30 }
	let objRef = ref(obj)
	const hdClick2 = () =>{
	  objRef.value.num++;
	}
	
	// 4、数组类型
	let games = ref([{id:'01',name:'王者荣耀'},{id:'02',name:'原神'}]}
	const hdClick3 = () =>{
	  games.value[0].name = '斗地主'
	}

    // 5、读取dom的内容
	let op = ref(); // 读取绑定了ref属性且值为op的标签
	
	console.log("setup",op.value); // undefined
	nextTick(()=>{
		console.log("setup",op.value); //这是p标签
	})
	
	// 6、可以使用在子组件上,子组件上需要使用defineExpose暴露
	let person = ref()
	onMounted(()=>{
	  console.log(person.value.val);
	  // 执行子组件的fn函数
	  person.value.fn()
	})
	
	return{
	  num1,
	  num2,
	  hdClick1,
	  objRef,
	  hdClick2
	}
  }
}
</script>
<!--子组件-->
<template>
  <div>
    <p>子组件</p>
  </div>
</template>
<script lang="ts">
import { defineExpose } from 'vue'; 
let num = ref(10)

let fn = ()=>{
    num1.value='我改变了子组件'
}
// 子组件使用defineExpose把那个变量暴露出去
defineExpose({num,fn})
</script>

注意

  • 基本类型的数据:响应式依然是靠 object.defineProperty() 的get与set 完成的
  • 对象类型的数据:内部"求助"了Vue3.0中的一个新函数 reactive 函数

# reactive的使用

<template>
  <div>
    <p>{{objRet.num}}</p>
  </div>
</template>

<script lang="ts">
import { ref,reactive } from 'vue'
export default {
  setup () {
	let obj = { num:30 }
	let objRet = reactive(obj)
	// 1、不需要写value
	console.log(objRet.num)
	
	interface person {
		id:number;
		name:string;
		age:number;
	}
	// 2、可以传递泛型
	let personList = reactive<Persons>([
		{id:'01', name:'张三', age:18},
		{id:'02', name:'李四', age:20},
		{id:'03', name:'王五', age:22}
	])
	
	// 3、reactive重新分配一个新对象,会失去响应式
	function change(){
		obj = { num:10 }
		
		// 可以使用 0bject.assign 去整体替换
		object.assign(obj,{num:10})
		
		// 但是ref转换的可以直接更新
		let objRet = ref(obj)
		obj = { num:10 }
	}
	
	// 4、reactive定义的响应式数据是"深层次的"
	// 当访问obj.b.c的时候,底层会自动读取value属性,因为c是在obj这个响应式对象中
	let obj = reactive({
		a:1,
		b:{
			c:1
		}
	})
	obj.b = 2 // 可以直接修改
	
	return{
	  objRet
	}
  }
}
</script>

ref和reactive的区别

  • ref:可以定义基本类型、对象类型的数据 => RefImpl,对象类型的数据可以直接赋值修改
  • reactive:只能定义对象类型的数据 => Proxy(Object),数据修改需要使用object.assign
  • ref 创建的变量必须使用.value
  • reactive 重新分配一个新对象,会失去响应式(可以使用 Object.assign 去整体替换)

# toRefs、toRef的使用

<template>
	<div>
		<p>{{num}}</p>
		<button @click="hdClick">按钮</button>
	</div>
</template>

<script lang="ts" setup>
import { reactive,toRefs } from 'vue'; 
let obj = {
	num:30,
	list:[10,20,30,40]
}
// 1、这样得到的num和list不具备响应式
let { num,list } = reactive(obj);

// 2、在解构reactive()得到的对象的时候,将他们解构成响应式数据
let { num,list } = toRefs(reactive(obj));

// 3、toRef可以解构单个参数
let num = toRef(reactive(obj),'num');

const hdClick = () =>{
    num.value++;
	console.log(num.value);
}
</script>

# shallowRef与shallowReactive

  • shallowRef:创建一个响应式数据,但只对顶层属性进行响应式处理,可以提高性能,只能写到.value
import {ref,shallowRef} from 'vue'

let sum = shallowRef(10)
let person = shallowRef({
	name:'zhangsan',
	age:18
})

//可以修改
function changeSum(){
	sum.value +=1
}
//不可以修改
function changeName(){
	person.value.name = 'lisi'
}
//不可以修改
function changeAge(){
	person.value.age = 20
}
//可以修改
function changePersom(){
	person.value = { name: 'tony', age: 100}
}
  • shallowReactive:创建一个响应式数据,但只对顶层属性进行响应式处理,可以提高性能
import {ref,shallowReactive} from 'vue'
let car = shallowReactive({
	brand:'zhangsan',
	optons:{
		color: 'red',
		engine:'v8'
	}
})
//可以修改
function changeBrand(){
	car.brand = 'lisi'
}
//不可以修改
function changeColor(){
	car.optons.color = 'green'
}
//不可以修改
function changeEngine(){
	car.optons.engine = 'v12'
}

# toRaw、UnwrapRef和markRaw

  • toRaw:用于获取一个响应式对象的原始对象,返回的对象不再是响应式的
import {reactive,toRaw} from 'vue'
let person1 = reactive({
	name:'zhangsan',
	age:18
})
let person2 = toRaw(person1)
  • UnwrapRef:用于获取 ref、reactive 创建的对象的原始类型
import { ref, reactive, UnwrapRef } from 'vue';

const count = ref(0); // 创建一个 ref
const person = reactive({ name: 'John', age: 30 }); // 创建一个 reactive
// 创建一个接口
interface FormState {
  count: number
  age: number
}

// 使用 UnwrapRef 获取原始值
type CountType = UnwrapRef<typeof count>; // number
type PersonType = UnwrapRef<typeof person>; // { name: string, age: number }
const formState: UnwrapRef<FormState> = reactive({
  count: 5000,
  age: 120
})
  • markRaw:标记一个对象,使其永远不会变成响应式的
import {reactive,markRaw} from 'vue'

let car = { brand:'奔驰',price:100}
let car1 = markRaw(car)
//car2不再是像是响应式数据
let car2 = reactive(car1)

# customRef 自定义的ref

可以对其依赖项修改和展示进行逻辑控制

let initValue = '你好'
// track(跟踪)、trigger(触发)
let msg = customRef((trace,trigger)=>{
	return {
		// 数据被读取时触发
		get(){
			trace() // //告诉vue数据msg很重要,你要对msg进行持续关注,旦msg变化就去更新
			return initValue 
		}
		// set何时调用?- msg被修改时触发
		set(value){
			initValue = value
			trigger()
		}
	}
})

# computed的使用

<template>
	<div>
		<p>{{num1}}</p>
		<button @click="num1++">按钮1</button>
		<p>{{num2}}</p>
		<button @click="num2++">按钮2</button>
	</div>
</template>

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

// 1、简单数据使用
let num = ref(20);
let num1 = computed(()=>{
	return num.value *2;
})

// 2、复杂数据使用
let obj = {
	num:30
}
let objRet = reactive(obj);
let num2 = computed(()=>{
	return objRet.num *2;
})

// 3、设置get和set
let data = reactve({
	checkList:[false,false,false,false]
})
let {list,checkList} = toRefs(data);
let checkAll = computed({
	get(){
		// checkList 包含有一个false,就应该返回false
		return !data.checkList.includes(false);
	},
	set(newVal){
		// 把checkList的所有值都改成newVal
		data.checkList=data.checkList.map(()=>newVal);
	}
})
</script>

# watch的使用

Vue3 中的 watch 只能监视以下四种数据:

  • ref定义的数据
  • reactive 定义的数据
  • 一个函数返回值
  • 一个包含上述内容的数组
<template>
	<div>
		<p>{{num}}</p>
		<button @click="num++">按钮</button>
	</div>
</template>

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

// 1、ref定义的简单数据
let num = ref(20);
const myWatch = watch(num,(newVal,oldVal)=>{ // 不需要加value
	console.log(newVal,oldVal);
	// 停止监视
	if (newVal>0) {
		myWatch()
	}
})

// 2、ref定义的复杂数据
let obj = { num:30 }
const myWatch = watch(obj,(newVal,oldVal)=>{
	console.log(newVal,oldVal);
	// 停止监视
	if (newVal>0) {
		myWatch()
	}
  },
  { deep:true }, // 开启深度检测
  { immediate:true } // 是否立即执行
) 

// 3、reactive 定义的数据的全部属性
let obj = { num:30 }
let objRet = reactive(obj);
// 默认开启深度检测
watch(objRet,(newVal,oldVal)=>{
	console.log(newVal,oldVal);
})

// 4、一个函数返回值,用于监视reactive对象的一个属性
watch(()=>objRet.num,(newVal,oldVal)=>{
	console.log(newVal,oldVal);
})
// 如果num是个对象,需要加deep
watch(()=>objRet.num,(newVal,oldVal)=>{
	console.log(newVal,oldVal);
},{ deep:true }) 


// 5、包含上述内容的数组
watch([num, age],(newVal,oldVal)=>{ // 简单类型
	console.log(newVal,oldVal); // 返回2个数组
})
watch([()=>objRet.num, ()=>objRet.age],(newVal,oldVal)=>{ // 复杂类型
	console.log(newVal,oldVal); // 返回2个数组
})

// 6、watchEffect的使用
watchEffect(()=>{
	// 凡是写在这里的数据,只要发生变化,都会触发这里的代码执行
	console.log(objRet.num);
})
</script>

注意

  • 若修改的是 ref 定义的对象中的属性,newValue 和 oldValue 都是新值,因为他们是同一个对象
  • 若修改整个 ref 定义的对象,newValue 是新值,oldValue 是旧值,因为不是同一个对象了
  • reactive 定义的响应式数据,newvalue 和 oldvalue 是一个值

# hooks的使用

将文件的一些单独功能的代码抽离出来,放到一个js文件中,或者是一些可以复用的公共方法/功能。
其实 hooks 和 vue2 中的 mixin 有点类似,但是相对 mixins 而言, hooks 更清晰易懂。

// 在hooks文件夹中新建一个文件useMousePosition.ts
import { ref, onMounted, onUnmounted, Ref } from 'vue'

interface MousePosition {
  x: Ref<number>,
  y: Ref<number>
}
function useMousePosition(): MousePosition {
  const x = ref(0)
  const y = ref(0)

  const updateMouse = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => {
    document.addEventListener('click', updateMouse)
  })

  onUnmounted(() => {
    document.removeEventListener('click', updateMouse)
  })

  return { x, y }
}
//向外部提供
export default useMousePosition
  • 在需要用到该hook功能的组件中的使用
<!--src/views/test.vue-->
<template>
  <div>
    <p>X: {{ x }}</p>
    <p>Y: {{ y }}</p>
  </div>
</template>

<script lang="ts" setup>
// 引入hooks
import useMousePosition from '../../hooks/useMousePosition'

// 使用hooks功能
const { x, y } = useMousePosition()
</script>

# Teleport的使用

可以把里面的内容传送到指定标签最后的位置

<template>
	<Teleport to=".app">
	<Teleport to="body">
	<Teleport to="#aaa">
	  <p>这是一个P标签</p>
	</Teleport>
</template>

# Suspense

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





 


 










 
 

 























<template>
  <div class="app">
    <h3>我是App组件</h3>
    <Suspense>
      <template v-slot:default>
        <Child />
      </template>
      <template v-slot:fallback>
        <h3>稍等,加载中...</h3>
      </template>
    </Suspense>
  </div>
</template>
 
<script>
// 静态引入
// import { defineAsyncComponent,Suspens } from "vue";

// 异步引入
import Child from './components/Child'  
const Child = defineAsyncComponent(() => import("./components/Child")); 

export default {
  name: "App",
  components: { Child },
};
</script>

<!----------子组件---------->
<template>
  <div class="child">
    <h3>我是Child组件</h3>
    {{ sum }}
  </div>
</template>
 
<script setup lang="ts">
let sum = ref(0)
let p = new Promise((resolve) => {
  setTimeout(() => {
	resolve({ sum })
  }, 3000)
})
</script>

注意

setup 默认带着async,类似:async setup() {}

# 动态创建组件并传值

// comp 传入的vue组件
// para 传入的vue组件的参数
// 返回:HTMLElement
export function initVue3Popup(comp, para) {
  const vNodeDom = document.createElement("div")
  document.body.appendChild(vNodeDom)

  const vNode = createApp(comp, { ...para }) // vue2中可使用extend
  vNode.mount(vNodeDom)
  return vNode._container
}
<!--被传入的组件-->
<template>
  <div>我是传入的参数:{{ props.remark }}</div>
</template>

<script setup lang="ts">
// 与传入的参数一一对应
const props = defineProps<{
  remark?: string
}>()
</script>

# 组件通信

# props通信(父子传值)

  • 父传子
<!----------父组件---------->
<template>
	<Chid :arr="state.arr" a="哈哈"></Chid>
</template>

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

let state =reactive({
	arr:[{
		name:'小明',
		age:18
	},{
		name:'小红',
		age:20
	}]
});
</script>

<!----------子组件---------->
<template>
	<table>
	  <tr v-for="item,index in arr" :key="index">
	     <td>{{(item as {name:string}).name}}</td>
		 <td>{{(item as {age:number}).age}}</td>
	  </tr>
	</table>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'; 
defineProps([
	arr:{
		type:Number,
		default:[]
	},
	// 可以接收固定值
	a
])

// 接收a,同时将props保存起来
let x= defineProps(['a'])
console.log(x)
</script>
  • 子传父(子组件调用父组件的方法)
<!----------父组件---------->
<template>
	<Chid :sendToy="getToy"></Chid>
</template>

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

let toy =ref('');
const getToy = (value:string) =>{
	toy.value = value
}
</script>


<!----------子组件---------->
<template>
	<p>玩具:{{toy}}</p>
	<button @click="sendToy(toy)">按钮</button>
</template>
<script lang="ts" setup>
import { defineProps,ref } from 'vue';

let toy =ref('奥特曼');
defineProps(['sendToy'])
</script>

# 自定义事件(主要用于子传父)

<!----------父组件---------->
<template>
	<Chid @sendToy="getToy"></Chid>
</template>

<script lang="ts" setup>
import Child from './Child.vue';
import { ref } from 'vue'; 

let toy =ref('');
const getToy = (value:string) =>{
	toy.value = value
}
</script>


<!----------子组件---------->
<template>
	<p>玩具:{{toy}}</p>
	<button @click="emit('sendToy',toy)">按钮</button>
</template>
<script lang="ts" setup>
import { ref } from 'vue';

let toy =ref('奥特曼');
// 声明事件
const emit = defineEmits(['sendToy'])

// 如果不在template中写emit('sendToy',toy),在script代码中
emit('sendToy',toy)
</script>

# mitt通信(子组件间通信)

  • 安装mitt
npm i mitt
  • 在uitls文件夹中创建文件emitter.ts
// 引入mitt
import mitt from 'mitt'

// 调用mitt得到emitter,emitter能绑定事件、触发事件
const emitter = mitt()

// 绑定事件
emitter.on('test1',()=>{
	console.log('test1被调用了')
})
emitter.on('test2',()=>{
	console.log('test2被调用了')
})

// 触发事件
setInterval(() => {
	emitter.emit('test1')
	emitter.emit('test2')
}, 1000)

// 卸载事件
setTimeout(() => {
	emitter.off('test1')
	emitter.off('test2')
	emitter.all.clear()
}, 3000)

// 暴露emitter
export default emitter
  • 在main.js中导入emitter
import emitter from '@/utils/emitter'
  • 子组件间通信
<!----------子组件1---------->
<template>
  <div class="child1">
    <h3>子组件1</h3>
	<h4>玩具:{{ toy }}</h4>
	<button @click="emitter.emit('sendToy',toy)">按钮</button>
  </div>
</template>

<script setup lang="ts">
 import { ref } from 'vue'
 import emitter from '@/utils/emitter'
 // 数据
 let toy = ref('奥特曼')
</script>

<!----------子组件2---------->
<template>
  <div class="child2">
    <h3>子组件2</h3>
	<h4>电脑:{{ computer }}</h4>
	<h4>哥哥给的玩具:{{ toy }}</h4>
  </div>
</template>

<script setup lang="ts">
 import { ref,onUnMounted } from 'vue'
 import emitter from '@/utils/emitter'
 // 数据
 let computer = ref('联想')
 let toy = ref('')
 
 // 给emitter绑定sendToy事件
 emitter.on('sendToy',(value)=>{
	 toy.value = value
 })
 
 // 卸载事件
 onUnMounted(() => {
	 emitter.off('sendToy')
 })
</script>

# v-modle通信(父子通信)

<!----------父组件---------->
<template>
	<Child v-model:num="num"></Child>
	<!--本质:<Child :num="num" @update:num="num = $event"></Child>-->
</template>

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

let num =ref(20);
</script>


<!----------子组件---------->
<template>
	<p>{{num}}</p>
	<button @click="hdClick">按钮</button>
</template>
<script lang="ts" setup>
import { defineProps,defineEmits } from 'vue'; 
const props = defineProps({
	num:{
		type:Number,
		default:30
	}
})

// 子传父的时候需要先定义好emit这个方法
const emit =defineEmits<{
	// update是固定写法,后面的变量是父组件v-model后面这个变量
	// n是参数
	(event: 'update:num',n:number):void
}>()

let m = props.num
const hdClick = () =>{
	m++;
	// $emit('上面event的值',要修改成的值)
	emit("update:num",m)
}
</script>

$event到底是啥?啥时候能.target

  • 对于原生事件,$event就是事件对象 =====> 能.target
  • 对于自定义事件,$event就是触发事件时,所传递的数据 =====> 不能.target

# $attrs通信(祖孙通信)

$attrs 可以获取绑定的其他值,作为中间组件传值

<!----------父组件---------->
<template>
  <div class="father">
    <h3>父组件</h3>
	<h4>a:{{ a }}</h4>
	<h4>b:{{ b }}</h4>
	<h4>c:{{ c }}</h4>
	<h4>d:{{ d }}</h4>
	<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}">
  </div>
</template>

<script setup lang="ts">
 import Child from './Child.vue'
 import { ref } from 'vue'
 // 数据
 let a = ref(1)
 let b = ref(2)
 let c = ref(3)
 let d = ref(4)
 
 function updateA(value:number){
	 a.vlue += value
 }
</script>

<!----------子组件只是中间传递---------->
<template>
  <div class="child2">
    <h3>子组件</h3>
	<h4>a:{{ a }}</h4>
	<h4>其他:{{ $attrs }}</h4>
	<GrandChild v-bind="$attrs"></GrandChild>
  </div>
</template>

<script setup lang="ts">
 import { ref } from 'vue'
 import GrandChild from './GrandChild.vue'
 
 defineProps(['a'])
 // 输出a:1 b:2 c:3 d:4 x:100 y:200
 console.log($attrs)
</script>

<!----------孙组件---------->
<template>
  <div class="grandChild">
    <h3>孙组件</h3>
	<h4>a:{{ a }}</h4>
	<h4>b:{{ b }}</h4>
	<h4>c:{{ c }}</h4>
	<h4>d:{{ d }}</h4>
	<h4>x:{{ x }}</h4>
	<h4>y:{{ y }}</h4>
	<button @click="updateA(6)">更新爷爷那的A</button>
  </div>
</template>

<script setup lang="ts">
 defineProps(['a','b','c','d','x','y','updateA'])
</script>

# useAttrs 父传子

使用useAttrs函数可以接收父组件传递的属性和事件

<!----------父组件---------->
<template>
  <div>
    <MyComponent title="标题" content="内容" @click="handleClick" />
  </div>
</template>

<script>
import MyComponent from '@/components/MyComponent.vue'

export default {
  components: { MyComponent },
  methods: {
    handleClick() {
      console.log('点击事件触发')
    }
  }
}
</script>

<!----------子组件---------->
<template>
  <div>
    <h2>{{ title }}</h2>
    <p>{{ content }}</p>
    <button @click="onClick">点击</button>
  </div>
</template>

<script setup>
import { useAttrs } from 'vue'

export default {
  setup() {
    const { title, content, onClick } = useAttrs()
    return { title, content, onClick }
  }
}
</script>

# $refs(父传子)、$parent(子传父)

  • $refs:值为对象,包含所有被 ref 属性标识的 DOM 元素或组件实例
  • $parent:值为对象,当前组件的父组件实例对象
<!----------父组件---------->
<template>
  <div class="father">
    <h3>父组件</h3>
	<h4>房产:{{ house }}</h4>
	<button @click="changeToy">修改Child1的玩具</button>
	<button @click="changeComputer">修改Child2的电脑</button>
	<button @click="getAllChild($refs)">获取所有的子组件实例对象</button>
	<h4>c:{{ c }}</h4>
	<h4>d:{{ d }}</h4>
	<Child1 ref="c1">
	<Child2 ref="c2">
  </div>
</template>

<script setup lang="ts">
 import Child1 from './Child1.vue'
 import Child2 from './Child1.vue'
 
 import { ref } from 'vue'
 // 数据
 let c1 = ref()
 let c2 = ref()
 let house = ref(4)
 // 方法
 function changToy(){
	c1.vlue.toy ='小猪佩奇'
 }
 function changeComputer(){
 	c2.vlue.toy ='华为'
 }
 function getAllChild(refs:any){ // 或者 getAllChild(refs:{[key:string]:any})
	// refs为所有的ref的集合
 	for(let key in refs){
		refs[key].book +=3
	}
 }
 
 // 把数据暴露给外部
 defineExprose({house})
</script>

<!----------子组件1---------->
<template>
  <div class="Child">
    <h3>子组件1</h3>
	<h4>玩具:{{ toy }}</h4>
	<h4>书籍:{{ book }} 本</h4>
	<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
  </div>
</template>

<script setup lang="ts">
 import { ref } from 'vue'
 
 let toy = ref('奥特曼')
 let book = ref(3)
 
 function minusHouse(parent:any){
	 parent.house -=1
 }
 
 // 把数据暴露给外部
 defineExprose({toy,book})
</script>

# provide、inject(祖孙传值\父子传值)

<!----------父组件---------->
<template>
  <div class="father">
    <h3>父组件</h3>
	<h4>银子:{{ money }}</h4>
	<h4>车子:品牌{{ car.brand }} 价格 {{ car.price }}</h4>
	<Child></Child>
  </div>
</template>

<script setup lang="ts">
 import { ref, reactive, provide } from 'vue'
 import Child from './Child.vue'
 
 let money = ref(100)
 let car = reactive({
	 brand:'奔驰',
	 price:100
 })
 function updateMoney(value:number){
	 money.value -= value
 }
 
 // 向后代提供数据,money不需要加value
 provide('moneyContext',money,updateMoney)
 provide('che',car)
</script>

<!----------子组件---------->
<template>
  <div class="child">
    <h3>子组件</h3>
	<h4>银子:{{ money }}</h4>
	<h4>车子:品牌{{ car.brand }} 价格 {{ car.price }}</h4>
	<button @click="updateMoney(6)">花爷爷的钱</button>
  </div>
</template>

<script setup lang="ts">
 import { inject } from 'vue'
 
 let {money, updateMoney} = inject('moneyContext',{money:0,updateMoney:(param:number)=>{}})
 // 设置默认值,否则ts报错
 let car = inject('che',{brand:'',price:0})
 // 设置为null
 let car = inject('che',null)
</script>

InjectionKey 可以解决子组件调用 inject 引入数据时也无法确定具体的数据类型是什么

// 1. 将 InjectionKey 定义的数据类型放到 keys/index.ts 下维护

// keys/index.ts
import {InjectionKey, Ref } from "vue"
// 限制了 provide 导出的数据必须是 ref 且 boolean 类型
export const showPopupKey: InjectionKey<Ref<boolean>> = Symbol()
// 限制了 provide 导出的数据必须是 string
export const titleKey: InjectionKey<string> = Symbol()


// 2. 在A.vue中调用 provide 导出数据,第一个参数是我们定义好的数据类型,第二个参数是数据类型的值
import { provide, InjectionKey, Ref } from "vue"
import { showPopupKey } '@/keys'
const showPopup = ref(false)
// 正确
provide(showPopupKey, showPopup) 
// TS 报错: 'Hello' 是字符串,与 showPopupKey 不匹配
provide(showPopupKey, 'Hello')
// 正确
provide(titleKey, 'Hello')


// 3. 在B.vue文件中导入数据
import { showPopupKey } from '@/keys'
import { inject } from 'vue'
inject(showPopupKey) // 现在获取到的数据类型是安全的

# 插槽用法

# 匿名插槽

<template>
  <Child>
     <a href="#">a标签</a>
	 <button></button>
  </Child>
</template>

<script lang="ts" setup>
import Child from "./Child.vue"
</script>

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

<script lang="ts" setup>
</script>

# 具名插槽

<template>
  <Child>
     <!--简写:<template #link>-->
     <template v-slot:link>
	    <a href="#">a标签</a>
	 </template>
	 <!--简写:<template #btn>-->
     <template v-slot:btn>
        <button></button>
     </template>
  </Child>
</template>

<script lang="ts" setup>
import Child from "./Child.vue"
</script>

<template>
  <slot name="link"></slot>
  <p>子组件</p>
  <slot name="btn"></slot>
</template>

<script lang="ts" setup>
</script>

# 插槽作用域

<template>
  <Child>
     <template #link>
	    <a href="#">a标签</a>
	 </template>
     <template #btn="scope">
        <button>按钮 {{scope.title}}{{scope.num}}</button>
		<li v-for="item in scope.games" :key="item.id">{{item.name}}</li>
     </template>
  </Child>
</template>

<script lang="ts" setup>
import Child from "./Child.vue"
</script>

<template>
  <slot name="link"></slot>
  <p>子组件</p>
  <slot name="btn" :youxi="games" title="哈哈" :num="num"></slot>
</template>

<script lang="ts" setup>
import {ref} from 'vue'
let num =ref(30)
let games = reactive({
	id: "001", name: "英雄联盟",
	id: "002", name: "王者荣耀"
})
</script>

注意

插槽作用域的数据在子那边,但根据数据生成的结构,却有父亲决定。

# 路由使用

# query参数

<!--第一种方式-->
<router-link :to="`news/details?id=${id}&title=${title}`">首页</router-link>
<!--第二种方式-->
<router-link :to="{
	path:'news/detail',
	query:{
		id:id,
		title:title
	}
}">首页</router-link>

<!--路由配置中-->
routes:[{
	name:'xiangqing',
	path:'detail',
	component:import('../views/Detais.vue')
}]

<!--被链接页面-->
<template>
  <div>{{query.id}}</div>
  <div>{{query.title}}</div>
</template>

<script setup lang="ts">
 import {toRefs} from 'vue'
 import {useRoute} from 'vue-router'
 
 let route = userRoute()
 let {query} = toRefs(route)
</script>

# params参数

<!--第一种方式-->
<router-link :to="`news/details/${id}/${title}`">首页</router-link>
<!--第二种方式-->
<router-link :to="{
	name:'xiangqing'
	params:{
		id:id,
		title:title
	}
}">首页</router-link>

<!--路由配置中-->
routes:[{
	name:'xiangqing',
	path:'detail/:x/:y',
	component:import('../views/Detais.vue')
}]

<!--被链接页面-->
<template>
  <div>{{params.id}}</div>
  <div>{{params.title}}</div>
</template>

<script setup lang="ts">
 import {toRefs} from 'vue'
 import {useRoute} from 'vue-router'
 
 let route = userRoute()
 let {params} = toRefs(route)
</script>

注意

params时to里只能写name,query时name和path都可以

# props的使用

  • 第一种写法:将路由收到的所有 params 参数作为props传给路由组件
// 1、路由配置中 props:true
routes:[{
	name:'xiangqing',
	path:'detail',
	component:import('../views/Detais.vue')
	props:true
}]

<!--被链接页面-->
<template>
  <div>{{id}}</div>
  <div>{{title}}</div>
</template>

<script setup lang="ts">
 import {defineProps} from 'vue'
 // 2、接收Props
 defineProps([
	{
	  id:Number,
	  default:0
	},
	{
	  title:String,
	  default:''
	}
 ])
</script>
  • 第二种写法:函数写法,将路由所有参数作为props给路由组件
// 1、路由配置中
routes:[{
	name:'xiangqing',
	path:'detail',
	component:import('../views/Detais.vue')
	props(route){
		// return route.params
		return route.query
	}
}]

<!--被链接页面-->
<template>
  <div>{{id}}</div>
  <div>{{title}}</div>
</template>

<script setup lang="ts">
 import {defineProps} from 'vue'
  // 2、接收Props
 defineProps([
	{
	  id:Number,
	  default:0
	},
	{
	  title:String,
	  default:''
	}
 ])
</script>
  • 第三种写法:对象写法,可以自己决定将什么作为props给路由组件
// 1、路由配置中
routes:[{
	name:'xiangqing',
	path:'detail',
	component:import('../views/Detais.vue')
	props:{
		a:100,
		b:200,
		c:300
	}
}]

<!--被链接页面-->
<template>
  <div>{{a}}</div>
  <div>{{b}}</div>
</template>

<script setup lang="ts">
 import {defineProps} from 'vue'
 // 2、接收Props
 defineProps([a,b,c])
</script>

# replace的使用

页面路由默认是push的规则,浏览器中的页面可以前进或者后退,使用replace后则不可以

<RouterLink replace to="/home" active-class="active">首页</RouterLink>
<RouterLink replace :to="{path:'/home'}" active-class="active">首页</RouterLink>
<RouterLink replace :to="{name:'shouye'}" active-class="active">首页</RouterLink>

# 编程式路由

import { onMounted } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
onMounted(()=>{
	setTimeOut(()=>{
		//注意push和replace的区别
		router.push('/news')
		//to有几种写法这里就有几种写法
		router.push({
			path:'news/detail',
			query:{
				id:id,
				title:title
			}
		})
	},3000)
})

# 状态管理

# Vuex4

# 现在默认是vuex3,所以加上next安装下一个版本
npm install vuex@next

# package.json文件中,查看版本
"dependencies": {
    "vuex": "^4.0.2"
 },

# 创建一个store对象









 











// 接收一个泛型,泛型的名称为 S(对 state的类型进行限定)
// 接收一个参数,参数为对象,类型为StoreOptions(包含:state/getters/actions/mutations/modules)
// 返回一个Store类的实例对象
export function createStore<S>(options: StoreOptions<S>): Store<S>

// 创建一个store实例对象,并且其类型定义为IRootState
import { createStore } from 'vuex'
interface IRootState { count: number }
const store = createStore<IRootState>({  }) 
export default store 

// 注册到项目的APP中
import { createApp } from 'vue'
import store from '@/store'
const app = createApp(App)
app.use(store)
app.mount('#app')

// 这样就可以直接在各个组件中使用store了

# 核心模块之state

state可以是一个函数,也可以是一个对象

// 定义state的类型限定
const store = createStore<IRootState>({   
	state() {   
        return {
			// 创建的时候,已经给state的类型进行了限定
            count: 0
        }
    }	
})
  • 第一种展示形式

 






// 直接在组件中使用$store
<h1>{{$store.state.count}}</h1>

// 但是ts是对$store不认识的,就会报警告
// 在vue3创建的项目中,有一个文件shims-vue.d.ts
// 使用.d.ts来申明一下$store, 就可解决了
declare let $store: any
  • 第二种展示形式








 









import { defineComponent, computed} from 'vue'
import { useStore } from 'vuex'

export default defineComponent({
  name: 'App',
  setup() {
	// useStore 是vuex内部提供的,因为在setup中是不能使用this的,不能像vue2中使用this.$store
    const store = useStore()
    const count = computed(() => store.state.count)
    return {
      count
    }
  }
})

// 在template中展示
<h1>{{count}}</h1>  

# 核心模块之getters

getters的作用就是对state进行变形后,然后进行返回展示


















 








interface IRootState {
    coder: any[]
}

const store = createStore<IRootState>({
    state() {
        return {
            coder: [
                {name: '前端人员', number: 5},
                {name: '后端人员', number: 15},
                {name: '测试人员', number: 3},
                {name: '产品', number: 2}
            ]
        }
    },
    getters: {
        coderAllCount(state) {
            return state.coder.reduce((prev, item) => {
                return prev + item.number
            }, 0)
        }
    }
})

export default store

在组件中展示,跟state的两种形式是一样的

//形式一
<h1>{{$store.getters.coderAllCount}}</h1>
//形式二
const store = useStore()
const coderAllCount = computed(() => store.getters.coderAllCount)

# 核心模块之mutations

mutation中的必须是同步代码,是更改vuex中的store的状态唯一方法

const store = createStore<IRootState>({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        increment(state, payload) {
            state.count += payload.count
        }
    }
})

在组件中触发(例如:点击事件)

// 字符串形式
const btn = () => {
    store.commit('increment', {count: 10})
    // 或则
    store.commit(INCREMENT, {count: 10})
}

// 对象的形式,payload是指向传递的整个对象
const btn = () => {
    store.commit({
        type: 'increment',
        count: 10
    })
    // 或则
    store.commit({
        type: INCREMENT,
        count: 10
    })
}

# 核心模块之actions

action就是专门用来处理异步的,当异步执行完成之后,触发mutation,从而修改state的状态

# actions中的方法,一般接收两个参数,第二个参数为可选参数

# 参数一: context(必选参数)是一个对象,包含以下属性:
# commit:触发mutations中的方法,修改state
# dispatch: 触发actions中的方法
# state: 拿到当前模块中的state中的值
# getters: 拿到当前模块中的getters中的值
# rootState: 拿到根节点中的state值
# rootGetters: 拿到根节点中的getters中的值

# 参数二: payload(可选参数)是否给action携带参数
const store = createStore<IRootState>({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        increment(state, payload) {  //这里的state的类型可以自动推导为IRootState
            state.count += payload.count
        }
    },
    actions: {
        increment(context, payload) {
            setTimeout(() => {
                context.commit({
                    type: 'increment',
                    count: payload.count
                })
            }, 1000)
        }
    }
})

没有携带参数

const store = useStore()

cosnt btn = () => {
	// 在这里就没有携带参数,所以payload是不存在的
    store.dispatch('increment')   
}

携带参数

const store = useStore()

// 两种形式: 直接传递参数形式 和  对象的形式
cosnt btn = () => {
	// 直接传递参数形式
    store.dispatch('increment', {count: 100})   
}  
// payload   ==>  {count: 100}

cosnt btn = () => {
	// 对象的形式
    store.dispatch({
        type: 'increment',
        count: 100
    }) 
}
// payload  ==> {type: 'increment', count: 100}

# 核心模块之module

当应用变得非常复杂时,store 对象就有可能变得相当臃肿,就需要对模块进行划分

  • 定义一个countModule模块
import { Module } from 'vuex'

// 接收两个泛型,第一个S 为当前模块的state的类型,第二个R:就是跟节点的state类型
// Module类型跟StoreOptions的类型基本是一样的,就是多了一个namespaced属性
const countModule: Module<ICountState, IRootState> = {
    state() {
        return {
            count: 0
        }
    },
    getters: {},
    mutations: {
        increment(state) {
            console.log('模块store中的increment方法')
            state.count += 1
        }
    },
    actions: {}
}
export default countModule
  • 在根store中注册模块
import { createStore } from 'vuex'
import countModule from './count/count'

const store = createStore<IRootState>({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        increment(state) {
            console.log('根store中的increment方法')
            state.count += 1 
        }
    },
    modules: {
        countModule
    }
})
  • 根store中的state和模块中的state的区分
<!--渲染根store-->
<div>{{$store.state.rootCount}}</div>
<!--正确渲染count模块中的count-->
<div>{{$store.state.countModule.count}}</div>

<!--不是下面的写法-->
<div>{{$store.countModule.state.count}}</div>
  • 根store中的mutation和模块中的mutation的区分
import { useStore } from 'vuex'

export default defineComponent({
    setup() {
      const store = useStore()
      const btn = () => {
          store.commit('increment')
      }
    }
})
  • 命名空间的使用
# 默认情况下,模块内的getters、action、mutations是注册在全局的命名空间中的
# 所以上面的方法commit中同一个名字的方法都会被触发,无论是模块中的还是根store中的
# 可以添加 namespaced: true 的方式使其成为独立的空间模块
//state (当然原来也是这么访问的)
store.state.countModule.count

//getters
store.getters['countModule/coderAllCount']

//mutations
store.commit('countModule/increment')

//actions
store.dispatch('countModule/incrementAction')
  • 模块内部触发根store中的mutation
// 模块内部的action异步拿到数据后,想修改根store中的mutation
// action的类型里有一个类型是专门针对于模块的
// 这里有个root属性,就是用来告诉是否用来修改根store中的mutation
actions: {
    incrementAction({commit}, payload) {
        setTimeout(() => {
            //默认情况下,就是触发模块内部mutation中的increment
            commit('increment', payload)
            //加上root:true, 那么这时候触发的就是根store中的increment了
            commit('increment', payload, {root: true})
        }, 1000)
    }
}

# Pinia

Pinia官网 (opens new window) 符合直觉的 Vue.js 状态管理库

# 环境搭建

  • 安装Pinia
npm install pinia
  • 修改main.js
import { createApp } from 'vue'
import App from './App.vue'
//第一步:引入pinia
import { createPinia } from 'pinia'

const app = createApp(App)
//第二步:创建pinia
const pinia = createPinia()
//第三步:安装pinia
app.use(pinia)
app.mount('#app')

# 数据读取和修改

  • 创建store文件夹并添加文件count.ts
import { defineStore } from 'pinia'

export const useCountStore = defineStore('count',{
	//actions里放置的是一个个的方法,用来对 state 里数据变化的业务逻辑
	//不但能处理同步操作 同样也可以处理异步操作
	actions:{
		increment(value){
			//修改数据
			console.log(this.sum)
			this.sum += value
		}
	}
	//存储数据的地方,用来存储全局状态
	state(){
		return {
			sum: 6,
			name:'lisi'
		}
	},
	//可以用于计算或转换存储的状态
	getters:{
		bigSum:state => state.sum*10
		upperName():string{
			return this.name.toUpperCase()
		}
	}
})
  • 数据读取和修改
<template>
  <div>{{countStore.sum}}</div>
  <button @click='add'>修改</button>
</template>

<script setip lang="ts">
import { useCountStore } from @/store/count

counst countStore = useCountStore()
//数据读取
console.log(countStore.sum)

function add(){
	//第一种修改方式:单个数据修改
	countStore.sum += 1
	//第二种方式:批量修改
	countStore.$patch({
		sum: 888
		name: '张三'
	})
	//第三种方式:使用actions的方法
	countStore.increment(1)
}

//storeToRefs只会关注store中的数据,不会对方法进行ref包裹
const {sum,name,bigSum,upperName} = storeToRefs(countStore)

//$subscribe--监听数据修改
countStore.$subscribe((mutate,state)=>{
	console.log("params",params);//修改的操作
	console.log("state",state);//修改后的数据
})
</script>

# SVG文件

# IconPark图标以及按需引入

IconPark官网 (opens new window)

npm i @icon-park/svg --save 安装
<!--子组件-->
<template>
  <span class="mars-icon" v-html="svgComponent"></span>
</template>
<script lang="ts">
import { computed, useAttrs, defineComponent } from "vue"
import * as svgModule from "@icon-park/svg"
import _ from "lodash"
export default defineComponent({
  name: "mars-icon",
  props: {
    icon: {
      type: String
    },
    color: {
      type: String
    },
    width: {
      type: [String, Number],
      default: "14"
    }
  },
  setup(props) {
    const attrs = useAttrs()
    const iconName = computed(() => _.upperFirst(_.camelCase(props.icon)))

    const svgComponent = svgModule[iconName.value]({
      theme: "outline",
      fill: props.color,
      size: props.width,
      ...attrs
    })

    return {
      attrs,
      svgComponent
    }
  }
})
</script>
<style lang="less" scoped>
.mars-icon {
  vertical-align: middle;
  line-height: 1;
}
</style>
<!--父组件-->
<template>
  <mars-icon icon="close-one" width="20" color="#FFFFFF"></mars-icon>
</template>

# 如何使用svg-sprite-loader

  • 普通的svg图片使用方式
<img src="../assets/svgicons/about.svg" />
  • 封装组件使用
// 依赖安装:npm i svg-sprite-loader -D  

// 打开vue.config.js文件,在chainWebpack函数中新增配置
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  chainWebpack: (config) => {
    const svgRule = config.module.rule('svg')
    svgRule.uses.clear()
    // 添加要替换的 loader
    svgRule.use('svg-sprite-loader')
           .loader('svg-sprite-loader')
           .options({symbolId: 'icon-[name]'})
  }
})

在项目src/component目录下新增SvgIcon文件夹,里面定义一个index.vue以及index.ts文件

<!--index.vue-->
<template>
  <svg class="svg-icon">
    <use :xlink:href="`#icon-${props.name}`" />
  </svg>
</template>
 
<script setup lang="ts">
type Props = {
  name: string
}
const props = withDefaults(defineProps<Props>(), {
  name: ''
})
</script>
<style lang="scss" scoped>
.svg-icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  overflow: hidden;
  vertical-align: -0.15em;
  fill: currentColor;
}
</style>
// index.ts
export default function importAllSvgIcons() {
  try {
    const request: __WebpackModuleApi.RequireContext = 
       require.context('../../assets/svgicons', false, /\.svg$/)
    request.keys().forEach(request)
  } catch(err) {
    console.log(err)
  }
}

打开main.ts文件,引入我们定义的SvgIcon.vue组件,通过app.component方式全局注册

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
 
import SvgIcon from '@/components/SvgIcon/index.vue'
import importAllSvgIcons from './components/SvgIcon'
 
const app = createApp(App)
app.component('svg-icon', SvgIcon)
// 动态引入svgicons文件夹下面的所有文件的方法并调用
importAllSvgIcons()
app.use(createPinia())
app.use(router)
app.mount('#app')

在项目中任意组件中通过如下方式进行使用:

<!--1. SvgIcon为全局组件,name表示要使用的svg图标名称--> 
<!--2. other.svg图标存放于assets/svgicons目录下--> 
<SvgIcon name="other"></SvgIcon>

# 其他使用

# Vue2和Vue3的区别

  • 响应式原理的区别:
# Vue2是Object.defineProperty
# Vue3是Proxy代理
// 模拟Vue3中实现响应式
const p = new Proxy(person, {
  //有人读取p的某个属性时调用
  get(target, propName){
     console.log(`有人读取了p身上的${propName}属性`)
     // return target[propName]
	 return Reflect.get(target, propName)
  },
  //有人修改p的某个属性、或给p追加某个属性时调用
  set(target, propName, value){
	  console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
	  // target[propName] = value
	  Reflect.set(target, propName, value)
  },
  deleteProperty(target,propName){
      console.log(`有人刷除了p身上的${propName}属性,我要去更新界面了!`)
	  // return delete target[propName]
	  return Reflect.deleteProperty(target, propName)
  }
}
  • API设计的区别:
# Vue2是OptionAPI(选项式)不便于维护和复用
# Vue3是CompositionAPI(组合式)可以让相关功能更加有序的组织在一起
  • VUE3基于Vite构建,按需编译,不再等待整个应用编译完成
  • 在Vue2中必须有一个根标签,在Vue3中可以没有根标签,将多个标签包含在一个Fragment虚拟元素中

# env.d.ts

// 为TS提供类型定义,方便TS的智能提示
/// <reference types="vite/client" />
/// <reference types="vite-svg-loader" />

# 如何修改组件的名称

//1、设置名称
<script lang="ts" setup name="person123"></script>

//2、安装插件
vite-plugin-vue-setup-extend

//3、在vite.config.ts中添加配置
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueSetupExtend from'vite-plugin-vue-setup-extend'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
	  vue(),
	  VueSetupExtend()
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: '@import "@/assets/css/main.scss";'
      }
    }
  }
})

# 全局API由Vue转移到app

在main.js文件中

import { createApp } from 'vue'
import App from './App.vue'

// 创建应用
const app = createApp(App)

// 全局组件
import Hello from './Hello.vue'
app.component('Hello',Hello)

// 全局变量
app.config.globalProperties.website = 'sylone';
app.config.globalProperties.$user = {
    name: '梅长苏',
    weapons: '长剑',
    title: '刺客'
}
// 如果ts提示需要定义变量类型
declare module 'vue' {
	interface ComponentCustomPropertites{
		website:string
	}
}

// 全局自定义指令
// 使用自定义指令 <div v-beauty="sum">好漂亮</div>
app.directive('beauty',(element,{value})=>{
	element.innerText +=value
	element.style.color = 'green'
	element.style.backgroundColor = 'yellow'
})

// 挂载应用
app.mount('#app')

// 卸载应用
setTimeout(() => {
	app.unmount()
},2000)

在template模板使用全局变量

<template>
  <div>
    <div>姓名:{{$user.name}} </div>
    <div>{{ website }}</div>
  </div>
</template>

在setup中使用全局变量

import { getCurrentInstance } from 'vue'
 
const app = getCurrentInstance()
const website = app.appContext.config.globalProperties.website
 
// 或者
const { proxy } = getCurrentInstance()
console.log(proxy.website)
 
// 使用解构赋值
const { website } = getCurrentInstance()!.appContext.config.globalProperties
console.log(website)
 
// 注意!getCurrentInstance()不能在回调函数、方法里使用
// 若要访问全局变量,需在函数外面调用getCurrentInstance()
const { proxy } = getCurrentInstance()
// 或者
const name = getCurrentInstance().proxy.$website;
const getUserInfo=()=>{
   console.log(proxy.$website);
   console.log(name);
}

注意

getCurrentInstance()不能在回调函数、方法里使用
若要访问全局变量,需在函数外面调用getCurrentInstance()

# 全局接口

  • 定义全局接口
// 根目录 -> types -> table.d.ts
interface UerType{
	name:string;
	age:number;
}

// 类型增强
declare var globalVar:string;
declare var globalObj:ObjType;
declare function fn(s:string):void;
  • 修改tsconfig.json文件
{
	"include":[
		……
		"types/**/*.d.ts"
	]
}
  • 在其他文件中使用
let arr = props.arr as UerType[]

// 使用类型增强
console.log(globalVar,globalObj)

# 配置项目路径别名

目前ts对@指向src目录的提示是不支持的,vite默认也是不支持的,需要手动配置@符号的指向

  • tsconfig.json中添加两项配置
"compilerOptions":{"baseUrl":"./",
	"paths":{
		// 对应src的路径
		//import HelloWorld from '@/components/HelloWorld.vue'
		"@/*":[
			"src/*"
		],
		// 对应types的路径
		//import { UserType } from "#/table"
		"#/*":[
			"types/*"
		]
	}
}
  • 在vite.config.ts中添加配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

import path from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve:{
	  alias:{
		  "@":path.join(__dirname,'src'),
		  "#":path.join(__dirname,'type')
	  }
  }
})
  • 需要安装关于node这个库的ts声明配置
npm i -D @type/node

# 定义接口返回值类型

interface AdminLoginData{
	username:string;
	password:string;
}
interface Result<T>{
	code:number;
	data:T;
	message:string;
}
interface AdminLoginRes{
	token:string
}

//如果接口定义在另外一个文件中,需要导入
import {type AdminLoginData} from "@/types/AdminLoginData"

export const adminLoginApi = (data:AdminLoginData) : Promise<Result<AdminLoginRes>> =>
                                                     request.post('/admin/login',data);