vuejs3 第6页
本节代码示例使用单文件组件的语法 本指南假定你已经阅读了组合式 API 简介和响应性基础。如果你不熟悉组合式 API,请先阅读此文章。 在使用组合式 API 时,响应式引用和模板引用的概念是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样声明 ref 并从 setup() 返回: <template> <div ref="root">This is a root element</div> </template> <script> import { ref, onMounted } from 'vue' export default { setup() { const root = ref(null) onMounted(() => { // DOM元素将在初始渲染后分配给ref console.log(root.value) // <div>这是根元素</div> }) return { root } } } </script> 这里我们在渲染上下文中暴露 root,并通过 ref="root",将其绑定到 div 作为其 ref。在虚拟 DOM 补丁算法中,如果 VNode 的 ref 键对应于渲染上下文中的 ref,则 VNode 的相应元素或组件实例将被分配给该 ref 的值。这是在虚拟 DOM 挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值。 作为模板使用的 ref 的行为与任何其他 ref 一样:它们是响应式的,可以传递到 (或从中返回) 复合函数中。 #JSX 中的用法 export default { setup() { const root = ref(null) return () => h('div', { ref: root }) // with JSX return () => <div ref={root} /> } } #v-for 中的用法 组合式 API 模板引用在 v-for 内部使用时没有特殊处理。相反,请使用函数引用执行自定义处理: <template> <div v-for="(item, i)...
本指南假定你已经阅读了 Provide / Inject、组合式 API 介绍和响应性基础。如果你不熟悉组合式 API,请先阅读这篇文章。 我们也可以在组合式 API 中使用 provide/inject。两者都只能在当前活动实例的 setup() 期间调用。 #设想场景 假设我们要重写以下代码,其中包含一个 MyMap 组件,该组件使用组合式 API 为 MyMarker 组件提供用户的位置。 <!-- src/components/MyMap.vue --> <template> <MyMarker /> </template> <script> import MyMarker from './MyMarker.vue' export default { components: { MyMarker }, provide: { location: 'North Pole', geolocation: { longitude: 90, latitude: 135 } } } </script> <!-- src/components/MyMarker.vue --> <script> export default { inject: ['location', 'geolocation'] } </script> #使用 Provide 在 setup() 中使用 provide 时,我们首先从 vue 显式导入 provide 方法。这使我们能够调用 provide 时来定义每个 property。 provide 函数允许你通过两个参数定义 property: property 的 name (<String> 类型) property 的 value 使用 MyMap 组件,我们提供的值可以按如下方式重构: <!-- src/components/MyMap.vue --> <template> <MyMarker /> </template> <script> import { provide } from 'vue' import MyMarker from './MyMarker.vue export default { components: { MyMarker },...
本指南假定你已经阅读了 组合式 API 简介和响应性基础。如果你不熟悉组合式 API,请先阅读这篇文章。 在 Vue Mastery 上观看关于生命周期钩子的免费视频 你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。 下表包含如何在 setup () 内部调用生命周期钩子: 选项式 API Hook inside setup beforeCreate Not needed* created Not needed* beforeMount onBeforeMount mounted onMounted beforeUpdate onBeforeUpdate updated onUpdated beforeUnmount onBeforeUnmount unmounted onUnmounted errorCaptured onErrorCaptured renderTracked onRenderTracked renderTriggered onRenderTriggered TIP 因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。 这些函数接受一个回调函数,当钩子被组件调用时将会被执行: // MyBook.vue export default { setup() { // mounted onMounted(() => { console.log('Component is mounted!') }) } }
本节使用单文件组件代码示例的语法 本指南假定你已经阅读了组合式 API 简介和响应性原理。如果你不熟悉组合式 API,请先阅读这篇文章。 #参数 使用 setup 函数时,它将接受两个参数: props context 让我们更深入地研究如何使用每个参数。 #Props setup 函数中的第一个参数是 props。正如在一个标准组件中所期望的那样,setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。 // MyBook.vue export default { props: { title: String }, setup(props) { console.log(props.title) } } WARNING 但是,因为 props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。 如果需要解构 prop,可以通过使用 setup 函数中的 toRefs 来安全地完成此操作。 // MyBook.vue import { toRefs } from 'vue' setup(props) { const { title } = toRefs(props) console.log(title.value) } #上下文 传递给 setup 函数的第二个参数是 context。context 是一个普通的 JavaScript 对象,它暴露三个组件的 property: // MyBook.vue export default { setup(props, context) { // Attribute (非响应式对象) console.log(context.attrs) // 插槽 (非响应式对象) console.log(context.slots) // 触发事件 (方法) console.log(context.emit) } } context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。 // MyBook.vue export default { setup(props, { attrs, slots, emit }) { ......
本节使用单文件组件语法作为代码示例 #计算值 有时我们需要依赖于其他状态的状态——在 Vue 中,这是用组件计算属性处理的,以直接创建计算值,我们可以使用 computed 方法:它接受 getter 函数并为 getter 返回的值返回一个不可变的响应式 ref 对象。 const count = ref(1) const plusOne = computed(() => count.value++) console.log(plusOne.value) // 2 plusOne.value++ // error 或者,它可以使用一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。 const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0 #watchEffect 为了根据响应式状态自动应用和重新应用副作用,我们可以使用 watchEffect 方法。它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。 const count = ref(0) watchEffect(() => console.log(count.value)) // -> logs 0 setTimeout(() => { count.value++ // -> logs 1 }, 100) #停止侦听 当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。 在一些情况下,也可以显式调用返回值以停止侦听: const stop = watchEffect(() => { /* ... */ }) // later stop() #清除副作用 有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发: 副作用即将重新执行时 侦听器被停止...
#声明响应式状态 要为 JavaScript 对象创建响应式状态,可以使用 reactive 方法: import { reactive } from 'vue' // 响应式状态 const state = reactive({ count: 0 }) reactive 相当于 Vue 2.x 中的 Vue.observable() API ,为避免与 RxJS 中的 observables 混淆因此对其重命名。该 API 返回一个响应式的对象状态。该响应式转换是“深度转换”——它会影响嵌套对象传递的所有 property。 Vue 中响应式状态的基本用例是我们可以在渲染期间使用它。因为依赖跟踪的关系,当响应式状态改变时视图会自动更新。 这就是 Vue 响应性系统的本质。当从组件中的 data() 返回一个对象时,它在内部交由 reactive() 使其成为响应式对象。模板会被编译成能够使用这些响应式 property 的渲染函数。 在响应性基础 API 章节你可以学习更多关于 reactive 的内容。 #创建独立的响应式值作为 refs 想象一下,我们有一个独立的原始值 (例如,一个字符串),我们想让它变成响应式的。当然,我们可以创建一个拥有相同字符串 property 的对象,并将其传递给 reactive。Vue 为我们提供了一个可以做相同事情的方法 ——ref: import { ref } from 'vue' const count = ref(0) ref 会返回一个可变的响应式对象,该对象作为它的内部值——一个响应式的引用,这就是名称的来源。此对象只包含一个名为 value 的 property : import { ref } from 'vue' const count = ref(0) console.log(count.value) // 0 count.value++ console.log(count.value) // 1 #Ref 展开 当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加 .value: <template> <div> <span>{{ count }}</span> <button @click="count ++">Increment count</button> </div> </template> <script> import {...
现在是时候深入了!Vue 最独特的特性之一,是其非侵入性的响应性系统。数据模型是被代理的 JavaScript 对象。而当你修改它们时,视图会进行更新。这让状态管理非常简单直观,不过理解其工作原理同样重要,这样你可以避开一些常见的问题。在这个章节,我们将研究一下 Vue 响应性系统的底层的细节。 在 Vue Mastery 上免费观看关于深入响应性原理的视频。 #什么是响应性 这个术语在程序设计中经常被提及,但这是什么意思呢?响应性是一种允许我们以声明式的方式去适应变化的一种编程范例。人们通常展示的典型例子,是一份 excel 电子表格 (一个非常好的例子)。 点击此处看视频 如果将数字 2 放在第一个单元格中,将数字 3 放在第二个单元格中并要求提供 SUM,则电子表格会将其计算出来给你。不要惊奇,同时,如果你更新第一个数字,SUM 也会自动更新。 JavaScript 通常不是这样工作的——如果我们想用 JavaScript 编写类似的内容: var val1 = 2 var val2 = 3 var sum = val1 + val2 // sum // 5 val1 = 3 // sum // 5 如果我们更新第一个值,sum 不会被修改。 那么我们如何用 JavaScript 实现这一点呢? 检测其中某一个值是否发生变化 用跟踪 (track) 函数修改值 用触发 (trigger) 函数更新为最新的值 #Vue 如何追踪变化? 当把一个普通的 JavaScript 对象作为 data 选项传给应用或组件实例的时候,Vue 会使用带有 getter 和 setter 的处理程序遍历其所有 property 并将其转换为 Proxy。这是 ES6 仅有的特性,但是我们在 Vue 3 版本也使用了 Object.defineProperty 来支持 IE 浏览器。两者具有相同的 Surface API,但是 Proxy 版本更精简,同时提升了性能。 点击此处实现 该部分需要稍微地了解下 Proxy 的某些知识!所以,让我们深入了解一下。关于 Proxy 的文献很多,但是你真正需要知道的是 Proxy 是一个包含另一个对象或函数并允许你对其进行拦截的对象。 我们是这样使用它的:new Proxy(target, handler) const dinner = { meal: 'tacos' } const handler = { get(target, prop) { return target[prop] }...
Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。 让我们深入一个简单的例子,这个例子里 render 函数很实用。假设我们要生成一些带锚点的标题: <h1> <a name="hello-world" href="#hello-world"> Hello world! </a> </h1> 锚点标题的使用非常频繁,我们应该创建一个组件: <anchored-heading :level="1">Hello world!</anchored-heading> 当开始写一个只能通过 level prop 动态生成标题 (heading) 的组件时,我们很快就可以得出这样的结论: const app = Vue.createApp({}) app.component('anchored-heading', { template: ` <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-else-if="level === 2"> <slot></slot> </h2> <h3 v-else-if="level === 3"> <slot></slot> </h3> <h4 v-else-if="level === 4"> <slot></slot> </h4> <h5 v-else-if="level === 5"> <slot></slot> </h5> <h6 v-else-if="level === 6"> <slot></slot> </h6> `, props: { level: { type: Number, required: true } } }) 这个模板感觉不太好。它不仅冗长,而且我们为每个级别标题重复书写了 <slot></slot>。当我们添加锚元素时,我们必须在每个 v-if/v-else-if 分支中再次重复它。 虽然模板在大多数组件中都非常好用,但是显然在这里它就不合适了。那么,我们来尝试使用 render 函数重写上面的例子: const app = Vue.createApp({}) app.component('anchored-heading', { render() { const { h } = Vue return h( 'h' + this.level, // tag name {}, // props/attributes this.$slots.default() // array of children )...
#基础 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。 例子: // define a mixin object const myMixin = { created() { this.hello() }, methods: { hello() { console.log('hello from mixin!') } } } // define an app that uses this mixin const app = Vue.createApp({ mixins: [myMixin] }) app.mount('#mixins-basic') // => "hello from mixin!" #选项合并 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。 比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。 const myMixin = { data() { return { message: 'hello', foo: 'abc' } } } const app = Vue.createApp({ mixins: [myMixin], data() { return { message: 'goodbye', bar: 'def' } }, created() { console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" } } }) 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。 const myMixin = { created() { console.log('mixin hook called') } } const app = Vue.createApp({ mixins: [myMixin], created() {...
Vue 的过渡系统提供了非常多简单的方法设置进入、离开和列表的动效。那么对于数据元素本身的动效呢,比如: 数字和运算 颜色的显示 SVG 节点的位置 元素的大小和其他的 property 这些数据要么本身就以数值形式存储,要么可以转换为数值。有了这些数值后,我们就可以结合 Vue 的响应性和组件系统,使用第三方库来实现切换元素的过渡状态。 #状态动画与侦听器 通过侦听器我们能监听到任何数值 property 的数值更新。可能听起来很抽象,所以让我们先来看看使用 GreenSock 一个例子: <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js" rel="external nofollow" rel="external nofollow" ></script> <div id="animated-number-demo"> <input v-model.number="number" type="number" step="20" /> <p>{{ animatedNumber }}</p> </div> const Demo = { data() { return { number: 0, tweenedNumber: 0 } }, computed: { animatedNumber() { return this.tweenedNumber.toFixed(0) } }, watch: { number(newValue) { gsap.to(this.$data, { duration: 0.5, tweenedNumber: newValue }) } } } Vue.createApp(Demo).mount('#animated-number-demo') 点击此处实现 更新数字时,更改将在输入下方设置动画。 #动态状态过渡 就像 Vue 的过渡组件一样,数据背后状态过渡会实时更新,这对于原型设计十分有用。当你修改一些变量,即使是一个简单的 SVG 多边形也可实现很多难以想象的效果。 点击此处实现 #把过渡放到组件里 管理太多的状态过渡会很快的增加组件实例复杂性,幸好很多的动画可以提取到专用的子组件。我们来将之前的示例改写一下: <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js" rel="external nofollow" rel="external nofollow" ></script> <div id="app"> <input v-model.number="firstNumber" type="number" step="20" /> + <input v-model.number="secondNumber" type="number" step="20" /> = {{ result }} <p> <animated-integer :value="firstNumber"></animated-integer> + <animated-integer :value="secondNumber"></animated-integer> = <animated-integer :value="result"></animated-integer> </p> </div> const app...