vue3如何实现响应式(vue3 + ts)
在 vue3.2 中 ,我们只需在script标签中添加setup 。就可以做到 ,组件只需引入不用注册 ,属性和方法也不用 return 才能于 template 中使用 ,也不用写setup函数 ,也不用写export default ,甚至是自定义指令也可以在我们的template中自动获得 。
一 、模板语法
1.使用 JavaScript 表达式
我们仅在模板中绑定了一些简单的属性名 。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:
{{ number + 1 }} {{ ok ? YES : NO }} {{ message.split().reverse().join() }} <div :id="`list-${id}`"></div>2.调用函数
可以在绑定的表达式中使用一个组件暴露的方法:
<span :title="toTitleDate(date)"> {{ formatDate(date) }} </span>3.ref 获取元素
<template> <div id="haha" ref="haha"></div> </template>得给 ref 指定类型 HTMLElement
setup() { let haha = ref<HTMLElement|null>(null) console.log(haha) return { haha, } }, haha.style.fontSize = 20px4.reactive
在模板使用直接 {{obj.name}} 即可
修改直接修改 obj[name] = ‘xxx’
ref 和 reactive的区别:
ref: 用来给基本数据类型绑定响应式数据 ,访问时需要通过 .value 的形式 , tamplate 会自动解析,不需要 .value
reactive: 用来给 复杂数据类型 绑定响应式数据 ,直接访问即可
<template> <div> <p>{{title}}</p> <h4>{{userInfo}}</h4> </div> </template> <script setup lang="ts"> import { ref, reactive } from "vue"; type Person = { name: string; age: number; gender: string; }; const title = ref<string>("彼时彼刻 ,恰如此时此刻"); const userInfo = reactive<Person>({ name: 树哥, age: 18 }) </script>5.toRefs
setup() { const user = reactive({ name: 小浪, age: 21, }) let userObj = toRefs(user) return { ...userObj, } }6.ref 和 reactive的区别?
在功能方面 ,ref 和 reactive ,都是可以实现响应式数据!
在语法层面,两个有差异 。ref定义的响应式数据需要用[data].value的方式进行更改数据;reactive定义的数据需要[data].[prpoerty]的方式更改数据 。
const actTitle: Ref<string> = ref(活动名称); const actData = reactive({ list: [], total: 0, curentPage: 1, pageSize: 10 }); actTitle.value = 活动名称2; actData.total = 100;但是在应用的层面 ,还是有差异的 ,通常来说:单个的普通类型的数据,我们使用ref来定义响应式 。表单场景中 ,描述一个表单的key:value这种对象的场景 ,使用reactive;在一些场景下 ,某一个模块的一组数据 ,通常也使用reactive的方式 ,定义数据 。
那么 ,对象是不是非要使用reactive来定义呢?其实不是的 ,都可以 ,根据自己的业务场景 ,具体问题具体分析!ref他强调的是一个数据的value的更改 ,reactive强调的是定义的对象的某一个属性的更改 。
7. 周期函数 onMounted
import { defineComponent, ref, onMounted } from vue; export default defineComponent({ name: Gift, setup() { const counter = ref(0); onMounted(() => { // 处理业务 ,一般进行数据请求 }) return { counter } } })8.store使用
import { useStore } from "vuex";
const store = useStore();
const storeData = computed(() => store); // 配合computed ,获取store的值 。 import { useStore } from "vuex"; import { defineComponent, ref, computed } from vue; export default defineComponent({ name: Gift, setup() { const counter = ref(0); const store = useStore(); const storeData = computed(() => store); // 配合computed,获取store的值 。 return { counter, storeData } } })9.router的使用
import { useRouter } from "vue-router";
const router = useRouter();
const onClick = () => {
router.push({ name: "AddGift" });
} import { useStore } from "vuex"; import { useRouter } from "vue-router"; import { defineComponent, ref, computed } from vue; export default defineComponent({ name: Gift, setup() { const counter = ref(0); const router = useRouter(); const onClick = () => { router.push({ name: "AddGift" }); } return { counter, onClick } } })10.关注点分离
关注点分离 ,应该分两层意思:第一层意思就是 ,Vue3的setup,本身就把相关的数据 ,处理逻辑放到一起 ,这就是一种关注点的聚合 ,更方便我们看业务代码 。
第二层意思 ,就是当setup变的更大的时候 ,我们可以在setup内部 ,提取相关的一块业务 ,做到第二层的关注点分离 。
import { useStore } from "vuex"; import { useRouter } from "vue-router"; import { defineComponent, ref, computed } from vue; import useMerchantList from ./merchant.js; export default defineComponent({ name: Gift, setup() { const counter = ref(0); const router = useRouter(); const onClick = () => { router.push({ name: "AddGift" }); } // 在该示例中 ,我们把获取商家列表的相关业务分离出去。也就是下面的merchant.ts const {merchantList} = useMerchantList(); return { counter, onClick, merchantList } } })merchant.ts
import { getMerchantlist } from "@/api/rights/gift"; import { ref, onMounted } from "vue"; export default function useMerchantList(): Record<string, any> { const merchantList = ref([]); const fetchMerchantList = async () => { let res = await getMerchantlist({}); merchantList.value = res.data.child; }; onMounted(fetchMerchantList); return { merchantList }; }11.interface
使用TS进行业务开发 ,一个核心的思维是 ,先关注数据结构 ,再根据数据结构进行页面开发 。以前的前端开发模式是 ,先写页面,后关注数据 。
比如要写一个礼品列表的页面 ,我们可能要定义这么一些interface。总而言之 ,我们需要关注的是:页面数据的interface 、接口返回的数据类型 、接口的入参类型等等 。
// 礼品创建 、编辑 、列表中的每一项,都会是这个数据类型 。 interface IGiftItem { id: string | number; name: string; desc: string; [key: string]: any; } // 全局相应的类型定义 // 而且一般来说 ,我们不确认 ,接口返回的类型到底是什么(可能是null 、可能是对象 、也可能是数组) ,所以使用范型来定义interface interface IRes<T> { code: number; msg: string; data: T } // 接口返回数据类型定义 interface IGiftInfo { list: Array<IGiftItem>; pageNum: number; pageSize: number; total: number; }在一个常见的接口请求中 ,我们一般使用TS这么定义一个数据请求 ,数据请求的req类型 ,数据请求的res类型 。
export const getGiftlist = ( params: Record<string, any> ): Promise<IRes<IGiftInfo>> => { return Http.get("/apis/gift/list", params); };12.支持多个v-model
//父组件 <template> <child v-model="name" v-model:email="email" /> <p>姓名:{{ name }}</p> <p>邮箱:{{ email }}</p> </template> <script lang="ts" setup> import child from ./child.vue import { ref } from vue const name = ref<string>(张三) const email = ref<string>(666@qq.com) </script> // 子组件 <template> <button @click="updateName">更新name</button> <button @click="updateEmail">更新email</button> </template> <script lang="ts" setup> // 定义emit const emits = defineEmits<{ (e: update:modelValue, value: string): void (e: update:email, value: string): void }>() const updateName = () => { emits(update:modelValue, 李四) } const updateEmail = () => { emits(update:email, 123456@qq.com) } </script>如果v-model没有使用参数 ,则其默认值为modelValue ,如上面的第一个v-model ,注意此时不再是像Vue2那样使用$emit(input)了 ,而是统一使用update:xxx的方式 。
13.watch
watch(data,()=>{},{})
参数一 ,监听的数据
参数二 ,数据改变时触发的回调函数(newVal,oldVal)
参数三,options配置项 ,为一个对象
1 、监听ref定义的一个响应式数据
<script setup lang="ts"> import { ref, watch } from "vue"; const str = ref(彼时彼刻) //3s后改变str的值 setTimeout(() => { str.value = 恰如此时此刻 }, 3000) watch(str, (newV, oldV) => { console.log(newV, oldV) //恰如此时此刻 彼时彼刻 }) </script>2 、监听多个ref
这时候写法变为数组的形式
<script setup lang="ts"> import { ref, watch } from "vue"; let name = ref(树哥) let age = ref(18) //3s后改变值 setTimeout(() => { name.value = 我叫树哥 age.value = 19 }, 3000) watch([name, age], (newV, oldV) => { console.log(newV, oldV) // [我叫树哥, 19] [树哥, 18] }) </script>3 、监听Reactive定义的响应式对象
<script setup lang="ts"> import { reactive, watch } from "vue"; let info = reactive({ name: 树哥, age: 18 }) //3s后改变值 setTimeout(() => { info.age = 19 }, 3000) watch(info, (newV, oldV) => { console.log(newV, oldV) }) </script>4 、监听reactive 定义响应式对象的单一属性
<script setup lang="ts"> import { reactive, watch } from "vue"; let info = reactive({ name: 树哥, age: 18 }) //3s后改变值 setTimeout(() => { info.age = 19 }, 3000) watch(()=>info.age, (newV, oldV) => { console.log(newV, oldV) // 19 18 } </script>停止监听
当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时 ,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止 。
但是我们采用异步的方式创建了一个监听器 ,这个时候监听器没有与当前组件绑定 ,所以即使组件销毁了 ,监听器依然存在 。
这个时候我们可以显式调用停止监听
<script setup lang="ts"> import { watchEffect } from vue // 它会自动停止 watchEffect(() => {}) // ...这个则不会! setTimeout(() => { watchEffect(() => {}) }, 100) const stop = watchEffect(() => { /* ... */ }) // 显式调用 stop() </script>(以下一样)
不同数据类型的监听
基础数据类型的监听:
const name = ref<string>(张三)
watch(name, (newValue, oldValue
) => {
console.log(watch===, newValue, oldValue)
})复杂数据类型的监听:
interface UserInfo
{
name: string
age: number
}const userInfo = reactive<UserInfo
>({
name: 张三,
age: 10
})
// 监听整个对象
watch(userInfo, (newValue, oldValue) =>{
console.log(watch userInfo, newValue, oldValue)
})// 监听某个属性
watch(() => userInfo.name, (newValue, oldValue) =>{
console.log(watch name, newValue, oldValue)
})支持监听多个源
const name = ref
const userInfo = reactive({
age: 18
})// 同时监听name和userInfo的age属性
watch([name, () => userInfo.age],([newName, newAge], [oldName, oldAge]) => {
//
})14.watch和watchEffect区别:
1 、watch是惰性执行 ,也就是只有监听的值发生变化的时候才会执行 ,但是watchEffect不同 ,每次代码加载watchEffect都会执行(忽略watch第三个参数的配置 ,如果修改配置项也可以实现立即执行)
2、watch需要传递监听的对象 ,watchEffect不需要
3 、watch只能监听响应式数据:ref定义的属性和reactive定义的对象 ,如果直接监听reactive定义对象中的属性是不允许的 ,除非使用函数转换一下
4 、watchEffect如果监听reactive定义的对象是不起作用的 ,只能监听对象中的属性 。
15.computed
<template> <div> <p>{{title}}</p> <h4>{{userInfo}}</h4> <h1>{{add}}</h1> </div> </template> <script setup lang="ts"> import { ref, reactive,computed } from "vue"; const count = ref(0) // 推导得到的类型:ComputedRef<number> const add = computed(() => count.value +1) </script>创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!