React 16.8 sürümü ile React Hooks eklentisi tanıtıldı. React Hooks ile artık sınıf yazmamıza gerek kalmadan state tanımlaması ve React ‘ın diğer özelliklerini kullanmamıza olanak sağlamaktadır.
Neden React Hooks geliştirildi?
- Component ‘ler arasında iletişimin sağlanması için props ya da High Order Component (HOC) kullanılmaktaydı, bu durumda da yazılımcı açısından component ‘lerin yeniden düzenlenmesi ya da kodun okunabilirliğini çok fazla düşürmekteydi. Ek olarak sürekli wrapper ile component ‘lerin iç içe olmasına sebep olmaktaydı.
- React lifecycle içerisinde yer alan componentDidMount() ve componentDidUpdate() gibi method içerisinde hem listener ‘ların oluşturulması, silinmesi gibi işlemler yapılırken hem de API request ‘leri yapılmaktaydı. Bu durumda ilgili method ‘a bir çok fazla iş yükü yüklediğinden kodun kalitesini düşürmekte ve component içerisinde hatalara sebep olmaktaydı.
- Custom olarak oluşturulan event ‘lerin “this” ile bind edilmesinin gerekliliği, bildiğiniz gibi butonun click event ‘ini aynı scope içerisinde kullanabilmemiz için constructor method ‘u içerisine “this.handleClick = this.handleClick.bind(this);” gibi işlemleri yapmaktaydık. Bu da aynı zamanda kod yığını olmakta ve minify işlemlerinde sorun olmasına sebep olmaktaydı.
Makalemize başlamadan önce bazı değinmek istediğim başlıkları belirteyim:
- Opsiyoneldir yani mevcut projeniz içerisinde herhangi bir kodu tekrar yazmadan kullanabilirsiniz. Fakat 16.8 sürümünü kullanmanız durumunda Hooks kullanmayabilirsiniz.
- Mevcut projeniz içerisindeki herhangi bir kodu değiştirmeniz gerekmemektedir. Yani sürümü yükseltmeniz durumunda Hooks kullanmayı zorunlu bırakmadığı ve this.setState gibi method lar depricated olmadığından dolayı yapınızda değişiklik yapmanız gerekmeyecektir.
- Hooks, v16.8 sürümü ile birlikte kullanılmaktadır.
Şimdi React Hooks içerisindeki API ‘lere göz atalım.
Basic Hooks:
- useState
- useEffect
- useContext
Additional Hooks:
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
useState
useState(), state hook olarak belirtilmektedir. Functional component içerisinde state işlemlerinizi gerçekleştirmenizi sağlamaktadır. Şimdi aşağıda kodu inceleyelim.
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
Yukarıdaki kod örneği tanıdık gelecektir. 🙂 Burada constructor içerisine “count” isimli state tanımlayıp, değerini “0” olarak set ‘liyoruz. Sonrasında ise buton ‘un click event ‘i ile “this.setState()” methodu ile değerini arttıyoruz.
Şimdi hooks ile örneğe göz atalım.
import React, { useState } from 'react';
function Example() {
// "count" adında yeni bir state değişkeni tanımlayın.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useState methodu iki tane değer return eder.
- State tanımlanızı sağlar, yani “count” değişkenini tanımlar. this.state sınıfının sağladığı özellikleri kullanmanızı sağlar ve parametre olarak almış olduğu değeri, tanımladığınız değişkene set ‘ler. (Parametre olarak değer vermeyebilirsiniz.) Kısaca özetlemem gerekirse, “count” isimli değişken oluştur ve değerini “0” olarak tanımla diyoruz.
- Diğer geri dönen değer ile tanımlamış olduğumuz state ‘in değerini değiştirmemizi sağlamaktadır. Yani this.setState method ‘unda gerçekleştirdiğimiz işlemi “setCount()” ile gerçekleştirmemize olanak tanımaktadır.
Tanımlamış olduğunuz değişkeni “this.state.count” yapmadan “count” olarak okuyabilirsiniz.
Çoklu state tanımlaması yapmak isterseniz aşağıdaki tanımlama yaparak işlemlerinizi gerçekleştirebilirsiniz.
function ExampleWithManyStates() {
// Birden fazla state değişkeni bildir!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
useEffect
useEffect methodu, componentDidMount, componentDidUpdate, ve componentWillUnmount mehod ‘larının birleşimi olarak düşünebilirsiniz. DOM işlemlerini tamamladıktan sonra useEffect methodu çalışmaktadır. useEffect methodu component içerisinde oluşturduğumuz için state ve props değişkenlerine erişebilmektedir ve default olarak ilk render ‘da dahil olmak üzere her render işleminden sonra useEffect methodu çalıştırılır.
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Yukarıdaki örneğimizde DOM güncellenmesinden sonra document.title değiştirilmesi sağlanmaktadır.
Eskiden olsa nasıl yapıyor olacaktık:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
Şimdi biraz daha detaylı inceleyelim. Aşağıda hooks öncesine ait bir kod bloğu bulunmaktadır. Bu kod bloğunda kullanıcı çevrimiçi olduğunda abone oluyor ve sonrasında nasıl temizlendiği ile ilgili örnek mevcut.
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
Yukarıdaki örnekte gördüğünüz gibi her iki method ‘u kullanmak yerine useEffect içerisine “cleanup” methodunu return ettiğimiz için temizlenmesini sağlamaktayız.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
Aynı zamanda birden fazla useEffect methodu kullanabilirsiniz.
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
Yukarıda gördüğünüz gibi lifecycle içerisindeki karmaşadan kurtuluyoruz. 🙂
useContext
useContext ‘e geçiş yapmadan önce 16.3.0 ile kullanıma sunulan React Context API ‘ye değinelim. React uygulamanız içerisinde her yerden erişmek istediğiniz değerleriniz olabilir. Örneğin, tema seçimi gibi. Bu gibi işlemleri gerçekleştirebilmeniz için Context API kullanabilirsiniz. Şimdi giriş yapan bir kullanıcı için Context API kullanalım ve useContext ile farkına göz atalım.
UserContext isimli context oluşturalım ve UserContext.Provider ve UserContext.Consumer değerlerinden oluşacak.
- Provider, UserContext içerine değer setlemizini sağlamaktadır.
- Consumer, UserContext içerisindeki değerleri okumanıza olanak sağlamaktadır.
// src/UserContext.js
import React from 'react'
const UserContext = React.createContext({})
export const UserProvider = UserContext.Provider
export const UserConsumer = UserContext.Consumer
export default UserContext
Context ‘imizi oluşturduk.
//src/App.js
import React from 'react'
import HomePage from './HomePage'
import { UserProvider } from './UserContext'
function App() {
const user = { name: 'Tania', loggedIn: true }
return (
<UserProvider value={user}>
<HomePage />
</UserProvider>
)
}
UserProvider ile değerimizi set ‘ledik.
// src/HomePage.js (class example)
import React, { Component } from 'react'
import UserContext from './UserContext'
class HomePage extends Component {
static contextType = UserContext
componentDidMount() {
const user = this.context
console.log(user) // { name: 'Tania', loggedIn: true }
}
render() {
return null
}
}
Yukarıdaki örneğimizde HomePage component içerisinde context ‘e erişim log yazdırdık.
// src/HomePage.js (hooks example)
import React, { useContext } from 'react'
import UserContext from './UserContext'
function HomePage() {
const user = useContext(UserContext)
console.log(user) // { name: 'Tania', loggedIn: true }
return null
}
Yukarıdaki örneğimiz ise useContext direkt olarak erişip, log yazdırabildik.
Kendi Hooks tasarlayın
React components arasında iletişimin sağlanması için props ya da HOC tarafından gerçekleştirildiğine değinmiştik. Bu da bazı sorunlara sebep olmaktaydı. Custom hooks ile bu durumun önüne geçmiş oluyoruz.
Önceki örneğimizde giriş yapan kullanıcının online olmasını ile ilgili geliştirme yapmıştık. Fakat bunu sadece o component içerisinde gerçekleştirdik. Diğer component ‘ler tarafından kullanmak istediğimizi varsayalım. Bu durum için önce abonelik ile ilgili işlemleri “useFriendStatus” methoduna alalım.
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
Yukarıdaki örneğimizde “friendID” parametresi ile status bilgisini return ediyoruz ve görmüş olduğunuz gibi useState ve useEffect gibi hooks kullanmaktayız. Bu kısımdaki parametre ve return edilecek değerler tamamen bizim ihtiyacımıza yönelik değişebilir. Yani kendi ihtiyacınıza göre parametre ve return değerlerinizi değiştirebilirsiniz.
React ‘ın custom hook olduğunu anlayabilmesi için function name “use” başlaması tavsiye edilmektedir.
Custom hook tanımladık. FriendStatus ve FriendListItem component olduğunu düüşünelim. Bu iki component içerisinde nasıl kullanacağımıza göz atalım.
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
Dikkat edilmesi gereken kurallar
- Hooks, JavaScript fonksiyonları içerisinde kullanmayız. React functional component içerisinde kullanınız.
- Component ‘lerin top-level kısmında çağırınız. İç içe geçmiş fonksiyonlarınızda hooks kullanmayınız.
Yukarıdaki iki kuralı projenizde kullanmak isterseniz, eslint-plugin-react-hooks ESLint plugin mevcut. Yüklemek için aşağıdaki kod bloğunu çalıştırınız.
npm install eslint-plugin-react-hooks --save-dev
// Your ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}