Vue2里用JSX怎么实现v model?要注意哪些细节?
不少同学在Vue2项目里尝试用JSX写组件时,总会疑惑“原来模板里用v - model很方便,换成JSX该咋实现双向绑定?有没有需要注意的地方?”其实JSX和Vue模板的运行逻辑有差异,得先理解v - model本质,再一步步拆解JSX里的实现思路,今天就从原理、写法、细节这些角度,把Vue2 + JSX实现v - model的事儿讲明白。
先搞懂Vue2里v - model的本质,才能明白JSX咋改造
Vue的v - model是语法糖,核心逻辑就两点:传值和监听变化,在模板里写 <input v - model="name" />
,相当于做了这两件事:
- 给input的
value
属性绑定name
变量(传值); - 监听input的
input
事件,把输入的新值赋值给name
(监听变化)。
如果是自定义组件,<MyInput v - model="name" />
,默认情况下等价于 <MyInput :value="name" @input="name = $event" />
,要是组件里用model
选项配置了,
export default { model: { prop: 'checked', event: 'change' } }
那v - model就变成 <MyInput :checked="name" @change="name = $event" />
。
但JSX和Vue模板不一样,JSX是“ JavaScript + XML ”的结合,它没有Vue模板那种“自动解析v - model语法糖”的能力,所以在JSX里,得手动把“传值 + 监听事件”这两步写出来,模拟v - model的效果。
JSX在Vue2里的基本运行逻辑,和模板有啥不同?
Vue2里用JSX,一般是在render
函数里返回JSX结构(也可以用jsx - loader
这类工具让单文件组件支持JSX),比如一个简单的函数式组件:
export default { render() { return <div>Hello JSX</div>; } };
JSX在Vue里会被编译成h
函数调用(h
是createElement的别名),最终生成虚拟DOM。
和模板相比,JSX更“自由”——它本质是JS代码,可以写变量、函数调用、三元表达式等,但失去了模板里的指令语法糖(像v - model、v - bind、v - on这些模板指令,在JSX里得用JS的方式处理),所以模板里自动帮我们做的“v - model绑定value和input事件”,在JSX里得自己动手。
原生元素(如input)的JSX“v - model”咋写?
拿最常见的输入框举例,模板里一行v - model
搞定,JSX得拆成“绑定value + 监听input事件”。
步骤1:给input的value传响应式数据
假设组件的data
里有个username
:
export default { data() { return { username: '初始值' }; }, render() { return ( <input value={this.username} // 下一步:绑定input事件 /> ); } };
步骤2:监听input事件,更新数据
输入框触发input
事件时,把输入的新值(event.target.value
)赋值给username
,所以要给input绑定onInput
事件(JSX里事件用onXxx
命名,对应模板的@xxx
):
render() { return ( <input value={this.username} onInput={(event) => { this.username = event.target.value; }} /> ); }
这样就实现了“输入时更新数据,数据变化时更新输入框value”的双向绑定,和模板的v - model效果一样。
对比模板写法,理解差异
模板里只需:
<input v - model="username" />
JSX里得手动写value和onInput,这就是因为JSX没有模板的指令解析逻辑,得用JS的方式显式处理。
自定义组件的JSX“v - model”咋处理?
自定义组件的v - model分两种情况:组件用默认的model(value + input) 和 组件用model选项自定义了prop和事件。
情况1:组件用默认的model(value + input)
假设我们写了个<MyInput>
组件,它内部接收value
prop,触发input
事件传新值,父组件用JSX调用它时,要做这两步:
- 给
<MyInput>
传value
prop,值是父组件的响应式数据; - 监听
<MyInput>
的input
事件,把事件参数赋值给父组件数据。
示例:
父组件代码:
export default { data() { return { user: '默认用户' }; }, components: { MyInput }, render() { return ( <MyInput value={this.user} onInput={(newValue) => { this.user = newValue; }} /> ); } };
子组件<MyInput>
的简化逻辑(接收value,输入时触发input事件):
export default { props: ['value'], render() { return ( <input value={this.value} onInput={(e) => { this.$emit('input', e.target.value); }} /> ); } };
这样父组件和子组件之间就通过value
和input
事件实现了双向绑定,和模板里的<MyInput v - model="user" />
效果一致。
情况2:组件用model选项自定义了prop和事件
如果子组件用model
选项改了默认的prop和事件名,
export default { model: { prop: 'checked', event: 'change' }, props: ['checked'], render() { return ( <input type="checkbox" checked={this.checked} onChange={(e) => { this.$emit('change', e.target.checked); }} /> ); } };
父组件用JSX调用时,要对应改成传checked
prop,监听change
事件:
export default { data() { return { isAgree: false }; }, components: { MyCheckbox }, render() { return ( <MyCheckbox checked={this.isAgree} onChange={(newVal) => { this.isAgree = newVal; }} /> ); } };
这时候就完全模拟了模板中<MyCheckbox v - model="isAgree" />
的效果,因为模板里v - model会自动根据子组件的model选项调整prop和事件名,JSX里得手动对应上。
实现JSX“v - model”时,这些细节容易踩坑!
响应式数据必须来自Vue的响应式系统
如果在JSX里给value传的是普通变量(不是data、computed、props里的),数据变化时UI不会更新,比如错误写法:
export default { render() { let username = '非响应式'; // 普通变量,不是Vue的响应式数据 return ( <input value={username} onInput={(e) => { username = e.target.value; // 改普通变量,UI不更新 }} /> ); } };
得把username
放到data
里,用this.username
访问,才能触发响应式更新。
事件处理函数里的this指向要注意
如果不用箭头函数,直接写onInput function(e) { ... }
,this可能指向不对(因为JSX里事件处理函数的this默认不是组件实例,除非绑定),所以推荐用箭头函数,保证this是组件实例:
onInput={(e) => { this.username = e.target.value; // 箭头函数,this指向组件 }}
或者在methods里定义函数,然后绑定this:
methods: { handleInput(e) { this.username = e.target.value; } }, render() { return <input onInput={this.handleInput.bind(this)} />; }
不过箭头函数更简洁,日常用得更多。
与模板v - model的“自动解包”差异
模板里v - model对数组、对象的修改是“自动响应式”的,但JSX里如果直接修改对象的属性,得确保触发响应式更新。
data() { return { form: { name: '张三' } }; }
模板里<input v - model="form.name" />
能正常更新,但JSX里如果这么写:
<input value={this.form.name} onInput={(e) => { this.form.name = e.target.value; // 直接改对象属性,Vue2可能检测不到变化 }} />
这时候因为Vue2的响应式是基于对象的getter/setter
,直接改属性可能不触发更新(虽然实际中很多情况能触发,但严格来说要遵循响应式规则),所以更安全的做法是用this.$set
或者重新赋值对象:
onInput={(e) => { // 方法1:$set this.$set(this.form, 'name', e.target.value); // 方法2:重新赋值整个对象 this.form = { ...this.form, name: e.target.value }; }}
多个输入项的双向绑定,要避免逻辑混乱
比如表单里有用户名、密码两个输入框,JSX里要分别处理value和事件:
data() { return { username: '', password: '' }; }, render() { return ( <div> <input value={this.username} onInput={(e) => { this.username = e.target.value; }} placeholder="用户名" /> <input type="password" value={this.password} onInput={(e) => { this.password = e.target.value; }} placeholder="密码" /> </div> ); }
可以把事件处理函数抽成通用方法,减少重复代码:
methods: { handleInput(key, e) { this[key] = e.target.value; } }, render() { return ( <div> <input value={this.username} onInput={(e) => this.handleInput('username', e)} placeholder="用户名" /> <input type="password" value={this.password} onInput={(e) => this.handleInput('password', e)} placeholder="密码" /> </div> ); }
进阶:用computed优化JSX的双向绑定逻辑
当双向绑定的逻辑比较复杂(比如要做格式转换、联动其他数据),可以用computed
的get
和set
来封装。
比如用户输入手机号,要自动去掉空格,同时数据层存的是无空格的手机号,模板里v - model结合computed很方便,JSX里也能这么玩:
export default { data() { return { rawPhone: '' // 数据层存无空格的手机号 }; }, computed: { formattedPhone: { get() { return this.rawPhone.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3'); // 显示时加空格分隔 }, set(newVal) { // 输入的新值(带空格),处理成无空格存到rawPhone this.rawPhone = newVal.replace(/\s/g, ''); } } }, render() { return ( <input value={this.formattedPhone} onInput={(e) => { this.formattedPhone = e.target.value; // 触发computed的set }} /> ); } };
这样输入框里显示的是带空格的格式,而数据层存的是纯数字,逻辑被computed封装起来,JSX里只需要绑定formattedPhone
的value和input事件,代码更简洁,也避免了在render里写复杂处理逻辑。
JSX实现v - model的核心思路
不管是原生元素还是自定义组件,JSX里模拟v - model的核心就是“手动绑定值 + 监听变化事件”:
- 给元素或组件传对应的prop(如input的value、自定义组件的value/checked等);
- 监听对应的事件(如input的input事件、自定义组件的input/change等),在事件回调里更新响应式数据;
- 注意响应式数据的处理、this指向、复杂逻辑封装这些细节,避免踩坑。
虽然JSX没有模板v - model那样的“语法糖自动解析”,但手动拆分后,我们能更灵活地控制双向绑定的每一步,尤其是在处理复杂交互、自定义组件通信时,这种“显式控制”反而能减少隐藏的bug,多写几个例子,熟悉了JSX的事件和props绑定方式,实现v - model就会很顺手啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。