- 前端
- 教程
【免费下载链接】30-seconds-of-react
Short React code snippets for all your development needs
TextArea是 30-seconds-of-react 仓库中一个短小精悍的"非受控文本域"组件片段,源文件位于 snippets/text-area.md。它用不到 20 行代码演示了 React 非受控表单元素的核心范式:用defaultValue指定初始值、用onChange事件把最新值通过回调函数上报给父组件。读完本文,你将掌握非受控组件的实现原理、它与受控组件的取舍方法,以及如何基于该片段扩展出带字数限制、接入表单状态管理的实战版本。
片段概览:它解决什么问题
在 React 中,"受控"与"非受控"是表单元素(<input>、<textarea>、<select>)的两种状态管理模式。本片段针对<textarea>实现的是非受控方案:文本域的当前值由 DOM 自己维护,组件内部不持有状态,只负责两件事:
- 用父组件传下来的
defaultValue作为初始值; - 在用户输入时通过
onChange事件触发onValueChange回调,把新值"上报"给父组件。
从片段文件的 front matter(snippets/text-area.md)可以看到,它被标记为type: snippet、tags: [components, input],dateModified为 2020-11-25,属于仓库中"组件 + 输入"分类的经典片段。它和仓库内的 uncontrolled-input.md(非受控 input)、controlled-input.md(受控 input)构成一组互相印证的姊妹片段。
核心机制:非受控组件与 defaultValue
组件核心实现如下(snippets/text-area.md):
const TextArea = ({ cols = 20, rows = 2, defaultValue, onValueChange, ...rest }) => { return ( <textarea cols={cols} rows={rows} defaultValue={defaultValue} onChange={({ target: { value } }) => onValueChange(value)} {...rest} /> ); };拆解这段代码,可以提炼出三个关键设计:
1.defaultValue只负责"初始值",不负责"持续控制"
非受控元素的标志性 prop 就是defaultValue。React 只在元素首次挂载时读取它一次,将其写入 DOM;此后即便父组件再次传入不同的defaultValue,已经显示在页面上的文本也不会随之变化——因为值的所有权在 DOM 手里,React 不再插手。这与受控元素中valueprop 的"双向同步"语义有本质区别。
2.onChange是值上报的唯一通道
onChange={({ target: { value } }) => onValueChange(value)}这里对事件对象做了解构:({ target: { value } })等价于"取出事件对象上的target.value",再直接作为参数调用onValueChange。也就是说,用户每敲一个字符,父组件都会收到一个最新的字符串值。父组件拿到值后如何存储(useState、useForm或直接打印),完全由父组件自己决定。
3.{...rest}透传,让组件保持可复用
解构掉cols、rows、defaultValue、onValueChange之后,剩余的所有 props(如placeholder、name、id、disabled等)都会通过{...rest}原样透传给底层的<textarea>。这正是"包装原生元素"时最常用的可复用性技巧:调用方可以按需附加任意原生属性,无需在组件里逐一声明。
此外,cols = 20、rows = 2为这两个布局属性提供了默认值,未显式传入时文本域按 20 列 2 行渲染。
使用示例:父组件如何接收值
仓库给出的渲染示例([snippets/text-area.md](https://link.gitcode.com/i/63699bcc18bbc70056810d0535c88da5#L34-L40)如下:
ReactDOM.createRoot(document.getElementById('root')).render( <TextArea placeholder="Insert some text here..." onValueChange={val => console.log(val)} /> );这个示例传递了两个信息:
placeholder未在组件 props 中声明,它正是通过{...rest}透传到达<textarea>的,验证了透传机制的实际效果;onValueChange在这里直接接到了console.log,每次输入都会在控制台打印当前值。在实际项目中,你通常会把它接到setState或表单状态管理函数上。
示例使用ReactDOM.createRoot(...).render(...)渲染,这与仓库统一采用的片段模板(snippet-template.md)保持一致,也说明该片段面向 React 18 及以上版本的挂载方式。
受控 vs 非受控:仓库内对照实现
要真正理解TextArea的价值,最好把它和仓库中的受控版本对照来看。
受控 input(controlled-input.md):
const ControlledInput = ({ value, onValueChange, ...rest }) => { return ( <input value={value} onChange={({ target: { value } }) => onValueChange(value)} {...rest} /> ); };非受控 input(uncontrolled-input.md):
const UncontrolledInput = ({ defaultValue, onValueChange, ...rest }) => { return ( <input defaultValue={defaultValue} onChange={({ target: { value } }) => onValueChange(value)} {...rest} /> ); };对照后差异一目了然:
| 维度 | 受控组件(value) | 非受控组件(defaultValue) |
|---|---|---|
| 值的所有权 | 父组件(React state) | DOM 自身 |
| 当前值的来源 | 父组件每次渲染传入的valueprop | DOM 内部状态,仅首次挂载读defaultValue |
| 父组件能否事后强制改值 | 能,更新valueprop 即可 | 不能,defaultValue更新不会反映到已渲染的 DOM |
| 状态管理成本 | 父组件必须维护并回传 state | 父组件无需持有值,只负责接收回调 |
| 适用场景 | 需要校验、联动、重置、格式化等强控制场景 | 初始值固定、无需程序化干预的简单表单 |
本片段的TextArea与 uncontrolled-input.md 的实现思路完全一致,只是把原生元素换成了<textarea>,并额外提供了cols/rows的默认值。如果你需要"父组件能在任意时刻重置/覆盖文本域内容"的能力,则应改用value+onChange的受控写法。
实战进阶:带限制的 textarea 变体
基础版TextArea只解决"值上报",不限制内容。仓库另外提供了两个基于状态管理(Hooks)的限制版本,可作为该片段的直接延伸。
字符限制版(limited-textarea.md):
const LimitedTextarea = ({ rows, cols, value, limit }) => { const [content, setContent] = React.useState(value.slice(0, limit)); const setFormattedContent = React.useCallback( text => { setContent(text.slice(0, limit)); }, [limit, setContent] ); return ( <> <textarea rows={rows} cols={cols} onChange={event => setFormattedContent(event.target.value)} value={content} /> <p> {content.length}/{limit} </p> </> ); };它用useState持有content,用useCallback把"截断到limit字符"的逻辑记忆化,并渲染内容长度/限制值的计数器。注意它与基础版的本质区别:这里改用了value+onChange的受控模式——因为必须由 React 状态在每次输入后执行slice(0, limit)截断并回写显示值,非受控模式无法做到这一点。
**单词限制版(limited-word-textarea.md)**思路相同、算法不同:用String.prototype.split(' ')把输入拆成单词数组,配合Array.prototype.filter(Boolean)过滤空串,当单词数超过limit时用words.slice(0, limit).join(' ')截断;同时用useEffect在首次渲染时格式化初始value,并在下方显示wordCount/limit计数。
这两个变体与TextArea共同覆盖了文本域的常见需求:纯上报(非受控)、受控截断(字符/单词限制),可以根据是否需要"程序化干预内容"来选择对应模式。
组合应用:接入表单状态管理
onValueChange回调的设计让TextArea可以无缝接入仓库中的表单 Hook——use-form.md:
const useForm = initialValues => { const [values, setValues] = React.useState(initialValues); return [ values, e => { setValues({ ...values, [e.target.name]: e.target.value }); } ]; };useForm返回[values, setValues],其更新函数依赖事件对象上的e.target.name与e.target.value。把TextArea的onValueChange接上setValues,再把name通过{...rest}透传下去,即可让文本域与表单状态联动:
const Form = () => { const [values, setValues] = useForm({ bio: '' }); return ( <form onSubmit={e => { e.preventDefault(); console.log(values); }}> <TextArea name="bio" rows={4} cols={40} defaultValue="" onValueChange={e => setValues({ target: { name: 'bio', value: e } })} /> <button type="submit">Submit</button> </form> ); };需要说明的是:基础版TextArea的回调直接传字符串(onValueChange(value)),而useForm的更新函数期望事件对象形状,因此接入时需要在父组件做一次薄薄的适配封装;如果你偏好完全对齐事件对象,也可以参考useForm的写法自行调整回调签名。
常见误区与注意事项
结合源码与 React 的受控/非受控语义,使用本片段时有几点值得留意:
defaultValue更新无效是预期行为,不是 bug。非受控元素的值由 DOM 持有,父组件传入新的defaultValue不会覆盖屏幕上已显示的内容。若需要程序化重置/覆写,请切换到受控模式(参考 limited-textarea.md 的value用法)。onChange与受控的value不能混用。在一个元素上同时使用value和defaultValue会导致 React 告警,且行为不可预测;本片段只使用defaultValue,语义自洽。- 回调是"通知"而非"同步"。
onValueChange只是把值告诉父组件,父组件是否保存、保存到哪里(state、ref、外部 store),都不影响 DOM 中已显示的文本——这正是非受控模式"父组件零状态维护"特性的来源。 - 事件解构假设
target存在。React 的onChange事件是SyntheticEvent,其target必然指向触发的 DOM 元素,因此({ target: { value } })在原生<textarea>上是安全的;但如果把它泛化到自定义组件上,需要自行保证事件形状一致。 - 限制版本是受控的。字符/单词限制必须依赖 React 状态回写截断后的内容,因此 limited-textarea.md 与 limited-word-textarea.md 走的是
value路线,与本文的defaultValue路线恰好互补,二者并非同一模式的重复。
小结
TextArea(snippets/text-area.md)是 30-seconds-of-react 中"非受控输入元素"家族的一员:它用defaultValue定义初始值,用onChange+onValueChange回调把最新值上报给父组件,配合{...rest}透传保持极高的可复用性。与仓库内 uncontrolled-input.md、controlled-input.md、limited-textarea.md、limited-word-textarea.md、use-form.md 等片段对照阅读,可以完整掌握 React 表单元素从"非受控上报"到"受控截断"再到"表单状态集成"的完整技术谱系。该片段及其姊妹片段均遵循仓库 README.md 中声明的 CC-BY-4.0 许可,可自由参考与再实现。
- 前端
- 教程
【免费下载链接】30-seconds-of-react
Short React code snippets for all your development needs
相关推荐
30-seconds-of-react:用 React Hooks 实现带词数限制的 textarea 组件(LimitedWordTextarea)
30 seconds of react:用 React Hooks 实现带词数限制的 textarea 组件(LimitedWordTextarea) 本文以
前端教程30-seconds-of-react:用 ControlledInput 代码片段彻底掌握 React 受控输入组件
30 seconds of react:用 ControlledInput 代码片段彻底掌握 React 受控输入组件 导读 :本文以 30 seconds o
前端教程30 seconds of React:用非受控 Select 组件实现回调式下拉选择
30 seconds of React:用非受控 Select 组件实现回调式下拉选择 本指南围绕 30 seconds of react 仓库中的 Selec
前端教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考