Vue进阶(一篇进入Vue3的世界)

一、初识Vue3

Vue3是一款流行的JavaScript框架Vue的最新版本,它于2020年9月正式发布。Vue3主要着重于性能的提升、更好的开发体验以及更好的代码组织方式。

Vue3的主要特性和改进:

  1. 更快的性能:Vue3对虚拟DOM进行了重构,通过优化渲染和更新算法,可以显著提高性能。
  2. 更小的包大小:Vue3对代码进行了精简和优化,使其比Vue2更小。
  3. Composition APIVue3引入了Composition API,这是一种新的API风格,提供了更好的代码组织方式,使得开发人员可以更好地管理代码复杂度。
  4. 改进的TypeScript支持:Vue3通过TypeScript提供了更好的类型检查支持,这有助于减少代码错误和提高开发效率。
  5. TeleportVue3引入了Teleport,这是一种新的组件传送方式,可以方便地将组件渲染到DOM中的任何位置。
  6. 其他改进:Vue3还提供了许多其他改进,包括更好的错误处理、更好的调试支持、更好的跨平台支持等。

总体来说,Vue3是一个更强大、更易于使用和更快的框架,它为开发人员提供了更好的工具来构建现代Web应用程序。

二、Vue3新语法糖setup

setupVue3引入的新选项,用于定义组件的状态和行为。在Vue2中,我们使用datacomputedmethods等选项来定义组件的状态和行为,但在Vue 3中,setup选项被引入,以提供更清晰的代码组织和更灵活的API

setup函数接收两个参数:propscontextprops是组件的属性对象,可以在函数内部使用,context是一个对象,包含了一些属性和方法,如attrsslotsemit等,用于访问组件的属性、插槽和自定义事件。

setup函数内部,我们可以使用refreactivecomputed等函数来定义组件的响应式数据和计算属性,还可以使用普通的JavaScript语法来定义方法和计算逻辑。例如:

import { ref, reactive, computed } from 'vue'
export default {
  props: {
    message: String
  },
  setup(props, context) {
    const count = ref(0)
    const state = reactive({
      name: 'Tom',
      age: 18
    })
    const doubleCount = computed(() => count.value * 2)

    function increment() {
      count.value++
    }
    return {
      count,
      state,
      doubleCount,
      increment
    }
  }
}

在模板中,我们可以直接访问setup函数返回的响应式数据、计算属性和方法。例如:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <p>Name: {{ state.name }}, Age: {{ state.age }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

setupVue3中定义组件状态和行为的主要方式,它提供了更灵活、更清晰的API,可以帮助我们更好地组织代码。

三、响应式数据函数

3.1 ref函数

ref函数用于创建一个包装器对象,使得基本类型的值也可以响应式地更新。它接受一个初始值作为参数,并返回一个包装器对象,可以通过.value来访问包装的值,如下所示:

import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++ // 更新值
console.log(count.value) // 1

在模板中使用ref时,可以直接访问.value属性,如下所示:

<template>
  <div>
    Count: {{ count.value }}
  </div>
</template>
<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)
    return {
      count
    }
  }
}
</script>

3.2 reactive函数

reactive函数用于创建一个响应式的对象。它接受一个普通的JavaScript对象作为参数,并返回一个响应式代理对象,可以通过访问代理对象的属性来触发更新。例如:

import { reactive } from 'vue'
const state = reactive({
  count: 0,
  message: 'Hello, Vue 3!'
})
console.log(state.count) // 0
state.count++ // 更新对象属性
console.log(state.count) // 1

在模板中使用响应式对象时,可以直接访问对象的属性,如下所示:

<template>
  <div>
    Count: {{ state.count }}
    Message: {{ state.message }}
  </div>
</template>
<script>
import { reactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      count: 0,
      message: 'Hello, Vue 3!'
    })
    return {
      state
    }
  }
}
</script>

3.3 ref和reactive函数的异同

refreactiveVue3中用于创建响应式数据的两个函数,它们有一些相同点和不同点。

相同点:

  1. 都可以创建响应式数据,使得数据状态的更新更加自然和高效。
  2. 在使用上都可以像普通对象一样访问和修改属性。
  3. 都可以在setup函数内部使用。

