在如今的前端开发里,React 可是响当当的角色,是咱搭建用户界面的得力帮手。一碰到表单处理和用户输入交互这些事儿,受控组件和非受控组件就派上大用场了,它们就像是两种不一样的工具,各有各的厉害之处。要是咱能把它们摸透了,知道啥时候用啥,那开发出来的 React 应用肯定既好用又靠谱,用户体验也差不了。
受控组件,简单讲,就是让表单元素的值跟 React 组件的state绑得死死的,完全同步。用户在表单上不管是打字还是选东西,都像触动了个开关,马上让组件状态更新,接着组件就重新渲染一遍,保证表单显示的值和组件里存的值一模一样,一点差错都没有。这其实就是 React 响应式编程的一种典型做法,用状态这个 “老大” 管着表单元素,让一切都井井有条。
先看个多功能文本输入框的例子:
import React, { useState } from'react';
function ControlledInput() {
const [inputValue, setInputValue] = useState('');
const [isFocused, setIsFocused] = useState(false);
const [charCount, setCharCount] = useState(0);
const handleChange = (e) => {
const newValue = e.target.value;
setInputValue(newValue);
setCharCount(newValue.length);
};
const handleFocus = () => {
setIsFocused(true);
};
const handleBlur = () => {
setIsFocused(false);
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
<p>Character count: {charCount}</p>
{isFocused? (
<span style={{ color: 'blue', fontWeight: 'bold' }}>Input is focused</span>
) : null}
</div>
);
}
export default ControlledInput;
在这个例子里,文本输入框的value属性就像和组件的inputValue状态锁在一起了,不离不弃。用户一输入,onChange事件立马响应,handleChange函数上场,不光更新inputValue,让输入框显示最新内容,还顺便算出字符个数,更新charCount状态,在界面上实时显示字符统计。同时,onFocus和onBlur事件分别管着isFocused状态,输入框聚焦就显示个蓝字提示,失焦就不显示,通过巧妙地操控状态,把输入框的各种行为和界面反馈安排得明明白白。
再看个更复杂的,模拟在线笔记编辑,有表单验证和自动保存功能:
import React, { useState, useEffect } from'react';
function NoteEditor() {
const [noteContent, setNoteContent] = useState('');
const [isValidNote, setIsValidNote] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [lastSavedTime, setLastSavedTime] = useState(null);
const handleNoteChange = (e) => {
const newContent = e.target.value;
setNoteContent(newContent);
const noteRegex = /^.{1,100}$/;
setIsValidNote(noteRegex.test(newContent));
};
useEffect(() => {
if (isValidNote && noteContent) {
const interval = setInterval(() => {
setIsSaving(true);
// 模拟保存操作,实际可能是API调用
setTimeout(() => {
setIsSaving(false);
setLastSavedTime(Date.now());
}, 2000);
}, 5000);
return () => clearInterval(interval);
}
}, [isValidNote, noteContent]);
return (
<div>
<textarea
value={noteContent}
onChange={handleNoteChange}
/>
{isValidNote? null : (
<span style={{ color:'red' }}>Note must be between 1 and 100 characters</span>
)}
{isSaving? (
<span style={{ color: 'orange', fontWeight: 'bold' }}>Saving...</span>
) : null}
<p>Last saved: {lastSavedTime? new Date(lastSavedTime).toLocaleString() : 'Never'}</p>
</div>
);
}
export default NoteEditor;
这里,textarea是核心表单元素,它的值和noteContent状态绑得紧紧的。onChange回调不光更新内容、实时查错,还根据结果决定错误提示的显示。同时,用useEffect钩子监听内容合法性和有没有值,条件满足就启动定时自动保存,模拟真实应用里的数据保存操作,保存过程中切换状态显示保存状态,最后记录并显示上次保存时间,全方位展示了受控组件在复杂业务场景下的本事。
非受控组件呢,就像是个比较随性的家伙,表单元素的值由 DOM 自己管着,React 组件平时不插手,就等关键时候,用ref这个 “工具” 去拿 DOM 元素的值。它不折腾那些复杂的状态同步,尊重 DOM 本来的样子,和表单元素打交道简单直接,就像是给咱开了条捷径。
先看个简单的非受控组件文本输入框:
import React, { useRef } from'react';
function UncontrolledInput() {
const inputRef = useRef(null);
const [submittedValue, setSubmittedValue] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
const inputValue = inputRef.current.value;
setSubmittedValue(inputValue);
console.log('You entered:', inputValue);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={inputRef}
/>
<button type="submit">Submit</button>
<p>Submitted: {submittedValue}</p>
</form>
);
}
export default UncontrolledInput;
在这个例子里,useRef搞出来的inputRef就像一根线,把 React 组件和 DOM 里的文本输入框连上了。平时用户输入,输入框的值在 DOM 里自己变,React 不管。等提交按钮一点,handleSubmit函数靠inputRef.current.value把 DOM 里的值拿过来,放到submittedValue状态里存着,还显示出来,就这么实现了从 DOM 到 React 的过渡。
再看个进阶的多文件上传例子:
import React, { useRef, useState } from'react';
function MultiFileUpload() {
const fileInputRef = useRef(null);
const [fileList, setFileList] = useState([]);
const handleUpload = (e) => {
e.preventDefault();
const files = fileInputRef.current.files;
if (files) {
const fileArray = Array.from(files);
setFileList(fileArray);
console.log('Uploading files:', fileArray.map((file) => file.name));
// 后续可添加文件上传至服务器的逻辑
}
};
return (
<form onSubmit={handleUpload}>
<input
type="file"
multiple
ref={fileInputRef}
/>
<button type="submit">Upload</button>
<ul>
{fileList.map((file, index) => (
<li key={index}>{file.name}</li>
))}
</ul>
</form>
);
}
export default MultiFileUpload;
这个多文件上传的例子里,fileInputRef紧紧跟着文件输入框。用户批量选文件再提交的时候,handleUpload函数靠fileInputRef.current.files拿到文件,转成数组存到fileList状态里,实时显示要上传的文件列表,既尊重了文件输入框 DOM 的原生行为,又把它融进了 React 的数据管理,给文件上传流程搭了个好框架。
在 React 开发里,受控组件和非受控组件就像一对好搭档,各有千秋,互相补充。咱开发者得从项目全局考虑,看业务需求是啥样,团队技术水平咋样,再决定用哪个。把它们的原理、用法、适用场景都搞清楚了,开发表单和用户输入交互功能的时候,就能得心应手。复杂业务咱能处理得稳稳当当,特殊场景也能轻松应对。