Evakuacioni izlazi

Advanced

Neke od vaših komponenata će možda trebati da kontrolišu i da se sinhronizuju sa sistemima izvan React-a. Na primer, možda ćete morati da fokusirate input uz pomoć API-ja pretraživača, da pustite i pauzirate video plejer koji nije implementiran u React-u ili da se povežete i slušate poruke sa udaljenog servera. U ovom poglavlju naučićete evakuacione izlaze koji vam omogućavaju da “izađete iz” React-a i povežete se na eksterne sisteme. Većina logike u vašoj aplikaciji, kao i tok podataka, ne bi trebalo da se oslanjaju na ove funkcionalnosti.

Referenciranje vrednosti sa ref-ovima

Kada želite da komponenta “upamti” neku informaciju, ali ne želite da ta informacija pokrene nove rendere, možete koristiti ref:

const ref = useRef(0);

Kao i state, React čuva ref-ove između rendera. Međutim, postavljanje state-a ponovo renderuje komponentu. Promena ref-a to ne radi! Možete pristupiti trenutnoj vrednosti tog ref-a kroz ref.current polje.

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('Kliknuli ste ' + ref.current + ' puta!');
  }

  return (
    <button onClick={handleClick}>
      Klikni me!
    </button>
  );
}

Ref je kao tajni džep vaše komponente koji React ne prati. Na primer, možete koristiti ref-ove da čuvate timeout ID-eve, DOM elemente i ostale objekte koji ne utiču na izlaz renderovanja komponente.

Ready to learn this topic?

Pročitajte Referenciranje vrednosti sa Ref-ovima da naučite kako da koristite ref-ove da upamtite informaciju.

Read More

Manipulisanje DOM-om sa ref-ovima

React automatski ažurira DOM kako bi odgovarao izlazu renderovanja, tako da vaše komponente neće često morati da manipulišu DOM-om. Međutim, ponekad vam može biti potreban pristup DOM elementima kojima upravlja React—na primer, da biste se fokusirali na čvor, scroll-ovali na njega ili izmerili njegovu veličinu i poziciju. Ne postoji ugrađeni način da to uradite u React-u, pa će vam biti potreban ref na DOM čvor. Na primer, klik na dugme će fokusirati input upotrebom ref-a:

import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Fokusiraj input
      </button>
    </>
  );
}

Ready to learn this topic?

Pročitajte Manipulisanje DOM-om sa Ref-ovima da naučite kako da pristupite DOM elementima kojima upravlja React.

Read More

Sinhronizacija sa Effect-ima

Neke komponente trebaju da se sinhronizuju sa eksternim sistemima. Na primer, želite da kontrolišete komponente koje nisu pisane u React-u na osnovu React state-a, da uspostavite konekciju sa serverom ili da pošaljete analitički log kad se komponenta pojavi na ekranu. Za razliku od event handler-a, koji vam omogućavaju da rukujete određenim event-ima, Effect-i vam omogućavaju da izvršite kod nakon rendera. Koristite ih da sinhronizujete vaše komponente sa sistemom izvan React-a.

Kliknite na Pusti/Pauziraj nekoliko puta da vidite kako video plejer ostaje sinhronizovan sa vrednošću isPlaying prop-a:

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pauziraj' : 'Pusti'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

Mnogi Effect-i takođe i “čiste” za sobom. Na primer, Effect koji uspostavlja konekciju sa serverom za dopisivanje bi trebao da vrati cleanup funkciju koja govori React-u kako da diskonektuje vašu komponentu sa tog servera:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>Dobro došli u čet!</h1>;
}

U toku razvoja, React će odmah pokrenuti i očistiti vaš Effect jedan dodatni put. Zbog toga vidite poruku "✅ Konektovanje..." dvaput. Ovo osigurava da ne zaboravite da implementirate cleanup funkciju.

Ready to learn this topic?

Pročitajte Sinhronizacija sa Effect-ima da biste naučili kako da sinhronizujete komponente sa eksternim sistemima.

Read More

Možda vam neće trebati Effect

Effect-i su evakuacioni izlaz u React paradigmi. Omogućavaju vam da “izađete iz” iz React-a i sinhronizujete vaše komponente sa nekim eksternim sistemom. Ako eksterni sistem ne postoji (na primer, ako želite da ažurirate state komponente kada se neki props-i ili state promene), neće vam trebati Effect. Uklanjanje nepotrebnih Effect-a će vaš kod učiniti lakšim za praćenje, bržim za pokretanje i biće manje podložan greškama.