不同点:

  1. ref函数用于创建一个包装器对象,使得基本类型的值也可以响应式地更新,而reactive函数用于创建一个响应式的对象。
  2. 使用ref函数创建的响应式数据在访问时需要通过.value来访问包装的值,而使用reactive函数创建的响应式数据直接访问对象属性即可。
  3. ref函数只能包装一个值,而reactive函数可以包装一个对象,可以包含多个属性。
  4. ref函数返回的是一个包装器对象,而reactive函数返回的是一个响应式代理对象。
  5. ref函数还有一些其他的API,如.unref().isRef()等,用于对包装器对象进行操作和判断,而reactive函数则没有。

refreactive函数是Vue 3中用于创建响应式数据的两种常用方式,它们使得数据状态的更新更加自然和高效。使用哪种方式取决于需要创建的数据类型,如果是基本类型,可以使用ref,如果是对象类型,可以使用reactive

四、Vue3的响应式原理

Vue3的响应式原理与Vue2有所不同。Vue2使用了Object.defineProperty来实现响应式,而Vue3则使用了ES6Proxy对象。

Vue3中,当我们创建一个响应式的数据对象时,Vue3会使用一个叫做reactive的函数将该对象转换成响应式对象。reactive函数会使用Proxy对象对原始数据对象进行代理,拦截对该对象的所有访问和修改操作,并且在必要的时候触发响应式更新。

当我们对响应式对象进行属性的访问或修改时,Proxy对象会拦截这些操作,并通知Vue3进行响应式更新。这个过程中,Vue3会使用一个叫做effect的函数来收集依赖,然后在属性发生变化时触发依赖的更新。

effect函数是Vue3中实现副作用的核心函数,我们可以通过调用它来创建响应式的副作用函数。当我们创建一个副作用函数时,effect函数会自动执行该函数,并且在副作用函数中访问到的所有响应式数据都会被自动收集为依赖。当依赖发生变化时,副作用函数会自动被重新执行。

总的来说,Vue3的响应式原理与Vue2有所不同,但是它仍然是基于数据劫持的原理来实现响应式的。Vue3使用Proxy对象和effect函数来实现数据劫持和响应式更新,使得代码更加简洁、高效,并且可以更好地支持TypeScript等现代JavaScript特性。

五、语法更新

5.1 Vue3使用computed计算属性

Vue3中,可以使用computed函数来定义计算属性。computed函数接受一个函数作为参数,这个函数返回的值就是计算属性的值。

下面是一个示例:

import { computed } from 'vue'
export default {
  setup() {
    const count = ref(0)
    const doubledCount = computed(() => count.value * 2)
    return {
      count,
      doubledCount
    }
  }
}

在这个示例中,我们使用computed函数定义了一个计算属性doubledCount,它的值是count的两倍。count是一个响应式变量,使用ref函数创建。

computed函数的参数中,我们定义了一个箭头函数,它返回了count.value * 2。由于count是一个响应式变量,所以当count的值改变时,doubledCount的值也会自动更新。

setup函数中,我们将countdoubledCount返回给模板使用。在模板中,我们可以通过{{ count }}{{ doubledCount }}来获取它们的值。

5.2 Vue3使用watch监视属性的注意点

5.2.1 监视ref对象

Vue3中,ref对象是一个包装器,它包含一个value属性和一些辅助方法。当我们创建一个ref对象时,我们实际上是在创建一个带有value属性的JavaScript对象。因此,在监视ref对象时,我们需要监视的是ref对象的value属性而不是ref对象本身。这就意味着,当我们调用watch函数时,需要传入ref对象的value属性而不是ref对象本身。例如:

