LICENSE-MIT Documentation

Official TypeScript developer resources and documentation for orhun/LICENSE-MIT. Verified technical context for AI.

0 stars TypeScript TypeScript 1 file · ~46,295 tokens
RAW Doc

1. Project Overview & Quickstart (orhun/LICENSE-MIT)

LICENSE-MIT

Open-source repository orhun/LICENSE-MIT

Repository Details

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

svelte:head

Faruk Oruç

Ö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

İlgili Tweet

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.

bash
git clone https://github.com/bufgix/cpython
cd cpython

Mac bir cihaz kullandığım için yapmam gereken şu

bash
brew install openssl xz gdbm

sonrasında configure scriptini çalıştıralım

bash
./configure --with-pydebug --with-openssl=$(brew --prefix openssl)

daha sonra build almaya geçebiliriz.

bash
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

bash
~/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.

gram {8-10}
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 }

gram {8-10}
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.

bash
~/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.c

Güzel şimdi tekrar build alalım.

bash
make -s -j8

Ve o da ne

bash
~/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 1

200 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.

bash
~/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


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.

shell
$ npx react-native init BestAppEver --template react-native-template-typescript

Detaylı 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.

ts:./src/types/index.d.ts
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ı

shell
❯ 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 files

Genel olarak böyle bir dizin yapısı kullanıyorum. Kısaca bir özet geceyim, daha sonra daha detaylı inceleyelim.

  • api burada backend servisi ile haberleşmek için gerekli dosyalar bulunur. Axios, Apollo gibi kütühanelerin örneklemeleri burada bulunur
  • assets burada uygulamada kullanılacak resimler ve fontlar gibi static dosyalar bulunur
  • components burada uygulama bileşenleri bulunur

    Tercihen ben bu dizin içinde bir de index.ts ekleyip tüm componentleri buradan exportluyorum. Bu, komponenti
    kullanırken daha temiz bir kod sağlıyor

    ts
    export { 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

    ts
    import { Button } from '@/components';
  • icons Burada svg tipinde iconlar bulunur.
  • lang Burada dil objeleri bulunur
  • navigation Burada sayfaların navigasyondaki yollarını belirleyen tanımlamalar bulunur
  • store Burada uygulamanın store dosyaları bulunur
  • styles Burada renkler, fontlar ve margin-padding değerleri bulunur.
  • typings Burada uygulamamız için tip tanımlamaları bulunur
  • utils Burada hooklar, yardımcı methodlar ve sabit değelerin olduğu dosyalar bulunur
  • views Burada uygulamada kullancağımız sayfalar bulunur
  • bootstrap.tsx Bu 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:

tsx:./src/bootstrap.tsx
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

shell
$ yarn add @svgr/cli --dev
$ yarn add react-native-svg

Gerekli kütüphaneleri kurduktan sonra npm scriptleri yardımıyla icon ekleme işlemini otomatikleştiriyorum

json:package.json
"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:

json:./scgorc.json
{ "plugins": [{ "removeViewBox": false }] } // viewboxu kaldırmak için plugin kullandık

Şimdi scripti çalıştırdığımızda

shell
❯ 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

tsx
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

ts:./src/utils/index.ts
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.

ts:./src/style/fonts.ts
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.

ts:/api/httpclient.ts
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

ts:/api/services/cars.ts
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

tsx:/views/cars/index.tsx
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

tsx:/views/cars/index.tsx
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.

ts:/store/user.slice.ts
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;
ts:/store/index.ts
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ı.

ts
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.

ts
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.

tsx
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.

python
from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

Gö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.

python
>>> 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;

python
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.

python
>>> p = Person("Ömer", 19)
>>> p == Person("Ömer", 19)
True
>>> np = NPerson("Ahmet", 30)
>>> np == NPerson("Ahmet", 30)
False

Bunun 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;

python
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;

python
@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.

python
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.

python
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)
python
>>> st = Student("Ömer", 19, [85,97,67])
>>> st.average()
83.0

Biraz @dataclass decoratoru hakkında konuşalım. @dataclassdecoratoru birçok parametre alabilir.

python
@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ğer True ise __init__ fonksiyonunu override eder.

  • repr: eğer True ise __repr__ fonksiyonunu override eder.

  • eq: eğer True ise __eq__ fonksiyonunu override eder. Bu konuya yukarda değinmiştik.

  • order: eğer True ise (varsayılan False) __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ğer True ise nesne oluşturduktan sonra gelen değer atamaları FrozenInstanceError hatasını raise edecektir.

python
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)
python
>>> 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.

python
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)
python
>>> st = Student("Ömer", 19, [85,97,67])
>>> st.say("Hello world")
Ömer: Hello world

Alternatifler

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.

python
person_tuple = (19, "Ömer") # Tuple
person_dict = {'age': 19, 'name': 'Ömer'} # Dict

Ama 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.

python
person_dict['name']  ## person_dict.name desek daha hoş olmaz mı?

Aslında dict veri 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

python
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  # Ömer

Tabi bunun ne kadar zahmetli olduğunu görüyorsunuz. Ama durun yukarıdaki kodun daha iyisini yapan bir veri yapısı var zaten. namedtuple

python
from collections import namedtuple

Person = namedtuple("Person", ['age', 'name'])
person = Person(16, "Ömer")

person
# Person(age=16, name='Ömer')
person.name
# Ömer

E 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

python
>>> 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.

python
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 attribute

field()

Bir seneryo üzerinden devam edelim.

python
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

python
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.

python
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.

python
@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.

python
---------------------------------------------------
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_factory

Bunun önüne geçmek için field methodunun default_factory diye bir parametresi var.

python
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.

python
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: int

Hafızada sahip olduğu büyüklüğe bakalım.

python
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.

python
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.012095599999156548

tabi 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 requestAnimationFrame fonksiyonunu ç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 bridge aracı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.

jsx {11}
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

js {8-13}
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

js
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

js {4}
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 height veya width ö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

text
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