Ovo su dva uobičajena slučaja u kojima vam ne trebaju Effect-i:

  • Ne trebaju vam Effect-i da transformišete podatke za renderovanje.
  • Ne trebaju vam Effect-i da rukujete korisničkim event-ima.

Na primer, ne treba vam Effect da prilagodite neki state na osnovu drugog state-a:

function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');

// 🔴 Izbegavati: suvišan state i nepotreban Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}

Umesto toga izračunajte koliko god možete tokom renderovanja:

function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Dobro: izračunato tokom renderovanja
const fullName = firstName + ' ' + lastName;
// ...
}

Međutim, Effect-i su vam potrebni da se sinhronizujete sa eksternim sistemima.

Ready to learn this topic?

Pročitajte Možda vam neće trebati Effect da znate kako da uklonite nepotrebne Effect-e.

Read More

Životni ciklus reaktivnih effect-a

Effect-i imaju drugačiji životni ciklus od komponenata. Komponente se mogu montirati, ažurirati i demontirati. Effect može uraditi samo dve stvari: da započne sinhronizaciju nečega i da kasnije prekine tu sinhronizaciju. Ovaj ciklus se može dogoditi više puta ako vaš Effect zavisi od props-a i state-a koji se vremenom menjaju.

Ovaj Effect zavisi od vrednosti roomId prop-a. Props-i su reaktivne vrednosti, što znači da se mogu promeniti pri ponovnom renderu. Primetite da se Effect ponovo sinhronizuje (i ponovo konektuje na server) ako se roomId promeni:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Dobro došli u sobu {roomId}!</h1>;
}

export default function App() {
  const [roomId, setRoomId] = useState('opšte');
  return (
    <>
      <label>
        Odaberi sobu za dopisivanje:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="opšte">opšte</option>
          <option value="putovanje">putovanje</option>
          <option value="muzika">muzika</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

React vam pruža linter pravilo za proveru da li ste ispravno specificirali zavisnosti Effect-a. Ako zaboravite da specificirate roomId u listi zavisnosti u primeru iznad, linter će automatski pronaći taj bug.

Ready to learn this topic?

Pročitajte Životni ciklus reaktivnih Effect-a da biste naučili kako se životni ciklus Effect-a razlikuje od životnog ciklusa komponente.

Read More

Odvajanje event-ova od Effect-a

Under Construction

Ova sekcija opisuje eksperimentalni API koji još uvek nije deo stabilne verzije React-a.

Event handler-i se ponovo pokreću samo kada ponovo izvršite istu interakciju. Za razliku od event handler-a, Effect-i se ponovo sinhronizuju kada se bilo koja vrednost koju čitaju, poput props-a ili state-a, razlikuje u odnosu na prethodni render. Ponekad želite kombinaciju ta dva ponašanja: Effect koji se ponovo pokreće kao odgovor na neke vrednosti, ali ne i na neke druge.

Sav kod unutar Effect-a je reaktivan. Pokrenuće se ponovo ako se neka reaktivna vrednost promenila zbog ponovnog rendera. Na primer, ovaj Effect će se ponovo konektovati na čet ako se roomId ili theme promene:

import { useState, useEffect } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Konektovano!', theme);
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, theme]);

  return <h1>Dobro došli u sobu {roomId}!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('opšte');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Odaberi sobu za dopisivanje:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="opšte">opšte</option>
          <option value="putovanje">putovanje</option>
          <option value="muzika">muzika</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Koristi tamnu temu
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'} 
      />
    </>
  );
}

Ovo nije idealno. Želite da se ponovo konektujete na čet samo ako se roomId promeni. Promenom theme vrednosti ne bi trebalo da se ponovo konektujete na čet! Pomerite kod koji čita theme izvan vašeg Effect-a u Effect Event:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification('Konektovano!', theme);
  });

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      onConnected();
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Dobro došli u sobu {roomId}!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('opšte');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Odaberi sobu za dopisivanje:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="opšte">opšte</option>
          <option value="putovanje">putovanje</option>
          <option value="muzika">muzika</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Koristi tamnu temu
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'} 
      />
    </>
  );
}

Kod unutar Effect Event-ova nije reaktivan, pa promena theme vrednosti ne pokreće ponovnu konekciju u Effect-u.

Ready to learn this topic?

Pročitajte Odvajanje Event-ova od Effect-a da biste naučili kako da sprečite da neke vrednosti ponovo pokrenu Effect-e.

Read More

Uklanjanje zavisnosti Effect-a

