# 创建项目

  • 选择一个你常用的命令进行安装
npm create vite
yarn create vite

# 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')

# 安装Vuex

  • 在终端输入选择的命令
npm install vuex@next

# 安装axios

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

# 项目相关命令

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

# 更换Vue模板支持工具

Vue模板支持的VSCode扩展是Vetur,Vue3建议换成更加友好的Vue-Official

# 生命周期

// 引入需要的
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() 期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例(正在调用 setup() 的组件实例)
  • 组件实例上下文也是在生命周期钩子同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将自动删除

# 常用方法

# 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>
  </div>
</template>

<script lang="ts">
import { ref,Ref,nextTick } from 'vue'; 
export default {
  setup () {
	//直接这样定义的不是响应式数据
    let num1 = 20;
	
	//上面template中依旧使用{{num}},因为vue在编译模板的时候会自动用.value获取
	let num2 = ref(20);
	//实质上是Ref<T>类型
	//let num2:Ref<number> = ref(20);
	const hdClick1 = ()=>{
		num2.value++;
		console.log(num2.value)
	}
	
	//复杂类型数据
	let obj = {
	  num:30
	}
	let objRef = ref(obj)
	const hdClick2 = ()=>{
	  objRef.value.num++;
	  console.log(objRef.value.num)
	}
	console.log(objRef.value.num)

    //读取dom的内容
	let op = ref(); //读取绑定了ref属性且值为op的标签
	console.log("setup",op.value); //undefined
	
	nextTick(()=>{
		console.log("setup",op.value); //这是p标签
	})
	return{
	  num1,
	  num2,
	  hdClick1,
	  objRef,
	  hdClick2
	}
  }
}
</script>

# 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);
	//不需要写value
	console.log(objRet.num);
	
	return{
	  objRet
	}
  }
}
</script>

# setup的使用

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

<script lang="ts" setup>
import { reactive } from 'vue'; 
//不需要export和setup
let obj = {
	num:30
}
let objRet = reactive(obj);
</script>

# toRefs的使用

<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]
}
//在解构reactive()得到的对象的时候,将他们解构成响应式数据
let { num,list } = toRefs(reactive(obj));
const hdClick = () =>{
    num.value++;
	console.log(num.value);
}
</script>

# watch的使用

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

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

//简单数据使用
let num = ref(20);
watch(num,(newVal,oldVal)=>{
	console.log(newVal,oldVal);
})

//复杂数据使用
let obj = {
	num:30
}
let objRet = reactive(obj);
let { num } = toRefs(objRet);
watch(num,(newVal,oldVal)=>{
	console.log(newVal,oldVal);
})
watch(()=>objRet.num,(newVal,oldVal)=>{
	console.log(newVal,oldVal);
})
watch([()=>objRet.num],(newVal,oldVal)=>{
	console.log(newVal,oldVal); //返回2个数组
})

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

# 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'; 

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

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

//其他使用
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>

# Teleport的使用

<template>
    <!--可以把里面的内容传送到指定标签最后的位置-->
	<Teleport to=".app">
	<Teleport to="body">
	<Teleport to="#aaa">
	  <p>这是一个P标签</p>
	</Teleport>
</template>

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

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

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

//其他使用
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>

# 组件通信

# 父传子

<!--父组件-->
<template>
    <!--调用子组件-->
	<Chid :arr="state.arr"></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:[]
	}
})
</script>

# 子传父(子组件调用父组件的方法)

<!--父组件-->
<template>
    <!--子组件-->
	<Chid @fn="chanNum"></Chid>
</template>

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

let num =ref(20);
const chanNum = () =>{
	num.value++
}
</script>


<!--子组件-->
<template>
	<p>{{num}}</p>
	<button @click="hdClick">按钮</button>
</template>
<script lang="ts" setup>
import { defineEmits } from 'vue';

// 子传父的时候需要先定义好emit这个方法
const emit =defineEmits<{
	(event: 'fn'):void
}>()

const hdClick = () =>{
	//通过$emit('自定义参数名','参数')
	emit("fn")
}
</script>

# 子传父(子组件修改父组件的属性)

<!--父组件-->
<template>
    <!--子组件-->
	<Chid v-model:num="num"></Chid>
</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'; 
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>

# 插槽用法

# 匿名插槽

<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>
     </template>
  </Child>
</template>

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

</script>


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

<script lang="ts" setup>
import {ref} from 'vue'
let num =ref(30)
</script>

# 其他使用

# Vue2和Vue3响应式原理的区别

  • Vue2是Object.defineProperty
  • Vue3是Proxy代理

# 全局接口

  • 定义全局接口
//根目录 -> 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
}

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