Vue3中的reactive

617 阅读1分钟

这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战

reactive

之前我们看了一个关于ref和computed的将原始类型转换为响应式对象的小案例:

<template>
    <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" />
        <h1>{{ count }}</h1>
        <h1>{{ double }}</h1>
        <button @click="increase">👍+1</button>
    </div>
</template>

<script lang="ts">
import { ref, computed } from "vue";
export default {
    name: "App",
    setup() {
        const count = ref(0);
        const double = computed(() => {
            return count.value * 2;
        });
        const increase = () => {
            count.value++;
        };
        return {
            count,
            increase,
            double,
        };
    },
};
</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一般使用的是原始类型,而且几个变量也非常分散,那如果我们想让这些变量全部都包裹在一个变量当中,这时候我们就可以使用reactive来实现。我们来看一下改写的代码:

<template>
    <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" />
        <h1>{{ data.count }}</h1>
        <h1>{{ data.double }}</h1>
        <button @click="data.increase">👍+1</button>
    </div>
</template>

<script lang="ts">
import { ref, computed, reactive } from "vue";
interface DataProps {
    count: number;
    double: number;
    increase: () => void;
}
export default {
    name: "App",
    setup() {
        const data: DataProps = reactive({
            count: 0,
            increase: () => {
                data.count++;
            },
            double: computed(() => data.count * 2),
        });
        return {
            data,
        };
    },
};
</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 data = reactive({
            count: 0,
            increase: () => {
                data.count++;
            },
            double: computed(() => data.count * 2),
        });

这里如果我们不使用interface他会给我们提示一个类型错误报错。报错信息如下:

 'data' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.  
    35 |         // };
    36 |         // const data: DataProps = reactive({
  > 37 |         const data = reactive({
       |               ^^^^
    38 |             count: 0,
    39 |             increase: () => {
    40 |                 data.count++;

这是因为我们在computed回调中呢使用data.count会造成一个类型推论的循环。所以我们要使用interface新建一个类型来解决。

优化:

这个时候又会发现我们频繁地使用data.那么我们能不能使用ES6中的扩展运算符( spread )操作来把对象展开。

<template>
    <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" />
        <h1>{{ count }}</h1>
        <h1>{{ double }}</h1>
        <button @click="increase">👍+1</button>
    </div>
</template>

<script lang="ts">
import { ref, computed, reactive } from "vue";
interface DataProps {
    count: number;
    double: number;
    increase: () => void;
}
export default {
    name: "App",
    setup() {
        const data: DataProps = reactive({
            count: 0,
            increase: () => {
                data.count++;
            },
            double: computed(() => data.count * 2),
        });
        return {
            ...data,
        };
    },
};
</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>
    <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" />
        <h1>{{ count }}</h1>
        <h1>{{ double }}</h1>
        <button @click="increase">👍+1</button>
    </div>
</template>

<script lang="ts">
import { ref, computed, reactive } from "vue";
interface DataProps {
    count: number;
    double: number;
    increase: () => void;
}
export default {
    name: "App",
    setup() {
        const data: DataProps = reactive({
            count: 0,
            increase: () => {
                data.count++;
            },
            double: computed(() => data.count * 2),
        });
        return {
            count: data.count,
            increase: data.increase,
            double: data.double,
        };
    },
};
</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>

效果:

这是为什么呢,我们看一下count的类型:

那么它变成了number类型所以它不具备响应式了才会出现这个结果。

toRefs

这个时候我们使用toRefs就解决了。

将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref

代码:

<template>
    <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" />
        <h1>{{ count }}</h1>
        <h1>{{ double }}</h1>
        <button @click="increase">👍+1</button>
    </div>
</template>

<script lang="ts">
import { ref, computed, reactive } from "vue";
interface DataProps {
    count: number;
    double: number;
    increase: () => void;
}
export default {
    name: "App",
    setup() {
        const data: DataProps = reactive({
            count: 0,
            increase: () => {
                data.count++;
            },
            double: computed(() => data.count * 2),
        });
        const refData = toRefs(data);
        return {
            ...refData,
        };
    },
};
</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>

效果: