963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
4.9 KiB
4.9 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | ||||
|---|---|---|---|---|---|---|---|---|
| Implement v-model Correctly in Render Functions | MEDIUM | Incorrect v-model implementation breaks two-way binding with components | best-practice |
|
Implement v-model Correctly in Render Functions
Impact: MEDIUM - When using v-model on components in render functions, you must manually handle both the modelValue prop and the update:modelValue event. Missing either part breaks two-way binding.
In templates, v-model is syntactic sugar that expands to a modelValue prop and an update:modelValue event listener. In render functions, you must implement this expansion manually.
Task Checklist
- Pass
modelValueas a prop for the bound value - Pass
onUpdate:modelValuehandler to receive updates - For named v-models, use the corresponding prop and event names
- Use emit in child components to trigger updates
Incorrect:
import { h } from 'vue';
import CustomInput from './CustomInput.vue';
export default {
setup() {
const text = ref('');
return () =>
h(CustomInput, {
// WRONG: Missing the update handler
modelValue: text.value,
});
},
};
import { h } from 'vue';
import CustomInput from './CustomInput.vue';
export default {
setup() {
const text = ref('');
return () =>
h(CustomInput, {
// WRONG: Wrong event name format
modelValue: text.value,
onModelValueUpdate: (val) => (text.value = val),
});
},
};
Correct:
import { h, ref } from 'vue';
import CustomInput from './CustomInput.vue';
export default {
setup() {
const text = ref('');
return () =>
h(CustomInput, {
// CORRECT: modelValue prop + onUpdate:modelValue handler
modelValue: text.value,
'onUpdate:modelValue': (value) => (text.value = value),
});
},
};
Native Input Elements
For native inputs, use value and onInput:
import { h, ref } from 'vue';
export default {
setup() {
const text = ref('');
return () =>
h('input', {
value: text.value,
onInput: (e) => (text.value = e.target.value),
});
},
};
Named v-models (Multiple v-models)
import { h, ref } from 'vue';
import UserForm from './UserForm.vue';
export default {
setup() {
const firstName = ref('');
const lastName = ref('');
return () =>
h(UserForm, {
// v-model:firstName
firstName: firstName.value,
'onUpdate:firstName': (val) => (firstName.value = val),
// v-model:lastName
lastName: lastName.value,
'onUpdate:lastName': (val) => (lastName.value = val),
});
},
};
v-model with Modifiers
Handle modifiers in the child component:
import { h, ref } from 'vue'
import CustomInput from './CustomInput.vue'
// Parent - passing modifier
export default {
setup() {
const text = ref('')
return () => h(CustomInput, {
modelValue: text.value,
'onUpdate:modelValue': (val) => text.value = val,
modelModifiers: { trim: true, capitalize: true }
})
}
}
// Child - handling modifier
export default {
props: ['modelValue', 'modelModifiers'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const handleInput = (e) => {
let value = e.target.value
if (props.modelModifiers?.trim) {
value = value.trim()
}
if (props.modelModifiers?.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
return () => h('input', {
value: props.modelValue,
onInput: handleInput
})
}
}
JSX Syntax
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
export default {
setup() {
const text = ref('');
const count = ref(0);
return () => (
<div>
{/* v-model on custom component */}
<CustomInput modelValue={text.value} onUpdate:modelValue={(val) => (text.value = val)} />
{/* v-model on native input */}
<input value={text.value} onInput={(e) => (text.value = e.target.value)} />
{/* Named v-model */}
<Counter count={count.value} onUpdate:count={(val) => (count.value = val)} />
</div>
);
},
};
Creating v-model-compatible Components
import { h } from 'vue';
export default {
props: {
modelValue: String,
},
emits: ['update:modelValue'],
setup(props, { emit }) {
return () =>
h('input', {
value: props.modelValue,
onInput: (e) => emit('update:modelValue', e.target.value),
});
},
};