github icontwitter icon

Kıssadan React [Yaşayan Yazı]

Author imageEnes Başpınar /12 Ara 2022
15 min read •––– views
Warning icon

Yaşayan Yazı, bir yerimden uydurduğum bir terimdir ve ileriki dönemde içeriği zenginleşecek ama şu anki haliyle de faydalı olabilecek yazıları belirtir.

Yeni React dökümantasyonunun çıkmasıyla birlikte baştan takip etmek istedim. Dolayısıyla sıfırdan öğrenen birisine yardımcı olacak kilit noktaları az ve öz paylaşıyor olacağım.

Genel

  • React'ın "Strict Mode"u development esnasında her komponent fonksiyonunu iki kez çağırır ve Pure olmayan komponentleri yakalamanızı sağlar.
  • Animasyon ve ekranın yenilenmesi gibi işlemler side effect olarak adlandırılırlar. Renderlama anında değil yanında meydana gelen şeylerdir.
  • Uygulama başladığında yalnızca bir kez çalıştırılacak kod komponent dışına koyulabilir.

_8
if (typeof window !== 'undefined') {
_8
checkAuthToken();
_8
loadDataFromLocalStorage();
_8
}
_8
_8
function App() {
_8
// ...
_8
}

Rendering

  • React komponentler esasen render edilmek üzere HTML döndüren fonksiyonlardır. Aslında HTML gibi dursa da JSX dediğimiz ve HTML çok benzeyen bir uzantı kullanır. Kendisi de JS olduğundan kodların elementlerle kullanılmasını kolaylaştırır. Nihayetinde kendisi de HTML'e çevrilir.

_14
function MyButton() {
_14
return (
_14
<button>I'm a button</button>
_14
);
_14
}
_14
_14
export default function MyApp() {
_14
return (
_14
<div>
_14
<h1 className="heading-1">Welcome to my app</h1>
_14
<MyButton />
_14
</div>
_14
);
_14
}

  • Tek bir element/komponent döndürmesi gerekir. Eğer iç içe olmaması gereken bir şeyler varsa etrafına <div> ya da <> sarabiliriz.

_4
<div>
_4
<CustomElement />
_4
<div />
_4
</div>

  • Child komponenti yoksa her zaman kapanan etiket (closing tag) ile yazmalıyız.

_2
<CustomElement />
_2
<div />

  • Birden fazla element return edilemez. Bunları <div>...</div> ya da <>...</> etrafına sarmalıyız. İkincisinin adı Fragment olarak geçer ve HTML'e ekstra bir element eklemeden bu işi görür. Sebebi ise bir fonksiyonun array olmadığı takdirde birden fazla sonuç döndürememesidir.

_4
<div>
_4
<CustomElement />
_4
<div />
_4
</div>

  • JavaScript değişkenleri ve ifadelerini (expression) JSX'de süslü parantez ile kullanabiliriz.

_22
const user = {
_22
name: 'Hedy Lamarr',
_22
imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
_22
imageSize: 90,
_22
};
_22
_22
export default function Profile() {
_22
return (
_22
<>
_22
<h1>{user.name}</h1>
_22
<img
_22
className="avatar"
_22
src={user.imageUrl}
_22
alt={`Photo of ${user.name}`}
_22
style={{
_22
width: user.imageSize,
_22
height: user.imageSize
_22
}}
_22
/>
_22
</>
_22
);
_22
}

  • Koşullu render için 3 farklı yaklaşım uygulayabiliriz. false, null ve undefined değerleri JSX'de gözardı edilir.

_12
let content;
_12
_12
if (isLoggedIn) {
_12
content = <AdminPanel />;
_12
} else {
_12
content = <LoginForm />;
_12
}
_12
return (
_12
<div>
_12
{content}
_12
</div>
_12
);


_7
<div>
_7
{isLoggedIn ? (
_7
<AdminPanel />
_7
) : (
_7
<LoginForm />
_7
)}
_7
</div>


_3
<div>
_3
{isLoggedIn && <AdminPanel />}
_3
</div>

  • Koşullu render yapmak istersek condition ? result1 : result2 , condition && result şeklinde kısaltma ya da if koşulunu kullanabiliriz.

_5
if (isPacked) {
_5
return <li className="item">{name} ✔</li>;
_5
}
_5
_5
return <li className="item">{name}</li>;


_5
return (
_5
<li className="item">
_5
{isPacked ? name + ' ✔' : name}
_5
</li>
_5
);


_5
return (
_5
<li className="item">
_5
{name} {isPacked && '✔'}
_5
</li>
_5
);

  • Listeleri render etmek istersek array metodlarıyla ürettiğimiz JSX element element listesi kullanabiliriz. Burda dikkat edilmesi gereken listedeki değişikliklere adapte olabilmesi için elementlere key özelliği iletilmelidir. Aksi halde indeks değerleri kullanır ve elemanların sıraları değişirse ya da içlerinden birisi silinirse doğru şeyleri render edemez.

