@tangmu1121/r-model
v0.1.0
Published
Vue-style v-model experience for React via compile-time transform + runtime hooks.
Maintainers
Readme
r-model
一个让 React 拥有几乎和 Vue3 一样的双向绑定书写体验的小库,但属性名改为 r-model,和包名保持统一:
- 运行时:提供
useVModel/useModel等 Hook - 编译期:提供 Babel 插件,把
<Comp v-model={foo} />自动改写成对应的 props + setter
目前主要支持在「自定义组件」上使用
r-model(例如<MyInput r-model={value} />)。
安装
npm install r-model
# 或
yarn add r-model注意:本库假设你的 React 版本
>=16.8(支持 Hooks)。
API 概览
- 编译期:Babel 插件
<Comp r-model={foo} />→<Comp modelValue={foo} onUpdateModelValue={val => setFoo(val)} />- 要求:在同一组件函数里用
useState定义const [foo, setFoo] = useState(...)(或者至少有一个setFoo函数)。
- 运行时:Hooks
useVModel(props, propName)- 用在 子组件内部,把
props.modelValue/props.xxx和对应的onUpdateModelValue/onUpdateXxx封成[value, setValue]。
- 用在 子组件内部,把
useModel({ value?, defaultValue?, onChange? })- 用在 你自己写的受控/非受控组件内部,统一处理 value / defaultValue / onChange 逻辑。
配置 Babel 插件(r-model 语法)
假设你使用 Babel(或 Vite/Next 等内置 Babel 流程),在 Babel 配置中加入:
{
"plugins": ["r-model"]
}或明确指定路径:
{
"plugins": ["r-model/dist/babel.cjs"]
}然后在 React 组件里就可以直接写:
import React, { useState } from "react";
import { MyInput } from "./MyInput";
export function Demo() {
const [name, setName] = useState("");
return <MyInput r-model={name} />;
}插件会在编译期把上面的代码变成:
<MyInput
modelValue={name}
onUpdateModelValue={(val) => setName(val)}
/>约定:setter 按变量名推断,例如
foo→setFoo,userName→setUserName。你需要确保组件里真的有对应的setXxx存在,一般通过const [foo, setFoo] = useState(...)。
useVModel:在子组件中消费 r-model
父组件用法
import React from "react";
import { useVModel } from "r-model";
interface TextInputProps {
// 由编译器自动生成:
// <TextInput r-model={name} />
// → modelValue={name} onUpdateModelValue={val => setName(val)}
modelValue: string;
onUpdateModelValue?: (v: string) => void;
}
export function TextInput(props: TextInputProps) {
const [value, setValue] = useVModel(props, "modelValue");
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="请输入名字"
/>
);
}将来可扩展为
r-model:name→name+onUpdateName等多模型场景。
useModel:统一 value / defaultValue / onChange
这个 Hook 主要给「组件内部」使用,帮助你轻松支持:
- 受控模式:由外部传入
value+onChange - 非受控模式:由外部传入
defaultValue,内部自己维护 state
示例:可受控的 Counter
import React from "react";
import { useModel, ModelProps } from "r-model";
interface CounterProps extends ModelProps<number> {
step?: number;
}
export function Counter(props: CounterProps) {
const [count, setCount] = useModel<number>(props);
const step = props.step ?? 1;
return (
<div>
<button onClick={() => setCount((count ?? 0) - step)}>-</button>
<span style={{ margin: "0 8px" }}>{count ?? 0}</span>
<button onClick={() => setCount((count ?? 0) + step)}>+</button>
</div>
);
}使用:非受控
// 内部自己管 state
<Counter defaultValue={10} />使用:受控
const [count, setCount] = useState(0);
<Counter value={count} onChange={setCount} />;在你自己的项目中使用
在你的库或项目中引入:
import { useVModel, useModel } from "r-model";对于「可复用输入组件」,推荐:
- 父组件:直接写
<MyInput r-model={value} /> - 编译器:自动生成
modelValue/onUpdateModelValue - 子组件:通过
useVModel把它们封装成[value, setValue]
- 父组件:直接写
对于「更复杂的组件(例如带默认值、可受控/非受控切换)」:
- 在组件内部使用
useModel,暴露出value/defaultValue/onChange这套标准接口。
- 在组件内部使用
未来可以扩展的方向
- 支持类似
v-model:xxx的多字段绑定(如model:checked、model:value) - 支持对嵌套对象路径(如
"user.name")做不可变更新 - 集成常见 UI 库(antd, MUI 等)的最佳实践示例
如果你需要这些功能,可以在你的工程里基于当前实现继续扩展,或者发布到 npm 作为你自己的私有/开源包。package.json、tsconfig.json 和基础源码已经在本仓库中为你准备好了,直接执行:
npm install
npm run build即可打包生成 dist,然后用 npm publish 发布到 npm。
