Understanding Vue Form Validation, Scoped Data Attributes, Build Output, Remote Component Loading, and Render‑Function Dropdown
This article explains the principles and implementation details of Vue form validation, the generation of scoped data‑v‑xxx attributes, the composition of build output files, remote loading of .vue components, and how to create a dropdown menu using the render function, providing code examples and step‑by‑step explanations.
1. Form Validation Implementation
Every front‑end developer uses form components, but the underlying validation mechanism is often hidden. The article demonstrates how a form validates fields in real time by combining three parts: form rules, form fields, and form values, using Vue's provide / inject mechanism.
<template>
<div>
<a-form :rules="rules" :data="formData">
<a-form-item field="name">
<a-input v-model="formData.name" />
</a-form-item>
</a-form>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import AForm from './Form.vue';
import AFormItem from './Form-Item.vue';
import AInput from './Input.vue';
const rules = { name: { required: true } }
const formData = reactive({ name: '哈哈哈' })
</script>The validation core consists of three elements:
Form rules defined in a-form
Form field defined in a-form-item
Form value bound in a-input
The process is:
Form passes rules and validate to Form‑Item.
Form‑Item passes field and onChange to Input.
When Input's value changes, it triggers validate and onChange , then shows or hides error messages.
Data is shared via provide / inject (Vue 3 style).
1.1 Specific Implementation – Form
The Form component provides a validate function that checks a field against its rule and stores the result in an errorMap .
<template>
<form>
<slot></slot>
</form>
</template>
<script lang="ts" setup>
import { provide, reactive } from 'vue'
const props = defineProps<{ rules: any; data: any }>();
const errorMap = reactive<any>({})
const validateFn = (field: string): Promise<void> => {
return new Promise((resolve, reject) => {
const { rules, data } = props;
const ruleItem = rules[field]
const dataItem = data[field]
if (ruleItem.required && dataItem === '') {
return reject()
}
resolve()
})
}
const validate = (field: string) => {
validateFn(field).then(() => { errorMap[field] = false })
.catch(() => { errorMap[field] = true })
}
provide('a-form', { validate, getErrorMap: () => errorMap })
</script>1.2 Specific Implementation – Form‑Item
Form‑Item supplies the field name and an onChange callback, and displays an error message when validation fails.
<template>
<div>
<slot></slot>
<div style="color: red" v-if="data.showError">字段必填</div>
</div>
</template>
<script lang="ts" setup>
import { provide, inject, reactive } from 'vue'
const props = defineProps<{ field: string }>();
const AForm = inject<{ validate: (field: string) => Promise
; getErrorMap: any }>('a-form');
const data = reactive({ showError: false })
const onChange = () => {
setTimeout(() => {
if (AForm) {
const showError = AForm.getErrorMap()[props.field]
data.showError = showError
}
})
}
provide('a-form-item', { getField: () => props.field, onChange })
</script>1.3 Specific Implementation – Input
Input watches modelValue , updates the bound value, and calls the validation logic from Form and Form‑Item on each input event.
<template>
<input @input="onChange" :value="data.inputValue" />
</template>
<script lang="ts" setup>
import { reactive, watch, inject } from 'vue'
const props = defineProps<{ modelValue: string }>();
const emits = defineEmits(['update:modelValue'])
const AForm = inject('a-form')
const AFormItem = inject('a-form-item')
const data = reactive({ inputValue: props.modelValue })
watch(() => props.modelValue, v => { data.inputValue = v })
const onChange = (e: Event) => {
emits('update:modelValue', (e.target as HTMLInputElement).value)
if (AForm && AFormItem) {
AForm.validate(AFormItem.getField())
AFormItem.onChange()
}
}
</script>2. Generation of data‑v‑xxx in Vue Projects
The scoped CSS attribute data‑v‑xxx is derived from the file's relative path. When two micro‑frontend Vue sub‑projects share the same path structure, they generate identical IDs, which can cause style collisions.
Both webpack + vue‑loader (Vue 2) and vite + @vitejs/plugin‑vue (Vue 3) compute the ID in a similar way:
In development, the ID is based solely on the file's relative path.
In production, the ID combines the relative path with the file content, providing a stable hash for E2E testing.
// webpack + vue‑loader (Vue 2)
const shortFilePath = path
.relative(rootContext || process.cwd(), filename)
.replace(/^(..[/])+/,'')
const id = hash(isProduction ? shortFilePath + '\n' + source.replace(/\r\n/g,'\n') : shortFilePath)
// vite + @vitejs/plugin‑vue (Vue 3)
import path from "node:path";
import { createHash } from "node:crypto";
import slash from "slash";
function getHash(text) { return createHash("sha256").update(text).digest("hex").substring(0,8); }
const normalizedPath = slash(path.normalize(path.relative(root, filename)));
descriptor.id = getHash(normalizedPath + (isProduction ? source : ""));3. JS Requests After Vue Project Build
When a Vue project is built, two main JavaScript bundles appear:
app.js – contains the application code (components, main.js , etc.). Its size changes when component templates or business logic change.
chunk‑vendors.js – contains third‑party libraries from node_modules . Its size grows when new dependencies (e.g., moment.js ) are added but remains unchanged when only application code changes.
Additional route‑level chunks (e.g., 0.js , 1.js ) are generated for lazy‑loaded routes. The request pattern depends on which route the user navigates to.
4. Remote Loading of .vue Components
Instead of bundling components at build time, vue3‑sfc‑loader can load a .vue file at runtime via HTTP, compile it in the browser, and render it with :is . This works for both Vue 2 and Vue 3.
// Import the loader (Vue 2 example)
import { loadModule } from 'vue3-sfc-loader/dist/vue2-sfc-loader.js'A wrapper component uses component :is="remote" together with v-bind="$attrs" and v-on="$listeners" to render the remote component.
<template>
<div class="async-component">
<component :is="remote" v-if="remote" v-bind="$attrs" v-on="$listeners" />
</div>
</template>The loader configuration provides a moduleCache (to pass the Vue instance), a getFile function that fetches the remote file, and an addStyle helper that injects the component's scoped CSS into the document.
const com = await loadModule(url, {
moduleCache: { vue: Vue },
async getFile(url) {
const res = await fetch(url)
if (!res.ok) throw Object.assign(new Error(`${res.statusText} ${url}`), { res })
return { getContentData: asBinary => (asBinary ? res.arrayBuffer() : res.text()) }
},
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent })
const ref = document.head.getElementsByTagName('style')[0] || null
document.head.insertBefore(style, ref)
}
})5. Render Function to Implement a Dropdown Menu
A dropdown component ( Select.vue ) is defined with static menu items. The parent component creates a virtual node with createVNode , renders it into a temporary container using render , and appends the resulting DOM to document.body . Positioning is calculated from a ref element.
// Select.vue (template only)
<template>
<div class="select-wrap">
<span>福利商城</span>
<span>Saas平台</span>
<span>活动定制</span>
</div>
</template> // App.vue (render logic)
import { ref, createVNode, render } from 'vue'
import Select from './Select.vue'
const select = ref()
function createDom() {
const left = select.value.offsetLeft + "px"
const width = select.value.getBoundingClientRect().left + "px"
const props = { left, width }
const container = document.createElement('div')
const vm = createVNode(Select, props)
render(vm, container)
document.body.appendChild(container.firstElementChild)
}To destroy the dropdown, calling render(null, container) removes the rendered vnode. The child component can emit a destroy event, which the parent handles by invoking the same render‑null call.
Reference
[1] http://localhost:8080/demo/ – example project used for the build‑output analysis.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.