vue3学习之应用变化(一)

本文主要包括以下内容:

  1. v-model使用的变化
  2. 渲染函数API的变化
  3. 函数式组件使用变化
  4. 异步组件使用变化

model选项和v-bind.sync修饰符移除,统一为v-model参数形式

v-model变化
  • 在vue2.0中,组件上使用v-model 相当于绑定value prop 和 input 事件:

    <ChildComponent v-model="pageTitle" />
    
    <!-- 等效于: -->
    
    <ChildComponent :value="pageTitle" @input="pageTitle = $event" />
    复制代码
  • 在vue2.2中,加入model组件选项。如果将属性或事件名称改为其他名称,则需要ChildComponent 组件中添加model选项:

    <!-- ParentComponent.vue -->
    
    <ChildComponent v-model="pageTitle" />
    <!-- 等效于: -->
    <ChildComponent :title="pageTitle" @change="pageTitle = $event" />
    复制代码
    // ChildComponent.vue
    
    export default {
      model: {
        prop: 'title',
        event: 'change'
      },
      props: {
        // 这将允许 `value` 属性用于其他用途
        value: String,
        // 使用 `title` 代替 `value` 作为 model 的 prop
        title: {
          type: String,
          default: 'Default title'
        }
      }
    }
    复制代码

    另一种方式是使用v-bind.sync

    对某一个prop进行双向数据绑定,子组件可以使用update:myPropName emit事件将新value值传给父级:

    this.$emit('update:title', newValue)
    复制代码
    <ChildComponent :title.sync="pageTitle" />
    <!-- 等效于: -->
    <ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
    复制代码
  • vue2中.sync和v-model功能有重叠,vue3做了统一:model选项和v-bind.sync修饰符移除,统一为v-model参数形式

    自定义组件上的v-model相当于传递了modelValue prop并接收抛出的update:modelValue 事件

    <comp v-model="data"></comp>
    <!-- 等效于: -->
    <comp :modelValue="data" @update:modelValue="data=$event"></comp>
    复制代码
    app.component('comp', {
    	template: `
    		<div @click="$emit('update:modelValue', 'new value')">
    			{{modelValue}}
    		</div>
    	`,
    	props: ['modelValue'],
    })
    复制代码

    如果要绑定其他名称的prop,则可以通过传递一个argument给model:

    <comp v-model:title="data"></comp>
    <!-- 等效于: -->
    <comp :title="data" @update:title="data=$event"></comp>
    复制代码

渲染函数API的变化

主要包括以下内容:

  1. 什么是h()
  2. 3.x 相较于2.x ,渲染函数有哪些变化?
h函数

h()函数是一个用于创建VNode 的实用程序,更准确的应命名为createVNode(),但由于使用频繁和简洁需求,被简称为h(),函数接收三个参数:

// @returns {VNode}
h(
  // {String | Object | Function | null} tag
  // 一个 HTML 标签名、一个组件、一个异步组件,或者 null。
  // 使用 null 将会渲染一个注释。
  //
  // 必需的。
  'div',

  // {Object} props
  // 与 attribute、prop 和事件相对应的对象。
  // 我们会在模板中使用。
  //
  // 可选的。
  {},

  // {String | Array | Object} children
  // 子 VNodes, 使用 `h()` 构建,
  // 或使用字符串获取 "文本 Vnode" 或者
  // 有插槽的对象。
  //
  // 可选的。
  [
    'Some text comes first.',
    h('h1', 'A headline'),
    h(MyComponent, {
      someProp: 'foobar'
    })
  ]
)
复制代码
渲染函数变得更加简单好用,修改主要有以下几点:
  • render函数不再传入h 参数,而是通过手动导入

    2.x 中,render函数自动接收一个h函数作为参数:

    export default {
    	render(h) {
    		return h('div');
    	}
    }
    复制代码

    3.x 中,h 需要我们手动导入,而不是自动传参,而且render函数常用于setup函数内,这对于作用域内定义的响应式状态,函数以及传入setup()中的参数的访问更加便利

    import { h, reactive } from 'vue'
      export default {
        props: {
          counter: {
            type: Number,
            default: 0
          }
        },
        setup(props, {emit}) {
          const state = reactive({
            count: props.counter
          })
    
          function increment() {
            state.count++;
            emit('update:counter', state.count);
          }
    	 // 渲染函数
          return () =>
            h(
              'div',
              {
                onClick: increment
              },
              state.count
            )
        }
      }
    复制代码
  • 扁平化的props参数结构

    2.x 中,domProps包含了由VNode属性构成的嵌套列表:

            {
                class: ['color-tips'],
                style: { color: '#34495E' },
                attrs: {
                    title: '文本',
                    target: '_blank',
                    href: `www.baid.com`,
                },
                domProps: {
                	innerText: '内容',
                },
                on: {
                    click: () => {
                    	console.log('点击事件')
                	},
                },
                key: 'xx'
            }
    复制代码

    3.x 中,整体的VNode属性结构进行了扁平化处理,上述示例改写如下:

           {
                class: ['color-tips'],
                style: { color: '#34495E' },
                title: '文本',
                target: '_blank',
                href: `www.baid.com`,
                innerText: '内容',
                onClick: () => {
                   console.log('点击事件')
                },
                key: 'xx'
            }
    复制代码
  • scopedSlots 删掉了,统一到slots

    2.x 通过scopedSlots获取插槽内容

    render() {
      // `<div><slot></slot></div>`
      return h('div', {}, this.$scopedSlots.default())
    }
    复制代码

    3.x 统一用slots访问静态插槽的内容,每个插槽都是一个VNode

    render() {
      // `<div><slot></slot></div>`
      return h('div', {}, this.$slots.default())
    }
    复制代码

