Praca z elementami DOM w React
W tradycyjnym JavaScript, aby uzyskać dostęp do elementów modelu DOM, często sięga się po metodę getElementById
, tak jak w poniższym przykładzie:
document.getElementById("ID_ELEMENTU");
Jednak w React, bezpośrednie manipulowanie elementami DOM w ten sposób nie jest zalecane. React zachęca do korzystania z podejścia deklaratywnego, gdzie to biblioteka zarządza aktualizacjami interfejsu użytkownika, a nie programista bezpośrednio przez JavaScript. Użycie getElementById
i podobnych metod może prowadzić do konfliktów, ponieważ React może nie być świadomy zmian wprowadzonych poza jego systemem zarządzania stanem.
Co to jest useRef?
React oferuje rozwiązanie w postaci hooka useRef
, który umożliwia zarówno dostęp do elementów DOM, jak i przechowywanie danych, które nie wywołują ponownego renderowania komponentu przy zmianie. Hook useRef
zwraca obiekt z właściwością .current
, w której przechowywana jest wartość referencji. Ta wartość jest „stała” przez cały cykl życia komponentu.
Dlaczego useRef jest użyteczny?
- Dostęp do elementów DOM: Umożliwia interakcję z elementami DOM, np. ustawienie focusu na polu tekstowym, obsługę animacji czy zarządzanie formularzami.
- Przechowywanie wartości: Zapewnia możliwość przechowywania danych przez cały cykl życia komponentu bez konieczności ponownego renderowania przy ich zmianie.
Przykład użycia useRef do pracy z elementem DOM
import React, { useRef } from 'react';
function App() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
const onChangeEvent = () => {
console.log("Zmiana inputa: ", inputEl.current.value);
}
return (
<>
<input ref={inputEl} type="text" onChange={onChangeEvent} />
<button onClick={onButtonClick}>Ustaw focus na input</button>
</>
);
}
export default App;
W tym przykładzie inputEl.current
odnosi się do elementu input w DOM, co umożliwia wywołanie na nim metody focus()
oraz innych metod z jego API.
Przykład użycia useRef do przechowywania danych
import React, { useRef } from 'react';
function App() {
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1;
console.log(`Przycisk kliknięty ${countRef.current} razy`);
};
return (
<div>
<button onClick={handleClick}>Kliknij mnie</button>
</div>
);
}
export default App;
I tutaj także musimy odwoływać się do właściwości current
aby dostać się do przechowywanej wartości.
Przekazywanie refrencji między komponentami
React umożliwia przekazywanie referencji między komponentami, co jest szczególnie przydatne w przypadku konieczności bezpośredniej interakcji z elementem dziecka z poziomu komponentu rodzica.
Przykład
Komponent App
przekazuje referencję do komponentu ChildInput
, który z kolei używa forwardRef
do odbioru tej referencji.
Komponent App:
import React, { useRef } from 'react';
import ChildInput from './ChildInput';
function App() {
const inputRef = useRef();
const focusChildInput = () => {
inputRef.current.focus();
};
function handleChange() {
console.log("Aktualna wartość inputa: ", inputRef.current.value);
}
return (
<div>
<ChildInput ref={inputRef} onChange={handleChange} className="form-input" />
<button onClick={focusChildInput}>Fokus w komponencie dziecka</button>
</div>
);
}
export default App;
Oraz komponent ChildInput
:
import React, { forwardRef } from 'react';
const ChildInput = forwardRef(({ onChange, className, children }, ref) => {
return <input ref={ref} type="text" className={className} onChange={onChange} />;
});
export default ChildInput;
forwardRef
umożliwia ChildInput
przyjęcie ref
od App
, co umożliwia rodzicowi kontrolę nad elementem input dziecka.
useRef() vs useState()
Główna różnica między useRef
a useState
polega na tym, że aktualizacja referencji za pomocą useRef
nie powoduje ponownego renderowania komponentu. Zmiany wartości przechowywanej w useRef
są „ciche” dla cyklu renderowania Reacta. W przeciwieństwie do tego, useState
zawsze wywołuje ponowne renderowanie przy zmianie stanu, co jest kluczowe dla aktualizacji interfejsu użytkownika.
Wartości przechowywane za pomocą useRef
pozostają jednak niezmienione między renderowaniami, co jest przydatne do śledzenia wartości, które nie mają wpływać na wygląd komponentu.