Short notes about React
Geçenlerde yeni React dökümantasyonuna baştan sona göz atmaya karar vermiştim. Bu esnada aldığım ve framework'e dair anahtar noktaları kısa notlarımı örneklerle paylaşacağı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. Render sayısını hesaplarken buna dikkat edin.
-
Uygulama başladığında yalnızca bir kez çalıştırılacak kod komponent dışına koyulabilir.
1if (typeof window !== "undefined") { 2 checkAuthToken(); 3 loadDataFromLocalStorage(); 4} 5 6function App() { 7 // ... 8}
Rendering
-
Komponent, renderlanacak HTML elementleri döndüren fonksiyondur. İşin aslına bakarsak HTML gibi duran ancak JSX dediğimiz bir dil uzantısıdır. Kendisi de typescript olduğundan typescript'in elementlerle kullanılmasını kolaylaştırır. Nihayetinde kendisi de HTML'e çevrilir.
1function MyButton() { 2 return <button>I'm a button</button>; 3} 4 5export default function MyApp() { 6 return ( 7 <div> 8 <h1 className="heading-1">Welcome to my app</h1> 9 <MyButton /> 10 </div> 11 ); 12}
-
Bir komponent içerisine öğe almayacaksa kapanan etiket (closing tag) ile yazılmalıdır.
1<CustomElement />
-
Birden fazla element return edilemez. Bunları
ya da<div>...</div>
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.<>...</>
1<div> 2 <CustomElement /> 3 <div /> 4</div>
-
typescript değişkenleri ve ifadeleri (expression) JSX'de süslü parantez içinde kullanılabilir.
1const user = { 2 fullName: "Hedy Lamarr", 3 imageUrl: "https://i.imgur.com/yXOvdOSs.jpg", 4 imageSize: 90, 5}; 6 7export default function Profile() { 8 return ( 9 <> 10 <h1>{user.fullName}</h1> 11 <img 12 className="avatar" 13 src={user.imageUrl} 14 alt={`Photo of ${user.fullName}`} 15 style={{ 16 width: user.imageSize, 17 height: user.imageSize, 18 }} 19 /> 20 </> 21 ); 22}
-
Koşullu render için 3 farklı yaklaşım uygulayabiliriz. Koşullardan dönen
,false
venull
değerleri JSX'de gözardı edilir.undefined
1// first way: if statement 2if (isPacked) { 3 return <li className="item">{name} ✔</li>; 4} 5 6return <li className="item">{name}</li>; 7 8// second way: ternary operator 9return <li className="item">{isPacked ? name + " ✔" : name}</li>; 10 11// third way: and operator 12return ( 13 <li className="item"> 14 {name} {isPacked && "✔"} 15 </li> 16);
-
Birden fazla elementi render etmek için JSX array'leri kullanabiliriz. Array değiştiğinde React'ın doğru öğeleri render etmesi için her bir öğeye
prop vermeliyiz.key
1const products = [ 2 { title: "Cabbage", id: 1 }, 3 { title: "Garlic", id: 2 }, 4 { title: "Apple", id: 3 }, 5]; 6 7const listItems = products.map((product) => ( 8 <li key={product.id}>{product.title}</li> 9)); 10 11return <ul>{listItems}</ul>;
-
React state değiştikten sonra kod bloğu tamamlandığında DOM'u günceller. State'in değiştiği satırın ardından yeni DOM'a erişmemiz gerekirse
metodunu kullanabiliriz.flushSync
1import { useState, useRef } from "react"; 2import { flushSync } from "react-dom"; 3 4export default function TodoList() { 5 const listRef = useRef(null); 6 const [todos, setTodos] = useState(initialTodos); 7 8 function handleAdd() { 9 flushSync(() => { 10 setTodos([...todos, { id: nextId++, text: text }]); 11 }); 12 13 listRef.current.lastChild; // can access new todo dom element 14 } 15 16 return ( 17 <> 18 <button onClick={handleAdd}>Add</button> 19 <input value={text} onChange={(e) => setText(e.target.value)} /> 20 <ul ref={listRef}> 21 {todos.map((todo) => ( 22 <li key={todo.id}>{todo.text}</li> 23 ))} 24 </ul> 25 </> 26 ); 27}
Event Handler
-
Kullanıcı bir olay tetiklediğinde çalışacak fonksiyona event handler denir. Bu fonksiyonları props olarak iletebiliriz. Varsayılan olarak fonksiyona
nesnesi geçilir. Ekstra veri göndermek istersek arrow function kullanabiliriz.Event
1function MyButton() { 2 function handleClick() { 3 alert("You clicked me!"); 4 } 5 6 return ( 7 // <button onClick={(e) => handleClick(e, customProp)}> 8 <button onClick={handleClick}>Click me</button> 9 ); 10}
-
Event props isimlerinin başına
, handler isimlerinin başına iseon
konulması yaygın bir kullanımdır. Örn:handle
onMouseEnter={handleMouseEnter}
-
Event propagation komponentlerde de geçerlidir. Örneğe bakacak olursak, önce button sonra div
metodu çağrılır.onClick
harici tüm olaylar yukarıya yayılır. Bunu engellemek içinonScroll
kullanılır.e.stopPropagation()
1export default function Toolbar() { 2 return ( 3 <div 4 className="Toolbar" 5 onClick={() => { 6 alert("You clicked on the toolbar!"); 7 }} 8 > 9 <button 10 onClick={() => { 11 e.stopPropagation(); 12 alert("Playing!"); 13 }} 14 > 15 Play Movie 16 </button> 17 </div> 18 ); 19}
-
Formun
olayı gibi bazı olaylar varsayılan tarayıcı davranışına sahiptir ve sizin kodunuzdan sonra çalıştırır. Bunu engellemek içinonSubmit
kullanabiliriz.e.preventDefault
-
Event handler'ları yukarıdan aşağıya doğru (capturing) çalıştırmak istersek olayların capture versiyonlarını kullanabiliriz.
1<div 2 onClick={() => { 3 alert("Call 1"); 4 }} 5> 6 <div 7 onClickCapture={() => { 8 alert("Call 2"); 9 }} 10 > 11 <div 12 onClickCapture={() => { 13 alert("Call 3"); 14 }} 15 > 16 Click! 17 </div> 18 </div> 19</div>
Call 2 Call 3 Call 1
Props
-
Bileşenden bileşene veri aktarmak için
kullanırız. Klasik fonksiyona parametre geçme mantığı.props
1function Avatar({ person, size = 100 }) { 2 // ... 3} 4 5function Profile() { 6 return ( 7 <div className="card"> 8 <Avatar person="Enes" size={80} /> 9 </div> 10 ); 11}
-
Children elementleri props'un
özelliğinden okuyabiliriz.children
1function Heading({ children }) { 2 return <div className="heading">{children}</div>; 3} 4 5export default function Text() { 6 return <Heading>Hello</Heading>; 7}
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. Klasik değişkenlerden farklı olarak render esnasında değeri korunur.
-
State, komponentin DOM'daki yeriyle ilişkilendirilir. Aynı konumda render olan komponentlerde state'ini sıfırlamak istersek
parametresini güncelleyebiliriz.key
-
State tanımlamak için
hook'u kullanılır.useState
1import { useState } from "react"; 2 3function MyButton() { 4 // count: current value 5 // setCount: setter function 6 // 0: initial value 7 const [count, setCount] = useState(0); 8 9 function handleClick() { 10 setCount(count + 1); 11 } 12 13 return <button onClick={handleClick}>Clicked {count} times</button>; 14}
-
After the state setter is called, the rerender is triggered when the code block is executed.
-
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.
-
State değeri obje ise güncellerken spread operatörünü kullanmalıyız.
1const [person, setPerson] = useState({ 2 name: "Niki de Saint Phalle", 3 artwork: { 4 title: "Blue Nana", 5 city: "Hamburg", 6 image: "https://i.imgur.com/Sd1AgUOm.jpg", 7 }, 8}); 9 10function handleTitleChange(e) { 11 setPerson({ 12 ...person, 13 artwork: { 14 ...person.artwork, 15 title: e.target.value, 16 }, 17 }); 18}
-
Setter metodu çalıştığı anda state değeri yenilenmez. Örneğe bakarsak üç kez çağırılan setNumber metoduyla birlikte son değerin 3 olmasını bekleriz ancak yanılırız. Her setter çalıştığında state değeri render başında okunan değer yani 0 olacaktır. onClick bloğu çalışmayı tamamladıktan sonra rerender tetiklenir ve state değeri güncellenir. Komponent'in bellekteki değerini anlık elde etmek istersek updater fonksiyonu kullanabiliriz.
1import { useState } from "react"; 2 3export default function Counter() { 4 const [count, setCount] = useState(0); 5 6 return ( 7 <> 8 <h1>{count}</h1> 9 <button 10 onClick={() => { 11 setCount(count + 1); // setCount(0 + 1) 12 setCount(count + 1); // setCount(0 + 1) 13 setCount(count + 1); // setCount(0 + 1) 14 // yeni değer sonraki rerender'da 1 olur. 15 }} 16 > 17 +3 18 </button> 19 <button 20 onClick={() => { 21 setCount((count) => count + 1); // setCount(0 + 1) 22 setCount((count) => count + 1); // setCount(1 + 1) 23 setCount((count) => count + 1); // setCount(2 + 1) 24 // yeni değer sonraki rerender'da 3 olur. 25 }} 26 > 27 +3 28 </button> 29 </> 30 ); 31}
Reducer
-
Karmaşık state'leri yönetmekte zorlanabiliriz. Reducer yapısı, aksiyon bazlı bir state update mekanizması kurar. Reducer tanımlamak için
hook'unu kullanabiliriz.useReducer
1import { useReducer } from "react"; 2 3function tasksReducer(tasks, action) { 4 switch (action.type) { 5 case "add": { 6 return [ 7 ...tasks, 8 { 9 id: action.id, 10 text: action.text, 11 done: false, 12 }, 13 ]; 14 } 15 case "change": { 16 return tasks.map((t) => { 17 if (t.id === action.task.id) { 18 return action.task; 19 } else { 20 return t; 21 } 22 }); 23 } 24 case "delete": { 25 return tasks.filter((t) => t.id !== action.id); 26 } 27 default: { 28 throw Error("Unknown action: " + action.type); 29 } 30 } 31} 32 33const initialTasks = [ 34 { id: 0, text: "Visit Kafka Museum", done: true }, 35 { id: 1, text: "Watch a puppet show", done: false }, 36 { id: 2, text: "Lennon Wall pic", done: false }, 37]; 38 39const useTasksReducer = () => useReducer(tasksReducer, initialTasks); 40 41export { tasksReducer, useTasksReducer };
1import AddTask from "./AddTask.js"; 2import TaskList from "./TaskList.js"; 3import { useTasksReducer } from "./tasksReducer.js"; 4 5export default function TaskApp() { 6 const [tasks, dispatch] = useTasksReducer(); 7 8 function handleAddTask(text) { 9 dispatch({ 10 type: "add", 11 id: nextId++, 12 text, 13 }); 14 } 15 16 function handleChangeTask(task) { 17 dispatch({ 18 type: "change", 19 task, 20 }); 21 } 22 23 function handleDeleteTask(taskId) { 24 dispatch({ 25 type: "delete", 26 id: taskId, 27 }); 28 } 29 30 return { 31 // ... 32 }; 33}
Ref
-
Ref, HTML elementlerine erişebilmek için kullanılılır dolayısıyla querySelector ile uğraşmaya gerek kalmaz. Referans tanımlamak için
hook'u kullanabiliriz.useRef
1import { useRef } from "react"; 2 3export default function Form() { 4 const inputRef = useRef(null); 5 6 function handleClick() { 7 inputRef.current.focus(); 8 } 9 10 return ( 11 <> 12 <input ref={inputRef} /> 13 <button onClick={handleClick}>Focus the input</button> 14 </> 15 ); 16}
-
DOM rendering esnasında erişilemez olduğu için ref değeri komponent gövdesinde okunamaz ve değiştirilemez. Yalnızca render sonrasında çalışacağından emin olduğumuz
ve event handler içerisinde erişilebilir.useEffect
1function VideoPlayer({ src, isPlaying }) { 2 const ref = useRef(null); 3 4 // WRONG! this will crash. 5 if (isPlaying) { 6 ref.current.play(); 7 } else { 8 ref.current.pause(); 9 } 10 11 // CORRECT! instead we can use useEffect 12 useEffect(() => { 13 if (isPlaying) { 14 ref.current.play(); 15 } else { 16 ref.current.pause(); 17 } 18 }); 19 20 return <video ref={ref} src={src} loop playsInline />; 21}
-
Renderlar esnasında değeri korunduğu için değişken saklamak için de kullanılabilir. State'den farklı olarak değeri değiştiğinde rerender tetiklemez.
1import { useRef } from "react"; 2 3export default function Counter() { 4 let count = useRef(0); 5 6 function handleClick() { 7 count.current = count.current + 1; 8 alert("You clicked " + count.current + " times!"); 9 } 10 11 return ( 12 <div> 13 <p>Click count: {count.current}</p> 14 <button onClick={handleClick}>Click me!</button> 15 </div> 16 ); 17}
-
Ref'leri map içerisinde kullanamayacağımızdan ötürü ref callback kullanmalıyız.
1import { useRef } from "react"; 2 3export default function CatFriends() { 4 const itemsRef = useRef(new Map()); 5 6 return ( 7 <ul> 8 {catList.map((cat) => ( 9 <li 10 key={cat.id} 11 ref={(node) => { 12 if (node) { 13 itemsRef.current.set(cat.id, node); 14 } else { 15 itemsRef.current.delete(cat.id); 16 } 17 }} 18 > 19 ... 20 </li> 21 ))} 22 </ul> 23 ); 24}
-
Bir komponentin içerisindeki HTML elemente de ref atamak isteyebiliriz. Props olarak geçtiğimiz ref otomatik olarak DOM'a eklenmez. Bunun için
kullanılmamız gerekir.forwardRef
1import { forwardRef, useRef } from "react"; 2 3const MyInput = forwardRef((props, ref) => { 4 return <input {...props} ref={ref} />; 5}); 6 7export default function Form() { 8 const inputRef = useRef(null); 9 10 return <MyInput ref={inputRef} />; 11}
Context
-
Komponentler arasında veri paylaşımını kolaylaştıran ve komponentler arası props olarak iletmeyi engelleyen yapıya context denir.
-
Props'ları komponentler arasında 100 kat aşağıya indirmek yerine bir context tanımlayıp
hook'u yardımıyla okuyabiliriz.useContext
1import React, { createContext, useContext, useState } from "react"; 2 3// İlk olarak context oluşturulur. 4const LanguageContext = createContext(); 5 6// Provider içerisindeki elemanların context'e erişebilmesini sağlar. 7const LanguageProvider = ({ children }) => { 8 const [language, setLanguage] = useState("en"); 9 10 // value props context'in sunacağı değerleri tutar. 11 return ( 12 <LanguageContext.Provider value={{ language, setLanguage }}> 13 {children} 14 </LanguageContext.Provider> 15 ); 16}; 17 18// useContext hook'u context'in anlık değerlerini okur. 19const useLanguageContext = () => useContext(LanguageContext); 20 21export { LanguageProvider, useLanguageContext };
1import { LanguageProvider } from "./LanguageContext.tsx"; 2import { Navbar } from "./Navbar.tsx"; 3import { PageTitle } from "./PageTitle.tsx"; 4 5export default function App() { 6 return ( 7 <div className="App"> 8 <LanguageProvider> 9 <Navbar /> 10 <PageTitle /> 11 </LanguageProvider> 12 </div> 13 ); 14}
1import { useLanguageContext } from "./LanguageContext.tsx"; 2 3export const PageTitle = () => { 4 const { language } = useLanguageContext(); 5 6 const titleByLang = { 7 en: "Welcome to my App!", 8 tr: "Uygulamama hoşgeldiniz!", 9 }; 10 11 return <h1>{titleByLang[language]}</h1>; 12};
1import { useLanguageContext } from "./LanguageContext.tsx"; 2 3export const Navbar = () => { 4 const { setLanguageHandler } = useLanguageContext(); 5 6 return ( 7 <nav> 8 <button onClick={() => setLanguageHandler("en")}>English</button> 9 <button onClick={() => setLanguageHandler("tr")}>Türkçe</button> 10 </nav> 11 ); 12};
Effect
-
Render'ın meydana getirdiği yan etkileri gerçekleştirmek için kullanılır. Örneğin sayfa yüklendiğinde analitik verisi kaydetmek ya da chat uygulamasında sunucuyla bağlantı kurmak için kullanabiliriz. Bunun için
hook'u kullanılır.useEffect
1useEffect(() => { 2 // her render'dan sonra çalışır 3}); 4 5useEffect(() => { 6 // bağımlılık array'indeki değerlerin sebep olduğu renderlarda çalışır (Object.is ile kontrol edilir) 7}, [someCompVariable, someProps, someState]); 8 9useEffect(() => { 10 // yalnızca ilk renderdan sonra çalışır 11 12 return () => { 13 // komponent dom'dan siliniyorsa çalışır 14 }; 15}, []);
-
Server-side rendering esnasında çalışmaz.