A brief discussion on the role of Vue3 defineComponent

A brief discussion on the role of Vue3 defineComponent

The defineComponent function simply encapsulates the setup function and returns an options object;

export function defineComponent(options: unknown) {
  return isFunction(options) ? { setup: options } : options
}

The most important thing about defineComponent is that in TypeScript, it gives the component the correct parameter type inference.

insert image description here

defineComponent overload function

1: direct setup function

// overload 1: direct setup function
// (uses user defined props interface)
export function defineComponent<Props, RawBindings = object>(
  setup: (
    props: Readonly<Props>,
    ctx: SetupContext
  ) => RawBindings | RenderFunction
): DefineComponent<Props, RawBindings>

insert image description here

2: object format with no props

// overload 2: object format with no props
// (uses user defined props interface)
// return type is for Vetur and TSX support
export function defineComponent<
  Props = {},
  RawBindings = {},
  D = {},
  C extends ComputedOptions = {},
  M extends MethodOptions = {},
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = EmitsOptions,
  EE extends string = string
>(
  options: ComponentOptionsWithoutProps<Props,RawBindings,D,C,M,Mixin,Extends,E,EE>
): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>

insert image description here

3: object format with array props declaration

// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: any }
// return type is for Vetur and TSX support
export function defineComponent<
  PropNames extends string,
  RawBindings,
  D,
  C extends ComputedOptions = {},
  M extends MethodOptions = {},
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = Record<string, any>,
  EE extends string = string
>(
  options: ComponentOptionsWithArrayProps<
    PropNames,
    RawBindings,...>
): DefineComponent<
  Readonly<{ [key in PropNames]?: any }>,
  RawBindings,...>

insert image description here

4: object format with object props declaration

// overload 4: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function defineComponent<
  // the Readonly constraint allows TS to treat the type of { required: true }
  // as constant instead of boolean.
  PropsOptions extends Readonly<ComponentPropsOptions>,
  RawBindings,
  D,
  C extends ComputedOptions = {},
  M extends MethodOptions = {},
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = Record<string, any>,
  EE extends string = string
>(
  options: ComponentOptionsWithObjectProps<
    PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>

insert image description here

Development Practice

Apart from the basic usage in unit testing, there are several points to note in the following ParentDialog component:

How to write custom components and global components

Type constraints for inject, ref, etc.

How to write setup and the corresponding h injection problem

How to write v-model and scopedSlots in tsx

ParentDialog.vue

<script lang="tsx">
import { noop, trim } from 'lodash';
import {
  inject, Ref, defineComponent, getCurrentInstance, ref
} from '@vue/composition-api';
import filters from '@/filters';
import CommonDialog from '@/components/CommonDialog';
import ChildTable, { getEmptyModelRow } from './ChildTable.vue';
 
export interface IParentDialog {
  show: boolean;
  specFn: (component_id: HostComponent['id']) => Promise<{ data: DictSpecs }>;
}
 
export default defineComponent<IParentDialog>({
  // Custom components in tsx still need to register components: {
    ChildTable
  },
  props: {
    show: {
      type: Boolean,
      default: false
    },
    specFn: {
      type: Function,
      default: noop
    }
  },
 
  // note: setup must use arrow function setup: (props, context) => {
 
    // Fix the problem that 'h' function cannot be automatically injected in tsx // eslint-disable-next-line no-unused-vars
    const h = getCurrentInstance()!.$createElement;
 
    const { emit } = context;
    const { specFn, show } = props;
 
    // Usage of filter const { withColon } = filters;
 
    // inject usage const pageType = inject<CompSpecType>('pageType', 'foo');
    const dictComponents = inject<Ref<DictComp[]>>('dictComponents', ref([]));
    
    // ref type constraint const dictSpecs = ref<DictSpecs>([]);
    const loading = ref(false);
 
    const _lookupSpecs = async (component_id: HostComponent['id']) => {
      loading.value = true;
      try {
        const json = await specFn(component_id);
        dictSpecs.value = json.data;
      finally
        loading.value = false;
      }
    };
 
    const formdata = ref<Spec>({
      component_id: '',
      specs_id: '',
      model: [getEmptyModelRow()]
    });
    const err1 = ref('');
    const err2 = ref('');
    
    const _doCheck = () => {
      err1.value = '';
      err2.value = '';
      
      const { component_id, specs_id, model } = formdata.value;
      if (!component_id) {
        err1.value = 'Please select a component';
        return false;
      }
      for (let i = 0; i < model.length; i++) {
        const { brand_id, data } = model[i];
        if (!brand_id) {
          err2.value = 'Please select a brand';
          return false;
        }
        if (
          formdata.value.model.some(
            (m, midx) => midx !== i && String(m.brand_id) === String(brand_id)
          )
        ) {
          err2.value = 'Brand duplicate';
          return false;
        }
      }
      return true;
    };
 
    const onClose = () => {
      emit('update:show', false);
    };
    const onSubmit = async () => {
      const bool = _doCheck();
      if (!bool) return;
      const params = formdata.value;
      emit('submit', params);
      onClose();
    };
 
    // note: In tsx, globally registered components such as element-ui still need to use kebab-case format????‍
    return () => (
      <CommonDialog
        class="comp"
        title="New"
        width="1000px"
        labelCancel="Cancel"
        labelSubmit="OK"
        vLoading={loading.value}
        show={show}
        onClose={onClose}
        onSubmit={onSubmit}
      >
        <el-form labelWidth="140px" class="create-page">
         <el-form-item label={withColon('Component type')} required={true} error={err1.value}>
            <el-select
              class="full-width"
              model={{
                value: formdata.value.component_id,
                callback: (v: string) => {
                  formdata.value.component_id = v;
                  _lookupSpecs(v);
                }
              }}
            >
              {dictComponents.value.map((dictComp: DictComp) => (
                <el-option key={dictComp.id} label={dictComp.component_name} value={dictComp.id} />
              ))}
            </el-select>
          </el-form-item>
          {formdata.value.component_id ? (
              <el-form-item labelWidth="0" label="" required={true} error={err2.value}>
                <child-table
                  list={formdata.value.model}
                  onChange={(v: Spec['model']) => {
                    formdata.value.model = v;
                  }}
                  onError={(err: string) => {
                    err3.value = err;
                  }}
                  scopedSlots={{
                      default: (scope: any) => (
                        <p>{ scope.foo }</p>
                      )
                  }}
                />
              </el-form-item>
          ) : null}
        </el-form>
      </CommonDialog>
    );
  }
});
</script>
 
