1. Project Overview & Quickstart (orhun/LICENSE-APACHE)
LICENSE-APACHE
Open-source repository orhun/LICENSE-APACHE
Repository Details
- Repository: orhun/LICENSE-APACHE
- Primary Language: Code
Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.
2. Official Technical Reference & Guides (orhun/website)
File: README.md
Personel Web Site
Technologies I use
File: package.json
{
"name": "faruk-website",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"package": "svelte-kit package",
"preview": "svelte-kit preview",
"prepare": "svelte-kit sync",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "next",
"@types/node": "^17.0.21",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^3.2.1",
"prettier": "^2.5.1",
"prettier-plugin-svelte": "^2.5.0",
"svelte": "^3.44.0",
"svelte-check": "^2.2.6",
"svelte-preprocess": "^4.10.1",
"tslib": "^2.3.1",
"typescript": "~4.6.2"
},
"type": "module",
"dependencies": {
"@notionhq/client": "^1.0.2",
"@sveltejs/adapter-static": "^1.0.0-next.29",
"@sveltejs/adapter-vercel": "1.0.0-next.46",
"@tailwindcss/typography": "^0.5.2",
"@upstash/redis": "^1.0.1",
"autoprefixer": "^10.4.2",
"dayjs": "^1.11.0",
"mdsvex": "^0.10.5",
"mdsvex-relative-images": "^1.0.1",
"notion-to-md": "^2.3.2",
"postcss": "^8.4.8",
"reading-time": "^1.5.0",
"rehype-figure": "^1.0.1",
"remark-code-titles": "^0.1.2",
"remark-heading-id": "^1.0.0",
"sass": "^1.49.9",
"tailwindcss": "^3.0.23"
}
}
File: tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020", "DOM"],
"target": "es2020",
"importsNotUsedAsValues": "error",
"preserveValueImports": true,
"isolatedModules": true,
"resolveJsonModule": true,
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"allowJs": true,
"checkJs": true,
"paths": {
"$lib": ["src/lib"],
"$lib/": ["src/lib/"]
},
"rootDirs": ["./types"]
}
}
File: src/routes/index.md
Ömer Faruk Oruç
Hi, I am Faruk, a frontend developer. Working at Binalyze
Creator of the open source projects. Github CV, Passwall Extension, Gihub Blog and React Native Notify.
I created mobile apps with React Native for PapyonApps
Find me on Github
Mail me at [email protected]
You can see my full resume
File: src/routes/blog/artik-pythonda-elif-yazmak-zorunda-degilsiniz/index.md
title: Artık Python'da elif yazmak zorunda değilsiniz!
date: 2021-11-26
tags:
- Python
- CPython
- Grammar
Twitterda gezinirken şöyle bir şey gördüm.
Python yüzünden dünya bi data engineer kaybetti
pic.twitter.com/t1W8392Nrz
— Ilter (@koseilteer)
November 20, 2021
Insanların bir keyword yüzünden Pythonu bırakmasına gönlüm razı olmadı (!)
Bu yüzden bu elif keywordunu Python da elseif ile değişmeye karar verdim.
Neden?
Makalenin amacı troll olsa da Python'u kaynak kodu üzerinden derleyip içeride ne döndüğüne bakmak, bu build
aşamalarının ne olduğunu görmek yapmak istediğim bir şeydi. Bir frontend dev olarak bu işler nasıl oluyor bakmak istedim.
CPython'ı Derlemek
Şimdi şunu belirtmekte fayda var, terminalinize gelip python yazdığınızda çalışan şey %90 ihtimalle CPython. Peki ne bu CPython?
En kısa tanımı ile, Pythonun bir implementasyonu. Şöyle ki Python interpreteri de Python kodlarını çalıştıran bir program. CPython ise
bu programın C ile yazılmış versiyonu. Pek çok version yazılmış tabii ki ama en çok kullanılan bu version. CPythonun kaynak kodları
burada bulunuyor. Buradan kendinize bir fork çakıp o forku bilgisayarınıza klonlayın. Ben kendi adıma
klonlamıştım.
git clone https://github.com/bufgix/cpython
cd cpythonMac bir cihaz kullandığım için yapmam gereken şu
brew install openssl xz gdbmsonrasında configure scriptini çalıştıralım
./configure --with-pydebug --with-openssl=$(brew --prefix openssl)daha sonra build almaya geçebiliriz.
make -s -j2-j2 flagı make in aynı anda 2 işi yapmabilmesini sağlar. Eğer 4 çekirdeğiniz varsa -j4 diyebilirsiniz.
Bu noktadan itibaren bilgisayarınızın fan seslerini ve tkinter gibi paketlerinden çıkan hataları görmezden gelebilirsiniz.
İşlem bittiğinde tertemiz Python 3.11 binary si çıkmış olacak. Hemen test edelim
~/cpython on main
❯ ./python.exe
Python 3.11.0a2+ (heads/main-dirty:253b7a0a9f, Nov 26 2021, 15:26:42) [Clang 12.0.5 (clang-1205.0.22.11)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>Nice!
Dile keyword eklemek
Aslında yapacağımız şey dile yep yeni bir keyword eklemek değil. Var olan bir keywordün ismini değiştirmek. Dolayısıyla
işimiz çok daha kolay.
Python dilin syntaxını belirtmek için Grammar/python.gram dosyasını kullanıyor. Dil ile ilgili bir yazımı değiştirmek istediğimizde
bu dosyayı değiştirmek gerekiyor.
Dokümantasyona bakmadan ilk aklıma gelen IDE de elif keywordunu bulup bunu elseif değiştirmekti. 354. satırın oraları şöyle değiştirdim.
if_stmt[stmt_ty]:
| invalid_if_stmt
| 'if' a=named_expression ':' b=block c=elif_stmt {
_PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) }
| 'if' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
elif_stmt[stmt_ty]:
| invalid_elif_stmt
| 'elif' a=named_expression ':' b=block c=elif_stmt {
_PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) }
| 'elif' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
else_block[asdl_stmt_seq*]:
| invalid_else_stmt
| 'else' &&':' b=block { b }if_stmt[stmt_ty]:
| invalid_if_stmt
| 'if' a=named_expression ':' b=block c=elif_stmt {
_PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) }
| 'if' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
elif_stmt[stmt_ty]:
| invalid_elif_stmt
| 'elseif' a=named_expression ':' b=block c=elif_stmt {
_PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) }
| 'elseif' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) }
else_block[asdl_stmt_seq*]:
| invalid_else_stmt
| 'else' &&':' b=block { b }
# 1220 li satırlara doğru ise
invalid_elif_stmt:
| 'elseif' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
| a='elseif' named_expression ':' NEWLINE !INDENT {
RAISE_INDENTATION_ERROR("expected an indented block after 'elif' statement on line %d", a->lineno) }Bu dosyayı her değiştiriğimzde make regen-pegen komutu ile parser.c dosyasının bu grammar file ile
yeniden generate edilmesini sağlamalıyız.
~/cpython on main
❯ make regen-pegen
PYTHONPATH=./Tools/peg_generator python3.9 -m pegen -q c \
./Grammar/python.gram \
./Grammar/Tokens \
-o ./Parser/parser.new.c
python3.9 ./Tools/scripts/update_file.py ./Parser/parser.c ./Parser/parser.new.cGüzel şimdi tekrar build alalım.
make -s -j8Ve o da ne
~/cpython on main ⇡1 !2
❯ make -j8 -s
ld: warning: directory not found for option '-L/usr/local/opt/zlib/lib'
File "<frozen importlib._bootstrap_external>", line 108
elif new_root.endswith(':'):
^^^^
SyntaxError: invalid syntax
make: *** [Python/frozen_modules/importlib._bootstrap_external.h] Error 1
make: *** Waiting for unfinished jobs....
File "<frozen importlib._bootstrap>", line 299
elif hasattr(loader, 'module_repr'):
^^^^
SyntaxError: invalid syntax
make: *** [Python/frozen_modules/importlib._bootstrap.h] Error 1
File "<frozen ntpath>", line 99
elif p_drive and p_drive != result_drive:
^^^^
SyntaxError: invalid syntax
File "<frozen _collections_abc>", line 435
elif not _is_param_expr(t_args):
^^^^
SyntaxError: invalid syntax
make: *** [Python/frozen_modules/ntpath.h] Error 1
File "<frozen posixpath>", line 85
elif not path or path.endswith(sep):
^^^^
SyntaxError: invalid syntax
make: *** [Python/frozen_modules/_collections_abc.h] Error 1
make: *** [Python/frozen_modules/posixpath.h] Error 1
File "<frozen genericpath>", line 149
elif isinstance(s, bytes):
^^^^
SyntaxError: invalid syntax
make: *** [Python/frozen_modules/genericpath.h] Error 1200 IQ hareket . elif i değiştik ama Python internal modullerinde hala elif kullanıyor. Dolayısıyla parser hata çıkartıyor.
Bunun çözmek için elif keywordunu tamamen değişmek yerine elseif i de kullanılabilir yapmak gerekiyor. Dokümantasyonda biraz gezindikten sonra| operatoru ile bunu yapabileceğimi anlıyorum. Aynı yerlerdeki 'elseif' stringlerini ('elif' | 'elseif') bununla değiştiriyorum. make regen-pegen
ve make -s -j8 komutunu çalıştırdığımızada.
~/cpython on main ⇡1 !2
❯ ./python.exe
Python 3.11.0a2+ (heads/main-dirty:36e1a69ba8, Nov 26 2021, 19:37:05) [Clang 12.0.5 (clang-1205.0.22.11)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> if True:
... pass
... elseif False:
... pass
...
>>>Bingo!!
Artık rahatlıkla Python kullanabilirsiniz.
Kaynaklar
- https://devguide.python.org/grammar/
- https://realpython.com/cpython-source-code-guide/
- https://isidentical-archive.github.io/cpython-yeni-operator.html
File: src/routes/blog/building-bulletproof-react-native-applications/index.md
title: Kurşun Geçirmez React Native Uygulamaları Oluşturmak - I
date: 2022-01-01
tags:
- React
- React Native
Evet biliyorum clickbait başlık ama son zamanlarda (7 ay) React Native
ile baya bi' uygulama geliştirdim. Bu süreçte öğrendiğim bazı şeyleri paylaşmak istiyorum.
Burada anlatacağım her şey belki en doğru yöntem değil fakat React Native projelerimi
bu şekilde oluşturuyorum ve şimdiye kadar sorun yaşamadım.
Bu yazıda gelir getirmesi istenen bir uygulamanın
uygulama-içi-satın-alma, abonelik, metadata, build aşamaları, markete vermeden update etme gibi
konuları ele alacağım. Bunun yanında React tarafında
store yapısı, hookların efektif kullanımı, API dizaynı gibi başlıklardan da bahsetmek istiyorum.
Projeyi Oluşturmak
Bir React Native uygulaması oluşturmanın birden fazla yolu var. Bunlardan biri
Expo kullanmak. Expo kullanmak yeni başlayan biri için oldukça kolay bir yol olabilir.
Fakat kişisel olarak ben artık tercih etmiyorum nedenleri ise:
- Uygulamanın boyutunu fazla büyütmesi (Bir QR kod okuyucu yapmıştım ve production
buildi 67mb olmuştu :D. Bunu azaltmanın yolları var tabi ama o toplara girmeyi tercih etmiyorum) - Bazı native modulleri Expo üzerinden kullanamamak.
Bunlar dışında çok da yok aslında ama normal React Native kurulumuyla başlamak bana
daha fazla kontrol sağlıyor. (Expo kullanıp memnun olanlar da var tabi. Buradan
okuyabilirsiniz. Anlaşılan Expo'nun yeni sürümleri yukarda bahsettiğim sorunları çözüyor. Yakında yeniden denerim kendisini.)
Typescript
Evet bu zımbırtı olmadan ben artık uygulama yazmıyorum. İlerde anlatacağım çoğu şeyde Typescript
yardımını kullandığım için Typescriptin olmaması kabul edilemez. Unutmayın amacımız kurşun geçirmeyen,
sürdürülebilir bir uygulama oluşturmak. Typescript bu konuda baya kolaylik sağlıyor.
bu yüzden React Native uygulamasını Typescript ile kurmanızı tavsiye ederim.
$ npx react-native init BestAppEver --template react-native-template-typescriptDetaylı kurulumu tabii ki React Native'in kendi sitesinden
bulabilirsiniz.
Ayrıca ilerde proje içi tipler için bir *.d.ts dosyanızı oluşturmanızı tavsiye ederim.
declare global {
type MyType = {
name: string;
//...
};
}Bu dosyayı oluşturduktan sonra import etmeye gerek kalmadan tiplerinizi kullanabileceksiniz. Fazlalık
importların oluşmasını önlemek için güzel bir yöntem.
Dizin Yapısı
❯ tree -L 2 src -C --dirsfirst
src
├── api
│ ├── queries
│ └── index.ts
├── assets
│ ├── fonts
│ └── images
├── components
│ ├── icons
│ ├── Button.tsx
│ ├── Card.tsx
│ ├── CheckBox.tsx
│ ├── Page.tsx
│ ├── Text.tsx
│ └── index.ts
├── icons
│ ├── single-man.svg
│ ├── support-human.svg
│ └── video-player-movie.svg
├── lang
│ ├── en.ts
│ ├── index.ts
│ └── tr.ts
├── navigation
│ └── index.tsx
├── store
│ ├── app.slice.ts
│ └── index.ts
├── styles
│ ├── colors.ts
│ ├── fonts.ts
│ ├── helpers.ts
│ ├── index.ts
│ └── spacing.ts
├── typings
│ └── index.d.ts
├── utils
│ ├── hooks
│ ├── constants.ts
│ └── index.ts
├── views
│ ├── Main
│ ├── ScreenOne
│ └── ScreenTwo
└── bootstrap.tsx
32 directories, 92 filesGenel olarak böyle bir dizin yapısı kullanıyorum. Kısaca bir özet geceyim, daha sonra daha detaylı inceleyelim.
apiburada backend servisi ile haberleşmek için gerekli dosyalar bulunur.Axios,Apollogibi kütühanelerin örneklemeleri burada bulunurassetsburada uygulamada kullanılacak resimler ve fontlar gibi static dosyalar bulunurcomponentsburada uygulama bileşenleri bulunurTercihen ben bu dizin içinde bir de
index.tsekleyip tüm componentleri buradan exportluyorum. Bu, komponenti
kullanırken daha temiz bir kod sağlıyortsexport { default as Page } from './Page'; export { default as Text } from './Text'; export { default as Card } from './Card'; export { default as Button } from './Button';kullanırken
tsimport { Button } from '@/components';iconsBurada svg tipinde iconlar bulunur.langBurada dil objeleri bulunurnavigationBurada sayfaların navigasyondaki yollarını belirleyen tanımlamalar bulunurstoreBurada uygulamanın store dosyaları bulunurstylesBurada renkler, fontlar ve margin-padding değerleri bulunur.typingsBurada uygulamamız için tip tanımlamaları bulunurutilsBurada hooklar, yardımcı methodlar ve sabit değelerin olduğu dosyalar bulunurviewsBurada uygulamada kullancağımız sayfalar bulunurbootstrap.tsxBu dosya uygulamamızın kök komponenti. Genelde burada uygulama açılmadan önce yüklencek
asenkron işlemler(network requestleri, local databaseden inital verilerin çekilmesi vb.) yapılır. Provider'ları da
bu dosyaya yazarım ve genelde şuna benzer:
Eğer SplashScreen kullancaksınız bütün asenkron işlemleri yaptıktan sonra Splash ekranını bu component içinde kaldırabilirsiniz.
Örnek olarak:
import React, { useEffect } from 'react';
import SplashScreen from 'react-native-splash-screen';
import RootStackScreen from './navigation';
export default function Bootstrap() {
useEffect(() => {
setupApp().then(() => SplashScreen.hide());
}, []);
return <RootStackScreen />;
}Iconlar ve SVG
Yaptığım uygulamaların hiçbirinde tasarımı ben yapmadım elimde her zaman bir Figma dosyası oldu. Figmada
iconları .svg olarak export alabildiğimden kolayca React Native içerisinde kullanbiliyordum.
Bunun için kullandığım araç SVGR. SVGR nin amacı, svg dosyalarını benim için React componentlerine
dönüştürmek.
SVGR kurulumu
$ yarn add @svgr/cli --dev
$ yarn add react-native-svgGerekli kütüphaneleri kurduktan sonra npm scriptleri yardımıyla icon ekleme işlemini otomatikleştiriyorum
"scripts": {
//...
"svgr": "svgr ./src/icons -d ./src/components/icons --native --no-dimensions --typescript --svgo-config .svgorc.json",
}Kısaca yaptığı işi açıklamak gerekirse, svgr ye ./src/icon dizinini okumasını --native parametresi ile react-native-svg
e uygun işlem yapmasını, --no-dimesion ile width ve height değerlerini kaldırmasını, --typescript ile Typescript uyumlu
olmasını, --svgo-config config dosyasını yolunu ve sonunda oluşturduğu çıktıları ./src/components/icons dizine
çıkartmasını istediğimizi belirttik. Config dosyasında ise basitçe:
{ "plugins": [{ "removeViewBox": false }] } // viewboxu kaldırmak için plugin kullandıkŞimdi scripti çalıştırdığımızda
❯ yarn svgr
yarn run v1.22.10
$ svgr ./src/icons -d ./src/components/icons --native --no-dimensions --typescript --svgo-config .svgorc.json
src/icons/diamond.svg -> src/components/icons/Diamond.tsx
src/icons/button-refresh.svg -> src/components/icons/ButtonRefresh.tsx
src/icons/arrow-left.svg -> src/components/icons/ArrowLeft.tsx
src/icons/heart.svg -> src/components/icons/Heart.tsx
src/icons/reload.svg -> src/components/icons/Reload.tsx
src/icons/office-file.svg -> src/components/icons/OfficeFile.tsx
src/icons/support-human.svg -> src/components/icons/SupportHuman.tsx
✨ Done in 0.98s.Daha sonra istediğim iconu herhangi bir yerde kullanmak çok kolay
import * as Icons from '@/components/icons/';
<Icons.ArrowLeft height={15} width={15} color="red" />;Boyutlar, Boşluklar
Elinizde bir tasarım varsa tasarımda elemente gelip width, height değerlerini pixel cinsiden olduğu gibi
React Native'e vermek çok mantıklı değil. Çünkü faklı ekranlarda aynı pixel değeri istediğimiz sonuçları vermeyebilir.
Bunu çözmek için kullandığım yöntem ise şöyle:
Diyelim ki referans alıdğınız bir tasarım var
Görüldüğü üzere tasarımın frame width değeri 375px. Dolayısıyla ekrana yerlemiş olan elementler de bu genişlik
değeri baz alınarak yapılmış. Biz de tasarımı uygulamaya geçirirken buna benzer bir yöntem kullanacağız.
Bunun için React Native tarafında PixelRatio kullanarak ekranın genişliğine göre şöyle bir
utility fonksiyon yazabiliriz
import { Dimensions, PixelRatio } from 'react-native';
let screenWidth = Dimensions.get('window').width;
// https://www.npmjs.com/package/react-native-responsive-screen
function widthPercentageToDP(widthPercent) {
// Parse string percentage input and convert it to number.
const elemWidth = typeof widthPercent === 'number' ? widthPercent : parseFloat(widthPercent);
// Use PixelRatio.roundToNearestPixel method in order to round the layout
// size (dp) to the nearest one that correspons to an integer number of pixels.
return PixelRatio.roundToNearestPixel((screenWidth * elemWidth) / 100);
}
/**
* @param size size given in the design
* @returns responsive size of the text
*/
export function responsiveSize(size: number) {
return widthPercentageToDP((size / 375) * 100);
}Daha sonra bu responsiveSize methodunu fontlarda, spacing değerlerinde kullanabilirsiniz.
import { responsiveSize as rs } from '@/utils';
export const size = {
h1: rs(30),
h2: rs(24),
h3: rs(20),
medium: rs(16),
normal: rs(14),
input: rs(16),
regular: rs(14),
small: rs(12),
mini: rs(8)
};API dizaynı
Api dizaynı projeden projeye değişebileceği gibi bir REST API ile çalışıyorsanız endpointleri servis dosyaları şeklinde yazmak mantıklı oluyor.
import Axios from 'axios';
let baseURL = 'https://api.product.com/';
const TOKEN = '<Token_here>';
const client = Axios.create({
baseURL,
headers: {
Accept: 'application/json, text/plain, */*',
Authorization: `TOKEN ${TOKEN}`
}
});
export default client;Diyelim ki API servisiniz size databasede olan araçları veriyor
import HTTPClient from '../HTTPClient';
export type Car = {
name: string;
model: string;
};
export async function fetch(limit?: number) {
const { data } = await HTTPClient.get<Car[]>('cars/');
return data;
}Bunun gibi bir servis dosyası yazmak, hem düzeni sağlıyor hem de okunabilirliği artırıyor.
Side Effects
Bir API isteği yapacağımız zaman loading, error gibi durumları da
yakalamamız ve kullanıcıya bildirmemiz gerekir.
Backend servisini basit bir istek yaptığımızı düşünelim. Sayfa açıldığı zaman
verileri çeksin
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList } from 'react-native';
import * as CarService from '@/api/services/Cars';
export default function Index() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState<CategoryService.Car[]>([]);
useEffect(() => {
setLoading(true);
CarService.fetch()
.then(cars => {
setData(cars);
})
.catch(e => setError(e))
.finally(() => setLoading(false));
}, []);
return (
<View>
{loading && <Text>Loading...</Text>}
{error && <Text>Error!</Text>}
{data && <FlatList .... />}
</View>
);
}Görüldüğü gibi her sayfada en az 3 tane state değişkeni tanımlamamız gerekiyor. Böyle 10 tane sayfanız oluduğunu düşünürseniz sürkeli
bu şekilde tanımlamalar yapmak işkence olacaktır. Bunu daha efektif bir hale getirebiliriz.
React tarafında backend servisinden veri çekerken React Query kullanıyorum.
Basit olarak şöyle
import React from 'react';
import { View, Text, FlatList } from 'react-native';
import { useQuery } from 'react-query';
import * as CarService from '@/api/services/Cars';
export default function Index() {
const { isLoading, isError, data } = useQuery('cars', () =>
CarService.fetch(),
);
return (
<View>
{isLoading && <Text>Loading...</Text>}
{isError && <Text>Error!</Text>}
{data && <FlatList {...} />}
</View>
);
}Yazması gerçekten keyifli oluyor tabi. Fakat React Query sadece "kodu kısaltmak" ile kalmıyor.
Caching, Memorizing, Performance Optimization gibi pek çok farklı sorunu çözüyor.
Store Yapısı
Ben global state için uygulamarımda Redux ve Redux-Toolkit kullanıyorum. Reduxdan önce Mobx,
Mobx-State-Tree gibi kütüphaneler de kullandım. Redux o zamanlar bana bir sürü reducer yazdığın,
bir ton boilerplate içeren kod yazmamın gerektiği, kötü bir kütüphane gibi geliyordu. Son zamanlarda
Redux Toolkit ile birlikte Redux yazmak olukça keyifli hale geldi. Redux Toolkit ile
daha az ve anlaşılır kod ile state'i yönetebildiğimi görünce artık projelerimde Redux kullanmaya
başladım.
Store yazarken dikkat ettiğim 2 şey var. Biri store'u typescript ile doğru ayarlamak.
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
expot type User = {
name: string
}
interface IUserState {
user: User | null;
}
const initialState: IUserState = {
user: null
};
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {
setUser: (state, action: PayloadAction<User>) => {
state.user = action.payload;
},
},
});
export const { setUser } = appSlice.actions;
export default userSlice.reducer;import { combineReducers, configureStore } from '@reduxjs/toolkit';
import UserSlice from './user.slice';
const reducers = combineReducers({
user: UserSlice
});
const store = configureStore({
reducer: reducers
});
export default store;Store tipine doğrudan erişmek için index.d.ts ye yazmak mantıklı.
import store from '@/store';
declare global {
// Oher Types...
// Store Types
type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;
}Ardından React uygulamasına özel store için iki basit hook yazabiliriz.
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;Diğer dikkat ettiğim şey ise. Redux slice'larına özel hooklar yazmak. Mesela yukardaki user
için söyle bir hook yazılabilir.
import { useContext } from "react";
import { useAppDispatch, useAppSelector, User } from "./useStore";
import { setUser as setUserReducer } from "@/store/user.slice";
export default function useUser() {
const dispatch = useAppDispatch();
const user = useAppSelector((state) => state.user);
const setUser = (user: User) => {
dispatch(setUserReducer(user));
};
return { user, setUser };
}
...
// Component içinde
function Index(){
const user = useUser(); // Storedaki user artık burada
return ...
}Burada ilk kısmı sonlandırıyorum. Buraya kadar hep React, React Native üzerinde durdum. İkinci kısımda (eğer üşenmeden yazarsam) build süreçleri, versyonlama,
uygulama içi satın alma ve abonelik gibi konulara gireceğim.
File: src/routes/blog/python-dataclasses/index.md
title: Python Dataclasses
date: 2020-11-01
tags:
- Python
Nedir?
Python 3.7 ile gelen güzel bir yapı olan dataclass'lar, Python geliştiricileri için mükemmel bir yenilik. Bu yazıda elimden geldiğince bu yapıyı anlatmaya çalısacağım.
Efendim dataclass'lar normal class'lardaki kendini sürekli tekrarlayan kodları yazmayı engelleyen, içinde (genelde) veri depolayan classlardır. Aşağıdaki örnekte bir dataclass'ın nasıl tanımladığını görebilirsiniz.
Unutmayın
dataclass'lar Python'un 3.7 sürümünde bulunur.
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: intGörüldüğü gibi dataclass'lar @dataclass decoratoru ile tanımlanır.
Sınıf değişkenlerinin yanında tipinin yazıldığını fark etmişsinizdir. Bu Python'da Type Annotations olarak geçer.
dataclass'lar bunu kullandığından dolayı bilmek önemlidir. Şurada type annotations ile ilgili cok güzel bir makale var.
Bir dataclass, bazı special method'ları sizin için değiştirir. Mesela __str__() methodu otomatik olarak sınıf içindeki değerleri bastıracaktır.
>>> p = Person("Ömer", 19)
>>> p
Person(name='Ömer', age=19)
>>> p.name = "Ahmet"
>>> p.age = 30
>>> p
Person(name='Ahmet', age=30)Eğer biz böyle bir çıktı isteseydik şöyle bir şey yazmamız gerekirdi;
class NPerson:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"{self.__class__.__name__}(name={self.name!r}, age={self.age})"
def __str__(self):
return self.__repr__()Görüldüğü gibi bir değişkeni __init__'den alıp nesne değişkeni yapmak için 3 kere yazmamız gerekiyor.
Ayrıca dikkatli bakarsanız objelerin tam olarak tanımlanmadığını göreceksiniz.
>>> p = Person("Ömer", 19)
>>> p == Person("Ömer", 19)
True
>>> np = NPerson("Ahmet", 30)
>>> np == NPerson("Ahmet", 30)
FalseBunun nedeni dataclass'ların __eq__ methodunu override etmiş olmaları. Normalde Python, obje karşılaştırma yaparken objelerin adreslerini karşılaştırır ama dataclass'lar sınıf içindeki değerleri karşılaştırır. Eğer bunu kendiniz yazmak isteseydiniz;
class NPerson:
[...]
def __eq__(self, other):
if other.__class__ is not self.__class__:
return NotImplemented
return (self.name, self.age) == (other.name, other.age)dataclass'lar bunu bizim için yapar.
dataclass'lara default değerler de verebiliriz;
@dataclass
class Person:
name: str
age: int = 18
>>> p = Person("Ahmet")
>>> p
Person(name='Ahmet', age=18)Not:
dataclass'lar (ve Python) aslında değişkenlerin tipine dikkat etmez. Type annotations sadece okunabilirliği artırır.
from dataclasses import dataclass
from typing import Any
@dataclass
class Person:
name: Any
age: str
>>> p = Person(14, "Foo")
>>> p
Person(name=14, age='Foo')Şu ana kadar hiç fonksiyon yazmadık ama dataclass'da fonksiyon yazmak ile normalde yazmak arasında hiç fark yoktur.
from dataclasses import dataclass
from typing import List
@dataclass
class Student:
name: str
age: int
results: List[int]
def average(self):
return sum(self.results) / len(self.results)>>> st = Student("Ömer", 19, [85,97,67])
>>> st.average()
83.0Biraz @dataclass decoratoru hakkında konuşalım. @dataclassdecoratoru birçok parametre alabilir.
@dataclass
class Foo:
[...]
# Aslında buna eşittir.
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class Foo:
[...]init: eğerTrueise__init__fonksiyonunu override eder.repr: eğerTrueise__repr__fonksiyonunu override eder.eq: eğerTrueise__eq__fonksiyonunu override eder. Bu konuya yukarda değinmiştik.order: eğerTrueise (varsayılanFalse)__lt__,__le__,__gt__ve__ge__fonksiyları override eder. Bu fonksiyonlar karşılaştırma fonksiyonlarıdır.dataclass,__eq__'de olduğu gibi class değerlerini karşılaştırır.frozen: eğerTrueise nesne oluşturduktan sonra gelen değer atamalarıFrozenInstanceErrorhatasını raise edecektir.
from dataclasses import dataclass
from typing import List
@dataclass(frozen=True)
class Student:
name: str
age: int
results: List[int]
def average(self):
return sum(self.results) / len(self.results)>>> st = Student("Ömer", 19, [85,97,67])
>>> st.name = "Ahmet"
---------------------------------------------------------------------------
FrozenInstanceError Traceback (most recent call last)
<file> in <module>
----> 1 st.name = "Ahmet"
<string> in __setattr__(self, name, value)
FrozenInstanceError: cannot assign to field 'name'dataclass ile kalıtım da yapabiliriz.
from dataclasses import dataclass
from typing import List
@dataclass
class Person:
name: str
age: int
def say(self, s):
print(f"{self.name}: {s}")
@dataclass
class Student(Person):
results: List[int]
def average(self):
return sum(self.results) / len(self.results)>>> st = Student("Ömer", 19, [85,97,67])
>>> st.say("Hello world")
Ömer: Hello worldAlternatifler
dataclass'ların (genellikle) veri depoladığını söylemiştik. Bunu Python'da sadece dataclass'ların yapmadığını görmüşsünüzdür. Basit veri yapıları olan tuple ve dict de veri depolar.
person_tuple = (19, "Ömer") # Tuple
person_dict = {'age': 19, 'name': 'Ömer'} # DictAma dikkat ederseniz dataclass'lar kadar kullanışlı olmadığını görürsünüz. Mesela tuple'de argumanların yerlerini karıştırabilirsiniz debug ederken bu işinizi çok zorlaştır. dict de ise dataya erişmek için mutlaka bir key'e ihtiyaç vardır.
person_dict['name'] ## person_dict.name desek daha hoş olmaz mı?Aslında
dictveri tipindeki objelerin verilerine nokta(.) notasyonu ile erişebiliriz. Şöyle:
Konu ile ilgili içerik Sözlük Veri Tipini Python Nesnesine Dönüştürme
class NDict(dict):
def __init__(self, *arg, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
super().__init__(*arg, **kwargs)
person = NDict(name="Ömer", age=19)
person.age # 19
person.name # ÖmerTabi bunun ne kadar zahmetli olduğunu görüyorsunuz. Ama durun yukarıdaki kodun daha iyisini yapan bir veri yapısı var zaten. namedtuple
from collections import namedtuple
Person = namedtuple("Person", ['age', 'name'])
person = Person(16, "Ömer")
person
# Person(age=16, name='Ömer')
person.name
# ÖmerE dataclass'lardan farkı ne bunun?
Öncelikle dataclass'ların çok daha fazla özelliği buluyor. Yukarıda anlattığım kalıtım ve fonksiyon ekleme işlemleri namedtuple'de çok daha zor. Öte yandan karşılaştırma yaparken namedtuple istediğinizi vermeyecektir. Yukarıdaki örnekten devam edelim
>>> person == (16, "Ömer")
Trueİyi bir şey gibi gözükse de sonuçta kendi türünde olmadığını zannettiğimiz objelerle tam anlamıyla doğru karşılaştırmalar vermiyor.
Ayırca namedtuple obje oluştuktan sonra verilerin değişmesine izin vermeyecektir.
Person = namedtuple("Person", ['age', 'name'])
person = Person(16, "Ömer")
person.age = 22
----------------------------------------
AttributeError Traceback (most recent call last)
<file> in <module>
3 Person = namedtuple("Person", ['age', 'name'])
4 person = Person(16, "Ömer")
----> 5 person.age = 22
AttributeError: can't set attributefield()
Bir seneryo üzerinden devam edelim.
from dataclasses import dataclass
from typing import List
@dataclass
class Student:
id: int
name: str
@dataclass
class Lesson:
students: List[Student]Buradan yeni nesneler üretelim
omer = Student(1, "Ömer")
bersu = Student(2, "Bersu")
math = Lesson([omer, bersu])
print(math)
# Lesson(students=[Student(id=1, name='Ömer'), Student(id=2, name='Bersu')])Şimdi Lesson sınıfına default deger vermeyi deneyelim. Bunu yaparken bir factory fonksiyon yazalım.
NAMES = ["Ömer", "Ahmet", "Cem", "Zehra", "Büşra", "Bersu"]
def collect_students():
return [Student(i + 1, v) for i, v in enumerate(NAMES)]
collect_students()
# [Student(id=1, name='Ömer'),
# Student(id=2, name='Ahmet'),
# Student(id=3, name='Cem'),
# Student(id=4, name='Zehra'),
# Student(id=5, name='Büşra'),
# Student(id=6, name='Bersu')]Teoride Lessona varsayılan değer vermek için şöyle yaparsınız.
@dataclass
class Lesson:
students: List[Student] = collect_students()Böyle bir tanım Python'ın en büyük anti-pattern'lerinden birisidir: Varsayılan olarak değişken değer kullanmak. Buradaki problem şu ki Lesson'nun tüm versiyonları aynı .students'in varsayılan liste objesini kullanacak. Kısacası bir Lesson'dan herhangi bir Student silindiği vakit Lesson'nun tüm versiyonlarından da silinecek. Aslına bakarsanız dataclass'lar bunun olmasının önüne geçip size ValueError döndürüyor.
---------------------------------------------------
ValueError Traceback (most recent call last)
<file> in <module>
12 name: str
13
---> 14 @dataclass
15 class Lesson:
16 students: List[Student] = collect_students()
\python\python37-32\lib\dataclasses.py in _get_field(cls, a_name, a_type)
725 # For real fields, disallow mutable defaults for known types.
726 if f._field_type is _FIELD and isinstance(f.default, (list, dict, set)):
--> 727 raise ValueError(f'mutable default {type(f.default)} for field '
728 f'{f.name} is not allowed: use default_factory')
729
ValueError: mutable default <class 'list'> for field students is not allowed: use default_factoryBunun önüne geçmek için field methodunun default_factory diye bir parametresi var.
from datacasses import dataclass, field
@dataclass
class Lesson:
students: List[Student] = field(default_factory=collect_students)
Lesson()
# Lesson(students=[Student(id=1, name='Ömer'), Student(id=2, name='Ahmet'), Student(id=3, name='Cem'), Student(id=4, name='Zehra'), Student(id=5, name='Büşra'), Student(id=6, name='Bersu')])field, sadece default_factory ile sınırlı değil. Bu bağlantıdan diğer parametrelere ve ne işe yaradıklarına ulaşabilirsiniz.
Optimizasyon
Bahsedeceğim şey __slots__, __slots__ kısaca, sınıflara dinamik olmayan sabit attributelar belirleyerek RAM'dan ve hızdan tasarruf sağlıyor. __slots__, kendi başına ele alınması gereken bir konu oluğu için şuradan daha fazla bilgiye ulaşabilirsiniz.
dataclass'larda __slots__ kullanımı normal classlardaki gibidir.
from dataclasses import dataclass, field
@dataclass
class NormalPerson:
name: str
age: int
salary: int
@dataclass
class SlotPerson:
__slots__ = ['name', 'age', 'salary']
name: str
age: int
salary: intHafızada sahip olduğu büyüklüğe bakalım.
from sys import getsizeof
getsizeof(NormalPerson("Ahmet", 33, 3000)), getsizeof(SlotPerson("Ahmet", 33, 3000))
# (32, 36)Ayırca Python'un veriye erişmesi de normal class'lara göre daha hızlıdır.
from timeit import timeit
timeit(setup="slot_p = SlotPerson('Ahmet', 33, 3000)", globals=globals())
# 0.012656699999752163
timeit(setup="normal_p = NormalPerson('Ahmet', 33, 3000)", globals=globals())
# 0.012095599999156548tabi yazmış olduğumuz sınıfın basitliğinden dolayı aradaki fark oldukça az. Daha büyük sınıflarda bu fark dikkate değer biçimde artıyor.
File: src/routes/blog/use-native-driver/index.md
title: useNativeDriver üzerine
date: 2021-11-01
tags:
- React Native
- Animation
Eğer Raact Native'de animasyon işleriyle uğraştıysanız, animasyonu çalıştırırken useNativeDriver parametresini görmüşsünüzdür. Bu paramereyi ilk gördüğümde düşündüğüm şey, "Sanırım animasyon native kısımda çalışacak" oldu. Evet gerçekten de useNativeDriver animasyonu native tarafda çalıştırıyor. Peki bunu nasıl yapıyor?
Her şeyden önce bir şeyi açıklığa kavuşturalım. React Native, bütün ekranları native olarak oluşturur yani animasyonların da native olarak oluşturulması gerekir. Javascript bir kutunun yerinin değiştirirken değişikliği haber vermek için bir şekilde işletim sistemi ile haberleşmesi gerekir. Javascript ile Native kod farklı threadlarda çalıştığı için (UI Thread) değişikliğin React Native'in sağladığı "bridge" ile işletim sistemine aktarılması gerekir.
Animasyonları JS'ye vermek
Bir animasyon javascript tarafında kabaca şu adımları takip ederek oluşturulur.
- Animasyon başlar
- JS
requestAnimationFramefonksiyonunu çalıştırır. (Bu fonksiyon her saniye 60 kere çalıştırılmaya çalışılır.) - JS değiştirilmek istenen şeyin bir sonraki konumunu/opaklığını/transformunu hesaplar
- JS hesapladığı değişikliği
bridgearacılığıyla işletim sistemine gönderir. - Köprünün diğer ucunda bulunan native kod, (Android için Java, IOS için Obj-C) belirtilen transform işlemini uygular.
- Ekranda yeni değişiklik güncellenir.
Burada dikkat etmeniz gereken nokta şudur; Animasyon devam ederken React re-render işlemi yapmaz. Bunun sebebi Animated API' nin değeri doğrudan native tarafa göndermesidir. Eğer öyle olmasaydı, animasyon değeri bir React state'i gibi olsaydı, 60 saniyede bir component render olacaktı. Bu çok ciddi bir *performans kaybı *olurdu.
Her şey güzel JS animasyonlarımızı güzel güzel oluşturuyor. Fakat...
JS tek thread'da çalışır yani animasyonun bir sonraki değeri hesaplanırken aynı anda başka bir işlem yapamayız. Bu da JS tarafında oluşturulan animasyonun "gecikmeli(laggy)" olmasına sebep olur.
Bir örnek bu gecikmeyi daha iyi anlamanıza yardımcı olabilir.
import React, { useRef } from 'react';
import { SafeAreaView, Animated, TouchableOpacity, Text } from 'react-native';
const App = () => {
const scale = useRef(new Animated.Value(1)).current;
const startAnimation = () => {
Animated.timing(scale, {
toValue: 2,
duration: 600,
useNativeDriver: false // Native dirver devre dışı
}).start();
};
return (
<SafeAreaView style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Animated.View
style={{
height: 100,
width: 100,
backgroundColor: 'red',
borderRadius: 9999,
transform: [{ scale }]
}}
/>
<TouchableOpacity onPress={startAnimation}>
<Text>Press Me</Text>
</TouchableOpacity>
</SafeAreaView>
);
};
export default App;Evet JS güzel bir şekilde animasyonu çalıştırıyor. Biraz daha zorlayalım
const startAnimation = () => {
Animated.timing(scale, {
toValue: 2,
duration: 600,
useNativeDriver: false,
}).start();
setTimeout(() => {
let r = 0
for(let i = 0; i < 1000000000; i++) {
r = i ** i
}
}, 10)Görüldüğü üzere JS threadinda FPS düştü ve animasyon* akıcılığını kaybetti.* Bunun nedeni daha önce dediğimiz gibi scale değerinin diğer kodlarla birlikte JS threadinda çalışması ve diğer kodları beklemesi.
Biraz daha açarsak, animasyon başladığı anda requestAnimationFramesaniyede 60 kere çalıştırılmaya çalışılacak. JS her defasında bir işlemi yapabileceği için
setTimeout(() => {
let r = 0;
for (let i = 0; i < 1000000000; i++) {
r = i ** i;
}
}, 10);bu kod JS threadi meşgul edecek bir bir sonraki animasyon karesi atlanacak. Sonuç olarak "smooth" olmayan animsayonlar oluşacak.
Neyse ki React Native, native tarafta animasyonları oluşturmamıza izin verir.
Animasyonları native tarafta çalıştırmak
Yukardaki problemi basitçe useNativeDriver parametresini true yaparak çözebilirsiniz
Animated.timing(scale, {
toValue: 2,
duration: 600,
useNativeDriver: true
}).start();Peki bu nasıl oluyor? Kabaca şöyle;
- Animasyon başlar
- JS animasyon için gerekli olan önbilgiyi "bridge" aracılığıyla aktarır
- Native tarafta animasyon oluşturulur.
Bu şekilde animasyon hesaplamalarını JS threadinden ayırdık. Böylelikle hem daha hızlı animasyonlar yaptık hem de React Native Bridge'i darboğaz yapmamış olduk.
Bu noktada aklınıza şu gelebilir; "E o zaman useNativeDriver'i her zaman açalım"
Animasyonları native tarafta çalıştırmanın bazi eksileri vardır.
- Animasyonlar üstünde daha az yetkiye sahip olursunuz. (JS, animasyon devam ederken olan biteni gözlemleyemez.)
- Daha az özeliği manipüle edebilirsiniz. Mesela
heightveyawidthözeliklerini native olarak animate edemezsiniz.
Sonuç olarak
Animasyonların React Native'de nasıl çalıştığını, hangi aşamalardan geçtiğini, useNativeDriver'in ne olduğunu, ne işe yaradığını göstermeye çalıştım. Umarım paylaştığım bilgiler yararlı olur.
File: .github/workflows/main.yaml
name: Fetch Bookmarks
on:
push:
branches:
- master
schedule:
- cron: '0 */6 * * *'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x]
steps:
- name: 🍽️ Get working copy
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Set up node.js
uses: actions/setup-node@v2
with:
node-version: '12'
- name: Install deps
run: yarn install --ignore-engines
- name: Update bookmarks
env:
RAINDROP_API_KEY: ${{ secrets.RAINDROP_API_KEY }}
run: yarn fetch:bookmarks
- name: Deploy
run: |
git config user.name "bufgix"
git config user.email "[email protected]"
git add .
git commit -am "feat(auto generate): Updated content"
git push --all -f https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git