Skip to content

Latest commit

 

History

History
1191 lines (843 loc) · 38.5 KB

day6.md

File metadata and controls

1191 lines (843 loc) · 38.5 KB

Kodluyoruz Earlybird Front-End Talent Bootcamp

GÜN 6 - 2021.01.10

Bu bölümde;

konularından bahsedeceğiz.


Fronted için deployment servisleri

Ürettiğimiz React projelerimizi internete açmak istediğimiz zaman bu projeleri uzak bilgisayarlarda her zaman çalışacak şekilde servis etmeliyiz. Bu işi yaparken kendi bilgisayarımızdan ngix gibi servisler ile bunu çözbilsek de 7/24 bilgisayarımızı bu işe ayıracamayağımızdan bu işi yapabilceğimiz servislere yöneliyoruz. Şimdi bu servislerden bir kaçını göreceğiz.

Amaç React projemiziden çıkan build dosyasını yayınlamak.

Surge.sh

https://surge.sh/

  • Surge İle deploy yapmadna önce manuel build almamız gerekiyor.
  • Kullanımı gayet basit.

img-name

img-name

Netlify

Netlify'a tüm github repolarınıza erişsin diye izin verebilirsiniz. Ama isterseniz sadece istediğiniz repolara erişimini de sağlayabilirsiniz.

img-name

Netlifay'a izin verdiğiniz repolar sizin için listelenecek istediğiniz repoyu seçip ilerleyebilirsiniz.

img-name

Projeneizi build etmek için kullandığınız komudu buraya girebilirsiniz ama genellikle default olan sizin işinizi görecektir.

img-name

Projenizde kullnadığınız ENV variable'ları da buraya ekeleyebilirsiniz.

img-name

img-name

Bu işlem de bittikten sonra ilk deployunuz alınmaya başlayacak. Bundan sonrasında siz her commit attıktan sonra sizin için yeni bir deploy alıp paylaşılacak. Yapılan her deployun live halini de netlify üzerinde bulabilirsiniz.

img-name

Ayrıca github üzerinde deployment durumlarını da sizin için monitör ediyor olacak.

img-name

Ayrıca deployment'larınzdan custom olarak bildirim almak için de çözümleri mevucut.

img-name

Web hook denemesi yapmak için kullanabilceğiniz bir servis. Hookbin

Bu servis ile webhook'ları test edebiliyoruz.

img-name

Custom DNS

Netlify üzerine paylaşılan deploy'ları kendi domainiz içinde gösterebilirsiniz. Yapmanız gereken tek şey domain'inize gerekli DNS kaydlarını eklemek.

img-name

img-name

img-name

Netlify Redirect Hatası çözümü

ParcelJS Nedir?

https://parceljs.org/

Daha optimize deployment almamıza yarayan bir araç.

img-name

Adem ilter'in konu ile ilgili anlatım videosu.

Backend Deployment

Digitalocean

Şimdi dün oluşturmuş olduğumuz backend'imizi digitalocean üzerine yükleyeceğiz. (deploy edeceğiz.)

Bunu yapmak için önce digitalocean üzerinden bir droplet açmamız gerkiyor. Bu kısımda en düşük opsiyonlar ile bir sunucu ayağa kaldırıyoruz.

imgName
5 dolarlık sunucuyu seçiyoruz.

ubuntu s adında bir droplet hazırladık şimdi bu sunucu üzerinde çalışmaya başlayacağız.

imgName

Dropletimizin ip'sine ssh ile terminal üzerinden şu şekilde bağlanıyoruz.

Sunucumuz içinde nodejs kurulu değil.

imgName

Sunucumuz içine nodejs'si kuruyoruz.

Kurulumu buradaki referans ile gerçekleştiriyoruz

# Using Ubuntu
curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs

Bu adımlardan sonra sunucumuz üzerindeh hem node ve hem de npm var olmuş oluyor.

root@ubuntu-s:~# npm -v
6.14.11
root@ubuntu-s:~# node -v
v14.16.0