<style lang="scss" scoped>
</style>

Summary

  • Introduced defineComponent() to correctly infer parameter types of setup() components
  • defineComponent can correctly adapt to forms such as no props, array props, etc.
  • defineComponent can accept an explicit custom props interface or be automatically inferred from a property validation object
  • In tsx, globally registered components such as element-ui still need to use kebab-case format
  • In tsx, v-model should use model={ { value, callback }} syntax
  • In tsx, scoped slots should use scopedSlots={ { foo: (scope) => () }} syntax
  • defineComponent does not apply to functional components, use RenderContext instead

This is the end of this article about the role of Vue3 defineComponent. For more information about the role of Vue3 defineComponent, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Thinking about vue3 binding all onSomething as v-on events by default
  • This article will show you how to use Vue 3.0 responsive
  • Implementation of vue3.0+vant3.0 rapid project construction
  • Use Vue3 to implement a component that can be called with js
  • Detailed explanation of the underlying principle of defineCustomElement added in vue3.2
  • Vue3 Vue Event Handling Guide

<<:  Using MySQL database in docker to achieve LAN access

>>:  MySQL 8.0.18 installation and configuration graphic tutorial

Recommend

MySQL briefly understands how "order by" works

For sorting, order by is a keyword we use very fr...

Detailed explanation of the difference between var, let and const in JavaScript

Table of contents As a global variable Variable H...

The difference between redundant and duplicate indexes in MySQL

MySQL allows you to create multiple indexes on a ...

MySQL data type selection principles

Table of contents Small but beautiful Keep it sim...

The submit event of the form does not respond

1. Problem description <br />When JS is use...

Web designer's growth experience

<br />First of all, I have to state that I a...

Detailed explanation of MySQL EXPLAIN output columns

1. Introduction The EXPLAIN statement provides in...

MYSQL performance analyzer EXPLAIN usage example analysis

This article uses an example to illustrate the us...

React antd realizes dynamic increase and decrease of form

I encountered a pitfall when writing dynamic form...

Use dockercompose to build springboot-mysql-nginx application

In the previous article, we used Docker to build ...

Simple use of Vue bus

Simple use of Vue bus Scenario description: Compo...

Detailed explanation of the minimum width value of inline-block in CSS

Preface Recently, I have been taking some time in...

Install Ubuntu 18 without USB drive under Windows 10 using EasyUEFI

1. Check BIOS First check which startup mode your...

Make a nice flip login and registration interface based on html+css

Make a nice flip login and registration interface...

MySQL batch removes spaces in a certain field

Is there any way to remove spaces from a certain ...