vue3-json-schema-form中StringField.vue报错 `<script setup>` cannot contain ES module exports vue/no-e

'# vue3-json-schema-form中StringField.vue报错 <script setup> cannot contain ES module exports vue/no-e

一、背景与问题

在使用 vue3-json-schema-form 框架开发表单组件时,开发者常会遇到 StringField.vue 组件报错:
<script setup> cannot contain ES module exports vue/no-e

该错误的根源在于 eslint-plugin-vue 的规则 vue/no-module-export,它禁止在 <script setup> 中使用 ES 模块的导出方式。例如:

export default {
  name: 'StringField',
  props: ['value'],
  emits: ['update:Value']
}

这种写法在 <script setup> 中是非法的,因为 <script setup> 是基于组合式 API 的封装,需要通过 definePropsdefineEmits 显式声明 props 和 emits。

二、基本原理

1. <script setup> 语法原理

Vue 3 的 <script setup> 是基于组合式 API 的封装,其核心机制是将代码逻辑绑定到组件实例上。它通过 definePropsdefineEmits 显式声明 props 和 emits,而不是直接使用 export default

2. ESLint 规则冲突

vue/no-module-export 规则会检测 <script setup> 中的 ES 模块导出(如 export default),这与 <script setup> 的语法规范冲突。

3. JSON Schema 表单组件的特殊性

vue3-json-schema-form 中,StringField.vue 作为基础组件,需要通过 props 接收 schema 配置,并通过 emits 触发值更新。这种模式要求严格遵守 <script setup> 的语法规范。

三、环境准备

确保项目已安装以下依赖:

npm install -S vue@3.2.0 eslint-plugin-vue@8.0.0

创建 StringField.vue 组件时,需在 .eslintrc.cjs 中配置规则:

module.exports = {
  rules: {
    'vue/no-module-export': 'warn'
  }
}

四、核心实现

1. 错误示例:违反 ESLint 规则的代码

<script setup>
export default {
  name: 'StringField',
  props: ['value'],
  emits: ['update:value']
}
</script>

错误原因<script setup> 中直接使用 export default,违反了 ESLint 规则。

2. 正确示例:使用 definePropsdefineEmits

<script setup>
const props = defineProps({
  value: {
    type: String,
    required: true
  }
})

const emit = defineEmits(['update:value'])

const handleChange = (e) => {
  emit('update:value', e.target.value)
}
</script>

<template>
  <input type="text" :value="props.value" @input="handleChange" />
</template>

关键点

  • 使用 defineProps 替代 props 选项
  • 使用 defineEmits 替代 emits 选项
  • 通过 props.value 访问 props
  • 通过 emit('update:value', value) 触发事件

3. 进阶示例:结合 JSON Schema 配置

<script setup>
const props = defineProps({
  schema: {
    type: Object,
    required: true
  },
  value: {
    type: [String, Number],
    required: true
  }
})

const emit = defineEmits(['update:value'])

const handleChange = (e) => {
  emit('update:value', e.target.value)
}
</script>

<template>
  <input 
    type="text" 
    :value="props.value" 
    @input="handleChange" 
    :placeholder="props.schema?.description || '请输入'"
  />
</template>

关键点

  • 接收 schema 配置
  • 使用 props.schema 访问 schema 信息
  • 通过 placeholder 展示 schema 描述

五、完整案例

1. 完整的 StringField.vue 组件

<template>
  <input 
    type="text" 
    :value="props.value" 
    @input="handleChange" 
    :placeholder="props.schema?.description || '请输入'"
    :class="{'is-invalid': props.schema?.errors?.length}"
  />
  <div class="error" v-if="props.schema?.errors?.length">
    {{ props.schema.errors.join(', ') }}
  </div>
</template>

<script setup>
const props = defineProps({
  schema: {
    type: Object,
    required: true
  },
  value: {
    type: [String, Number],
    required: true
  }
})

const emit = defineEmits(['update:value'])

const handleChange = (e) => {
  emit('update:value', e.target.value)
}
</script>

<style scoped>
.is-invalid {
  border-color: red;
}
.error {
  color: red;
  font-size: 12px;
}
</style>

2. 父组件使用示例

<template>
  <JsonSchemaForm :schema="schema" v-model:value="formData" />
</template>

<script setup>
import { ref } from 'vue'
import JsonSchemaForm from './JsonSchemaForm.vue'

const schema = {
  type: 'object',
  properties: {
    name: {
      type: 'string',
      description: '姓名'
    },
    email: {
      type: 'string',
      description: '邮箱'
    }
  }
}

const formData = ref({
  name: '',
  email: ''
})
</script>

