← Назад к вопросам

Как работает двустороннее связывание?

2.3 Middle🔥 182 комментариев
#JavaScript Core

Комментарии (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>;
}

Поток:

  1. User вводит текст
  2. onChange событие срабатывает
  3. setName обновляет state
  4. React перерендерит компоненты
  5. Новое значение приходит в 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 с состоянием.