vue3 破坏性变更– 函数式组件的变化

函数式组件变化较大,主要有以下几点:
  • 性能提升在vue3 中可以忽略不计,所以官方推荐使用状态组件
  • 函数式组件仅能通过纯函数形式声明,接收propscontext (对象包含slots , attrs ,emit)两个参数
  • 非兼容变更 : SFC中 <template> 不能添加functional` 特性声明函数式组件
  • 非兼容变更 : {functional: true} 组件选项移除
2.x 中的函数式组件

​ 在2.x 中,函数式组件主要有以下的优势:

	1. 作为性能优化,函数式组件的初始化速度比有状态组件快得多
	2. 返回多个根节点
	3. 创建简单组件
复制代码

示例:创建动态标题组件

// 函数组件 Fuctional.js
export default {
  functional: true,
  props: ['level'],
  render(h, ctx) {
    console.log('ctx', ctx);
    return h(`h${ctx.props.level}`, ctx.data, ctx.children);
  },
};

// 函数式组件示例使用 <template>   Fuctional.vue
<template functional>
  <component :is="`h${props.level}`" v-bind="attrs" v-on="listeners">
    <div v-if="slots()">
      <slot></slot>
    </div>
  </component>
</template>

<script>
export default { props: ['level'] };
</script>

// 父组件
<template>
  <div>
    <Functional :level="1">这是一个动态标题元素</Functional>
  </div>
</template>
<script>
import Functional from '@/components/Functional';
export default { components: { Functional } };
</script>

复制代码

控制台看到的效果:子组件中 ctx 为一个 FunctionalRenderContext 对象

image-20210420224118528.png

3.x中的函数式组件

vue3 中,有状态组件的性能提高到可以忽略不计的程度,而且状态组件还可以返回多个根节点,所以在vue2 函数式组件提到的优势荡然无存,只剩下简单组件创建的场景。

vue3中所有的函数式组件都是用普通函数创建,所以无需定义{ functional: true } 组件选项。该函数接收两个参数,propscontext

此外,render 函数中不隐式提供h, 而是手动导入h。

对于SFCs 上使用functional 的迁移,只需要删除fucntional属性,将propos改为props,attrs改为props, `attrs `改为 attrs,listeners 作为attr是的一部分传递,可删除

上述示例在vue3中的写法:

// 函数组件 Fuctional.js
import {h} from 'vue';
const Heading = (props, context) => {
    return h(`h${props.level}`, context.attrs, context.slots)
}
Heading.props = ['level'];

export default Heading;

// 单文件组件
<template>
  <component
    v-bind:is="`h${$props.level}`"
    v-bind="$attrs"
  />
</template>

<script>
export default {
  props: ['level']
}
</script>
复制代码

在控制台打印context ,结构更加清晰,包含attrs emit slots

image-20210420230955083.png

原先函数式组件是为了性能提高而生,但vue3状态组件性能已经提高,函数式组件意义不大,vue3对函数式组件进行破坏性变化, 后续开发中,建议用状态组件编写

异步组件要求使用defineAsyncComponent方法创建

由于vue3 中函数式组件必须定义为纯函数,异步组件定义时有以下变化:

  • 必须明确使用defineAsyncComponent 包裹,与函数式组件区分开来
  • component 选项重名为loader
  • loader函数不在接受resolvereject ,且必须返回一个Promise
2.x 中写法:
// 不带配置
const asyncPage = () => import('./NextPage.vue')

// 带配置
const asyncPage = {
  component: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  error: ErrorComponent,
  loading: LoadingComponent
}
复制代码
3.x 异步组件写法:

定义一个异步组件: defineAsyncComponent 包裹

import { definedAsyncCompnent} from 'vue';
// 不带配置
const asyncPage = definedAsyncCompnent(() => import('./NextPage.vue'))
复制代码

带配置的异步组件,loader选项为之前的component选项

import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})
复制代码

loader 函数不再接收 resolvereject 参数,且必须始终返回 Promise

// 2.x 版本
const oldAsyncComponent = (resolve, reject) => {
  /* ... */
}

// 3.x 版本
const asyncComponent = defineAsyncComponent(
  () =>
    new Promise((resolve, reject) => {
      /* ... */
    })
)
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享