import { ref, watch } from 'vue';
const count = ref(0);
watch(count.value, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`);
});

在上面的例子中,我们监视了count.value的变化,而不是count对象本身的变化。

5.2.2 监视reactive对象

Vue3中,reactive对象是一个响应式对象,它可以包含多个属性。当我们创建一个reactive对象时,我们实际上是在创建一个带有多个属性的JavaScript对象。因此,在监视reactive对象时,我们需要监视整个对象而不是它的某个属性。例如:

import { reactive, watch } from 'vue';
const state = reactive({
  count: 0,
  message: 'Hello World!'
});
watch(state, (newValue, oldValue) => {
  console.log('state changed', newValue, oldValue);
});

在上面的例子中,我们监视了整个state对象的变化,而不是state.countstate.message的变化。

5.2.3 监视嵌套属性

当监视reactive对象的嵌套属性时,需要在watch函数的选项对象中指定deep选项为true,否则只会监听属性的赋值操作,而不会监听属性内部的变化。例如:

import { reactive, watch } from 'vue';
const state = reactive({
  nested: {
    count: 0
  }
});
watch(
  () => state.nested,
  (newValue, oldValue) => {
    console.log('nested state changed', newValue, oldValue);
  },
  { deep: true }
);

5.2.4 避免循环引用:

如果reactive对象中包含循环引用,那么watch函数会陷入无限递归,导致程序崩溃。因此,在使用watch函数时需要注意避免循环引用的问题。例如:

import { reactive, watch } from 'vue';
const state = reactive({
  a: 1,
  b: {
    c: 2
  }
});
// 此处会陷入无限递归,导致程序崩溃
state.b.d = state;
watch(
  state,
  (newValue, oldValue) => {
    console.log('state changed', newValue, oldValue);
  },
  { deep: true }
);

5.3 watchEffect函数

Vue3中,watchEffect函数可以用来监视响应式变量的变化并执行副作用函数。当响应式变量发生变化时,副作用函数会被重新执行。

下面是一个示例:

import { reactive, watchEffect } from 'vue'
export default {
  setup() {
    const state = reactive({
      count: 0
    })
    watchEffect(() => {
      console.log('Count changed to:', state.count)
    })
    function increment() {
      state.count++
    }
    return {
      state,
      increment
    }
  }
}

在这个示例中,我们使用reactive函数创建了一个响应式对象state,其中包含一个属性count。然后我们使用watchEffect函数监视了state.count的变化,并在控制台中打印了它的值。

setup函数中,我们将stateincrement函数返回给模板使用。在模板中,我们可以通过{{ state.count }}来获取count的值,并通过increment函数来增加count的值。

当我们在模板中点击增加按钮时,state.count的值会发生变化,watchEffect函数会被重新执行并在控制台中打印新的值。这个过程是自动触发的,不需要显式地定义一个watcher函数。

六、Vue3的生命周期

6.1 生命周期钩子函数

  1. beforeCreate: 在实例被创建之初,组件的属性和方法还没有初始化。

在这个阶段,Vue实例已经被创建,但是datamethods还没有被初始化。此时,我们可以做一些初始化工作,例如配置一些全局变量等等。

const app = Vue.createApp({
  beforeCreate() {
    console.log("beforeCreate");
  },
  created() {
    console.log("created");
  },
});
app.mount("#app");
  1. created: 在实例创建之后,组件的属性和方法已经初始化完成,但是DOM元素还没有被挂载。

在这个阶段,Vue实例已经被创建,并且datamethods已经被初始化。此时,我们可以做一些异步操作,例如通过ajax获取数据。

const app = Vue.createApp({
  created() {
    console.log("created");
    axios.get("/api/data").then((response) => {
      this.data = response.data;
    });
  },
});
app.mount("#app");
  1. beforeMount: 在DOM元素被挂载到页面之前,该钩子函数会被触发。

在这个阶段,Vue实例已经被创建,并且datamethods已经被初始化,但是DOM元素还没有被挂载。此时,我们可以做一些DOM操作。

const app = Vue.createApp({
  data() {
    return {
      message: "Hello, world!",
    };
  },
  beforeMount() {
    console.log("beforeMount");
    const title = document.createElement("h1");
    title.textContent = this.message;
    document.body.appendChild(title);
  },
});
app.mount("#app");
  1. mounted: 在DOM元素被挂载到页面之后,该钩子函数会被触发。

在这个阶段,Vue实例已经被创建,并且datamethods已经被初始化,DOM元素也已经被挂载到页面上。此时,我们可以做一些DOM操作,例如绑定事件监听器等等。

const app = Vue.createApp({
  data() {
    return {
      count: 0,
    };
  },
  mounted() {
    console.log("mounted");
    this.$refs.button.addEventListener("click", () => {
      this.count++;
    });
  },
});
app.mount("#app");
  1. beforeUpdate: 在组件更新之前,该钩子函数会被触发。

在这个阶段,Vue实例的data发生了改变,但是这些改变还没有被更新到DOM上。此时,我们可以做一些 DOM操作。

const app = Vue.createApp({
  data() {
    return {
      message: "Hello, world!",
    };
  },
  beforeUpdate() {
    console.log("beforeUpdate");
    const title = document.querySelector("h1");
    title.textContent = this.message.toUpperCase();
  },
});
app.mount("#app");
setInterval(() => {
  app.message = "Hello, Vue.js 3!";
}, 1000);
  1. updated: 在组件更新之后,该钩子函数会被触发。

在这个阶段,Vue实例的data发生了改变,且这些改变已经被更新到DOM上。此时,我们可以做一些DOM操作。

const app = Vue.createApp({
  data() {
    return {
      message: "Hello, world!",
    };
  },
  updated() {
    console.log("updated");
  },
});
app.mount("#app");
setInterval(() => {
  app.message = "Hello, Vue!";
}, 1000);
  1. beforeUnmount: 在实例被销毁之前,该钩子函数会被触发。

在这个阶段,Vue实例即将被销毁,但是DOM元素还存在。此时,我们可以做一些清理工作,例如取消事件监听器、清除定时器等等。

const app = Vue.createApp({
  data() {
    return {
      count: 0,
    };
  },
  mounted() {
    this.timer = setInterval(() => {
      this.count++;
    }, 1000);
  },
  beforeUnmount() {
    clearInterval(this.timer);
  },
});
app.mount("#app");
  1. unmounted: 在实例被销毁之后,该钩子函数会被触发。

在这个阶段,Vue实例已经被销毁,DOM元素也已经被移除。此时,我们不能再访问Vue实例的属性和方法。

const app = Vue.createApp({
  data() {
    return {
      message: "Hello, world!",
    };
  },
  beforeUnmount() {
    console.log("beforeUnmount");
  },
  unmounted() {
    console.log("unmounted");
  },
});
app.mount("#app");
setTimeout(() => {
  app.unmount();
}, 5000);

6.2 生命周期的变化

Vue3相较于Vue2的生命周期函数一些变化和改进:

  1. beforeCreatecreated被合并为beforeCreatebeforeMountmounted被合并为onMountedbeforeUpdateupdated被合并为onUpdatedbeforeDestroydestroyed被合并为 onBeforeUnmountonUnmounted。这种变化使得生命周期函数名称更加简洁,并且每个阶段只有一个函数,更容易理解和使用。
  2. 新增了两个生命周期函数:onBeforeMountonBeforeUpdate,分别在组件渲染之前被调用。
  3. 新增了一个生命周期函数:onRenderTrackedonRenderTriggered,用于调试组件渲染性能问题。这两个函数需要与Vue3的新特性Reactivity结合使用。

Vue3的生命周期函数相比Vue2更加简洁、清晰,并且新增了一些实用的函数。

七、Vue3新增

7.1 toRef与toRefs

toReftoRefsVue3中的两个新函数,用于创建响应式对象。

  • toRef接收两个参数,第一个参数是响应式对象,第二个参数是该对象的属性名。它会返回一个新的ref对象,该对象指向原始对象的属性,并且任何对该属性的修改都会被响应式地更新到原始对象上。

例如,假设有一个包含name属性的对象person

import { ref, toRef } from 'vue';
const person = {
  name: 'Alice'
};
const nameRef = toRef(person, 'name');
console.log(nameRef.value); // 'Alice'
nameRef.value = 'Bob';
console.log(person.name); // 'Bob'

在上面的例子中,nameRef是一个指向person.name的ref对象。通过修改nameRef.valueperson.name的值也被修改了。

  • toRefs则接收一个响应式对象,并将其转换为一个包含相同属性名的对象,每个属性都是ref对象。这样做的好处是可以在模板中方便地使用对象的属性而不需要使用.value

例如:

import { reactive, toRefs } from 'vue';
const person = reactive({
  name: 'Alice',
  age: 30
});
const personRefs = toRefs(person);
console.log(personRefs.name.value); // 'Alice'
personRefs.age.value++;
console.log(person.age); // 31

在上面的例子中,personRefs是一个包含nameage属性的对象,每个属性都是一个ref对象。可以像访问普通对象一样访问personRefs的属性,而不需要使用.value

7.2 shallowReactive与shallowRef

shallowReactiveshallowRefVue3中的两个新函数,用于创建浅层响应式对象。

  • shallowReactive函数可以将一个普通对象转换成一个响应式对象。与reactive不同的是,shallowReactive只会将对象的第一层属性转换成响应式对象,而不会递归转换嵌套对象的属性。这个函数的作用是可以在需要响应式对象的时候避免不必要的性能开销。

例如,假设有一个包含嵌套对象的普通对象:

import { shallowReactive } from 'vue';
const person = {
  name: 'Alice',
  age: 30,
  address: {
    city: 'Beijing',
    country: 'China'
  }
};
const shallowPerson = shallowReactive(person);
console.log(shallowPerson.address); // {city: "Beijing", country: "China"}
console.log(shallowPerson.address.city); // "Beijing"

在上面的例子中,shallowPerson是一个响应式对象,但是address属性并没有被递归地转换成响应式对象。

  • shallowRef函数可以将一个普通的值转换成一个ref对象。与ref不同的是,shallowRef只会将值转换成一个ref对象,而不会递归地将值的属性转换成响应式对象。

例如:

import { shallowRef } from 'vue';
const person = {
  name: 'Alice',
  age: 30
};
const shallowPersonRef = shallowRef(person);
console.log(shallowPersonRef.value); // {name: "Alice", age: 30}
console.log(shallowPersonRef.value.name); // "Alice"

在上面的例子中,shallowPersonRef是一个ref对象,但是它的值person并没有被递归地转换成响应式对象。

7.3 readonly与shallowReadonly

readonlyshallowReadonlyVue3中的两个新函数,用于创建只读对象。

  • readonly函数可以将一个普通对象转换成一个只读对象。与reactive不同的是,readonly会将对象的所有属性都转换成只读属性,这样就不能修改对象的属性值了。

例如,假设有一个包含可修改属性的对象:

import { readonly } from 'vue';
const person = {
  name: 'Alice',
  age: 30
};
const readonlyPerson = readonly(person);
readonlyPerson.name = 'Bob'; // Error: Cannot assign to read only property 'name' of object '#<Object>'

在上面的例子中,readonlyPerson是一个只读对象,尝试修改它的属性值会抛出错误。

  • shallowReadonly函数可以将一个普通对象转换成一个浅层只读对象。与shallowReactive类似,shallowReadonly只会将对象的第一层属性转换成只读属性,而不会递归转换嵌套对象的属性。

例如:

import { shallowReadonly } from 'vue';
const person = {
  name: 'Alice',
  age: 30,
  address: {
    city: 'Beijing',
    country: 'China'
  }
};
const shallowReadonlyPerson = shallowReadonly(person);
console.log(shallowReadonlyPerson.address); // {city: "Beijing", country: "China"}
shallowReadonlyPerson.address.city = 'Shanghai'; // OK
shallowReadonlyPerson.address = {city: 'Shanghai', country: 'China'}; // Error: Cannot assign to read only property 'address' of object '#<Object>'

在上面的例子中,shallowReadonlyPerson是一个只读对象,但是address属性的属性值是可以修改的,因为它并没有被转换成只读对象。但是尝试修改shallowReadonlyPersonaddress属性本身的值会抛出错误。

7.4 toRaw与markRaw

toRawmarkRawVue3中的两个函数,用于在响应式对象和普通对象之间转换,并标记一个对象为“原始”对象,即不会被转换成响应式对象。

  • toRaw函数可以将一个响应式对象转换成一个普通对象,返回的对象是这个响应式对象的原始对象,不再是响应式对象。

例如:

import { reactive, toRaw } from 'vue';
const person = reactive({
  name: 'Alice',
  age: 30
});
const rawPerson = toRaw(person);
console.log(rawPerson === person); // false
console.log(rawPerson.name); // 'Alice'
console.log(rawPerson.age); // 30

在上面的例子中,rawPerson是一个普通对象,它的值与person相同,但是它已经不是响应式对象了。

  • markRaw函数可以标记一个对象为“原始”对象,这个对象不会被转换成响应式对象。

例如:

import { reactive, markRaw } from 'vue';
const person = reactive({
  name: 'Alice',
  age: 30,
  address: markRaw({
    city: 'Beijing',
    country: 'China'
  })
});
console.log(person.address.city); // 'Beijing'
person.address.city = 'Shanghai'; // OK
person.address = {city: 'Shanghai', country: 'China'}; // OK
person.address.city = 'Beijing'; // OK

在上面的例子中,address属性被标记为原始对象,所以它不会被转换成响应式对象。在这个例子中,我们可以修改person.address.city的值,也可以修改person.address的值,但是这个对象本身不会成为响应式对象,也就不会触发视图的更新。

7.5 customRef

customRefVue3新增的一个函数,用于创建自定义的ref。它的用法类似于ref函数,但是可以自定义ref的读写行为。

customRef接受一个工厂函数作为参数,这个函数必须返回一个对象,这个对象包含getset两个函数。get函数用于获取ref的值,set函数用于设置ref的值。这两个函数可以是同步的也可以是异步的,只要它们返回的值是正确的就可以。

例如,下面的例子是一个使用customRef实现的计数器,每次设置值时会在控制台输出一条消息:

import { customRef } from 'vue';
function createCounter() {
  let count = 0;
  return customRef((track, trigger) => ({
    get() {
      track();
      return count;
    },
    set(value) {
      count = value;
      console.log(`count is now ${count}`);
      trigger();
    }
  }));
}
const counter = createCounter();
console.log(counter.value); // 0
counter.value = 1; // count is now 1
console.log(counter.value); // 1

在上面的例子中,createCounter函数返回一个使用customRef创建的ref对象,它的读写行为由传递给customRef的函数决定。在这个例子中,我们定义了一个计数器,每次设置值时会在控制台输出一条消息,并且会调用trigger函数来通知Vue更新视图。

需要注意的是,customRef返回的是一个包含value属性的对象,与ref不同,它不直接返回值。所以在使用customRef时需要使用counter.value来获取和设置值。

customRef还有一个可选的第二个参数,它是一个布尔值,用于指示是否需要进行深度代理。默认值为false,表示不进行深度代理。如果将这个值设置为true,则可以对复杂的对象进行深度代理。

例如:

import { customRef } from 'vue';
function createDeepRef(obj) {
  return customRef((track, trigger) => ({
    get() {
      track();
      return obj;
    },
    set(value) {
      obj = value;
      trigger();
    }
  }), true);
}
const obj = { name: 'John', age: 20 };
const deepRef = createDeepRef(obj);
console.log(deepRef.value); // { name: 'John', age: 20 }
deepRef.value.name = 'Mike';
console.log(deepRef.value); // { name: 'Mike', age: 20 }

在上面的例子中,使用customRef创建了一个深度代理的ref对象,可以对其内部的对象进行修改。注意,修改内部的对象不会触发trigger函数,因为Vue无法监测到对象内部的属性的变化。

7.6 provide与inject

provideinjectVue3新增的两个API,用于在父子组件之间传递数据,相比于props和事件,它们可以更方便地在组件树中的任意层次之间进行数据传递,而不需要一级一级地手动传递。

provide函数用于在父组件中提供数据,接受一个keyvalue作为参数,key必须是一个Symbol类型的值,用于标识这个提供的数据,value可以是任何类型的值,可以是基本类型、对象、函数等等。

例如:

import { provide } from 'vue';
export default {
  setup() {
    provide(Symbol('message'), 'Hello, World!');
    // or provide('message', 'Hello, World!'); 
    // It is recommended to use a Symbol as the key to avoid naming conflicts
  }
}

在上面的例子中,我们使用provide函数在父组件中提供了一个值为Hello, World!的数据,key是一个Symbol类型的值。

inject函数用于在子组件中注入数据,接受一个key作为参数,这个key必须是在父组件中通过provide函数提供的key。如果父组件中没有提供这个key,则inject函数会返回undefined

例如:

import { inject } from 'vue';
export default {
  setup() {
    const message = inject(Symbol('message'));
    // or const message = inject('message');
    console.log(message); // 'Hello, World!'
  }
}

在上面的例子中,我们使用inject函数在子组件中注入了父组件提供的值为Hello, World!的数据,key是一个Symbol类型的值。

需要注意的是,provideinject并不是响应式的,它们只是提供了一个方便的方式在组件之间传递数据。如果需要在子组件中监听父组件提供的数据变化,可以使用响应式数据来实现。

7.7 Teleport

TeleportVue3中新增的组件,它可以让你在组件树中的任何位置渲染元素,而不需要受到父组件的限制。这个组件通常用于实现弹出层或者模态框等组件,可以将它的内容渲染到指定的DOM节点上。

Teleport 的用法类似于普通的组件,只需要将需要渲染的内容放在 <Teleport> 标签中即可。其中,to 属性用于指定要渲染到的目标节点,可以是DOM节点或者Vue组件。例如:

<template>
  <button @click="showModal = true">Show Modal</button>
  <Teleport to="#modal">
    <div v-if="showModal" class="modal">
      <h2>Hello World</h2>
      <button @click="showModal = false">Close</button>
    </div>
  </Teleport>
</template>
<script>
import { ref } from 'vue';
export default {
  setup() {
    const showModal = ref(false);
    return {
      showModal,
    };
  },
};
</script>

在上面的例子中,我们使用 Teleport 组件将弹出层渲染到页面上的一个 DOM 节点上,而不是作为父组件的子元素。具体来说,我们给 <Teleport> 标签添加了一个 to 属性,值为 #modal,这个值对应着一个 id 为 modal 的 DOM 节点。然后在 <Teleport> 中我们渲染了一个包含弹出层内容的 div 元素。当用户点击 "Show Modal" 按钮时,showModal 变量会被设置为 true,这个弹出层就会被渲染到 #modal 节点上。

需要注意的是,Teleport 组件在渲染时并不会改变组件的父子关系,所以在渲染时并不会受到父组件的CSS样式的影响,这样可以更灵活地控制样式。此外,由于 Teleport 是异步渲染的,所以它的内容可能不会立即出现,需要等待 Vue完成异步操作后才能正确地渲染。

7.8 Suspense

SuspenseVue3中的一个新特性,它可以让你在异步加载组件时,显示一些占位符或者loading状态,直到组件加载完成后再显示真正的内容。类似于React中的 Suspense 组件。

Suspense可以用于异步加载组件或者异步加载数据,当组件或者数据还没有准备好时,可以显示一个loading状态或者占位符,等待异步操作完成后再显示真正的内容。这个特性在提高用户体验方面非常有用,可以让用户感觉应用更加流畅。

Vue3中,Suspense组件通常和async setup()函数一起使用,可以将异步操作放在async setup()函数中,等待异步操作完成后再显示真正的组件内容。下面是一个例子:

<template>
  <Suspense>
    <template #default>
      <HelloWorld />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>
<script>
import { defineAsyncComponent } from 'vue';

const HelloWorld = defineAsyncComponent(() =>
  import('./components/HelloWorld.vue')
);
export default {
  components: {
    HelloWorld,
  },
};
</script>

在上面的例子中,我们使用了 Suspense 组件包裹了一个异步加载的 HelloWorld 组件。当 HelloWorld 组件还没有加载完成时,会显示一个loading状态,等到 HelloWorld 组件加载完成后再显示真正的内容。在 Suspense 中,我们可以使用 defaultfallback 两个插槽来分别指定真正的组件内容和loading状态或者占位符。

需要注意的是,Suspense 组件需要配合异步加载的组件或者数据一起使用才能发挥作用,如果没有异步加载的需求,使用 Suspense 可能会带来额外的开销。此外,在使用 Suspense 组件时,需要确保被包裹的异步组件能够正常返回一个组件,否则会导致显示异常。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jc_caterpillar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值