Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает двустороннее связывание?
Двустороннее связывание (two-way binding) - это паттерн где изменение в UI автоматически обновляет данные, и наоборот. В React это работает иначе чем в Angular или Vue, потому что React - это однонаправленный data flow.
Однонаправленный поток в React
React использует однонаправленный data flow:
// Props вниз, события вверх
function Parent() {
const [name, setName] = useState("");
return (
<>
<Input value={name} onChange={(e) => setName(e.target.value)} />
<Display text={name} />
</>
);
}
function Input({ value, onChange }: { value: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void }) {
return <input value={value} onChange={onChange} />;
}
function Display({ text }: { text: string }) {
return <p>{text}</p>;
}
Поток:
- User вводит текст
- onChange событие срабатывает
- setName обновляет state
- React перерендерит компоненты
- Новое значение приходит в Input через props
Двустороннее связывание - имитация
В React это называют "controlled components":
function Form() {
const [formData, setFormData] = useState({
name: "",
email: "",
message: ""
});
// Общий обработчик для всех полей
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
return (
<form>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
placeholder="Message"
/>
</form>
);
}
Почему это эффективно:
- State это source of truth
- Все изменения идут через один обработчик
- Легко валидировать
- Легко отправить на сервер
Создание кастомного двустороннего хука
// Собственный hook для двустороннего связывания
function useField<T extends object>(
initialValue: T
): [T, (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void] {
const [value, setValue] = useState(initialValue);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value: fieldValue, type } = e.target;
const val = type === "checkbox" ? (e.target as HTMLInputElement).checked : fieldValue;
setValue(prev => ({ ...prev, [name]: val }));
},
[]
);
return [value, handleChange];
}
// Использование
function MyForm() {
const [data, handleChange] = useField({ name: "", email: "" });
return (
<form>
<input name="name" value={data.name} onChange={handleChange} />
<input name="email" value={data.email} onChange={handleChange} />
<p>Name: {data.name}</p>
<p>Email: {data.email}</p>
</form>
);
}
Uncontrolled Components
Это альтернатива двустороннему связыванию, но менее предсказуемо:
// Плохо - uncontrolled
function Form() {
const inputRef = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
// Получаем значение из DOM, а не из state
console.log(inputRef.current?.value);
};
return (
<>
<input ref={inputRef} defaultValue="" />
<button onClick={handleSubmit}>Submit</button>
</>
);
}
// Хорошо - controlled
function Form() {
const [value, setValue] = useState("");
const handleSubmit = () => {
// Значение уже в state
console.log(value);
};
return (
<>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<button onClick={handleSubmit}>Submit</button>
</>
);
}
Синхронизация с внешним состоянием
// Двустороннее связывание с глобальным state
function useGlobalInput(key: string) {
const [localValue, setLocalValue] = useState("");
const globalValue = useSelector(state => state[key]);
const dispatch = useDispatch();
// Синхронизируем при изменении глобального state
useEffect(() => {
setLocalValue(globalValue);
}, [globalValue]);
// Обновляем глобальное state при изменении local
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setLocalValue(value);
dispatch(updateGlobalState(key, value));
},
[key, dispatch]
);
return { value: localValue, onChange: handleChange };
}
function MyComponent() {
const name = useGlobalInput("userName");
return <input {...name} />;
}
Двустороннее связывание с debounce
// Полезно когда нужна задержка перед отправкой на сервер
function useDebounceInput(initialValue: string, delay: number = 300) {
const [value, setValue] = useState(initialValue);
const [debouncedValue, setDebouncedValue] = useState(initialValue);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return {
value,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value),
debouncedValue
};
}
// Использование - синхронизация с сервером
function SearchUsers() {
const { value, onChange, debouncedValue } = useDebounceInput("");
const [results, setResults] = useState<User[]>([]);
useEffect(() => {
if (debouncedValue.length > 2) {
api.searchUsers(debouncedValue).then(setResults);
}
}, [debouncedValue]);
return (
<>
<input value={value} onChange={onChange} placeholder="Search..." />
<ul>
{results.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
</>
);
}
Копирование и синхронизация между компонентами
// Parent synchronizes data between two components
function DataSync() {
const [sharedData, setSharedData] = useState({
title: "",
description: ""
});
return (
<>
<EditorComponent data={sharedData} onDataChange={setSharedData} />
<PreviewComponent data={sharedData} />
<JsonDisplay data={sharedData} />
</>
);
}
function EditorComponent({
data,
onDataChange
}: {
data: any;
onDataChange: (newData: any) => void;
}) {
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
onDataChange({ ...data, [name]: value });
};
return (
<>
<input name="title" value={data.title} onChange={handleChange} />
<textarea name="description" value={data.description} onChange={handleChange} />
</>
);
}
Почему React не использует true двустороннее связывание
Angular и Vue поддерживают true two-way binding с v-model и [(ngModel)], но React специально избегает этого:
// Vue - двустороннее
<input v-model="message" />
// Это одновременно читает и пишет в message
// React - однонаправленное
<input value={message} onChange={(e) => setMessage(e.target.value)} />
// Явное разделение: чтение через props, запись через callback
Почему React правильнее:
- Явность - сразу видно кто управляет state
- Предсказуемость - легче отладить
- Контроль - можно добавить валидацию, трансформацию
- Performance - можно оптимизировать обновления
Итоговая практика
// Правильно для React
function Form() {
const [formData, setFormData] = useState({ name: "", email: "" });
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log("Submit:", formData);
};
return (
<form onSubmit={handleSubmit}>
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
<button type="submit">Submit</button>
</form>
);
}
Двустороннее связывание в React - это управляемые компоненты (controlled components). Это явный и предсказуемый способ синхронизировать UI с состоянием.