key olarak uuid üreten bir kütüphane yada apiden dönen id değerini kullanabilirsiniz. Aksi halde şu hatayı alırsınız: 'Warning: Each child in a list should have a unique “key” prop.'

key özelliğine her seferinde rastgele değer üreten bir metod verilirse (örneğin Math.random) DOM'a her seferinde yeniden render edilir.


_15
const products = [
_15
{ title: 'Cabbage', id: 1 },
_15
{ title: 'Garlic', id: 2 },
_15
{ title: 'Apple', id: 3 },
_15
];
_15
_15
const listItems = products.map(product =>
_15
<li key={product.id}>
_15
{product.title}
_15
</li>
_15
);
_15
_15
return (
_15
<ul>{listItems}</ul>
_15
);

  • Normal davranışta React değişen state ile birlikte ilgili tüm işlemler bittikten sonra DOM'u günceller. O kodun arkasından state değişikliğiyle gelecek yeni DOM'a erişmemiz gerekirse flushSync metodunu kullanabiliriz.

_34
import { useState, useRef } from 'react';
_34
import { flushSync } from 'react-dom';
_34
_34
export default function TodoList() {
_34
const listRef = useRef(null);
_34
const [todos, setTodos] = useState(
_34
initialTodos
_34
);
_34
_34
function handleAdd() {
_34
flushSync(() => {
_34
setTodos([ ...todos, { id: nextId++, text: text }]);
_34
});
_34
_34
listRef.current.lastChild // can access new todo dom element
_34
}
_34
_34
return (
_34
<>
_34
<button onClick={handleAdd}>
_34
Add
_34
</button>
_34
<input
_34
value={text}
_34
onChange={e => setText(e.target.value)}
_34
/>
_34
<ul ref={listRef}>
_34
{todos.map(todo => (
_34
<li key={todo.id}>{todo.text}</li>
_34
))}
_34
</ul>
_34
</>
_34
);
_34
}

Event Handler

  • Olaylara tepki gösterecel fonksiyonlarımızın ismini elementlere iletebiliriz. Çağrı parantezlerini eklerseniz render edilirken fonksiyon tek sefer çağrılır. Varsayılan olarak fonksiyona Event nesnesi geçilir ancak ekstra veri göndermek istersek arrow function kullanabiliriz.

_12
function MyButton() {
_12
function handleClick() {
_12
alert('You clicked me!');
_12
}
_12
_12
return (
_12
// <button onClick={(e) => handleClick(e)}>
_12
<button onClick={handleClick}>
_12
Click me
_12
</button>
_12
);
_12
}

  • Fonksiyon çağrılırsa rendering esnasında çalıştırılır.

_1
<button onClick={handleClick()}>

  • Elementlerin beklediği event props isimlerinde on, handler isimlerinde handle ile başlamak yaygındır. Örn: onMouseEnter={handleMouseEnter}
  • Event propagation komponentlerde de geçerlidir. Örnekte önce button sonra div onClick metodu çağrılır. onScroll harici tüm olaylar yukarıya yayılır. Engellemek için e.stopPropagation() kullanılır.

_14
export default function Toolbar() {
_14
return (
_14
<div className="Toolbar" onClick={() => {
_14
alert('You clicked on the toolbar!');
_14
}}>
_14
<button onClick={() => {
_14
e.stopPropagation();
_14
alert('Playing!')
_14
}}>
_14
Play Movie
_14
</button>
_14
</div>
_14
);
_14
}

  • Formun onSubmit olayı gibi bazı olaylar varsayılan tarayıcı davranışına sahiptir ve sizin kodunuzdan sonra çalıştırır. Bunu engellemek için e.preventDefault kullanabiliriz.
  • Event handler'ları yukarıdan aşağıya doğru (capturing) çalıştırmak istersek onClick yerine onClickCapture kullanımını yapabiliriz.

_13
<div onClick={() => {
_13
alert('Call 1');
_13
}}>
_13
<div onClickCapture={() => {
_13
alert('Call 2');
_13
}}>
_13
<div onClickCapture={() => {
_13
alert('Call 3');
_13
}}>
_13
Click!
_13
</div>
_13
</div>
_13
</div>

=== Output:

Call 2 Call 3 Call 1

Props

  • Bileşenden bileşene veri aktarmak için props kullanırız. Klasik fonksiyona parametre geçme mantığı.

