https://www.vue3js.cn/docs/zh/guide/composition-api-introduction.html
通过创建 Vue 组件,我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点可能是不够的,尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要。
假设在我们的应用程序中,我们有一个视图来显示某个用户的仓库列表。除此之外,我们还希望应用搜索和筛选功能。处理此视图的组件可能如下所示:
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
data () {
return {
repositories: [], // 1
filters: { ... }, // 3
searchQuery: ‘‘ // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: ‘getUserRepositories‘ // 1
},
methods: {
getUserRepositories () {
// 使用 `this.user` 获取用户仓库
}, // 1
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
该组件有以下几个职责:
searchQuery
字符串搜索存储库filters
对象筛选仓库用组件的选项 (data
、computed
、methods
、watch
) 组织逻辑在大多数情况下都有效。然而,当我们的组件变得更大时,逻辑关注点的列表也会增长。这可能会导致组件难以阅读和理解,尤其是对于那些一开始就没有编写这些组件的人来说。
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
如果我们能够将与同一个逻辑关注点相关的代码配置在一起会更好。而这正是组合式 API 使我们能够做到的。
随着Vue的日益普及,人们也开始在大型和企业级应用程序中采用Vue。 由于这种情况,在很多情况下,此类应用程序的组件会随着时间的推移而逐渐增长,并且有时使用单文件组件人们很难阅读和维护。 因此,需要以逻辑方式制动组件,而使用Vue的现有API则不可能。
代替添加另一个新概念,提议使用Composition API,该API基本将Vue的核心功能(如创建响应式数据)公开为独立功能,并且该API有助于在多个组件之间编写简洁且可复用的代码。
在引入新的API之前,Vue还有其他替代方案,它们提供了诸如mixin,HOC(高阶组件),作用域插槽之类的组件之间的可复用性,但是所有方法都有其自身的缺点,由于它们未被广泛使用。
<template>
<div>哈哈 我又变帅了</div>
<h1>{{number}}</h1>
</template>
<script lang="ts">
import { defineComponent } from ‘vue‘;
export default defineComponent({
name: ‘App‘,
setup() {
const number = Number.MAX_SAFE_INTEGER;
return {
number,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
作用:定义一个数据的响应式
语法:const xxx = ref(initValue):
一般用来定义一个基本类型的响应式数据
<template>
<h2>setup和ref的基本使用</h2>
<h2>{{ count }}</h2>
<hr/>
<button @click="update">更新</button>
</template>
<script lang="js">
import { defineComponent } from ‘vue‘;
export default defineComponent({
name: ‘App‘,
data() {
return {
count: 0,
};
},
methods: {
update() {
this.count += 1;
},
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>setup和ref的基本使用</h2>
<h2>{{ count }}</h2>
<hr/>
<button @click="update">更新</button>
</template>
<script lang="js">
import { defineComponent, ref } from ‘vue‘;
export default defineComponent({
name: ‘App‘,
setup() {
// 变量,此时的数据不是响应式的数据
// let count = 0;
// ref是一个函数,定义的是一个响应式的数据,返回的是一个一个包含响应式数据的**引用(reference)对象**
// 对象中有一个value属性,如果需要对数据进行操作,需要使用该ref对象调用value属性的方式进行数据的操作
// html模板中是不需要.value的
const count = ref(0);
console.log(count);
// 方法
function update() {
// count++;
count.value += 1;
}
// 返回的是一个对象
return {
count,
update,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
作用:定义多个数据的响应式
const proxy = reactive(obj)
:接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”:会影响对象内部所有嵌套的属性
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
<template>
<h2>reactive的使用</h2>
<h3>名字:{{user.name}}</h3>
<h3>年龄:{{user.age}}</h3>
<h3>媳妇:{{user.wife}}</h3>
<h3>性别:{{user.gender}}</h3>
<hr/>
<button @click="updateUser">更新</button>
</template>
<script lang="ts">
import { defineComponent, reactive } from ‘vue‘;
export default defineComponent({
name: ‘App‘,
// 需求:显示用户的相关信息,点击按钮,可以更新用户的相关信息数据
setup() {
// 把数据变成响应式的数据
// 返回的是一个Proxy(代理对象),被代理的是里面的obj对象
// user现在是代理对象,obj是目标对象
const obj:any = {
name: ‘小明‘,
age: 20,
wife: {
name: ‘小甜甜‘,
age: 18,
car: [‘斯堪尼亚‘, ‘奔驰‘, ‘DAF‘],
},
};
const user = reactive<any>(obj);
const updateUser = () => {
user.name = ‘小红‘;
user.gender = ‘男‘;
};
return {
user,
updateUser,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
核心:
Object.defineProperty(data, ‘count‘, {
get() {},
set() {}
})
问题
核心:
new Proxy(data, {
// 拦截读取属性值
get(target, prop) {
return Reflect.get(target, prop);
},
// 拦截设置属性值或添加新属性
set(target, prop, value) {
return Reflect.set(target, prop, value);
},
// 拦截删除属性
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop);
},
});
proxy.name = ‘tom‘;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const user = {
name: ‘John‘,
age: 12,
};
// proxyUser是代理对象, user是被代理对象
// 后面所有的操作都是通过代理对象来操作被代理对象内部属性
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log(‘劫持get()‘, prop);
return Reflect.get(target, prop);
},
set(target, prop, val) {
console.log(‘劫持set()‘, prop, val);
return Reflect.set(target, prop, val); // (2)
},
deleteProperty(target, prop) {
console.log(‘劫持delete属性‘, prop);
return Reflect.deleteProperty(target, prop);
},
});
// 读取属性值
console.log(proxyUser === user);
console.log(proxyUser.name, proxyUser.age);
// 设置属性值
proxyUser.name = ‘bob‘;
proxyUser.age = 13;
console.log(user);
// 添加属性
proxyUser.sex = ‘男‘;
console.log(user);
// 删除属性
delete proxyUser.sex;
console.log(user);
</script>
</body>
</html>
在 beforeCreate 之前执行(一次),此时组件对象还没有创建
this 是 undefined,不能通过 this 来访问 data/computed/methods / props
其实所有的 composition API 相关回调函数中也都不可以
一般都返回一个对象:为模板提供数据,也就是模板中可以直接使用此对象中的所有属性/方法
返回对象中的属性会与 data 函数返回对象的属性合并成为组件对象的属性
返回对象中的方法会与 methods 中的方法合并成功组件对象的方法
如果有重名,setup 优先
注意:
<template>
<h2>App父级组件</h2>
<div>msg: {{ msg }}</div>
<button @click="msg += ‘=====‘">更新数据</button>
<hr>
<child :msg="msg" msg2="真香" @xxx="xxx"/>
</template>
<script lang="ts">
import { defineComponent, ref } from ‘vue‘;
import Child from ‘@/components/Child.vue‘;
export default defineComponent({
name: ‘App‘,
components: { Child },
setup() {
const msg = ref(‘what are you nong sha lei‘);
function xxx(text: string) {
console.log(‘=============xxx in‘);
msg.value += text;
}
return {
msg,
xxx,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>Child子级组件</h2>
<div>msg: {{ msg }}</div>
<div>count: {{ count }}</div>
<button @click="emitXxx">分发事件</button>
</template>
<script lang="ts">
import { defineComponent } from ‘vue‘;
export default defineComponent({
name: ‘Child‘,
props: [‘msg‘],
// 数据初始化周期的回调
// beforeCreate() {
// console.log(‘beforeCreate执行了‘);
// },
// mounted() {
// console.log(‘mounted执行了‘);
// console.log(this);
// },
// setup细节问题
// setup是在beforeCreate生命周期回调之前就执行了,而且就执行一次
// 由此可以推断出:setup在执行的时候,当前的组件还没被创建出来,也就意味着:组件实例对象this根本就不能用
// setup中的对象内部的属性和data函数中的return对象的属性都可以在html模板中使用
// setup中的对象中的属性和data函数中的属性会合并为组件对象的属性
// setup和methods中的方法会合并为组件对象的方法
// 在vue3中尽量不要混合使用data和setup及methods和setup
// setup 不能是一个 async 函数:因为返回值不再是 return 的对象,而是 promise, 模板看不到 return 对象中的属性数据
setup(props, context) {
console.log(‘setup执行了‘, this);
// props参数是一个对象,里面有服及组件向自己组建传递的数据,并且是在子级组件中使用props接收到的所有属性
// 包含props配置声明且传入的所有属性的对象
console.log(props);
// context参数,是一个对象,里面有attrs对象(获取当前组件上的属性,但是该属性实在props中没有声明接受的对象),
// emit方法(分发事件的),slots对象(插槽)
console.log(context.attrs);
// const showMsg1 = () => {
// console.log(‘setup中的showMsg1方法‘);
// };
function emitXxx() {
console.log(‘=============emitXxx in‘);
context.emit(‘xxx‘, ‘++‘);
}
return {
// setup一般返回一个对象,对象中的属性和方法可以在html模板中直接使用
// showMsg1,
emitXxx,
};
},
// data() {
// return {
// count: 10,
// };
// },
// methods: {
// showMsg() {
// console.log(‘methods中的showMsg方法‘);
// },
// },
});
</script>
<style scoped>
</style>
是 Vue3 的 composition API 中 2 个最重要的响应式 API
ref 用来处理基本类型数据,reactive 用来处理对象(递归深度响应式)
如果用 ref 对象/数组,内部会自动将对象/数组转换为 reactive 的代理对象
ref 内部:通过给 value 属性添加 getter/setter 来实现对数据的劫持
reactive 内部:通过使用 Proxy 来实现对对象内部所有数据的劫持,并通过 Reflect 操作对象内部数据
ref 的数据操作:在 js 中要.value,在模板中不需要(内部解析模板时会自动添加.value)
<template>
<h2>ref和reactive更新数据的问题</h2>
<h3>m1: {{ m1 }}</h3>
<h3>m2: {{ m2 }}</h3>
<h3>m3: {{ m3 }}</h3>
<hr>
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from ‘vue‘;
export default defineComponent({
name: ‘App‘,
// 是 Vue3 的 composition API 中 2 个最重要的响应式 API
// ref 用来处理基本类型数据, reactive 用来处理对象(递归深度响应式)
// 如果用 ref 对象/数组, 内部会自动将对象/数组转换为 reactive 的代理对象
// ref 内部: 通过给 value 属性添加 getter/setter 来实现对数据的劫持
// reactive 内部: 通过使用 Proxy 来实现对对象内部所有数据的劫持, 并通过 Reflect 操作对象内部数据
// ref 的数据操作: 在 js 中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
setup() {
const m1 = ref(‘abc‘);
const m2 = reactive({
name: ‘小明‘,
wife: {
name: ‘小红‘,
},
});
const m3 = ref({
name: ‘小明‘,
wife: {
name: ‘小红‘,
},
});
const update = () => {
console.log(m3);
m1.value += ‘===‘;
m2.wife.name += ‘===‘;
m3.value.wife.name += ‘===‘;
};
return {
m1, m2, m3, update,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>计算属性和监视</h2>
<fieldset>
<legend>姓名操作</legend>
姓氏:<input type="text" placeholder="请输入姓氏" v-model="user.firstName"> <br>
名字:<input type="text" placeholder="请输入名字" v-model="user.lastName"> <br>
</fieldset>
<fieldset>
<legend>计算属性和监视的演示</legend>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName1"> <br>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName2"> <br>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName3"> <br>
</fieldset>
</template>
<script lang="ts">
import {
computed, defineComponent, reactive, ref, watchEffect,
} from ‘vue‘;
export default defineComponent({
name: ‘App‘,
setup() {
// 定义一个响应式的对象
const user = reactive({
firstName: ‘东方‘,
lastName: ‘不败‘,
});
// 通过计算属性的方式,实现第一个姓名的显示
// vue3的计算属性
// 计算属性中的函数中如果只传入一个回调函数,表示的是get
// 第一个姓名
const fullName1 = computed(() => `${user.firstName}_${user.lastName}`);
// 第二个姓名
const fullName2 = computed({
get() {
return `${user.firstName}_${user.lastName}`;
},
set(val:string) {
const names:Array = val.split(‘_‘);
// eslint-disable-next-line prefer-destructuring
user.firstName = names[0];
// eslint-disable-next-line prefer-destructuring
user.lastName = names[1];
},
});
// 监视指定的数据
const fullName3 = ref(‘‘);
// watch(user, ({ firstName, lastName }) => {
// fullName3.value = `${firstName}_${lastName}`;
// }, { immediate: true, deep: true });
// 监视,不需要配置immediate,本身默认就会进行监视,(默认执行一次)
watchEffect(() => {
fullName3.value = `${user.firstName}_${user.lastName}`;
});
return {
user,
fullName1,
fullName2,
fullName3,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>计算属性和监视</h2>
<fieldset>
<legend>姓名操作</legend>
姓氏:<input type="text" placeholder="请输入姓氏" v-model="user.firstName"> <br>
名字:<input type="text" placeholder="请输入名字" v-model="user.lastName"> <br>
</fieldset>
<fieldset>
<legend>计算属性和监视的演示</legend>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName1"> <br>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName2"> <br>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName3"> <br>
</fieldset>
</template>
<script lang="ts">
import {
computed, defineComponent, reactive, ref, watch, watchEffect,
} from ‘vue‘;
export default defineComponent({
name: ‘App‘,
setup() {
// 定义一个响应式的对象
const user = reactive({
firstName: ‘东方‘,
lastName: ‘不败‘,
});
// 通过计算属性的方式,实现第一个姓名的显示
// vue3的计算属性
// 计算属性中的函数中如果只传入一个回调函数,表示的是get
// 第一个姓名
const fullName1 = computed(() => `${user.firstName}_${user.lastName}`);
// 第二个姓名
const fullName2 = computed({
get() {
return `${user.firstName}_${user.lastName}`;
},
set(val:string) {
const [firstName, lastName] = val.split(‘_‘);
user.firstName = firstName;
user.lastName = lastName;
},
});
// 监视指定的数据
const fullName3 = ref(‘‘);
watch(user, ({ firstName, lastName }) => {
fullName3.value = `${firstName}_${lastName}`;
}, { immediate: true, deep: true });
// 监视,不需要配置immediate,本身默认就会进行监视,(默认执行一次)
// watchEffect(() => {
// fullName3.value = `${user.firstName}_${user.lastName}`;
// });
// 监视fullName3的数据,改变firstName和lastName
watchEffect(() => {
const [firstName, lastName] = fullName3.value.split(‘_‘);
user.firstName = firstName;
user.lastName = lastName;
});
// watch---可以监控多个数据的
// 当我们使用watch监控非响应式的数据是,代码需要改一下
watch([() => user.firstName, () => user.lastName, fullName3], () => {
console.log(‘===============‘);
});
return {
user,
fullName1,
fullName2,
fullName3,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
beforeCreate
-> 使用 setup()
created
-> 使用 setup()
beforeMount
-> onBeforeMount
mounted
-> onMounted
beforeUpdate
-> onBeforeUpdate
updated
-> onUpdated
beforeDestroy
-> onBeforeUnmount
destroyed
-> onUnmounted
errorCaptured
-> onErrorCaptured
组合式 API 还提供了以下调试钩子函数:
<template>
<h2>App父级组件</h2>
<button @click="isShow = !isShow">切换显示</button>
<hr>
<child v-if="isShow" />
</template>
<script lang="ts">
import {
defineComponent, ref,
} from ‘vue‘;
import Child from ‘@/components/Child.vue‘;
export default defineComponent({
name: ‘App‘,
components: { Child },
setup() {
const isShow = ref(true);
return {
isShow,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>Child子级组件</h2>
<h4>msg: {{ msg }}</h4>
<hr>
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import {
defineComponent,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
ref,
} from ‘vue‘;
export default defineComponent({
name: ‘Child‘,
// vue2.x的生命周期钩子
beforeCreate() {
console.log(‘2.x中的 beforeCreate‘);
},
created() {
console.log(‘2.x中的 created‘);
},
beforeMount() {
console.log(‘2.x中的 beforeMount‘);
},
mounted() {
console.log(‘2.x中的 mounted‘);
},
beforeUpdate() {
console.log(‘2.x中的 beforeUpdate‘);
},
updated() {
console.log(‘2.x中的 updated‘);
},
beforeUnmount() {
console.log(‘2.x中的 beforeUnmount‘);
},
unmounted() {
console.log(‘2.x中的 unmounted‘);
},
setup() {
console.log(‘3.0中的 setup‘);
const msg = ref(‘msg‘);
const update = () => {
msg.value += ‘===‘;
};
onBeforeMount(() => {
console.log(‘3.0中的 onBeforeMount‘);
});
onMounted(() => {
console.log(‘3.0中的 onMounted‘);
});
onBeforeUpdate(() => {
console.log(‘3.0中的 onBeforeUpdate‘);
});
onUpdated(() => {
console.log(‘3.0中的 onUpdated‘);
});
onBeforeUnmount(() => {
console.log(‘3.0中的 onBeforeUnmount‘);
});
onUnmounted(() => {
console.log(‘3.0中的 onUnmounted‘);
});
return {
msg, update,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
收集用户鼠标点击的页面坐标
<template>
<h2>自定义hook函数操作</h2>
<h2>x:{{ x }}, y: {{ y }}</h2>
</template>
<script lang="ts">
import { defineComponent } from ‘vue‘;
import useMousePosition from ‘@/hooks/useMousePosition‘;
export default defineComponent({
name: ‘App‘,
// 需求1:用户在页面中点击页面,把点击位置的横纵坐标展示出来
setup() {
const { x, y } = useMousePosition();
return {
x, y,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
import { onBeforeUnmount, onMounted, ref } from ‘vue‘;
export default function () {
const x = ref(-1);
const y = ref(-1);
// 点击事件的回调函数
const clickHandler = (event:MouseEvent) => {
x.value = event.pageX;
y.value = event.pageY;
};
// 页面接在完毕了,再进行点击的操作
// 页面接在完毕的生命周期
onMounted(() => {
window.addEventListener(‘click‘, clickHandler);
});
onBeforeUnmount(() => {
window.removeEventListener(‘click‘, clickHandler);
});
return {
x, y,
};
}
封装发 ajax 请求的 hook 函数
利用 TS 泛型强化类型检查
<template>
<h2>自定义hook函数操作</h2>
<h2>x:{{ x }}, y: {{ y }}</h2>
<hr>
<h3 v-if="loading">正在加载中</h3>
<h3 v-else-if="errorMsg">错误信息:{{ errorMsg }}</h3>
<ul v-else v-for="(item, index) in data" :key="index">
<li>firstName: {{ item.firstName }}</li>
<li>lastName: {{ item.lastName }}</li>
<li>email: {{ item.email }}</li>
</ul>
</template>
<script lang="ts">
import { defineComponent, watch } from ‘vue‘;
import useMousePosition from ‘@/hooks/useMousePosition‘;
import useRequest from ‘@/hooks/useRequest‘;
interface IProgrammerData {
firstName: string,
lastName: string,
email: string,
}
export default defineComponent({
name: ‘App‘,
// 需求1:用户在页面中点击页面,把点击位置的横纵坐标展示出来
setup() {
const { x, y } = useMousePosition();
const { loading, data, errorMsg } = useRequest<IProgrammerData[]>(‘/data/example.json‘);
watch(data, () => {
if (data.value) {
console.log(data.value.length);
}
});
return {
x, y, loading, data, errorMsg,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
// 引入axios
// 发送ajax请求
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,
};
}
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
应用:当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
问题:reactive 对象取出的所有属性值都是非响应式的
解决:利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
<template>
<h2>toRefs的使用</h2>
<h3>name: {{ name }}</h3>
<h3>age: {{ age }}</h3>
</template>
<script lang="ts">
import {
defineComponent, reactive, toRefs,
} from ‘vue‘;
export default defineComponent({
name: ‘App‘,
setup() {
const state = reactive({
name: ‘自来也‘,
age: 47,
});
const state2 = toRefs(state);
// toRefs可以吧reactive包裹的数据变成普通对象的每一个属性包裹的ref对象
// 定时器,更新数据
setInterval(() => {
state2.name.value += ‘==‘;
}, 2000);
return {
// ...state, // 不是响应式的数据
...state2,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
利用 ref 函数获取组件中的标签元素
功能需求:让输入框自动获取焦点
<template>
<h2>ref的另一个作用:可以获取页面中的元素</h2>
<input type="text" ref="inputRef">
</template>
<script lang="ts">
import {
defineComponent, onMounted, ref,
} from ‘vue‘;
export default defineComponent({
name: ‘App‘,
// 需求:当页面加载完毕之后,页面中的文本框可以直接获取焦点(自动获取焦点)
setup() {
// 默认是空的,页面加载完毕,说明组件已经存在了,获取文本框元素
const inputRef = ref<HTMLElement | null>(null);
// 页面加载后的生命周期API
onMounted(() => {
// eslint-disable-next-line no-unused-expressions
inputRef.value && inputRef.value.focus();
});
return {
inputRef,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
shallowReactive:只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef:只处理了 value 的响应式,不进行对象的 reactive 处理
什么时候用浅响应式呢?
<template>
<h2>ShallowReactive和ShallowRef</h2>
<h3>m1: {{ m1 }}</h3>
<h3>m2: {{ m2 }}</h3>
<h3>m3: {{ m3 }}</h3>
<h3>m4: {{ m4 }}</h3>
<hr>
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import {
defineComponent, reactive, ref, shallowReactive, shallowRef,
} from ‘vue‘;
export default defineComponent({
name: ‘App‘,
// 需求:当页面加载完毕之后,页面中的文本框可以直接获取焦点(自动获取焦点)
setup() {
// 深度劫持、深度监视
const m1 = reactive({
name: ‘鸣人‘,
age: 20,
car: {
name: ‘奔驰‘,
color: ‘red‘,
},
});
// 浅劫持
const m2 = shallowReactive({
name: ‘鸣人‘,
age: 20,
car: {
name: ‘奔驰‘,
color: ‘red‘,
},
});
const m3 = ref({
name: ‘鸣人‘,
age: 20,
car: {
name: ‘奔驰‘,
color: ‘red‘,
},
});
const m4 = shallowRef({
name: ‘鸣人‘,
age: 20,
car: {
name: ‘奔驰‘,
color: ‘red‘,
},
});
const update = () => {
// m1
// m1.name += ‘==‘;
// m1.car.name += ‘==‘;
// m2
// m2.name += ‘==‘;
// m2.car.name += ‘==‘;
// m3
// m3.value.name += ‘==‘;
// m3.value.car.name += ‘==‘;
// m4
m4.value.name += ‘==‘;
m4.value.car.name += ‘==‘;
};
return {
m1, m2, m3, m4, update,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>ShallowReadonly和Readonly</h2>
<h3>state: {{ state }}</h3>
<hr>
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, shallowReadonly } from ‘vue‘;
export default defineComponent({
name: ‘App‘,
setup() {
const state = reactive({
name: ‘佐助‘,
age: 20,
car: {
name: ‘奔驰‘,
color: ‘yellow‘,
},
});
// 只读的数据,深度只读
// const state2 = readonly(state);
const state2 = shallowReadonly(state);
const update = () => {
state2.name += ‘===‘;
state2.car.name += ‘===‘;
};
return {
state, state2, update,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
reactive
或 readonly
方法转换成响应式代理的普通对象。<template>
<h2>toRaw和markRaw</h2>
<h3>state: {{ state }}</h3>
<hr>
<button @click="testToRaw">测试toRaw</button>
<button @click="testMarkRaw">测试markRaw</button>
</template>
<script lang="ts">
import {
defineComponent, markRaw, reactive, toRaw,
} from ‘vue‘;
interface UserInfo {
name: string;
age: number;
likes?: string[];
}
export default defineComponent({
name: ‘App‘,
setup() {
const state = reactive<UserInfo>({
name: ‘小明‘,
age: 20,
});
const testToRaw = () => {
// 把代理对象变成了普通对象
const user = toRaw(state);
user.name += ‘===‘;
};
const testMarkRaw = () => {
// state.likes = [‘吃‘, ‘喝‘];
// state.likes[0] += ‘==‘;
const likes = [‘吃‘, ‘喝‘];
// 标记之后无法成为代理对象
state.likes = markRaw(likes);
setInterval(() => {
if (state.likes) {
state.likes[0] += ‘==‘;
}
}, 2000);
};
return {
state, testToRaw, testMarkRaw,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>toRef使用及特点</h2>
<h3>state: {{ state }}</h3>
<h3>age: {{ age }}</h3>
<h3>money: {{ money }}</h3>
<hr>
<button @click="update">更新数据</button>
<hr>
<child :age="age"/>
</template>
<script lang="ts">
import {
defineComponent, reactive, ref, toRef,
} from ‘vue‘;
import Child from ‘@/components/Child.vue‘;
export default defineComponent({
name: ‘App‘,
components: { Child },
setup() {
const state = reactive({
age: 5,
money: 100,
});
// 把响应式数据的state对象中的某个属性age变成了ref对象了
const age = toRef(state, ‘age‘);
// 把响应式对象中的某个属性使用ref进行包装,变成了一个ref对象
const money = ref(state.money);
console.log(age);
console.log(money);
const update = () => {
// 更新数据的
// state.age += 2;
age.value += 3;
money.value += 10;
};
return {
state, age, money, update,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>Child子级组件</h2>
<h3>age: {{ age }}</h3>
<h3>length: {{ length }}</h3>
</template>
<script lang="ts">
import {
computed, defineComponent, Ref, toRef,
} from ‘vue‘;
function useGetLength(age: Ref) {
return computed(() => age.value.toString().length);
}
export default defineComponent({
name: ‘Child‘,
props: {
age: {
type: Number,
required: true,
},
},
setup(props) {
const length = useGetLength(toRef(props, ‘age‘));
return {
length,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>CustomRef的使用</h2>
<input type="text" v-model="keyword">
<p>{{ keyword }}</p>
</template>
<script lang="ts">
import { customRef, defineComponent } from ‘vue‘;
// 自定义hook防抖的函数
// value传入的数据,将来的数据类型不确定,所以,用泛型,delay防抖的间隔事件,默认是200毫秒
function useDebounceRef<T>(value: T, delay = 200) {
let timeoutId: number;
return customRef((track, trigger) => ({
// 返回数据的
get() {
// 告诉vue追踪数据
track();
return value;
},
// 设置数据的
set(newValue: T) {
// 清理定时器
clearInterval(timeoutId);
// 开启定时器
setTimeout(() => {
// eslint-disable-next-line no-param-reassign
value = newValue;
// 告诉vue更新界面
trigger();
}, delay);
},
}));
}
export default defineComponent({
name: ‘App‘,
setup() {
// const keyword = ref(‘abc‘);
const keyword = useDebounceRef(‘abc‘, 500);
return {
keyword,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h2>provide 和 inject</h2>
<p>当前的颜色: {{ color }}</p>
<button @click="color = ‘red‘">红色</button>
<button @click="color = ‘yellow‘">黄色</button>
<button @click="color = ‘green‘">绿色</button>
<hr>
<son />
</template>
<script lang="ts">
import { defineComponent, provide, ref } from ‘vue‘;
import Son from ‘@/components/Son.vue‘;
export default defineComponent({
name: ‘App‘,
components: { Son },
setup() {
const color = ref(‘red‘);
provide(‘color‘, color);
return {
color,
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<template>
<h3>儿子组件</h3>
<hr>
<grand-son />
</template>
<script>
import { defineComponent } from ‘vue‘;
import GrandSon from ‘./GrandSon.vue‘;
export default defineComponent({
name: ‘Son‘,
components: { GrandSon },
});
</script>
<style scoped>
</style>
<template>
<h3 :style="{color}">孙子组件</h3>
</template>
<script>
import { defineComponent, inject } from ‘vue‘;
export default defineComponent({
name: ‘GrandSon‘,
setup() {
const color = inject(‘color‘);
return {
color,
};
},
});
</script>
<style scoped>
</style>
reactive
创建的响应式代理readonly
创建的只读代理reactive
或者 readonly
方法创建的代理原文:https://www.cnblogs.com/iamfatotaku/p/15223611.html