vue.js
用于构建用户界面的渐进式框架,双向数据绑定
双向数据绑定原理:通过数据劫持和发布-订阅模式实现,数据劫持通过Object.defineProperty()实现,
劫持属性的setter与getter,数据属性发生变动,发布消息给订阅,触发对应的监听回调
const app = new Vue({
el:'#app',
data:{ msg:'小猪皮' }
});
MVVM
model:模型,数据对象(data)
view:视图界面
viewModel:视图模型(vue的实例)
视图通过数据绑定读取model数据对象,
model数据对象通过DOM监听获取DOM内容
模板语法(动态的html)
1.双大括号表达式:{{kn}}
2.强制数据绑定:v-bind:src="kn"/:src="kn"
3.绑定事件监听:v-on:event="mn"/@event="mn"
<!-- kn:keyName mn:methodsName -->
计算属性computed/监视watch
<input type="text" v-model="computedValue">
<input type="text" v-model="comVal">
<script>
const vm = new Vue({
el:"#app",
data:{firstName:'luo', lastName:'xianze'},
computed:{// 计算属性存在缓存,多次读取只执行一次get
computedValue(){// 初始化显示/相关数据改变
return this.firstName + this.lastName;
},
comVal:{
get(){// 读取属性值,根据相关数据返回属性值
return this.firstName + '-' this.lastName;
},
set(val){// 当前属性发生改变,todo
const names = val.split('-');
this.firstName = names[0];
this.lastName = names[1];
}
}
},
watch:{// 监听firstName的改变,初始化不执行该函数
firstName:function(value){
this.computedValue = value + this.lastName;
},
todos:{
deep:true, // 深度监视
immediate:true,// 页面渲染完成,也会执行
handler:function(value){
// todo something
}
}
}
})
// 监听,keyName支持.模式,data.a.b
vm.$watch('keyName',function(value){})
</script>
class绑定/style绑定
:class="xxx" xxx字符串->data中data[xxx]
:class="{xClass:true/false,yClass:isY}" 对象
true,新增类,false,元素不新增该类;isY为data[isY]
:class='getClassName()' 函数
methods:{
getClassName:function(){
return {active:this.isActive}
}
}
:class="['active', 'line']" 数组字符串
className = 'active line'
:class="[active,line]" 数组变量
根据data中的active与line
:style="{color:dc, fontSize: dfs + 'px'}" 变量
dc、dfs为data中的属性值
v-if、v-else-if、v-else
使用示例:
<span v-if='变量值/表达式'></span>
<span v-else-if='变量值/表达式'></span>
<span v-else></span>
<div class="form-control" v-if='isHowName'>
<label for="userName">用户账号</label>
<input type="text" name="userName" id="userName">
</div>
<div class="form-control" v-else>
<label for="userEmail">用户邮箱</label>
<input type="text" name="userEmail" id="userEmail">
</div>
<!-- form-control切换时,input的输入内容不变,vue进行dom
渲染,出于性能的考虑,尽量复用已存在的元素,
若希望重新渲染dom,需要给元素新增不同的key -->
v-show
v-show 与 v-if的区别,v-show:false将DOM元素设置display:none;
v-if:false将DOM元素删除
显示与隐藏切换频繁建议使用v-show
v-for
建议使用v-for时,内容元素新增key,可更高效地更新虚拟DOM
数组触发响应式的方法:
push,pop,shift,unshift,splice,sort,reverse
不触发:arr[1] = 'xzp';
<!-- 遍历数组 -->
<li v-for="(item, index) in arr" :key="item.id">
{{index}}-{{item}}
</li>
<!-- 遍历对象 -->
<li v-for="(value, key, index) in obj" :key="key">
{{key}}-{{value}}
</li>
事件监听v-on
示例1:<button @click='add'>点击</button>
示例2:<button @click='add()'>点击</button>
示例3:<button @click='add(2)'>点击</button>
示例4:<button @click='add(2, $event)'>点击</button>
示例5:<button @click='add(name, $event)'>点击</button>
add:function(num){
console.log(num);
// 示例1:event对象,示例2:undefined,示例3:2
// 示例4:2,event对象,示例5:xzp,event对象
}
事件修饰符
<div>
<!-- stop阻止冒泡事件 -->
<button @click.stop='add'></button>
<!-- prevent阻止默认事件 -->
<button @contextmenu.prevent='add'></button>
<!-- 按键修饰符 keyCode 按键的keyCode:enter、space -->
<input @keyup.keyCode="xzpKeyUp">
<input @keyup.enter="xzpKeyUp">
<!-- 按键修饰符.once事件生效单次 -->
<input @click.once="xzpOnce">
</div>
<script>
new Vue({
methods:{
xzpKeyUp(event){
console.log(event.keyCode)
}
}
})
</script>
v-model数据双向绑定
v-model由 v-bind 与 v-on组合实现<input :value="name" @input="changeName">
<form @submit.prevent="handleSubmit">
<input type = 'text' v-model='name'>
<!-- 单选框 -->
<input type="radio" id="male" value="male" v-model='sex'>
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model='sex'>
<label for="female">女</label>
<!-- 复选框 like为数组 -->
<input type="checkbox" id="yyy" value='yyy' v-model='like'>
<label for="yyy">yyy</label>
<input type="checkbox" id="jsx" value='jsx' v-model='like'>
<label for="jsx">jsx</label>
<label v-for="(item, index) in likeArr" :for="index+item">
<input type="checkbox" id="index+item" :value="item" v-model='like'>{{item}}
</label>
<!-- 下拉框 -->
<select v-model='fruit'>
<option v-for="(item, index) in fruitArr"
:key="item.value" :value="item.value">{{item.name}}</option>
</select>
</form>
new Vue({ el:'#app',
data:{
name:'xzp',sex:'male', like:'jsx',
likeArr:['yyy','jsx'], fruit:'',
fruitArr:[
{name:'无',value:''},
{name:'橙子',value:'o'},
{name:'西瓜',value:'w'},
{name:'苹果',value:'a'}
]
},
methods:{
handleSubmit(){console.log(this);}
}
});
双向绑定的修饰符
<!-- 1.lazy,失去焦点或回车,数据更新 -->
<input type='text' v-model.lazy='city'>
<!-- 2.number,数字类型将输入框结果改为number -->
<input type='number' v-model.number='age'>
<!-- 3.trim,去掉输入框头尾空格 -->
<input type='text' v-model.trim='email'>
vue的生命周期
new Vue({ el:'#app',
data:{},
beforeCreate(){ /* 创建实例对象之前 */ },
created(){ /* 创建实例对象 */ },
beforeMount(){ /* 初始化渲染之前 */ },
mounted(){ /* 初始化渲染完成 */ },
beforeUpdate(){ /* 更新之前 */ },
updated(){ /* 更新完成 */ },
beforeDestroy(){ /* 销毁之前 */ },
destroyed(){ /* 销毁 */ }
methods:{
destroyVM(){
this.$destroy();// 销毁
}
}
});

