refs: ähnlich zu State, mit Unterschieden:
erster Use-Case:
Ablegen von Werten / Objektreferenzen, die das Rendering einer Komponente nicht beeinflussen:
zweiter Use-Case:
Speichern der aktuellsten Version eines States
Auf eine Ref kann mittels useRef
zugegriffen werden
Objekte werden in der .current
-Property der Ref abgelegt
ein Beispiel mit beiden Use-Cases:
function StopWatch() {
const [time, setTime] = useState(0);
const timeRef = useRef(time);
const intervalId = useRef<number>(null);
function start() {
setTime(0);
timeRef.current = 0;
intervalId.current = setInterval(() => {
timeRef.current++;
setTime(timeRef.current);
}, 1000);
}
function stop() {
clearInterval(intervalId.current);
}
return <div>...</div>;
}
Bemerkung: oft gibt es auch mögliche Implementierungen, die Refs vermeiden:
function StopWatch() {
const [time, setTime] = useState(0);
const [running, setRunning] = useState(false);
useEffect(() => {
if (running) {
const intervalId = setInterval(() => {
setTime((time) => time + 1);
}, 1000);
return () => clearInterval(intervalId);
}
}, [running]);
return <div>...</div>;
}
Ähnlich wie key hat auch die ref-Property eine besondere Bedeutung in JSX - sie ermöglicht den Zugriff auf gerenderte DOM-Elemente
Anwendungsgebiete:
Verwendung der ref-Property zusammen mit dem useRef-Hook:
function RefDemo() {
const myRef = useRef<HTMLInputElement>(null);
return (
<input
ref={myRef}
onChange={() => {
console.log(myRef.current.value);
}}
/>
);
}
Verwalten von Fokus, Textauswahl oder Medienwiedergabe
manche Änderungen können nicht deklarativ (via State) ausgedrückt werden - sie benötigen Zugriff zu einem bestimmten DOM-Element
Beispiel: es gibt Properties wie .value
zum Ändern des Werts eines Inputs oder .className
zum Ändern von Klassennamen, aber keine Property zum Verwalten des Fokus
Verwalten des Fokus:
function App() {
const inputEl = useRef<HTMLInputElement>(null);
useEffect(() => {
inputEl.current?.focus();
}, []);
return <input ref={inputEl} />;
};
Verwalten von Medienwiedergabe:
// https://codesandbox.io/s/media-playback-x3ci4
function Video() {
const [playing, setPlaying] = useState(false);
const videoEl = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (playing) {
videoEl.current?.play();
} else {
videoEl.current?.pause();
}
}, [playing]);
return (
<div>
<video
ref={videoEl}
width="250"
src={
'https://interactive-examples.mdn.mozilla.net/' +
'media/cc0-videos/flower.mp4'
}
/>
<button onClick={() => setPlaying(!playing)}>
{playing ? 'pause' : 'play'}
</button>
</div>
);
}
Integration von anderen DOM-Libraries
Andere Libraries können expliziten Zugriff auf DOM-Elemente benötigen
Beispiel: Die Google Maps Library nimmt ein DOM-Element entgegen, in dem die Karte gezeichnet wird
Für viele Libraries (so auch Google Maps) existieren Wrapper für React, sodass refs nicht benötigt werden
Integration von Google Maps mittels Ref:
function Map() {
const mapRef = useRef<HTMLElement>();
useEffect(() => {
new google.maps.Map(mapRef.current);
}, []);
return <div ref={mapRef} style={height: 400} />;
}
Alternative Möglichkeit zum Verwalten von Inputs
Verwendung von ref
Anstelle von value
und onChange
kann zu etwas kürzerem Code führen (wird aber in der Dokumentation nicht empfohlen)
Refs werden von react-hook-form verwendet, um Formularverwaltung einfacher und schneller zu machen
Verwalten von Inputs: Vergleich von useState
und useRef
:
const App = () => {
const [firstName, setFirstName] = useState('');
const lastNameInput = useRef<HTMLInputElement>(null);
return (
<div>
<input
value={firstName}
onChange={(event) =>
setFirstName(event.target.value)
}
/>
<input ref={lastNameInput} />
<button
onClick={() => {
console.log(firstName);
console.log(lastNameInput.current.value);
}}
>
log values
</button>
</div>
);
};
Wie bisher gesehen: Wir können ein Ref-Objekt an die ref-Property übergeben
Wir können auch eine Callbackfunktion übergeben, die nach dem Rendering mit dem Element als Parameter aufgerufen wird (react-hook-form verwendet dies)
<input
ref={(element) => {
console.log(element);
console.log(element.value);
element.focus();
}}
/>
Beispiel: TextField
-Komponente in MUI
const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
<TextField ref={containerRef} inputRef={inputRef} />
ref
: kann verwendet werden, um auf das äußere <div>
zuzugreifeninputRef
: kann verwendet werden, um auf das <input>
-Element zuzugreifenImplementierung einer Ref, deren Property-Name nicht "ref" ist:
type Props = {
inputRef: RefObject<HTMLInputElement>;
// ...
};
function TextField(props: Props) {
return (
<div>
...
<input ref={props.inputRef} />
</div>
);
}
Implementierung einer Ref, deren Property-Name "ref" ist:
type Props = {
// ...
};
const TextField = forwardRef<HTMLDivElement, Props>(
(props, ref) => {
return (
<div ref={ref}>
...
<input />
</div>
);
}
);
Kombination der Implementierungen:
type Props = {
inputRef: RefObject<HTMLInputElement>;
// ...
};
const TextField = forwardRef<HTMLDivElement, Props>(
(props, ref) => {
return (
<div ref={ref}>
...
<input ref={props.inputRef} />
</div>
);
}
);