关键点

  • 使用 v-model:value 绑定表单数据
  • 通过 schema 配置表单字段
  • 父组件无需关心子组件实现细节

六、源码解析

1. <script setup> 的执行顺序

// 代码执行顺序
setup() {
  // 初始化 props 和 emits
  const props = defineProps(...)
  const emit = defineEmits(...)
  
  // 业务逻辑
  const handleChange = (e) => {
    emit('update:value', e.target.value)
  }
  
  // 返回值
  return {
    handleChange
  }
}

2. defineProps 的类型校验机制

const props = defineProps({
  value: {
    type: [String, Number],
    required: true
  }
})
  • type 可以是单一类型或数组
  • required 表示是否必传
  • default 可设置默认值

3. defineEmits 的事件触发机制

const emit = defineEmits(['update:value'])

// 触发事件
emit('update:value', value)
  • 事件名必须与 v-model 绑定的事件名一致
  • 可以使用 defineEmits(['update:value'])defineEmits(['update:Value'])

七、进阶使用

1. 动态绑定 schema 配置

<script setup>
const props = defineProps({
  schema: {
    type: Object,
    required: true
  },
  value: {
    type: [String, Number],
    required: true
  }
})

const emit = defineEmits(['update:value'])

const handleChange = (e) => {
  emit('update:value', e.target.value)
}
</script>

2. 增加表单验证逻辑

const props = defineProps({
  schema: {
    type: Object,
    required: true
  },
  value: {
    type: [String, Number],
    required: true
  }
})

const emit = defineEmits(['update:value'])

const validate = () => {
  const errors = []
  if (!props.value) {
    errors.push('字段不能为空')
  }
  return errors
}

3. 支持多种输入类型

<template>
  <input 
    type="text" 
    :value="props.value" 
    @input="handleChange" 
    :placeholder="props.schema?.description || '请输入'"
    :class="{'is-invalid': props.schema?.errors?.length}"
  />
  <div class="error" v-if="props.schema?.errors?.length">
    {{ props.schema.errors.join(', ') }}
  </div>
</template>

八、性能与工程实践

1. 表单组件的性能优化

  • 避免不必要的重新渲染:使用 v-model 保持数据同步
  • 使用 v-on 懒加载:@input 事件改为 @change 可减少触发次数
  • 避免在 setup 中使用 refreactive 定义过多变量

2. 安全性考虑

  • 输入过滤:使用 v-sanitize 过滤用户输入
  • 输入校验:在 validate 方法中进行严格校验
  • 防止 XSS 攻击:使用 v-html 时要确保内容安全

3. 异常处理

const handleChange = (e) => {
  try {
    emit('update:value', e.target.value)
  } catch (err) {
    console.error('更新值时出错:', err)
  }
}

4. 组件复用

通过封装 StringField.vue,可以复用在多个表单场景中,如:

  • 用户信息表单
  • 表单配置页面
  • 数据录入界面

九、常见问题与踩坑

1. 常见错误

错误类型错误示例解决方案
导出错误export default { ... }使用 definePropsdefineEmits
事件命名错误emit('update:Value')确保事件名与 v-model 一致
类型校验错误type: String使用 type: [String, Number]
未定义 propsprops.value使用 defineProps 声明 props

2. 常见错误示例

<script setup>
export default {
  props: ['value'],
  emits: ['update:value']
}
</script>

错误原因<script setup> 中直接使用 export default
解决方法:使用 definePropsdefineEmits

3. 常见性能问题

  • 频繁触发 @input 事件导致性能问题
  • 大量使用 v-model 导致内存占用过高

优化建议

  • 使用 @change 代替 @input
  • 使用 v-model.lazy 延迟更新
  • 使用 v-model.number 强制类型转换

十、最佳实践

1. 推荐的使用场景

  • 需要严格遵循 <script setup> 语法规范的项目
  • 需要高度可维护的组件结构
  • 需要与 JSON Schema 配置深度集成的场景

2. 不推荐的使用场景

  • 需要使用 mixins 的项目
  • 需要兼容 Vue 2 的项目
  • 需要使用 this 的项目

3. 推荐的实现方式

  • 使用 definePropsdefineEmits 显式声明 props 和 emits
  • 使用 v-model 进行双向绑定
  • 使用 refreactive 管理组件状态
  • 使用 eslint-plugin-vue 配置规则

十一、总结

vue3-json-schema-formStringField.vue 组件报错 <script setup> cannot contain ES module exports vue/no-e 的根本原因在于违反了 ESLint 规则。通过正确使用 definePropsdefineEmits,可以避免该错误。同时,需要关注表单组件的性能、安全性和可维护性。在开发 JSON Schema 表单组件时,建议使用