Şimdi deploy etmek istediğimiz dosyaları sunucumuza yüklüyoruz. Yükleme işlemini yaparken git ile isteneilen repo clone edilebilir.

Ardından npm install'ı çalıştırarak gerekli paketleri sunucumuza kuruyoruz.

imgName

node index.js 

diyerek serverimizi çalıştırıyoruz.

imgName

Test edelim...

imgName

Evet sunucumuz çalışmakta.


pm2

Fakat bi sorun var biz uzak sunucudan bağlantımızı kesince server çalışmayı durduracak. Bu durumun önüne geçmek için kullancağımız aracın ismi pm2

Bu araç ile birlikte sunucudan çıksanız (terminal'i kapatsanız) bile görevler arka planda çalışamaya devam ediyorlar.

pm2'yu kuruyoruz.

npm i -g pm2
  • pm2 start index.js dediğimizde çalışmaya başlıyor

imgName

  • pm2 status ile de o an çalışan prosessleri görebiliyorsunu.

imgName

imgName

görüldüğü üzere hala çalışıyor.

  • pm2 logs

servermizde alakalı bir log tuyor olacağız

imgName

  • pm2 stop index.js

process'i durdurmak istersek

imgName

  • pm2 start index.js --name prod

bu şekilde tanımladığımızda farklı bir isimle görüntüleyebliyorz.

imgName

eğer kullanmadığınız process'leri silmek isterseniz;

pm2 delete index

index = processin ismi

imgName

Bu kodda bir güncelleme yapmak istersek nasıl yapabiliriz?

Ya kodu tekrar clone etmek gerekir ya da github actions ile bir otomasyon kurup kodunuz buraya otomatik olarak deploy edilmesini sağlayabiliriz.

Port ile ilgili bir sorunumuz oluyor mu 4000 portu dışında bir yerde çalışmasını nasıl sağlayabiliriz? - Nginx

burdada bir reverse proxy kullanmamız gerek 80 (http defautl port) protuna gelen istekleri 4000 portundan karşılamaız gerekiyor bunu nginx kullanarak halledebiliriz.

sudo apt install nginx

how-to-install-nginx-on-ubuntu

  • nginx'i kurduktan sonra

imgName

serverimizin (4000 portu) default path'de (80 portu) çalışmasını istiyoruz. Bunu yapabilmek için bir ayar yapmamız gerekiyor.

vi /etc/nginx/sites-available/default

how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04

proxy o gibi davran demek

. . .
    location / {
        proxy_pass http://localhost:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

/etc/nginx/sites-available/default dosyası içindeki location kısmını bu şekidle güncelledik.

https://gist.github.com/meseven/1bf1b903bd2c30e84d561ed889ce6698

root@ubuntu-s:~/sources/day5/1-formik/backend# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

bir sorun var mı diye test ettik.

bu noktada nginx'i restart etmemiz gerek

service nginx restart

ardından default port'da uygulamamızın çalıştığını görmekteyiz.

imgName

Neden bu port yönlendirmesini yaptık?

DNS servisine 4000 portu ile bir A kaydı girmemiz mümkün olmadığından serverimizin 8080 yani default http portunda çalışmasını sağladık.

Özet

1- Digitalocean üzerinden bir droplet oluşturduk.

2- NodeJs'i sunucumuza yükledik.

3- Github'dan projeyi sunucumuz'a çektik. Ve npm paketlerinin kurulumunu yaptık.

4- Projeyi orda ayağa kaldırdırk. Serverimiz çalışıyordu.

5- Serverimiz sadece terminalimiz ayaktaiken çalışıyordu.

6- Bu sorunu gidermek için de pm2 adlı aracı kuruduk. Bu sayede server arka planda çalışabilir hale geldi.

5- Serverimiz 4000 protunda çalışıyordu bunu çözmek için ng

6- Bunu çözmek için nginx'i kurduk ve sunucumuzu default portu 4000 portuna yönlendirdik.

Bu deployment işlemlerini otomatize hale getirmek için

https://dev.to/chathula/how-to-set-up-a-ci-cd-pipeline-for-a-node-js-app-with-github-actions-32h0

Bir backend server ayakta tutmak için ne kadar kapasitede bir sunucuya ihtiyacımız var?

Buna deneme yanılma yöntemi ile karar verebilirsiniz. Digitalocean üzerinde sistemi monitör edebilir ve aşırıya kaçan noktalarada dikey bir genişlemeye gidebilirsiniz.

Ayrıca yatay genişleme yapmak için optimizasyona başvurabilirsiniz. Load blancer'lar sizin bu noktada işinizi görecektir.

https://mehmetseven.net/nginx-ve-nodejs-ile-load-balancing/

Bu konu ile ilgili load balencer algoritmaları incelenebilir. (Round robin algoritması bknz açıklamsı: hasantezcan.dev/round robin algoritması)


Heroku deployment

https://devcenter.heroku.com/articles/deploying-nodejs

Heroku ile yapcağımız deployment'ler nispeten digitalocean'a göre daha kolayca gerçekleşecektir.

İçinde sadece backend'imizin buldunğu bir repo ile işe koyuluyoruz.

Port sorunu

imgName

Bu hali ile deployment işlemini yaptığımızda heroku bize "application error" verecektir. Bunun sebebi projeyi hangi portta ayağa kaldıracağını bilememsidir. Yapacağımız güncelleme ile birlikte eğer bir ortam değişkeni varsa onu kullan yoksa 3000 portunu kullanması gerektiğini söyleyeceğiz.

app.listen(process.env.PORT || 3000, () => console.log("Server is up!"));

imgName

Bu hatayı çözdükten sonra deployment işlemine başlıyoruz.
Herokuya deployment yapmanın iki yolu mevcut biri direk CLI üzerinden biri de GUI üzerinden. Biz şimdi bu örnekte CLI üzerinden bu işlemlere devam edeceğiz.

Öncelikle herokuya login olmamız gerek.

Tabi önce bilgisyarınıza herokunun CLI uygulamasını yüklemelisiniz.

Ardından komut satırına;

heroku login

diyoruz ve bizi bir web sayfasına yönlendriyor ve oradan login olmamızı istiyor bu noktadan sonra login olmuş şekilde kendi hesabımız ile çalışabileceğiz.

imgName

Ardından bu uygulamanın heroku içinde çalışabilmesi için bir app oluşturmamız lazım bunu yaparken terminal'e

$ heroku create
Creating app... done, ⬢ stormy-caverns-47195
https://stormy-caverns-47195.herokuapp.com/ | https://git.heroku.com/stormy-caverns-47195.git

yazıyoruz ve o bizim için gerekli tüm kurumları ayarlıyor.

Bize verdiği aderese gittiğimizde boş bir heroku projesi görmekteyiz

imgName

Bu arada bu adımı direk GUI izerinden de yapabilirdik.

imgName

Gördüğümüz üzere şu an ayakta olan sadece boş bir proje şimdi kodlarımızı buraya göndermemiz gerekli.

$ git push heroku main

Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Delta compression using up to 8 threads
Compressing objects: 100% (12/12), done.
Writing objects: 100% (13/13), 7.40 KiB | 2.47 MiB/s, done.
Total 13 (delta 3), reused 4 (delta 0), pack-reused 0
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Building on the Heroku-20 stack
remote: -----> Determining which buildpack to use for this app
remote: -----> Node.js app detected
remote:        
remote: -----> Creating runtime environment
remote:        
remote:        NPM_CONFIG_LOGLEVEL=error
remote:        NODE_VERBOSE=false
remote:        NODE_ENV=production
remote:        NODE_MODULES_CACHE=true
remote:        
remote: -----> Installing binaries
remote:        engines.node (package.json):  unspecified
remote:        engines.npm (package.json):   unspecified (use default)
remote:        
remote:        Resolving node version 14.x...
remote:        Downloading and installing node 14.16.0...
remote:        Using default npm version: 6.14.11
remote:        
remote: -----> Installing dependencies
remote:        Installing node modules
remote:        added 52 packages in 1.047s
remote:        
remote: -----> Build
remote:        
remote: -----> Caching build
remote:        - node_modules
remote:        
remote: -----> Pruning devDependencies
remote:        audited 52 packages in 0.774s
remote:        found 0 vulnerabilities
remote:        
remote:        
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote:        Procfile declares types     -> (none)
remote:        Default types for buildpack -> web
remote: 
remote: -----> Compressing...
remote:        Done: 32.6M
remote: -----> Launching...
remote:        Released v3
remote:        https://stormy-caverns-47195.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/stormy-caverns-47195.git
 * [new branch]      main -> main

Bu adamında localimizdeki kodu direkt herokuya push ediyoruz.

heroku create dediğimizde bizim için bir uzak repo oluşturmuşu ve şimdi biz bu repoya kodlarımı manuel olarak yüklemiş olduk.

Bu projeye bağlı uzak repoları görmek için

$ git remote -v

heroku  https://git.heroku.com/stormy-caverns-47195.git (fetch)
heroku  https://git.heroku.com/stormy-caverns-47195.git (push)
origin  [email protected]:hasantezcan/kodluyoruz-api.git (fetch)
origin  [email protected]:hasantezcan/kodluyoruz-api.git (push)

orgin olarak githubdaki repomuz tanımlanmış ayrıca herokunun da bu projeye bağlı olduğunu görmekteyiz.

imgName

Github Heroku deploy entegrasyonu

Bu deployment'i github ile entegre etmek isterseniz de heroku GUI üzerinden deployment kısmına github reponuzu bağlayarak github'a her commit attğınızda kodunuzun aynı zamanda heroku üzerine de deploy olmasını sağlayabilirsiniz.

imgName

  • Ayrıca bu kısım üzerinden hangi brach'in deploy olacağına da karar verebilirsiniz.

imgName

Enable Automatic Deploys tuşuna basmayı unutmayın!

  • Bir continues intergaration'unuz mevcutsa onu da beklemesini sağlayabilirsiniz. Böylece testler yapıldıktan (testleri geçtikten) sonra deploy işlemi gerçekleşecektir.

Şimdi denemek için bir commit atalım.

imgName

imgName
Görüldüğü üzere otomaik olark deployumuz alındı

Kodumuz otomatik olarak canlıda!

imgName

Travis CI ve Heroku Entegrasyonu

Şimdi Travis kullanrak bir continues integration yapısı hazırlayacağız.

imgName

Bunun için önce heroku üzerinden Wait for CI to pass before deploy kısmını aktive ettik.

Ve projenin kök dizininde .travis.yml içine bir görev tanımı yazıyoruz.

language: node_js
node_js:
  - 8
before_install:
  - npm install
  - export NODE_ENV=production

imgName

Travis'i limitsiz olarak kullanmak için reponuzu herkese açık hale getirmeniz gerekli. Ardından Travis üzerinden içinden etegrasyon kurmak istediğiniz repoyu aktif hale getirebilirsiniz.

imgName
Kodluyoruz-api projesini travis'e ekledik!

travis-ci.com vs travis-ci.org

Tam bu noktada fark ettiğim bir şey oldu. İki adet travis-ci sitesi mevcut. Birisi travis-ci.com diğeri travis-ci.org

2 Mayıs 2018 tarihinden itibaren Travis CI yapılarının ücretli version dışında gizli repolarda da travis-ci.com altında belli bir sınır dahilinde bedava çalışabilceğini duyurmuş.

travis-ci.org üzerinden ücretsiz şekilde private repolar ile çalışamıyorsunuz.
travis-ci.com üzerinde ise private repolar ile belli bir sınır dahilinde ücretsiz şekilde çalışabiliyorsunuz. Bu sınır aşmanı dahilinde ücretli programlara yönelebilirsiniz. (Travis CI bizlere her ay için private replarda 10000 bedava deploy kredisi tanımlıyor.)

Her deployment'da 10 kredi harcamış oluyorsunuz. Bu da ayda private repolarda ücretsiz olarak kullanabilceğiniz 1000 deployment'a karşılık geliyor!

imgName
Free plan kalan kredi ekranı

  • Fakat bu bedava planı seçmezseniz gizli repolarda CI'larınızı çalıştıramıyorsunuz illa bu free planı seçmeniz gerekiyor.

imgName

imgName
Travis ödeme planları

Ve bu dokümanı hazırladığım tarih itibari ile (25 Mart 2021) kısa süre sonra travis-ci.org'ın kapatılacağını ve tamamı ile travis-ci.com'a geçileceğini söylüyor.

.org dan .com'a geçerken nasıl bir yol izleyeceğinize burada ayrıntılı şekilde anlatmışlar.

imgName

Ben de bu noktadan sonra travis.com üzerinde çalışmaya devam edeceğim...

Bir test yazalım

Serverin ayakta olduğunu test eden bir test yazcağız.

# test/index.test.js

const chai = require("chai");
const chaiHttp = require("chai-http");
const should = chai.should();
const server = require("../index");

chai.use(chaiHttp);

describe("Node Server", () => {
	it("should be have 200 status code", (done) => {
		chai
			.request(server)
			.get("/")
			.end((err, res) => {
				res.should.have.status(200);
				done();
			});
	});
});

Ve ayrıca .travis.yml'a testlerimizi çalıştıracak script'i de eklememiz gerekiyor.

# .travis.yml
script:
  - npm run test

tabiki bu script önceliklte packge.json içinde bulunmalı

## package.json

"scripts": {
		"start": "node index.js",
		"dev": "nodemon index.js",
+		"test": "./node_modules/.bin/mocha --exit --recursive"
	},

imgName
Testimiz geçiyor!

Testimizi çalıştırıdğımızda bir sorun olmadığını görmekteyiz.

Fakat şimdi bu testi bozacağız ve test'de hata aldığımızda travis'in test geçmediğinde bizi uyardığını görebilelim!

Kodumuzu bu hali ile github'a gönderidiğimizde. Deploy olmadığını bize bildiren hatayı göreceksiniz.

imgName
Test geçmediği içi burada travis bizi uyarmakta!

Travis'in sitesinde gittiğimzde ise testin geçmediğini görüyor olacağız.

imgName

Ayrıca travis bize bir sorun olduğunu bildirmek için bir mail de atıyor. Status: Broken

imgName

Testimizi düzenleyip tekar geçer hale getirdikten sonra kodu tekrar pushladığımızda ise bize sorun çözüldü adında bir mail atıyor. Status: FIX

imgName

Ayrıca Mehmet Hocannın bu bloğuna da bakabilirsin.

Contex API

Contex = kaynak - içerik

React'ın çekirdeğinde bulunan bir state management yöntemidir. Componentler arasındaki veri akşını arap saçına dönürmeden (props drilling) sağlamak için state management araçları kullanılır. Bunlardan en popülerleri Redux ve Context API'dır. Redux'ı kullanması Contex API'ye göre biraz daha komplexdir.

Bu bölümde Contex API ile nasıl stateleri idare edebilceğimize bakacağız.

React Props drilling vs Context API image source

Propları en uç köke kadar taşımak zorunda kaldığımız için propları derinlere matkaplamak gibi bir ifade ile bunu ismimlendirmişler.

Şimdi bir contex yapısı kuralım ve bu state mekanizması bir sitenin css temasını idare etsin. Yani dark modda mı ligth modda mı olduğu bilgisini contex'imiz içinden componetlere göndereceğiz.

Contex API'nin mantığı temel de şu şekilde işler bir provider (sağlayıcı) -verileri gönderen bölüm- verileri servis etmek üzere hazır olarak bekler. Eğer bir consumer (alıcı) o veriye ulaşmak isterse provider'dan o veriyi alır ve kullanır. Bu iki birim aralarında bu şekilde bir iletişime sahiptir.

Haydi işe koyulalım.

Kök dizinimizde contexts adında bir klasör oluşturuyoruz.

İçersine ne ile ilgili bir context üreteceksek ona uygun bir isimlendirme ile context'imizi üretiyoruz biz bu örnekte sitenin temasını kontrol edecek bir context yapısı kuracağımızdan dosyanın ismini ThemeContext.js olarak belirliyorum.

Şimdi gelelim bu dosaynın içeriğine;

ilk iş olarak react'ın core'unda bulunan createContext methodu ile bir context üretiyoruz.

import { createContext, useState, useEffect } from "react";

const ThemeContext = createContext(null);

Context'i üretirken içini boş bırakabilceğiniz gibi default bir Context değeri de verebilirsiniz. Bu değer provider çalışmadığı zaman kullanılır.

const defaultContext = {
  theme: light
};

Ardından bir provider oluşturmamız gereklidir. Provider'ı oluşturuken üreteceğimiz methodun ismine ThemeProvider diyebiliriz.(Bunu bir high order componet gibi düşebilirsiniz)

Bu fonksiyon parametre olarak içine gönderilen her şeyi almalıdır. Bu provider'dan faydalanan tüm componetlerin içeriği children ile buraya gelmektedir. Bu sebeple içine { children } propsunu veririz.

Return ederken de ThemeContext.Provider ile birlikte (context'imizin provider'ı asıl olarak burasıdır ThemeProvider olarak isimlendirdiğimiz bölümün bir ehemmiyeti yoktur.) geriye provider içine tanımladığımız değerler (value) ve provider'a gönderilen tüm children'lar ile birlikte geri döndürürüz.

export const ThemeProvider = ({ children }) => {
	const theme = localStorage.getItem("defaultTheme");

	return (
		<ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
	);
};

Hepsi ile birleşmiş halde görmek istersek ise;

// context/ThemeContext.js
import { createContext, useState, useEffect } from "react";

const ThemeContext = createContext(null);

const defaultTheme = localStorage.getItem("defaultTheme");

export const ThemeProvider = ({ children }) => {
	const [theme, setTheme] = useState(defaultTheme || "light");

	const values = {
		theme,
		setTheme,
	};

	useEffect(() => {
		localStorage.setItem("defaultTheme", theme);
	}, [theme]);

	return (
		<ThemeContext.Provider value={values}>{children}</ThemeContext.Provider>
	);
};

export default ThemeContext;

Bu şeklide contextimizi oluşturmuş olduk. Şimdi bu contextimizi componetler içine yerleştirmemiz gerekiyor.

İlk uğratacağımız durak App.js

ThemeProvider highOrder Component'ini ThemeContext'imiz içinden buraya import ediyoruz.

Bu noktadan sonra ThemeProvider Component'ini tüm projeyi saracak şekilde yerleştiriyoruz. Bu sayaede köklere doğru gidildikçe provider'a ulaşmak isteyen tüm componentler ile bir bağ kurmuş oluyoruz.

// App.js
import "./App.css";

import { ThemeProvider } from "./contexts/ThemeContext";
import Container from "./Container";

function App() {
	return (
		<ThemeProvider>
			<Container />
		</ThemeProvider>
	);
}

export default App;

App.js'de yapammız gereken implementasyonu gerçekleştirdik.

Şimdi de componentlerimizden birine gidelim ve oradan contex'deki statelerimize erişmeye çalışalım.

Title componentimizde şu an aktif olan temayı göstermek istiyoruz bunu yapmak için;

Öncelikle React'ın core'unda bulunan useContext Hook'u ile kullanmak istediğimiz context'i de bu componenet'e dahil edip o context içinden ulaşmak istediğimiz value'lere uzanabilriz.

İşte context API'ı kullanmak bu kadar basit.

// src/components/Title.js
import { useContext } from "react";

import ThemeContext from "../contexts/ThemeContext";

function Title() {
	const { theme } = useContext(ThemeContext);

	return <div>Active Theme: {theme}</div>;
}

export default Title;

useContext vs. Consumer

Referance: Dave Ceddia Oct 22, 2020

Component içinden bu context'e ulaşamanın bir yolu daha var. Bu yol nispeten artık daha az kullanılsada görmeniz muhtemel o sebeple görünce bu da ne be! demeyin diye bundan da bahsetmek istedim.

// src/components/Title.js
import React from "react"; 
//EK BILGI: Artık react'ı import etmenize gerek yok!

import ThemeContext from "../contexts/ThemeContext";

function Title() {

  return (
      <ThemeContext.Consumer>
        {theme => <div>Active Theme: {theme}</div>}
      </ThemeContext.Consumer>
    );
}

export default Title;

useContext Hook'u olmadan önce provide ettiğimiz verileri bu şekilde consume ediyoruduk. Şimdi elimizde useContext Hook'u olduğunan ileriye dönük projelerde onu kullanmaızı tavisye ederim.

Genel kullanım bu şekilde..

Custom Context API Hook'ları

Context API with Hocs

Şimdi işleri biraz daha değiştiricez. Context'lerin içinde var olan provider'ları HOCS adlı bir klasör altında toplayacağız. Bu sayede contex ile logic kısmını context'den ayırmış olacağız.

Ayrıca bu kullanım ile birlikte ayrıdığımız provider'lar birer customHook olmuş olacaklar. Component'ler içinde onları useContext ile çağırmak yerine useContex'inAdı şekilde bir kullanım ile çağırmış olacağız. Bu durum kod okunurluğunu bir hayli artırmış olacak.

Biraz soyut kalmış olabilir hızlı bir pratikle bunu deneyelim.

imgName
Practice Progress GIF By Nueva Creative

Şimdi yine bir tema kontrol contex'i kuracağız. Bu aradaki farkı daha rahat görebilmemizi sağlayacak.

Başlangıç olarak yine src dizini altına Context ismi ile bir klasör açıyoruz context'lerimizi bu klasör altında birktireceğiz.

Context'imizi yazmaya başlayalım.

// src/Cotext/Theme.js

import { createContext, useContext } from "react";

const defaultContext = {
  theme: {},
  changeTheme: () => {}
};

const ThemeContext = createContext(defaultContext);

export const ThemeProvider = ThemeContext.Provider;
export const useTheme = () => useContext(ThemeContext);

Evet deminki örnekten farklı olan bir çok şey görüyoruz burada. Hadi bunlara detaylıca değinelim.

1- Bu örnekte useContext'i direk Context içinde kullandığımızı görmekteyiz. Bunun sebebi ThemeContext'i direkt useContext kullnarak export etmemiz.

// src/Cotext/Theme.js

+ import { createContext, useContext } from "react";

const defaultContext = {
  theme: {},
  changeTheme: () => {}
};

const ThemeContext = createContext(defaultContext);

export const ThemeProvider = ThemeContext.Provider;
+ export const useTheme = () => useContext(ThemeContext);

Bunu bu şekilde servis ettiğimiz için componetler içinde context çağırılırken useContext yerine useTheme'i kullanılacak. Bu da kod okunurluğuna katkı sağlayan bir detay.

// App.js - Call Context inside of Component
... 
...
...
import { useInfo } from "../../Context/Info";
import { useTheme } from "./Context/Theme";

import "./App.scss";

const App = () => {
  const { theme } = useTheme();
  const { group } = useInfo();

  useEffect(() => {
    window.send({ event: `PageView` });
  }, []);
...  
...  
...  

Görüldüğü gibi sadece useTheme'i import ederek kullanmak istediği state'lere onun aracılığı ile ulaşıyor.

// App.js - Theme Context'inin kullanımı
// Bir önceki code bloğunun daha da temizlenmiş hali!
import { useTheme } from "./Context/Theme";

const App = () => {
  const { theme } = useTheme();
...  
...  

Diğer kullanımda useContext'i sadece contex'i kullancağımız zaman componet'ler içinde kullanıyorduk!

Nasıl kullandığımızı hatırlayalım!

Bu örneği yukarda da bulabilirsiniz...

// HATIRLATMA - Bir önceki kulanım!
// src/components/Title.js

import { useContext } from "react";

import ThemeContext from "../contexts/ThemeContext";

function Title() {
	const { theme } = useContext(ThemeContext);

	return <div>Active Theme: {theme}</div>;
}

export default Title;

Ayrıca diğer kullanımda bütün logic işlemler context içinde gerçekleştiği için useState, useEffect gibi bir çok hook'u Context içinde kullanmıştık burada o kalabalıktan ziyade sadece useContext kullanıyoruz.

// Bu yöntem !
  import { createContext, useContext } from "react";
// Önceki kulanım !
import { createContext, useState, useEffect } from "react";

2- Peki logic kısmı nerede?

imgName
File Structure

Logic işlemleri kısmı tamami ile Hocs dizini içinde gerçekleşiyor. Bu dizin altında her context'in bir hocs'u oluyor. Hocs'lar içinde provider'lar çalışıyor. İsimlendirmesini de With-theme şeklinde yapıyoruz. Böylece bu dosya, theme context'ine yardımcı gibi bir mana veriyor.

Şimdi gelin With-theme.js yani Theme contex'inin provider'ının içeriğine bakalım...

import { useState } from "react";
import { ThemeProvider } from "../Context/Theme";

const themes = {
  dark: {
    name: 'dark',
    background: '#343b42'
  },
  light: {
    name: 'light',
    background: '#fff'
  }
}

const themeName = localStorage.getItem('theme');

const WithTheme = ({ children }) => {
  const [theme, setTheme] = useState(themes[themeName] || themes.light);

  const changeTheme = () => {
    if (theme === themes.dark) {
      localStorage.setItem('theme', 'light');
      setTheme(themes.light);
    } else {
      localStorage.setItem('theme', 'dark');
      setTheme(themes.dark);
    }
  }

  const props = {
    theme,
    changeTheme,
  };

  return <ThemeProvider value={props}>{children}</ThemeProvider>;
};

export { WithTheme };

Görüldüğü gibi mantıksal operasyonun tümü burada gerçekleşmiş. Öncelikle Contex'in provider'ı içeriye dahil edilmiş.

import { ThemeProvider } from "../Context/Theme";

Sonrasında withTheme high order componet'i içinde işlemler yapılmış ve provider içine taşınacak value'ler yani state'ler ile geri döndürülmüş.

return <ThemeProvider value={props}>{children}</ThemeProvider>;

Ve withTheme provider componet'i bu stateleri kullanacak componet grubunu kapsayacak component'in içinde çağırlmak üzere export edilir.

export { WithTheme };

Bu örnekte provider'larımızı index.js içinde çağırdık. index.js'i en tepede bulunan component olarak düşünebilirsiniz.

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import App from "./App";

import { WithInfo } from "./Hocs/With-info";
import { WithTheme } from "./Hocs/With-theme";

ReactDOM.render(
  <React.StrictMode>
    <WithTheme>
      <WithInfo>
        <App />
      </WithInfo>
    </WithTheme>
  </React.StrictMode>,
  document.getElementById("root")
);

DİKKAT! Context'ler genel olarak App.js içinde çağırıldığını görebilirsiniz. Bu kafanızı karıştırmasın. App.js component'i içinde kullanılan tüm component'leri container adında bir component'e taşıyıp. Tüm provider'ları yine App.js'de çağırabilirdik. Bu sadece yoğurt yeme şekli. Onun dışında değişen bir şey olmuyor.

Sonuç olarak bu şekilde de bir contex yapısı kurmuş olduk. Verdiği sonuç bakımından diğer yöntem ile bu yöntem arasında hiç bir fark yok. Sadece ikinci yöntem biraz daha titiz bir kullanım.

Tercih sizin :)