创建项目:
npm create vue@latest
npm install
页面渲染原理:
- vite项目中,index.html是项目的入口文件,在项目最外层。
- 加载index.html后,vite解析
<script type="module" src="xxx">
指向js。 - vue3中是通过createApp函数创建一个应用实例。
选项 API 和组合 API:
vue3中开始弱化this;
setup将数据和方法交出去,模板中才可以使用,
setup的返回值也可以是一个渲染函数,
setup中不能使用this.,
setup和data,methods可以并存,一般不同时写,
但是setup读不到data中的数据,data可以读到setup的数据。
语法糖:
<script lang= "ts" setup>let a = '1'
</script>
(如果想修改组件名可以下载一个插件npm i vite-plugin-vue-setup-extend -D )
定义响应式数据:
<script lang= "ts" setup>
import {ref,reactive,toRefs,toRef} from 'vue'
// 使用【ref】可以定义基本类型和对象类型的响应式数据
// 获取及赋值时记得用.value 也可以使用volar插件自动添加.value
let a = ref('1')
// 使用【reactive】只能定义对象类型的响应式数据
// 1,不过它无法替换整个对象 修改数据时如果重新分配了一个新的对象 则这个数据不再是响应式数据了 除非使用Object.assign(obj,{name:'姓名2',age:"182"}) 如果是使用ref定义的对象类型数据就可以重新分配一个新的对象 因为有.value
// 2,reactive定义的对象类型的数据不适合解构 如果要解构 要使用【toRefs】包裹对象
let obj = reactive({name:'姓名',age:"18"})
// 解构
let {name,age} = toRefs(obj)
let newname = toRef(obj,'name')
// 方法
function changeName(){obj.name = '123' // 使用reactivename.value= '123' // 使用reactive和toRefsnewname.value = '456' // 使用reactive和toRef
}
</script>
使用规则:
若需要一个基本类型的响应式数据,必须使用【ref】
若需要一个响应式对象,层级不深,使用ref reactive都可以
若需要一个响应式对象,且层级较深,推荐使用reactive
响应式的底层原理还是getter 和 setter,可以将 ref 视为:
const myRef = {_value: 0,get value() {track()return this._value},set value(newValue) {this._value = newValuetrigger()}
}
computed计算属性:
// 可读可写
<script setup>
import { ref, computed } from 'vue'const firstName = ref('John')
const lastName = ref('Doe')const fullName = computed({// getterget() {return firstName.value + ' ' + lastName.value},// setterset(newValue) {// Note: we are using destructuring assignment syntax here.[firstName.value, lastName.value] = newValue.split(' ')}
})
</script>
watch:
可以监听ref定义的基本类型的数据和对象类型的数据
可以监听reactive定义的对象数据类型的数据,且默认是开启深度监视并且无法 关闭的。
可以监听ref定义或者reactive定义的对象数据类型的数据中的某个属性,若该属性值不是对象类型,需要写成函数形式,若该属性值依然是对象类型,可直接编辑,也可写成函数形式(()=>obj.name)。
<script setup>
import { ref, watch } from 'vue'const sum = ref(0)
const obj = ref({name:'123',age:18})
watch(sum,(newValue,oldValue)=>{console.log(newValue,oldValue)
})
watch(obj,(newValue,oldValue)=>{console.log(newValue,oldValue)
},{deep:true,immediate:true})watch(()=>obj.name,(newValue,oldValue)=>{console.log(newValue,oldValue)
},{deep:true,immediate:true})</script>
watchEffect:
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更新时重新执行该函数,
与watch相比,watchEffect不用明确指出监视的数据(自动监听函数中用到的属性),
<script setup>
import { ref, watchEffect } from 'vue'const height = ref(0)
const width = ref(0)watchEffect(()=>{if(height.value>20 || width.value>30){console.log(1234)}
})</script>
defineExpose:
子组件抛出数据给父组件,
ts:
// 定义一个接口 用于限制person对象的具体属性
export interface PersonInter{id:string,name:string,age:number,x?:number // 可有可无的字段
}// 一个自定义类型
export type Persons = Array<PersonInter>
<script lang="ts" setup name="name">
import {reactive} from 'vue'
import { type PersonInter ,type Persons} from '@/types'let person:PersonInter = {id:'123',name:'张三',age:18}
let personList:Persons = [{id:'123',name:'张三',age:18}]// 给响应式数据加泛型
let personList2 = reactive<Persons>([{id:'123',name:'张三',age:18}])
</script>
生命周期:
<script setup>
import { onBeforeMounted,onMounted,onBeforeUpdate,onUpdate } from 'vue'
// setup就是创建了
onBeforeMounted(() => {console.log('挂载前')
})
onMounted(() => {console.log('挂载完毕*')
})
onBeforeUpdate(() => {console.log('更新前')
})
onUpdate(() => {console.log('更新完毕*')
})
onBeforeUnmounted(() => {console.log('卸载前*')
})
onUnmounted(() => {console.log('卸载完毕')
})
</script>
hooks: 模块化
建一个hooks文件,文件里可以建多个use开头的ts文件,
每个hooks里都有自己的数据,方法,钩子函数等,不同的业务数据可以建不同的hooks文件,这样就实现了数据分离,
例如,建一个useDog.ts文件,
import { reactive,onMounted} from 'vue'export default function(){// 数据let dogList = reactive(['img 地址'])// 方法async function getDog(){// 请求数据dogList.push()}// 钩子onMounted(()=>{})// 向外界提供数据return {dogList,getDog}
}
组件引入hooks:
<script lang="ts" setup>
import useDog from '@/hooks/useDog'const {dogList,getDog} = useDog()</script>
路由:
新建router文件,写好router抛出来,在main.ts中使用路由,同时要在App.vue中使用路由导航来占位内容。
分为history和hash模式
pinia:集中式(数据)状态管理
多个组件共享数据,
安装: npm install pinia
引入:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const pinia = createPinia()
const app = createApp(App)app.use(pinia)
app.mount('#app')
定义:
在store文件夹里新建一个业务名称的ts的文件
例如:用选项式写count.ts
import { defineStore } from 'pinia'export const useCountStore = defineStore('count', {// 放置方法,用于响应组件中的actionsactions:{increment(value:number){// this是当前的storethis.sum + = value}},// 真正存储数据的地方state(){return {sum:6}},getters:{bigSum(state){// return state.sum*10 或者return this.sum*10}}
})
在页面中使用:
<template><div>{{sum}}</div><button @click="add">+</button>
</template>
<script lang="ts" setup>
import {useCountStore} from '@/store/count'
import { storeToRefs } from 'pinia'// 使用
const countStore = useCountStore()
// *解构 使用storeToRefs 会把countStore中的数据转为ref数据(响应式数据)
const { sum } = storeToRefs(countStore)
// 订阅这个属性的变化
sum.$subscribe((mutate,state)=>{console.log(state.sum)
})function add(){countStore.increment(1)
}
</script>
*改为用组合式写count.ts
ref()
成为state
属性computed()
成为getters
function()
成为actions
import { ref,computed } from 'vue'
import { defineStore } from 'pinia'export const useCountStore = defineStore('count', ()=>{const sum = ref(0)const doubleCount = computed(() => sum.value * 2)function increment(value:number){sum.value + = value}return {sum,doubleCount,increment}
})
defineProps:父子组件传参
父组件:
<template><div></div><Child :list="list" :house="house" @send="onSend"></Child>
</template><script setup lang="ts">import {ref} from 'vue'let childdata = ref('')let house = ref('别墅')function onSend(value:string){childdata.value = value}
</script>
子组件:list是父组件传过来的数据
<template><div>{{name}}{{list}}{{house}}</div><button @click="send(name)"></button>
</template><script setup lang="ts">
import {ref} from 'vue'
interface Props {foo: stringbar?: number
}
interface HouseProps {house?:string
}
let name = ref('sansan')
const props = defineProps(['list','send']) // 不限制类型
const props = defineProps<{list:Props}>() // 接收list+限制类型
const props = withDefaults(defineProps<{list?:Props}>(),{list:()=>[]} )// 接收list+限制类型+限制必要性+指定默认值
const props = defineProps<HouseProps>() // 接收house+限制类型</script>
自定义事件:子传父
父组件:
<template><div></div><Child @send-data="onSend"></Child>
</template><script setup lang="ts">import Child from './child.vue'import {ref} from 'vue'let childdata = ref('')// 取到传过来的数据function onSend(value:string){childdata.value = value}
</script>
子组件:
<template><div>{{name}}</div><button @click="emit('send-data',name)"></button>
</template><script setup lang="ts">import {ref} from 'vue'let name = ref('sansan')const emit = defineEmits(['send-data'])// 使用emit('send-data',value)就可以给父级传参数和事件了
</script>
mitt:不管多么深度或者多么远的组件之间都可以通信;
安装:npm i mitt
在util中引入mitt;
emitter.emit('on-send',name)
emitter.on('on-send',(value:string)=>{// 操作传过来的数据
})
// 销毁组件时解绑emitt的事件
onUnmounted(()=>{emitter.off('on-send')
})
v-model
v-model用在html标签上:
v-model 的本质是input框的value和@input事件
v-model用在组件标签上:
$event到底是什么,
对于原生事件,$event就是事件对象,这时候能用.target,
对于自定义事件,$event就是触发事件时,所传递的数据,不能.target,
举个例子:
父组件:
<child v-model="name"></child>
// 优化
<child v-model:qwe="name"></child>
<child :qwe="name"></child>
// 本质是
<child :modelValue="name" @update:modelValue="name=$event"></child>
子组件:
<template><input :value="modelValue" @input="emit('update:modelValue',$event.target.value)"><input :value="qwe" @input="emit('update:qwe',$event.target.value)">
</template><script setup lang="ts">defineProps(['modelValue','qwe')const emit = defineEmits(['update:modelValue','update:qwe'])
</script>
爷孙组件传参:
v-bind="$attrs" v-on="$listeners"
组件通信:$refs 和
$parent
$refs值为对象,能取到所有被ref属性标识的DOM元素或组件实例,用于父级获取子集数据并且可修改子集数据。
$parent值为对象,能取到当前组件的父组件实例对象,用于子级获取父级数据并修改父级数据。
子级:
<template><div>{{name}}</div><button @click="changeParent($parent)">修改父级的数据</button>
</template><script setup lang="ts">import {ref} from 'vue'let name = ref('sansan')// 改动父组件的数据function changeParent(parent:any){// 因为parent是响应式对象了,当访问parent.parentData的时候,底层会自动读取value属性,所以里面的parentData不用再.value了parent.parentData = 10}// 使用defineExpose把数据交给外部defineExpose({name})
</script>
父级:
<template><Child ref="child"></Child><button @click="changeName($refs)">修改Child组件的数据</button>
</template><script setup lang="ts">import Child from './child.vue'import {ref} from 'vue'let child = ref()let parentData = ref(4) // 父级数据// 改动子组件的数据function changeName(refs:object){// child.value.name="xxx"// 因为refs是响应式对象了,所以里面的name不用再.value了refs.child.name="xxx"}// 使用defineExpose把数据交给外部defineExpose({parentData})
</script>
祖孙之间通信:provide 和inject
爷级:
<template><Child ></Child>
</template><script setup lang="ts">import Child from './child.vue'import {ref,provide} from 'vue'const location = ref('North Pole')function updateLocation() {location.value = 'South Pole'}// 向后代提供数据 所有后代都能收到provide('location',{location,updateLocation})</script>
孙级:
<template><button click="updateLocation">{{location }}</button>
</template><script setup lang="ts">import {inject} from 'vue'// 注入上层组件提供的数据const { location, updateLocation } = inject('location')
</script>
插槽
默认插槽,
具名插槽,
作用域插槽:数据在孩子那边,但根据数据生成的结构,由父亲决定,
父级:
<template>// <Child /><Child>// *默认插槽<div>{{name}}</div>// *默认插槽语法糖<div #default>{{name}}</div>// *具名插槽<template v-slot:s2><div>{{name}}</div></template><template v-slot:s1><h2>标题</h2></template><template #s1><h2>标题</h2></template> // 具名插槽语法糖// *作用域插槽 控制子组件的数据展示的结构<template v-slot="params"><div>{{params.game}} {{params.x1}}</div></template>// 作用域插槽结合具名插槽<template v-slot:slotname="params"><div>{{params.game}} {{params.x1}}</div></template></Child>
</template><script setup lang="ts">import Child from './child.vue'import {ref} from 'vue'let name= ref('里面是默认插槽的内容')
</script>
子级:
<template><div>...<slot>默认内容</slot> // 插槽占位<slot name="s1">s1内容</slot> // *具名插槽<slot name="s2">s2内容</slot> // *具名插槽<slot :game="game" :x1="x1"></slot> // *作用域插槽<slot name="slotname" :game="game" :x1="x1"></slot> // *作用域插槽也可以具名</div>
</template>
其他api:
shallowRef() 和shallowReactive() 绕开深度响应,只作用与顶层属性,避免了对每一个内部属性做响应式所带来的性能成本,提高性能。
readonly:用于创建一个对象的深只读副本,对象的所有嵌套属性都将变为只读,
shallowReadonly:只作用于对象的顶层属性,
toRaw:用于获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式,不会触发试图更新,何时使用:在需要将响应式对象传递给非vue的库或外部系统时,使用toRaw可以确保得到的是普通对象。
markRaw:标记一个对象,使其永远不会变成响应式的。
customRef:创建一个自定义ref,并对其依赖的
回调函数有两个参数,track(跟踪),trigger(触发),
track()在get中调用,告诉vue数据msg很重要 你要对数据msg进行持续关注,
trigger()在set中调用,通知vue一下数据msg变化了
teleport :
小知识:样式:filter:saturate(200%) 就可以修改内容的颜色,可以整体置灰,
Vue 2和Vue 3 的区别(非兼容性改变):
全局API应用实例:
2.x 全局 API | 3.x 实例 API ( app ) |
---|---|
Vue.config | 应用程序配置 |
Vue.config.生产提示 | 移除(见下方) |
Vue.config.ignoredElements | app.config.compilerOptions.isCustomElement(见底部) |
Vue.组件 | 应用程序组件 |
Vue.指令 | 应用程序指令 |
Vue.mixin | 应用程序.mixin |
Vue.use | app.use(见下方) |
Vue.原型 | app.config.globalProperties(见底部) |
Vue.扩展 | 移除(见下方) |
…
项目搭建:
1,环境准备:
node > v16.
pnpm
2,初始化项目:
使用vite进行构建,
安装pnpm:npm i pnpm -g
项目初始化命令:pnpm create vite
进入项目根目录安装依赖: pnpm install
运行:pnpm run dev
引入图标:
<template>
<!-- <use xlink:href="#icon-home" fill="red"></use> -->
<!-- 封装成组件 -->
<use :xlink:href="name" :fill="color"></use>
</template>
<script setup lang="ts">// 接受父组件传递过来的参数defineProps({name:String,color:String})
</script>
api封装,用户信息存储,router,
TS
ts是js的超集,js是一种弱类型动态语言
类型声明:
// 类型断言 用来告诉解析器 变量的实际类型
s=a as string
// 复合类型
let b:number | boolean
// 表示空 没有返回值
function fn():void{
}
// 永远不会返回结果
function fn2():never{
}
// 枚举
enum Gender = {male=0,female=1,name,age
}
let i:Gender
// 类型的别名 声明一个某类型的字段
type myType = 1|2|3
let k:myType
// 数组
let arr:number[] = [1,2,3]
let arr:Array<string> = ['a','s','d']
// 元组
let t1:[number,string,number?] = [1,'a']
面向对象
类:对象的模型,程序中可以根据类创建指定类型的对象,
class 类名{属性名:类型;
}// 例子:
class Person{// 实例属性name:string='124'// 静态属性 可以不创建实例直接访问类static age:number=18// 只读属性readonly id:string='1111'// 可以在任意位置访问(修改)public _name:string// 私有属性 只能在类内部进行访问 可以通过在类中添加方法使得私有属性可以被外部访问private _name:string// 受包含的属性 只能在当前类和当前类的子类中使用protected _name:string// 定义方法sayHollow(){ }// 构造函数 会在对象创建时调用constructor(name:string,age:number){// 给属性赋值console.log(this,'this表示当前的实例,可以通过this向新建的对象中添加属性')this.name= namethis.age = age}
}
// 创建实例对象
const per = new Person()
const per = new Person(name:'shanshan',age:12)
console.log(per.name)
console.log(Person.age)
per.sayHollow() // 调用方法
继承:
子类将会拥有父类所有的方法和属性,可以将多个类中共有的代码写在一个父类中,
如果想在子类中加一些自己的方法也可以直接加
class Cat extends Person{// 子类自己的方法run(){}// 子类也可以重写从父类继承过来的方法sayHollow(){}// 使用supermyMethod(){// super就表示当前类的父类super.sayHollow()}// 如果在子类中也写了构造函数,必须要再掉一次父类的构造函数constructor(name:string,age:number){super(name)this.age = age}}
抽象类:专门用来被子类继承 被子类继承时 子类不能创建实例对象 constructor
可以添加抽象方法,
abstract class Animal{name:stringconstructor(name:string,age:number){this.name= name}// 定义抽象方法 子类必须对抽象方法进行重写abstract sayHollow():void
}class Cat extends Animal{sayHollow(){// 必须要实现}
}let cat = new Cat(name:'shanshan')
cat.sayHollow()
接口:
// 用来定义一个类结构
// 接口可以在定义类的时候去限制类的结构
// 接口中的所有属性都不能有实际的值
// 接口只定义对象的结构 不考虑实际值
// 在接口中所有的方法都是抽象方法
interface myInter{name:string;sayHellow():void;
}
const obj:myInter={name:'sansan',
}
// 定义类时,可以使类去实现一个接口,实现接口就是使类满足接口的要求
class MyClass implements myInter {name:string;constructor(name:string){this.name=name}sayHellow(){console.log(1)}
}
泛型:
// 在定义函数或是类的时候,如果遇到类行不明确就可以使用泛型
function fn<T>(a:T):T{return a
}
// 使用
fn(a:10) // 不指定泛型
fn<number>(a:1) // 指定泛型
fn<string>(a:'123') // 指定泛型
// 声明接口
interface Inter {b:number
}
// T extends Inter表示泛型T必须是Inter 实现类(子类)
function fn3<T extends Inter>(a:T):number{return a.b
}
class Myclass<T>{name:T
}
const mc = new Myclass<string>(name:'shanshan')