_14
function Avatar({ person, size = 100 }) {
_14
// ...
_14
}
_14
_14
function Profile() {
_14
return (
_14
<div className="card">
_14
<Avatar
_14
person="Enes"
_14
size={80}
_14
/>
_14
</div>
_14
);
_14
}

  • Children elementleri props'un children özelliğinden okuyabiliriz.

_11
function Heading({ children }) {
_11
return (
_11
<div className="heading">
_11
{children}
_11
</div>
_11
);
_11
}
_11
_11
export default function Text() {
_11
return <Heading>Hello</Heading>;
_11
}

State

  • Zamanla değişebilen ve ekranın güncellenmesine yol açacak değerler için state değişkenlerini kullanırız. Bi nevi komponent bazlı bellektir. Render'lar arasında değeri korunur.
  • State komponentin DOM'daki yeriyle ilişkilendirilir. State'in rerender'lar esnasında korunmasını istiyorsanız, DOM ağacının yenisiyle 'eşleşmesi' gerekir. Eğer aynı konumda render olan komponentlerde state'i sıfırlamak istersek farklı key parametresi verebiliriz.
  • React'da state tanımlamak için useState hook'u kullanılır.

React'da use ile başlayan fonksiyonlara hook diyoruz ve esasen React'ın özelliklerini kullanabilmemize yarıyorlar. Komponentlerin root'unda kullanılabilirler. Koşul ya da döngü içine kullanmamalıyız. Gerekiyorsa yeni bir bileşen oluşturabiliriz.


_18
import { useState } from 'react';
_18
_18
function MyButton() {
_18
// count: mevcut değer
_18
// setCount: değeri güncelleyecek fonksiyon
_18
// 0: ilk değer
_18
const [count, setCount] = useState(0);
_18
_18
function handleClick() {
_18
setCount(count + 1);
_18
}
_18
_18
return (
_18
<button onClick={handleClick}>
_18
Clicked {count} times
_18
</button>
_18
);
_18
}

  • State setter çağırıldıktan sonra rerender tetiklenir ve yeni değer kullanılır.
  • React Hook'ların değerlerini çağrılma sırasına göre anlar. Arka arkaya tanımladığımız state değerleri dizide tutulur ve okunacağı zamanda indeksi birer arttırarak okur. Bu sebeptendir ki sadece komponentin root'unda tanımlanmalıdır.
  • Obje tutuyorsak spread kullanmayı unutmamalıyız.

_18
const [person, setPerson] = useState({
_18
name: 'Niki de Saint Phalle',
_18
artwork: {
_18
title: 'Blue Nana',
_18
city: 'Hamburg',
_18
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
_18
}
_18
});
_18
_18
function handleTitleChange(e) {
_18
setPerson({
_18
...person,
_18
artwork: {
_18
...person.artwork,
_18
title: e.target.value
_18
}
_18
});
_18
}

  • State güncelleme metodu çalıştığı anda değeri yenilemez. Örnekte üç kez çağırılan setNumber metoduyla birlikte son değerin 3 olmasını bekleyebiliriz ancak öyle olmaz. Her bir setter çalıştığında state değeri mevcuttaki değer yani 0'dır. Metod çalışmayı bitirdikten sonra rerender olur ve state değeri güncellenir. Komponent context'deki değeri elde etmek istersek parametre olarak güncel değeri alan updater fonksiyon verebiliriz.

_23
import { useState } from 'react';
_23
_23
export default function Counter() {
_23
const [number, setNumber] = useState(0);
_23
_23
return (
_23
<>
_23
<h1>{number}</h1>
_23
<button onClick={() => {
_23
setNumber(number + 1); // setNumber(0 + 1)
_23
setNumber(number + 1); // setNumber(0 + 1)
_23
setNumber(number + 1); // setNumber(0 + 1)
_23
// yeni değer sonraki rerender'da 1 olur.
_23
}}>+3</button>
_23
<button onClick={() => {
_23
setNumber(number => number + 1); // setNumber(0 + 1)
_23
setNumber(number => number + 1); // setNumber(1 + 1)
_23
setNumber(number => number + 1); // setNumber(2 + 1)
_23
// yeni değer sonraki rerender'da 3 olur.
_23
}}>+3</button>
_23
</>
_23
)
_23
}

  • Karmaşık state'leri yönetmek çok karmaşık olabilir. React'ın useReducer hook'u ile reducer oluşturup kullanılabiliriz. Esasen alınan aksiyonlar sonunda yapılacak state değişikliklerini tanımlamamızı sağlar.
TaskApp.tsx
tasksReducer.js

