Magia `useImperativeHandle` w React oczami fullstack developera

by

Wstęp

Jestem full-stack developerem TypeScript i ostatnio dużo uczę się języka Elm. Czy zamierzam przejść na Elm? Zdecydowanie nie. Jednak proces nauki pomógł mi zrozumieć i wymagać więcej rzeczy od Reacta.

Dziś pokażę Ci antywzorzec w React, który pozwoli Ci dzielić stan poza komponentami. Dlaczego miałbyś to robić? Dlaczego nie użyć hooka? itd. itd. Pokażę to na przykładzie małego formularza.

Uwaga: To nie jest przykład „produkcyjny”. W środowisku produkcyjnym używam tego hooka do tworzenia zsynchronizowanej przeglądarki plików S3, ale na potrzeby tego artykułu byłoby to zbyt rozbudowane.

Hook

Ta kombinacja State i Callback pozwala nam połączyć funkcję ref później i nasłuchiwać, kiedy ref się zmienia:

import { useState, useCallback } from "react";

export const useImperativeRef = <T,>() => {
  const [refState, setRefState] = useState<T>();
  const ref = useCallback((n: T | null) => {
    if (n) {
      setRefState(n);
    }
  }, []);
  return [refState, ref] as const;
};

Formularz

Do tego przykładu nie potrzebujemy niczego wyszukanego, więc użyjemy prostego formularza z 2 polami:

import React, { useImperativeHandle, useState } from "react";

interface FormValues {
  username: string;
  password: string;
}

export interface MyFormHandle {
  values: FormValues;
}

export const MyForm = React.forwardRef(
  ({}, ref: React.ForwardedRef<MyFormHandle>) => {
    const [values, setValues] = useState<FormValues>({
      username: "",
      password: "",
    });
    useImperativeHandle(ref, () => ({
      values,
    }));
    return (
      <div>
        <input
          type="text"
          value={values.username}
          onChange={(e) =>
            setValues({
              ...values,
              username: e.target.value,
            })
          }
        />
        <input
          type="password"
          value={values.password}
          onChange={(e) =>
            setValues({
              ...values,
              password: e.target.value,
            })
          }
        />
      </div>
    );
  },
);

Użycie formularza

Voilà! Możemy nasłuchiwać na zmiany stanu komponentów niższego poziomu:

export default () => {
  const [values, setRef] = useImperativeRef<MyFormHandle>();
  useEffect(() => {
    console.log(`Values changed!, Values: ${JSON.stringify(values, null, 4)}`);
  }, [values]);
  return (
    <div>
      <MyForm ref={setRef} />
    </div>
  );
};

Oczywiście możemy przekazywać funkcje zmieniające i wartości, ale bądźmy szczerzy — to jest Yuck! Moim zdaniem ten sposób jest w wielu przypadkach dużo bardziej elegancki i pozbawiony zbędnego kodu.

Ready to start?

Let's talk about your project.

Free consultation and quote within 48h. No strings attached.

Contact us