Magia `useImperativeHandle` w React oczami fullstack developera
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.