Kada pišete Effect, linter će verifikovati da li ste uključili svaku reaktivnu vrednost (poput props-a i state-a) koju Effect čita u listi zavisnosti vašeg Effect-a. Ovo osigurava da vaš Effect ostane sinhronizovan sa poslednjim props-ima i state-om vaše komponente. Nepotrebne zavisnosti mogu prouzrokovati da se vaš Effect pokreće previše često ili da čak naprave beskonačnu petlju. Način njihovog uklanjanja zavisi od slučaja.

Na primer, ovaj Effect zavisi od options objekta koji se ponovo kreira svaki put kada izmenite input:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  const options = {
    serverUrl: serverUrl,
    roomId: roomId
  };

  useEffect(() => {
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [options]);

  return (
    <>
      <h1>Dobro došli u sobu {roomId}!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('opšte');
  return (
    <>
      <label>
        Odaberi sobu za dopisivanje:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="opšte">opšte</option>
          <option value="putovanje">putovanje</option>
          <option value="muzika">muzika</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Ne želite da se ponovo konektujete na čet svaki put kada pišete poruku u taj čet. Da biste popravili ovaj problem, pomerite pravljenje options objekta unutar Effect-a tako da Effect zavisi samo od roomId stringa:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Dobro došli u sobu {roomId}!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('opšte');
  return (
    <>
      <label>
        Odaberi sobu za dopisivanje:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="opšte">opšte</option>
          <option value="putovanje">putovanje</option>
          <option value="muzika">muzika</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Primetite da izmenu liste zavisnosti niste započeli uklanjanjem options zavisnosti. To bi bilo pogrešno. Umesto toga, promenili ste okolni kod tako da je ta zavisnost postala nepotrebna. Zamislite listu zavisnosti kao listu svih reaktivnih vrednosti koje se koriste u kodu vašeg Effect-a. Ne birate namerno šta da stavite u tu listu. Ta lista opisuje vaš kod. Da biste promenili listu zavisnosti, promenite kod.

Ready to learn this topic?

Pročitajte Uklanjanje zavisnosti Effect-a da biste saznali kako da učinite da se vaš Effect ređe pokreće.

Read More

Upotreba reusable logike sa prilagođenim Hook-ovima

React dolazi sa ugrađenim Hook-ovima poput useState, useContext i useEffect. Ponekad ćete poželeti da postoji Hook sa konkretnijom svrhom: na primer, za fetch-ovanje podataka, za praćenje da li je korisnik online ili za konekciju na sobu za dopisivanje. Da biste ovo uradili, možete napraviti sopstvene Hook-ove za potrebe vaše aplikacije.

U ovom primeru, usePointerPosition prilagođeni Hook prati poziciju kursora, dok useDelayedValue prilagođeni Hook vraća vrednost koja “lag-uje” određeni broj milisekundi za vrednošću koju ste prosledili. Pomerajte kursor po sandbox-u da biste videli pokretni trag tačaka koji prati kursor:

import { usePointerPosition } from './usePointerPosition.js';
import { useDelayedValue } from './useDelayedValue.js';

export default function Canvas() {
  const pos1 = usePointerPosition();
  const pos2 = useDelayedValue(pos1, 100);
  const pos3 = useDelayedValue(pos2, 200);
  const pos4 = useDelayedValue(pos3, 100);
  const pos5 = useDelayedValue(pos4, 50);
  return (
    <>
      <Dot position={pos1} opacity={1} />
      <Dot position={pos2} opacity={0.8} />
      <Dot position={pos3} opacity={0.6} />
      <Dot position={pos4} opacity={0.4} />
      <Dot position={pos5} opacity={0.2} />
    </>
  );
}

function Dot({ position, opacity }) {
  return (
    <div style={{
      position: 'absolute',
      backgroundColor: 'pink',
      borderRadius: '50%',
      opacity,
      transform: `translate(${position.x}px, ${position.y}px)`,
      pointerEvents: 'none',
      left: -20,
      top: -20,
      width: 40,
      height: 40,
    }} />
  );
}

Možete napraviti prilagođene Hook-ove, sastaviti ih zajedno, proslediti podatke kroz njih i reuse-ovati ih između komponenata. Kako vaša aplikacija raste, pisaćete manje Effect-a ručno jer ćete moći da reuse-ujete prilagođene Hook-ove koje ste već napisali. Takođe, postoji mnogo odličnih prilagođenih Hook-ova koje održava React zajednica.

Ready to learn this topic?

Pročitajte Upotreba reusable logike sa prilagođenim Hook-ovima kako biste naučili da delite logiku između komponenata.

Read More

Šta je sledeće?

Pređite na Referenciranje vrednosti sa Ref-ovima da biste počeli da čitate ovo poglavlje stranicu po stranicu!