_33
import AddTask from './AddTask.js';
_33
import TaskList from './TaskList.js';
_33
import { useTasksReducer } from './tasksReducer.js';
_33
_33
export default function TaskApp() {
_33
const [tasks, dispatch] = useTasksReducer();
_33
_33
function handleAddTask(text) {
_33
dispatch({
_33
type: 'added',
_33
id: nextId++,
_33
text: text,
_33
});
_33
}
_33
_33
function handleChangeTask(task) {
_33
dispatch({
_33
type: 'changed',
_33
task: task,
_33
});
_33
}
_33
_33
function handleDeleteTask(taskId) {
_33
dispatch({
_33
type: 'deleted',
_33
id: taskId,
_33
});
_33
}
_33
_33
return {
_33
// ...
_33
}
_33
}

Ref

  • Referans tanımlamak için useRef hook'u kullanabiliriz. Değeri render'lar sırasında korur ancak rerender'a sebep olmaz. Event handler içerisinde kullanılabilir. Belirli bir event yoksa Effect ihtiyacı olabilir.

_16
import { useRef } from 'react';
_16
_16
export default function Counter() {
_16
let count = useRef(0);
_16
_16
function handleClick() {
_16
count.current = count.current + 1;
_16
alert('You clicked ' + count.current + ' times!');
_16
}
_16
_16
return (
_16
<button onClick={handleClick}>
_16
Click me!
_16
</button>
_16
);
_16
}

  • Elementlere verilen ref rendering esnasında çalıştırılan komponent gövdesinde olmamalıdır.

_11
function VideoPlayer({ src, isPlaying }) {
_11
const ref = useRef(null);
_11
_11
if (isPlaying) {
_11
ref.current.play();
_11
} else {
_11
ref.current.pause();
_11
}
_11
_11
return <video ref={ref} src={src} loop playsInline />;
_11
}

  • Ref'leri map içerisinde kullanamayacağımızdan ötürü ref callback kullanmalıyız.

_24
import { useRef } from "react";
_24
_24
export default function CatFriends() {
_24
const itemsRef = useRef(new Map());
_24
_24
return (
_24
<ul>
_24
{catList.map((cat) => (
_24
<li
_24
key={cat.id}
_24
ref={(node) => {
_24
if (node) {
_24
itemsRef.current.set(cat.id, node);
_24
} else {
_24
itemsRef.current.delete(cat.id);
_24
}
_24
}}
_24
>
_24
...
_24
</li>
_24
))}
_24
</ul>
_24
);
_24
}

  • DOM elementlerini manipüle etmek istersek ref parametresini kullanabiliriz.

_18
import { useRef } from 'react';
_18
_18
export default function Form() {
_18
const inputRef = useRef(null);
_18
_18
function handleClick() {
_18
inputRef.current.focus();
_18
}
_18
_18
return (
_18
<>
_18
<input ref={inputRef} />
_18
<button onClick={handleClick}>
_18
Focus the input
_18
</button>
_18
</>
_18
);
_18
}

  • Komponente ilettiğimiz ref otomatik olarak DOM'a eklenmez. Bunun için forwardRef API'sinin kullanılması gerekir.

_11
import { forwardRef, useRef } from 'react';
_11
_11
const MyInput = forwardRef((props, ref) => {
_11
return <input {...props} ref={ref} />;
_11
});
_11
_11
export default function Form() {
_11
const inputRef = useRef(null);
_11
_11
return <MyInput ref={inputRef} />;
_11
}

Context

  • Props'ları komponentler arasında 100 kat aşağıya indirmek yerine bir context tanımlayıp useContext hook'u yardımıyla okuyabiliriz.
Section.jsx
Heading.jsx
LevelContext.jsx

_11
import { LevelContextProvider } from './LevelContext.js';
_11
_11
export default function Section({ level, children }) {
_11
return (
_11
<section className="section">
_11
<LevelContextProvider value={level}>
_11
{children}
_11
</LevelContextProvider>
_11
</section>
_11
);
_11
}

Effect

  • Komponentlerin harici sistemlerle senkronize edilmesi için rendering sonrasında kod çalıştırmak için kullanılır. React harici bir komponentin yönetilmesi, sunucu bağlantısının kurulması analitik log'u gönderilmesi vs. Render'ın sebep olduğu yan etkileri gerçekleştirmek için kullanılır. Render'dan sonra çalışır. useEffect metodu kullanılır.

_13
useEffect(() => {
_13
// run after every render
_13
});
_13
useEffect(() => {
_13
// run after every render when dependency changed (will compare with Object.is)
_13
}, [someCompVariable, someProps, someState]);
_13
useEffect(() => {
_13
// run on mount (appears on the screen for the first time)
_13
_13
return () => {
_13
// run on before each effect runs and unmount
_13
}
_13
}, []);

  • Sunucuda yani server-side rendering esnasında çalışmaz.
2022 © No rights are reserved.Inspired by Lee Robinson's blog.