defineComponent函数,只是对setup函数进行封装,返回options的对象;
1 2 3 | export function defineComponent(options: unknown) { return isFunction(options) ? { setup: options } : options } |
defineComponent最重要的是:在TypeScript下,给予了组件 正确的参数类型推断 。
defineComponent重载函数
1:direct setup function
1 2 3 4 5 6 7 8 | // 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> |
2:object format with no props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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> |
3:object format with array props declaration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // 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,...> |
4: object format with object props declaration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 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> |
开发实践
除去单元测试中几种基本的用法,在以下的 ParentDialog 组件中,主要有这几个实际开发中要注意的点:
自定义组件和全局组件的写法
inject、ref 等的类型约束
setup 的写法和相应 h 的注入问题
tsx 中 v-model 和 scopedSlots 的写法
ParentDialog.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | <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>({ // tsx 中自定义组件依然要注册 components: { ChildTable }, props: { show: { type: Boolean, default : false }, specFn: { type: Function, default : noop } }, // note: setup 须用箭头函数 setup: (props, context) => { // 修正 tsx 中无法自动注入 'h' 函数的问题 // eslint-disable-next-line no-unused-vars const h = getCurrentInstance()!.$createElement; const { emit } = context; const { specFn, show } = props; // filter 的用法 const { withColon } = filters; // inject 的用法 const pageType = inject<CompSpecType>( 'pageType' , 'foo' ); const dictComponents = inject<Ref<DictComp[]>>( 'dictComponents' , ref([])); // ref的类型约束 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 = '请选择部件' ; return false ; } for (let i = 0; i < model.length; i++) { const { brand_id, data } = model[i]; if (!brand_id) { err2.value = '请选择品牌' ; return false ; } if ( formdata.value.model.some( (m, midx) => midx !== i && String(m.brand_id) === String(brand_id) ) ) { err2.value = '品牌重复' ; 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: 在 tsx 中,element-ui 等全局注册的组件依然要用 kebab-case 形式 ???? return () => ( <CommonDialog class = "comp" title= "新建" width= "1000px" labelCancel= "取消" labelSubmit= "确定" vLoading={loading.value} show={show} onClose={onClose} onSubmit={onSubmit} > <el-form labelWidth= "140px" class = "create-page" > <el-form-item label={withColon( '部件类型' )} 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> |
全文总结
- 引入 defineComponent() 以正确推断 setup() 组件的参数类型
- defineComponent 可以正确适配无 props、数组 props 等形式
- defineComponent 可以接受显式的自定义 props 接口或从属性验证对象中自动推断
- 在 tsx 中,element-ui 等全局注册的组件依然要用 kebab-case 形式
- 在 tsx 中,v-model 要用 model={{ value, callback }} 写法
- 在 tsx 中,scoped slots 要用 scopedSlots={{ foo: (scope) => () }} 写法
- defineComponent 并不适用于函数式组件,应使用 RenderContext 解决
到此这篇关于浅谈Vue3 defineComponent有什么作用的文章就介绍到这了,更多相关Vue3 defineComponent作用内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!
- 本文固定链接: https://zxbcw.cn/post/219600/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)