Vue源码小问答一:为什么option.data的类型必须是function

本文深入解析了使用Vue.extend方法创建Vue实例的过程,并强调了option.data必须是function类型的原因。若非function,则所有实例将共享同一data对象,可能导致状态混乱。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在调用Vue.extend( options )进行Vue实例初始化时,option.data的类型必须是function,否则对导致该方法构造出的实例共享同一data对象。

具体原因分析如下:

Vue.extend方法本身的返回值是一个构造函数,通过new调用返回的构造方法我们就可以得到一个vue实例对象。这个构造函数会调用core模块中的_init函数已完成对象的初始化工作。

_init(option)方法会读取option.data

如果option.data是function类型,则执行这个方法得到data对象。如果不是方法类型,则直接使用这个对象作为data。

所以使用extend方法时,option.data必须为function

Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    // this在这里指向Vue方法
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name

    // 返回的vue实例构造方法
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // Super就是Vue方法,Super.prototype上有_init方法
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}
<template> <el-select :remote-method="remoteMethod" :no-data-text="t('common.empty')" :placeholder="t('common.chooseText')" v-bind="$attrs" v-model="state" remote-show-suffix fit-input-width filterable @visible-change="handleFetch" @focus="fetch" @input="handleInput" @blur="handleBlur" allow-input="true" > <el-option :title="$attrs.title?item.label:''" v-for="item in getOptions" :key="item.value" :label="item.label" :value="$attrs.valueKey ? item : item.value" :disabled="item.disabled" /> <template #[item]="data" v-for="item in Object.keys($slots)"> <slot :name="item" v-bind="data"></slot> </template> <template #suffixIcon v-if="loading"> <!-- <LoadingOutlined spin /> --> </template> <template #empty v-if="loading"> <span> {{ t('component.form.apiSelectNotFound') }} </span> </template> </el-select> </template> <script lang="ts"> import { defineComponent, PropType, ref, watchEffect, computed, unref } from 'vue'; import { isFunction } from '@/utils/is'; import { useRuleFormItem } from '@/hooks/component/useFormItem'; import { useAttrs } from '@/hooks/core/useAttrs'; import { get } from 'lodash-es'; import { t } from '@/utils/common'; import { propTypes } from '@/utils/propTypes'; type OptionsItem = { label: string; value: string; disabled?: boolean }; export default defineComponent({ name: 'ApiSelect', inheritAttrs: false, props: { value: propTypes.string || Array, numberToString: propTypes.bool, api: { type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>, default: null, }, afterFetch: { type: Function as PropType<(items: OptionsItem[]) => (OptionsItem & Record<string, any>)[]>, default: null, }, // api params params: { type: Object as PropType<Recordable>, default: () => {}, }, // support xxx.xxx.xx filterField: propTypes.string.def(''), resultField: propTypes.string.def(''), labelField: propTypes.string.def('label'), valueField: propTypes.string.def('value'), labelRender: { type: Function as PropType<(items: Recordable) => string>, default: null, }, immediate: { type: Boolean, default: true, }, // 只有条数据是默认选中 defalutSelect: { type: Boolean, default: false, } }, emits: ['options-change', 'change'], setup(props, { emit }) { const options = ref<OptionsItem[]>([]); const loading = ref(false); const attrs = useAttrs(); const inputValue = ref(''); // Embedded in the form, just use the hook binding to perform form verification const [state, setState] = useRuleFormItem(props); const getOptions = computed(() => { const { labelField, valueField, numberToString, labelRender } = props; return unref(options).reduce((prev, next: Recordable) => { if (next) { const value = next[valueField]; let label = next[labelField]; if (labelRender) { label = labelRender(next); } prev.push({ ...next, label, value: numberToString ? `${value}` : value, }); } return prev; }, [] as OptionsItem[]); }); const _valueField = ref(props.value); let lastFetchId = 0; // 处理竞态问题 watchEffect(() => { // 增加了个立即执行prop,默认为true, 有个场景需要做到默认不加载,打开下拉框再加载. if (props.immediate) fetch(); }); function remoteMethod(val) { if (val !== '') { lastFetchId = 0; fetch({ [props.filterField]: val }) } } async function fetch(params = {}) { const api = props.api; const afterFetch = props.afterFetch; if (!api || !isFunction(api)) return; options.value = []; try { loading.value = true; lastFetchId += 1; const fetchId = lastFetchId; let res = await api({ ...props.params, ...params}); if (fetchId === lastFetchId) { if (afterFetch && isFunction(afterFetch)) { res = afterFetch(res); } if (Array.isArray(res)) { options.value = res; emit('options-change', unref(options)); return; } if (props.resultField) { options.value = get(res, props.resultField) || []; } // 只有条数数据是默认选中 if(options.value?.length === 1 && props.defalutSelect) { if(attrs.value?.valueKey) { attrs.value?.multiple ? setState([getOptions.value[0]]) : setState(getOptions.value[0]) if(props?.params?.approvalType&&props?.params?.approvalType=="EDQMS-ESER审批"){ emit('change',options.value[0]); } } else { attrs.value?.multiple ? setState([options.value[0][props.valueField]]) : setState(options.value[0][props.valueField]) } } emit('options-change', unref(options)); } } catch (error) { console.warn(error); } finally { loading.value = false; } } function handleFetch(visible) { if (visible && !props.immediate) { fetch(); } } // 新增:处理输入事件,保存完整输入值 const handleInput = (val: string) => { console.log(state,'🍨🍨🍨🍨🍨🍨', val.data); inputValue.value = val; // 存储用户输入的完整文本 }; // 新增:处理失焦事件,传递完整输入值 const handleBlur = () => { // 调用外部绑定的 onBlur 事件,传入完整输入值 if (typeof attrs.onBlur === 'function') { attrs.onBlur(inputValue.value); } }; return { state, attrs, getOptions, loading, t, handleFetch, fetch, remoteMethod , handleInput, handleBlur }; }, }); </script> 如何使组件在handleInput事件中获取到用户每次输入的完整内容
最新发布
08-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值