vue2.jpeg
v-once
单次渲染,data.message改变时,页面将不变
<span v-once>{{message}}</span>
v-html
将数据解析为html,data.url = '<a href="www.baidu.com"></a>
<span v-html='url'></span>
v-text (不经常使用)
使用v-text 会覆盖标签原有的内容
<span v-text='text'></span>
v-pre (不解析内容)
<span v-pre>{{name}}</span>
界面显示:{{name}}
v-cloak
<div id='app' v-cloak></div>
vue解析之前,div存在属性v-cloak
vue解析之后,div属性v-cloak消失
样式写入[v-cloak]{ display:none; }
防止闪屏(不常用)
ref
<input ref='keyName' value='xxx'>
<script>
// input对象
this.$refs.keyName;
</script>
过渡/动画
<transition name = 'xxx'>
<div v-show='isShow'>示例显示隐藏</div>
</transition>
/* 显示隐藏 过渡效果 xxx 为transition的name */
.xxx-enter-active,.xxx-leave-active{
transition: opacity 1s;
}
.xxx-enter, .xxx-leave-to{ opacity: 0; }
<transition name = 'move'>
<div v-show='isShow'>示例移动</div>
</transition>
/* 显示 过渡效果 */
.move-enter-active{ transition:all 1s; }
/* 隐藏 过渡效果 */
.move-leave-active{ transition: all 2s; }
.move-enter, .move-leave-to{
opacity: 0;
transform:translateX(20px);
/* >0px向右移出隐藏,<0px向左移出隐藏 */
}
过滤器
/* 全局 */
Vue.filter('filterName', function(value, format){// todo
return filterValue;
});
/* 局部 */
filters{'filterName':function(value, format){// todo
return filterValue;
}};
<div>{{data | filterName(format)}}</div>
指令新增directive
/* 全局 */
Vue.directive('directive-name', function(el, binding){
// el 指令所在的标签对象,binding指令相关信息数据对象
})
/* 局部 */
directives:{
'directive-name'(el, binding){
// el 指令所在的标签对象,binding指令相关信息数据对象
}
}
/* 使用指令 */
v-directive-name
插件plugin
(function(){
const MyPlugin = {};
MyPlugin.install = function(Vue, options){
Vue.globalFunction = function(){
// 添加全局方法
}
Vue.directive('directive-name', function(el, binding){
// 添加全局资源(指令)
})
Vue.prototype.$myMethod = function(){
// 添加实例方法
}
}
window.MyPlugin = MyPlugin; // 向外暴露
})()
// 声明使用插件
Vue.use(MyPlugin);
Vue.globalFunction();
vm.$myMethod();
组件实例
<template>
<div>组件示例:App</div>
</template>
<script>
export default {
data(){// 组件data必须为函数
return {};
}
}
</script>
<style lang="less"></style>
import Vue from 'vue'
import App from './App.vue'
new Vue({
el:'#app',
components:{
App
},
template:`<App />
})
组件通信(父组件->子组件/子组件->父组件)
<template>
<div @click="deleteItem">{{itemprops}}</div>
</template>
<script>
export default {
// props:['itemprops']数组类型(新版本中不可用)
props:{
// 限制props的类型
itempropstype:Array|String|Number|Object|Function,
itemsex:{
type:String,
// 设置默认值,若默认值为对象/数组,default为函数
default:'male', // default(){return []/{}}
required:true // 必须传值
validator(value){ return true/false;} //验证规则
}
},
methods:{
deleteItem(){
let {deleteF} = this;
deleteF();
// 触发自定义事件
this.$emit('deleteLove', value);
}
}
}
</script>
<style lang="less"></style>
<!-- 父组件 -->
<template>
<div>
父组件传递props
<item :propstype="type" :itemsex="sex"
:deleteF="deleteF" @deleteLove="deleteL" />
</div>
</template>
<script>
export default {
data:{ type:'String',sex:'male' }
methods:{
deleteF(){}, // todo
deleteL(){} // todo
}
}
</script>
PubSub
安装npm install --save pubsub-js
// 引入
import PubSub from 'pubsub-js'
// 消息监听
PubSub.subscribe('titleName',function(msg, data){
// todo
});
PubSub.publish('titleName',data)
子组件访问父组件
this.$parent 访问父组件
this.$root 访问根组件
插槽slot
<!-- 父组件插入元素 -->
<cpn slot><button>我是插槽按钮</button></cpn>
<!-- 子组件cpn插槽 -->
<div>
<span>我是子组件</span>
<slot><button>我是插槽默认值</button></slot>
</div>
具名插槽
<!-- 子组件需要多个插槽 -->
<div>
<slot name='left'></slot>
<slot name='center'></slot>
<slot name='right'></slot>
</div>
<!-- 父组件需要传入名称 -->
<cpn>
<span slot='center'>center插槽内容</span>
<span slot='right'>right插槽内容</span>
</cpn>
插槽作用域
父组件替换插槽的数据,由子组件提供
<!-- 父组件 -->
<cpn>
<template slot-scope="slot">
<!-- 获取子组件绑定数据slot.data -->
<span v-for="item in slot.data">{{item}}</span>
</template>
</cpn>
<!-- 子组件插槽 -->
<slot :data="nameArr"></slot>
<!--
data(){
return { nameArr:['yyy','jsx','hh'] }
}
-->
vue cli脚手架
安装脚手架:npm install -g vue-cli
vue 路由的使用
npm install--> vue-router
新增路径文件夹router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
Vue.use(VueRouter);
export default new VueRouter({
routes:[
{ path:'/about', component:About },
{ path:'/home', component:Home },
{ path:'/', redirect:'/home'} //默认路径
]
})
/* 根组件引入router */
import router from './router'
new Vue({
router
})
/* 入口页面引用 */
<div class="list-link">
<router-link to="/about">about</router-link>
<router-link to="/home">home</router-link>
</div>
<div class="div-view"><!-- 显示内容 -->
<router-view></router-view>
</div>
vue嵌套路由
export default new VueRouter({
routes:[
{ path:'/about', component:About },
{ path:'/home', component:Home,
children:[
{
path:'/home/new',
component:New
},
{
path:'message',
component:Message
},
{ path:'', redirect:'/home/new'}
]
},
{ path:'/', redirect:'/home'} //默认路径
]
})
<!-- Home组件 -->
<router-link to="/home/new">new</router-link>
<router-link to="/home/message">message</router-link>
缓存路由组件
切换路由组件,原有的数据将被初始化
需要使用标签keep-alive
<div id="app" class='wrapper'>
<keep-alive>
<!-- 需要缓存的视图组件 -->
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 不需要缓存的视图组件 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
路由组件传递数据
// params参数
{
path:"/home/message/detail/:id",// 声明接收
component:Detail
}
// query参数路径不需要声明
<!-- 父页面 params -->
<router-link :to="`/home/message/detail/${msg.id}`">{{msg.title}}</router-link>
<!-- 父页面 query -->
<router-link :to="`/home/message/detail/?id=${msg.id}`">{{msg.title}}</router-link>
<!-- 子页面接收数据 params -->
<span>{{$route.params.id}}</span>
<!-- 子页面接收数据 query -->
<span>{{$route.query.id}}</span>
<script>
export default {
mounted(){
const id = this.$route.params.id;
},
watch:{
$route:function(value){
const id = value.param.id;
}
}
}
</script>
<!-- props传递 -->
<router-view :id='id'></router-view>
<!-- 子页面接收 -->
<script>
export default {
props:{
id:{
type:Number,
required:true
}
}
}
</script>
vuex
对vue应用中多个组件的共享状态进行集中式管理
状态自管理应用(单向数据流)
state:驱动应用的数据源
view:已声明方式将state映射到视图
actions:响应在view上的用户输入导致状态变化
多组件共享状态
1.多个视图依赖相同的状态
2.不同视图行为需要变更同一状态

vuex.png
/* vuex的核心管理对象模块:store */
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {// 初始化状态
count:0
}
const mutations = {
ADD(state, val){
state.count += val
},
SUB(state, val){
state.count -= val
}
}
const actions = {
add({commit}, val){// 提交mutation
commit('ADD',val)
},
sub({commit}, val){// 提交mutation
commit('SUB',val)
},
oddAdd({commit, state}, val){
if(state.count%2===1){
commit('ADD',val)
}
}
}
const getters = {
evenOrOdd(state){ // 读取属性
return state.count%2 ===0 ? '偶数' : '奇数';
}
}
import otherVuex from './otherVuex.stroe'
const modules = {
{
//单个模块的vuex
otherVuex.state, otherVuex.getters, otherVuex.mutations, otherVuex.actions
}
}
export default new Vuex.Store({
state, // 状态对象
mutations, // 包含多个更新state函数的对象(同步)
actions, // 包含多个对应事件回调函数的对象
getters // 包含多个getter计算属性函数的对象
})
/* ------------------------------ */
/* 在入口文件配置store */
import store from './store'
new Vue({
store
}).$mount('#app')
/* ------------------------------ */
/* 组件获取state */
this.$store.state[keyName];
/* template <span>{{$store.state[keyName]}}</span> */
export default{
computed:{
evenOrOdd(){ // 获取getters
return this.$store.getters.evenOrOdd
}
},
methods:{
add(val){ // 通知vuex修改state
this.$store.dispatch('add', val)
},
sub(val){
this.$stor.dispatch('sub',val)
}
}
}
/* vuex语法糖 */
import { mapState, mapGetters, mapActions } from 'vuex'
export default{
computed:{
...mapState(['count']), // 直接使用<span>count</span>
...mapGetters(['evenOrOdd'])// 直接使用evenOrOdd
// ...mapGetters({evenOrOdd:'eoo'})// 名称不一致,使用eoo
},
methods:{
...mapActions(['add','sub','oddAdd']) // 名称需要一致
}
}
总结:vuex通过全局注入store对象实现组件间状态共享;
组件通过dispatch分发action给store,store调用对应事件的回调函数;
回调函数提交对mutations的请求,mutations对state进行修改;
state改变后直接分发或通过getter将数据给组件。
vue3
1.性能提升
2.新增特性 composition、setup、新组件、新API
安装命令 npm install -g @vue/cli
新建项目 vue create 项目名称
vue3与vue2
1.vue3模板template可以没有根标签
2.lang='ts'可以使用typescript
3.defineComponent函数,定义组件,传入组件对象
vue3-setup、ref、reactive
1.setup-函数,执行一次
返回对象,对象中属性可在模板中直接使用
2.ref-定义响应式数据(基本类型:number,boolean,sting,undefined,null)
const num = ref(0),num为对象
3.reactive-定义响应式数据(复杂数据,对象,数组)
const obj = reactive({}),proxy代理对象(深层次)
<template>
<!-- 直接使用,不需要.value -->
<span>{{count}}</span>
<button @click="add">+1</button>
<button @click="updateAge">birthday</button>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from 'vue';
export default defineComponent({
name: 'App',
setup(){
console.log('执行一次')
const count = ref(0)
const obj = {
name:'xzp',
age:18,
fruit:['apple']
}
const user = reactive(obj)
function add(){
count.value ++
}
const updateAge = ()=>{
// 代理user可删除,修改,新增属性
// 修改obj,对象会改变,界面渲染不更新
user.age +=1
user.fruit.push('org')
user.love = 'yyy'
}
return { count, add, updateAge }
}
});
</script>
setup细节问题
1.setup于beforeCreate回调之前执行,且执行单次
2.不可以在setup中使用this
3.composition API相关回调不可用
4.setup返回值为对象,内部属性与方法,模板可用对象属性
5.setup对象、data对象、methods对象合并,模板中可用对象属性
6.vue3中尽量不要混合使用data,methods,setup(重名,setup优先)
7.methods可访问setup提供的属性与方法,setup不可访问data与methods
8.setup不可以异步
setup参数
setup(props, context){
// props,对象:父级传递子组件的数据,
// 子组件使用props接收的数据
// context,对象:attrs对象(获取子组件未声明属性)
// emit方法(分发事件),slots对象(插槽)
context.emit('xxx', str:string); //xxx父级setup返回的函数
}
ref/reactive细节问题
1.ref传入对象,需要经过reactive处理,形参proxy类型的对象
2.ref在js中需要.value获取内容,模板中不需要(模板解析自动添加.value)
属性计算computed和监视watch
import {computed, watch, watchEffect} from 'vue'
setup(){
const user = reactive({
firstName:'x',
lastName:'zp'
})
// vue3计算属性(只读)
const fullName = computed(()=>{
return user.firstName + '_' + user.lastName
})
// vue3计算属性(读写,反向修改user)
const fullName2 = computed({
get(){
return user.firstName + user.lastName
},
set(val:string){
let names = val.split('_')
user.firstName = names[0]
user.lastName = names[1]
}
})
// 监视数据变化 val =>新的user
watch(user, (val)=>{
fullName3.value = val.firstName + '_' + val.lastName
},{immediate:true, deep:true})
// immediate默认执行一次,若不配置,需要组件user数据存在更新才会执行
// deep深度监视
return {
user,
fullName,
fullName2,
fullName3
}
}
watchEffect 与 watch
1.watchEffect不需要配置immediate,默认执行
2.watch可以监视多个数据
3.watch监视非响应式数据需要
watch([()=>user.firstName, full3Name],()=>{
// todo
})
vue3生命周期
beforeCreate -> setup
created -> setup
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
<template>
<div class="about">
<h2>msg: {{msg}}</h2>
<hr>
<button @click="update">更新</button>
</div>
</template>
<script lang="ts">
import {
ref, onMounted, onUpdated,
onUnmounted, onBeforeMount,
onBeforeUpdate, onBeforeUnmount
} from "vue"
export default {
/* ----2.x---- */
beforeCreate () {
console.log('beforeCreate()')
},
created () {
console.log('created')
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
},
/* 3.x中beforeDestroy */
beforeUnmount () {
console.log('beforeUnmount')
},
/* 3.x中destroyed */
unmounted () {
console.log('unmounted')
},
/* ----3.x---- */
setup() {
const msg = ref('abc')
const update = () => {
msg.value += '--'
}
onBeforeMount(() => {
console.log('--onBeforeMount')
})
onMounted(() => {
console.log('--onMounted')
})
onBeforeUpdate(() => {
console.log('--onBeforeUpdate')
})
onUpdated(() => {
console.log('--onUpdated')
})
onBeforeUnmount(() => {
console.log('--onBeforeUnmount')
})
onUnmounted(() => {
console.log('--onUnmounted')
})
return { msg, update}
}
}
</script>
3.x的生命周期函数执行比2.x中的快
hook函数
新建文件夹hooks,文件命名useXXX.ts
/* 封装hooks,useMouseP */
import { ref, onMounted, onBeforeUnmount } from 'vue'
export default function(){
const x = ref(0)
const y = res(0)
const clickEvent = (event:MouseEvent){
x.value = event.pageX
y.value = event.pageY
}
onMounted(()=>{
window.addEventListener('click', clickEvent)
})
onBeforeUnmount(()=>{
window.removeEventListener('click', clickEvent)
})
return {x,y}
}
/* 使用hooks */
import useMouseP from './hooks/useMouseP'
setup(){
const {x,y} = useMouseP()
}
自定义hooks函数
封装request
import { ref } from 'vue'
import axios from 'axios'
export default function <T>(url: string) {
const loading = ref(true)
const data = ref<T | null>(null)
const errorMsg = ref('')
axios.get(url).then(response => {
loading.value = false
data.value = response.data
}).catch(error => {
loading.value = false
errorMsg.value = error.message || '未知错误'
})
return {
loading, data, errorMsg
}
}
/* 调用 */
const {
loading,
data, errorMsg } = useRequest<object>('/login')
toRefs
可以将响应式对象转换成普通对象,该普通对象的每个属性
都是单个的ref
setup(){
const state = reactive({
name:'xzp',
age:25
})
const {name, age } = toRefs(state)
function updateAge(){
age.value ++
}
return {
name, age
}
}
ref获取元素
<template>
<input type='text' ref='inputName'>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent({
name: 'app',
setup(){
const inputRef = ref<HTMLElement | null>(null)
onMounted(()=>{
inputRef.value && inputRef.value.focus() //自动获取焦点
})
return { inputRef }
}
})
</script>
<style>
</style>
shallowReactive 与 shallowRef
1.shallowReactive 浅监控(data.a)
2.shallowRef 浅监控(处理value的响应式
不进行对象的reactive处理)
readonly 与 shallowReadonly
1.readonly 深度只读
2.shallowReadonly 浅只读(data.a.b深数据可以改)
toRaw 与 markRow
import {toRaw, markRow } from 'vue'
interface UserInfo {
name:string;
age:number;
likes?:string[];
}
setup(){
const state = reactive({
name:"xzp",
age:25
})
const testToRaw =()=>{
const user = toRaw(state)
user.age += 1
// 将代理对象变成普通对象,数据改变,界面不刷新
}
const testMarkRaw = ()=>{
const like = [ 'yyy', 'jsx' ]
state.likes = markRaw(likes)
// markRaw标记的对象数据,不可以成为代理对象
// 修改state.likes 界面将不改变
}
return {
state
}
}
toRef特点与使用
setup()【
const state = reactive({
age:25,
money:25
})
// 响应式对象的属性变成ref对象
const age = toRef(state, 'age')
// 响应式对象的属性使用ref包装,成为ref对象
const money = ref(state.money)
自定义ref
自定义hook防抖函数
import { customRef } from 'vue'
function useDebouncedRef<T>(value:T, delay =200){
let timeOutId:number
return customRef((track, trigger)=>{
return {
get(){
// 启用vue数据追踪
track()
return value
},
set(newValueT){
clearTimeout(timeOutId)
// 开启定时器
setTimeout(()=>{
value = newValue
// 更新数据
trigger()
}, delay)
}
}
})
}
provide 与 inject
组件通信(数据非响应式)
import { provide, inject } from 'vue'
/* 父组件声明提供数据 */
setup(){
const name = ref('name')
// 声明提供
provide('name', name)
return { name }
}
/* 子组件声明接收 */
setup(){
const name = inject('name')
return { name }
}
响应式数据判断
1.isRef 判断数据是否为ref对象
2.isReactive 判断数据是否为reactive创建的响应式代理
3.isReadonly 判断数据是否为readonly创建的只读代理
4.isProxy 判断数据是否为reactive或readonly方法创建的代理
vue2和vue3的区别
vue3支持大多数vue2的特性
vue3存在一套更强的组合API代替vue2中option IPI 复用性更强
更好的支持typescript
重写虚拟dom,性能更好