├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .yarnrc.yml ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _document.tsx ├── index.mdx ├── posts │ ├── artik-pythonda-elif-yazmak-zorunda-degilsiniz │ │ ├── index.mdx │ │ └── tw_image.jpg │ ├── bu-siteyi-nasil-olusturdum │ │ ├── img.png │ │ ├── img_1.png │ │ ├── img_2.png │ │ ├── img_3.png │ │ ├── img_4.png │ │ ├── img_5.png │ │ ├── img_6.png │ │ ├── index.mdx │ │ ├── mdsvex.png │ │ ├── svelte_proc.png │ │ └── svelte_routing.png │ ├── building-bulletproof-react-native-applications │ │ ├── design1.png │ │ ├── index.mdx │ │ └── provider.jpg │ ├── gece-sikilmasi-redux-kaynak-kodundan-ognrediklerim │ │ └── index.mdx │ ├── index.mdx │ ├── python-dataclasses │ │ └── index.mdx │ └── use-native-driver │ │ ├── Fixed.gif │ │ ├── Panic.gif │ │ ├── SmoothAnimation.gif │ │ └── index.mdx └── tags │ └── [tag].mdx ├── theme.config.jsx ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import("eslint").Linter.Config} */ 2 | const config = { 3 | root: true, 4 | extends: ['@callstack/eslint-config/react'], 5 | plugins: ['unused-imports'], 6 | env: { 7 | es2022: true, 8 | node: true, 9 | }, 10 | parser: '@typescript-eslint/parser', 11 | ignorePatterns: [ 12 | '**/.eslintrc.cjs', 13 | '**/*.config.js', 14 | '**/*.config.cjs', 15 | '.next', 16 | 'dist', 17 | 'pnpm-lock.yaml', 18 | ], 19 | reportUnusedDisableDirectives: true, 20 | rules: { 21 | 'no-unused-vars': 'off', 22 | 'unused-imports/no-unused-imports': 'error', 23 | 'unused-imports/no-unused-vars': [ 24 | 'warn', 25 | { 26 | vars: 'all', 27 | varsIgnorePattern: '^_', 28 | args: 'after-used', 29 | argsIgnorePattern: '^_', 30 | }, 31 | ], 32 | 'react/react-in-jsx-scope': 'off', 33 | 'react-native-a11y/has-valid-accessibility-ignores-invert-colors': 'off', 34 | 'promise/prefer-await-to-then': 'off', 35 | }, 36 | settings: { 37 | 'import/resolver': { 38 | typescript: {}, 39 | }, 40 | }, 41 | }; 42 | 43 | module.exports = config; 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | .next 39 | 40 | .yarn/* 41 | !.yarn/releases 42 | !.yarn/plugins 43 | .pnp.* 44 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: "all", 6 | arrowParens: "avoid", 7 | endOfLine: "auto", 8 | }; 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withNextra = require('nextra')({ 2 | theme: 'nextra-theme-blog', 3 | themeConfig: './theme.config.jsx', 4 | }); 5 | 6 | module.exports = { 7 | ...withNextra(), 8 | async redirects() { 9 | return [ 10 | { 11 | source: '/cv', 12 | destination: 'https://docs.google.com/document/d/1cxKNXz4m6_x5T3T_7xn1sCwJTSc8ispb/edit?usp=sharing&ouid=102920743883960398765&rtpof=true&sd=true', 13 | permanent: false, 14 | basePath: false, 15 | }, 16 | ]; 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:bufgix/website.git", 6 | "author": "bufgix ", 7 | "license": "MIT", 8 | "scripts": { 9 | "dev": "next dev", 10 | "build": "next build", 11 | "start": "next start", 12 | "lint": "eslint --ext .ts,.tsx .", 13 | "lint:fix": "eslint --fix --ext .ts,.tsx ." 14 | }, 15 | "dependencies": { 16 | "next": "^14.0.4", 17 | "nextra": "^2.13.2", 18 | "nextra-theme-blog": "^2.13.2", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0" 21 | }, 22 | "devDependencies": { 23 | "@callstack/eslint-config": "^14.1.0", 24 | "@types/node": "20.10.5", 25 | "@typescript-eslint/eslint-plugin": "^6.16.0", 26 | "@typescript-eslint/parser": "^6.16.0", 27 | "eslint": "^8.56.0", 28 | "eslint-import-resolver-typescript": "^3.6.1", 29 | "eslint-plugin-unused-imports": "^3.0.0", 30 | "typescript": "^5.3.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Main, Head, NextScript } from 'next/document'; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 10 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /pages/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | type: page 3 | title: About 4 | --- 5 | 6 | Hi 👋 I'm Faruk, a software developer specializing in building (and occasionally _designing_) _websites_, _applications_, and everything in between. 7 | 8 | Check out my [bento 🍱](https://bento.me/bufgix) page and my [CV 📃](/cv) for more info about me and my work. For business inquiries, you can reach me at [farukoruc.dev[at]gmail.com 📩](mailto:farukoruc.dev@gmail.com)
9 | -------------------------------------------------------------------------------- /pages/posts/artik-pythonda-elif-yazmak-zorunda-degilsiniz/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Artık Python'da `elif` yazmak zorunda değilsiniz! 3 | date: 2021-11-26 4 | tag: 5 | - python 6 | - cpython 7 | - fun 8 | --- 9 | 10 | Twitterda gezinirken şöyle bir şey gördüm. 11 | 12 | ![Twitter Image](./tw_image.jpg) 13 | 14 | 20 | 21 | [İlgili Tweet](https://twitter.com/koseilteer/status/1461979376199819266) 22 | 23 | Insanların bir keyword yüzünden Pythonu bırakmasına gönlüm razı olmadı (!) 24 | Bu yüzden bu `elif` keywordunu Python da `elseif` ile değişmeye karar verdim. 25 | 26 | ### Neden? 27 | 28 | Makalenin amacı troll olsa da Python'u kaynak kodu üzerinden derleyip içeride ne döndüğüne bakmak, bu build 29 | 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. 30 | 31 | ### CPython'ı Derlemek 32 | 33 | Şimdi şunu belirtmekte fayda var, terminalinize gelip `python` yazdığınızda çalışan şey %90 ihtimalle CPython. Peki ne bu CPython? 34 | En kısa tanımı ile, Pythonun bir implementasyonu. Şöyle ki Python interpreteri de Python kodlarını çalıştıran bir program. CPython ise 35 | 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ı 36 | [burada](https://github.com/python/cpython) bulunuyor. Buradan kendinize bir fork çakıp o forku bilgisayarınıza klonlayın. Ben kendi adıma 37 | klonlamıştım. 38 | 39 | ```bash 40 | git clone https://github.com/bufgix/cpython 41 | cd cpython 42 | ``` 43 | 44 | Mac bir cihaz kullandığım için yapmam gereken şu 45 | 46 | ```bash 47 | brew install openssl xz gdbm 48 | ``` 49 | 50 | sonrasında `configure` scriptini çalıştıralım 51 | 52 | ```bash 53 | ./configure --with-pydebug --with-openssl=$(brew --prefix openssl) 54 | ``` 55 | 56 | daha sonra build almaya geçebiliriz. 57 | 58 | ```bash 59 | make -s -j2 60 | ``` 61 | 62 | `-j2` flagı `make` in aynı anda 2 işi yapmabilmesini sağlar. Eğer 4 çekirdeğiniz varsa `-j4` diyebilirsiniz. 63 | Bu noktadan itibaren bilgisayarınızın fan seslerini ve `tkinter` gibi paketlerinden çıkan hataları görmezden gelebilirsiniz. 64 | 65 | İşlem bittiğinde tertemiz Python 3.11 binary si çıkmış olacak. Hemen test edelim 66 | 67 | ```bash 68 | ~/cpython on main 69 | ❯ ./python.exe 70 | 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 71 | Type "help", "copyright", "credits" or "license" for more information. 72 | >>> 73 | ``` 74 | 75 | Nice! 76 | 77 | ### Dile keyword eklemek 78 | 79 | 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 80 | işimiz çok daha kolay. 81 | 82 | 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 83 | bu dosyayı değiştirmek gerekiyor. 84 | 85 | 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. 86 | 87 | ```gram {8-10} 88 | if_stmt[stmt_ty]: 89 | | invalid_if_stmt 90 | | 'if' a=named_expression ':' b=block c=elif_stmt { 91 | _PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) } 92 | | 'if' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) } 93 | elif_stmt[stmt_ty]: 94 | | invalid_elif_stmt 95 | | 'elif' a=named_expression ':' b=block c=elif_stmt { 96 | _PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) } 97 | | 'elif' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) } 98 | else_block[asdl_stmt_seq*]: 99 | | invalid_else_stmt 100 | | 'else' &&':' b=block { b } 101 | 102 | ``` 103 | 104 | --- 105 | 106 | ```gram {8-10} 107 | if_stmt[stmt_ty]: 108 | | invalid_if_stmt 109 | | 'if' a=named_expression ':' b=block c=elif_stmt { 110 | _PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) } 111 | | 'if' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) } 112 | elif_stmt[stmt_ty]: 113 | | invalid_elif_stmt 114 | | 'elseif' a=named_expression ':' b=block c=elif_stmt { 115 | _PyAST_If(a, b, CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p, c)), EXTRA) } 116 | | 'elseif' a=named_expression ':' b=block c=[else_block] { _PyAST_If(a, b, c, EXTRA) } 117 | else_block[asdl_stmt_seq*]: 118 | | invalid_else_stmt 119 | | 'else' &&':' b=block { b } 120 | 121 | # 1220 li satırlara doğru ise 122 | invalid_elif_stmt: 123 | | 'elseif' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") } 124 | | a='elseif' named_expression ':' NEWLINE !INDENT { 125 | RAISE_INDENTATION_ERROR("expected an indented block after 'elif' statement on line %d", a->lineno) } 126 | 127 | ``` 128 | 129 | Bu dosyayı her değiştiriğimzde `make regen-pegen` komutu ile `parser.c` dosyasının bu grammar file ile 130 | yeniden generate edilmesini sağlamalıyız. 131 | 132 | ```bash 133 | ~/cpython on main 134 | ❯ make regen-pegen 135 | PYTHONPATH=./Tools/peg_generator python3.9 -m pegen -q c \ 136 | ./Grammar/python.gram \ 137 | ./Grammar/Tokens \ 138 | -o ./Parser/parser.new.c 139 | python3.9 ./Tools/scripts/update_file.py ./Parser/parser.c ./Parser/parser.new.c 140 | 141 | ``` 142 | 143 | Güzel şimdi tekrar build alalım. 144 | 145 | ```bash 146 | make -s -j8 147 | ``` 148 | 149 | Ve o da ne 150 | 151 | ```bash 152 | ~/cpython on main ⇡1 !2 153 | ❯ make -j8 -s 154 | ld: warning: directory not found for option '-L/usr/local/opt/zlib/lib' 155 | File "", line 108 156 | elif new_root.endswith(':'): 157 | ^^^^ 158 | SyntaxError: invalid syntax 159 | make: *** [Python/frozen_modules/importlib._bootstrap_external.h] Error 1 160 | make: *** Waiting for unfinished jobs.... 161 | File "", line 299 162 | elif hasattr(loader, 'module_repr'): 163 | ^^^^ 164 | SyntaxError: invalid syntax 165 | make: *** [Python/frozen_modules/importlib._bootstrap.h] Error 1 166 | File "", line 99 167 | elif p_drive and p_drive != result_drive: 168 | ^^^^ 169 | SyntaxError: invalid syntax 170 | File "", line 435 171 | elif not _is_param_expr(t_args): 172 | ^^^^ 173 | SyntaxError: invalid syntax 174 | make: *** [Python/frozen_modules/ntpath.h] Error 1 175 | File "", line 85 176 | elif not path or path.endswith(sep): 177 | ^^^^ 178 | SyntaxError: invalid syntax 179 | make: *** [Python/frozen_modules/_collections_abc.h] Error 1 180 | make: *** [Python/frozen_modules/posixpath.h] Error 1 181 | File "", line 149 182 | elif isinstance(s, bytes): 183 | ^^^^ 184 | SyntaxError: invalid syntax 185 | make: *** [Python/frozen_modules/genericpath.h] Error 1 186 | 187 | ``` 188 | 189 | 200 IQ hareket . `elif` i değiştik ama Python internal modullerinde hala `elif` kullanıyor. Dolayısıyla parser hata çıkartıyor. 190 | 191 | Bunun çözmek için `elif` keywordunu tamamen değişmek yerine `elseif` i de kullanılabilir yapmak gerekiyor. Dokümantasyonda biraz gezindikten sonra 192 | `|` operatoru ile bunu yapabileceğimi anlıyorum. Aynı yerlerdeki `'elseif'` stringlerini `('elif' | 'elseif')` bununla değiştiriyorum. `make regen-pegen` 193 | ve `make -s -j8` komutunu çalıştırdığımızada. 194 | 195 | ```bash 196 | ~/cpython on main ⇡1 !2 197 | ❯ ./python.exe 198 | 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 199 | Type "help", "copyright", "credits" or "license" for more information. 200 | >>> if True: 201 | ... pass 202 | ... elseif False: 203 | ... pass 204 | ... 205 | >>> 206 | ``` 207 | 208 | Bingo!! 209 | 210 | Artık rahatlıkla Python kullanabilirsiniz. 211 | 212 | ### Kaynaklar 213 | 214 | - https://devguide.python.org/grammar/ 215 | - https://realpython.com/cpython-source-code-guide/ 216 | - https://isidentical-archive.github.io/cpython-yeni-operator.html 217 | -------------------------------------------------------------------------------- /pages/posts/artik-pythonda-elif-yazmak-zorunda-degilsiniz/tw_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/artik-pythonda-elif-yazmak-zorunda-degilsiniz/tw_image.jpg -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/img.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/img_1.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/img_2.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/img_3.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/img_4.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/img_5.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/img_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/img_6.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bu Siteyi Nasıl Oluşturdum (eski) 3 | date: 2022-03-16 4 | description: Bu yazıda bu web sitesini Svelte ve SvelteKit ile nasıl oluşturduğumu anlayacağım. 5 | tag: 6 | - svelte 7 | - svetlekit 8 | - ssg 9 | --- 10 | 11 | import { Callout } from 'nextra/components' 12 | 13 | 14 | Bu yazi suan goruntulediginiz web sitesinin eski versiyonu icin yazilmistir. Sıkıldığım için Next.js tabanli [Nextra](https://nextra.site/) ile tekrar olusturdum 🤷‍♂️ 15 | 16 | 17 | Uzun zamandır aklımda kolayca içerik üretebileceğim, hızlı, 18 | sade bir blog yapmak vardı. Pek çok farklı şekilde iyi-kötü(çoğunlukla kötü) 19 | kendim için blog sayfaları oluşturmuştum. Backendini kendim yazdığım da oldu, 20 | sadece frontendini yazıp backendini [Strapi](https://strapi.io/)'ye verdiğim de. Ama hep 21 | bir şeyler istediğim gibi olmuyordu. Bir türlü ihtiyaçlarımı tam anlamıyla 22 | karşılayacak bir web sitesi yapamamıştım kendime. Ama son 1 haftadır 23 | Svelte ve SvelteKit ile istediğim siteyi oluşturduğumu düşünüyorum. 24 | Bu yazıda bu web sitesini _Svelte_ ve _SvelteKit_ ile nasıl oluşturduğumu 25 | anlatacağım 26 | 27 | ## İhtiyaçlarım 28 | 29 | Websitesini yapmaya girişmeden önce ihtiyaçlarımı belirlemeye karar verdim. 30 | Bunlar: 31 | 32 | - Olabildiğince sade bir tasarım. 33 | 34 | Websitemin daha çok içeriğe odaklı olması 35 | gerektiğini düşündüğüm için, içeriği ön plana alacak şeklide bir 36 | tasarım düşündüm. [Bu](https://antfu.me/), 37 | [bu](https://www.aleksandrhovhannisyan.com/) ve 38 | [bu](https://ademilter.com/) sitelerden ilham aldığım söylenebilir. 39 | 40 | - Hız. 41 | 42 | Yaptığım şey altı üstü blog post paylaşmak. Bu dönemde böyle bir iş 43 | çok fazla kaynak tüketmese gerek. Dolayısıyla sitenin ve sayfaların açılış 44 | hızı olabilidiğince hızlı olmalı. 45 | 46 | - Hızlıca içerik üretebilme. 47 | 48 | Önceki denediğim sistemlerde, bir şeyler paylaşmak 49 | hiç kolay değildi. Kolaycadan kastım şu: Aklıma gelen veya paylaşmak 50 | istediğim bir şeyi telefonda olsam bile yazıp tek tuşla yayına almak. 51 | 52 | - Bol özellikli makale yazma formatı. 53 | 54 | Bir yazılım geliştirici olduğum için aşına olduğum metin yazma formatı 55 | `markdown`. Ama sadece bu da yetmiyor, yeri geliyor makalede interaktif 56 | bir şeyler yapabilmek istiyorum. Ayırca kod formatlama, syntax highlighting 57 | de gelişmiş olmalı. 58 | 59 | ## Neler kullandım 60 | 61 | - [Svelte](https://svelte.dev/) - _Kullandığım frontend framework_ 62 | - [SvelteKit](https://kit.svelte.dev/) - _Svelte ile birlikte çalışan, server-side-rendering (SSR), 63 | static-site-generator (SSG) için framework_ 64 | - [Tailwind](https://tailwindcss.com/) - _CSS framework_ 65 | - [MDsveX](https://mdsvex.pngwn.io/) - _Markdown ve SvelteKit ile birlikte çalışan, markdown dosyalarını 66 | process eden kütüphane_ 67 | - [Notion.so](https://www.notion.so) - _[snippets](/snippets) bölümü için kullandığım platform_ 68 | - [Upstash](https://upstash.com/) - Sayfaların ne kadar görüntülenme aldığını tutmak için `key-value` 69 | database 70 | - [Raindrop.io](https://raindrop.io/) - _[bookmarks](/bookmarks) bölümü için kullandığım platform_ 71 | 72 | ## SvelteKit ve SSG(Static Site Generator) nasıl çalışıyor? 73 | 74 | Bir SvelteKit uygulaması oluşturduğunuzda, `/routes` adlı bir dizinle 75 | gelir. Bu dizin içindeki dosyalara göre web sitenizin yolları belirlenir. 76 | 77 | ![Svelte Routing](./svelte_routing.png) 78 | 79 | Bu sayfalar ayrıca static olarak `.html` dosyalarına dönüştürülebilir. Eğer 80 | `svete.config.js` dosyasında `kit.prerender.default = true` yaparsanız, 81 | varsayılan olarak `/routes` içindeki dosyaları statik olarak çalışmasını 82 | sağlarsınız. `build` komutunu çalıştırmanız yeterli olur. 83 | 84 | ![Svelte Static Adapter](./svelte_proc.png) 85 | 86 | Eger backend'e işiniz düştüyse bunu bir endpoint yardımıyla çözebilirsiniz. 87 | 88 | ### Endpoints 89 | 90 | SvelteKit uygulamalarınızda kendinize özel api uçları tasarlayabilirsiniz. 91 | Bu uçlarda çalışacak kodlar backend içinde çalışır. Ben tamamen static 92 | bir site yapmak istediğim için bu uçları, build alınırken çalıştırıp 93 | sonuçlarını static olarak dosyalara yazacağım... Yani SvelteKit yazacak. 94 | 95 | Bir endpoint içine temel HTTP methodları için ayrı ayrı fonksiyonlar 96 | yazılabilir. Bunlar: 97 | 98 | ```js 99 | export function get(event) {...} // GET 100 | export function post(event) {...} // POST 101 | export function put(event) {...} // PUT 102 | export function patch(event) {...} // PATCH 103 | export function del(event) {...} // DELETE 104 | ``` 105 | 106 | Benim işime şuan `GET` methodu yaradığı için `get` fonskiyonunu yazacağım 107 | sadece. 108 | 109 | ```ts:/routes/api/bookmarks.json.ts 110 | import type { RequestHandler } from '@sveltejs/kit'; 111 | import variables from '$lib/variables'; 112 | 113 | const RAINDROP_URL = 'https://api.raindrop.io/rest/v1/raindrops/0?perpage=30'; 114 | 115 | export const get: RequestHandler = async () => { 116 | const bookmarks = await ( 117 | await fetch(RAINDROP_URL, { 118 | headers: { 119 | Authorization: `Bearer ${variables.RAINDROP_API_KEY}` 120 | } 121 | }) 122 | ).json(); 123 | 124 | return { 125 | body: { 126 | bookmarks 127 | } 128 | }; 129 | }; 130 | ``` 131 | 132 | Burada [Raindrop](https://raindrop.io/) ile kişisel bookmarklarımı çekiyoum. Ardından 133 | aldığım değeri olduğu gibi endointden dönüyorum. 134 | 135 | Sonrasında tarayıcımda `/api/bookmarks.json`'a gidersem dönen değeri 136 | görebilirim. 137 | 138 | ![](./img_6.png) 139 | 140 | Burada dikkat etmem gereken bu endpointin build zamanda çalışması için 141 | dosya ismin `*.json.{js,ts}` şeklinde yazmış olmam. Sveltekit bu formatı 142 | anlayıp build esnasında bu dosyayı çalıştırıyor ve sonuçlarını 143 | `bookmarks.json` olarak bir dosyaya yazıyor. 144 | 145 | Şimdi bunu sayfa componentimin içinde nasıl göstereceğim? 146 | 147 | Svelte bunun için bana güzel bir API sunmuş. 148 | Componentin server tarafında çalışmasını 149 | istediğim kodlarını ` 170 | 171 | 174 | 175 |

Bookmarks

176 |
177 | 178 | {#each bookmarks.items as bookmark} 179 |
180 |

{bookmark.title}

181 |
182 | {/each} 183 | ``` 184 | 185 | Bu sitedeki [`/bookmarks`](/bookmarks) yolundan bu kodların çıktısına 186 | bakabilirsiniz. 187 | 188 | Aynı şekilde [`/snippets`](/snippets) kısmını da bu yolla yaptım. Aynı 189 | şeyleri yaptığım için anlatmaya gerek duymuyorum. [Notion.so](https://www.notion.so/)'dan gelen 190 | verileri parse ettiğim sıkıcı kodlar var sadece :) 191 | İsteyen [buradan](https://github.com/bufgix/website/blob/master/src/routes/api/snippets/index.json.ts) bakabilir 192 | 193 | ### Blog Postlar ve MDsveX 194 | 195 | Peki, blog postlar için de `.svelte` dosyaları mı kullanıyorum? 196 | Tabii ki hayır. Bunun için [MDsveX](https://mdsvex.pngwn.io/) markdown processor kullanıyorum. 197 | Yaptığı şey basitçe `/routes` içinde bir `.md` dosyası varsa bunu parse 198 | edip aynı bir svelte sayfası gibi göstermek. Bunu yaparken bol 199 | eklenti desteği, metadata, layouts, syntax highlighting gibi pek çok 200 | özelliğini de kullanbiliyorsunuz. 201 | 202 | ![](./mdsvex.png) 203 | 204 | Kullandığım eklentiler ise şu şekilde: 205 | 206 | ```js 207 | import relativeImages from 'mdsvex-relative-images'; 208 | import remarkHeadingId from 'remark-heading-id'; 209 | import figure from 'rehype-figure'; 210 | import codeTitle from 'remark-code-titles'; 211 | 212 | mdsvex({ 213 | extensions: ['.md', '.svx'], 214 | remarkPlugins: [relativeImages, remarkHeadingId, codeTitle], 215 | rehypePlugins: [figure], 216 | }); 217 | ``` 218 | 219 | Bu websitesinin kodlarına açık kaynak olarak [Github](https://github.com/bufgix/website)'dan erişebilirsiniz. 220 | 221 | ### Kaynakça 222 | 223 | - https://joshcollinsworth.com/blog/build-static-sveltekit-markdown-blog 224 | - https://kit.svelte.dev/ 225 | -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/mdsvex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/mdsvex.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/svelte_proc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/svelte_proc.png -------------------------------------------------------------------------------- /pages/posts/bu-siteyi-nasil-olusturdum/svelte_routing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/bu-siteyi-nasil-olusturdum/svelte_routing.png -------------------------------------------------------------------------------- /pages/posts/building-bulletproof-react-native-applications/design1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/building-bulletproof-react-native-applications/design1.png -------------------------------------------------------------------------------- /pages/posts/building-bulletproof-react-native-applications/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Kurşun Geçirmez React Native Uygulamaları Oluşturmak - I 3 | date: 2022-01-01 4 | description: 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. 5 | tag: 6 | - react 7 | - react-native 8 | - typescript 9 | - redux 10 | --- 11 | 12 | Evet biliyorum _clickbait_ başlık ama son zamanlarda (_7 ay_) React Native 13 | ile baya bi' uygulama geliştirdim. Bu süreçte öğrendiğim bazı şeyleri paylaşmak istiyorum. 14 | Burada anlatacağım her şey belki en doğru yöntem değil fakat React Native projelerimi 15 | bu şekilde oluşturuyorum ve şimdiye kadar sorun yaşamadım. 16 | 17 | Bu yazıda gelir getirmesi istenen bir uygulamanın 18 | _uygulama-içi-satın-alma_, _abonelik_, _metadata_, _build aşamaları_, _markete vermeden update etme_ gibi 19 | konuları ele alacağım. Bunun yanında React tarafında 20 | _store yapısı, hookların efektif kullanımı, API dizaynı_ gibi başlıklardan da bahsetmek istiyorum. 21 | 22 | ## Projeyi Oluşturmak 23 | 24 | Bir React Native uygulaması oluşturmanın birden fazla yolu var. Bunlardan biri 25 | [Expo](https://expo.dev/) kullanmak. Expo kullanmak yeni başlayan biri için oldukça kolay bir yol olabilir. 26 | Fakat kişisel olarak ben artık tercih etmiyorum nedenleri ise: 27 | 28 | - Uygulamanın boyutunu fazla **büyütmesi** (Bir QR kod okuyucu yapmıştım ve production 29 | buildi 67mb olmuştu :D. Bunu azaltmanın yolları var tabi ama o toplara girmeyi tercih etmiyorum) 30 | - Bazı native modulleri Expo üzerinden **kullanamamak**. 31 | 32 | Bunlar dışında çok da yok aslında ama normal React Native kurulumuyla başlamak bana 33 | daha fazla kontrol sağlıyor. (Expo kullanıp memnun olanlar da var tabi. [Buradan](https://twitter.com/enesozt_/status/1474039877893296140) 34 | okuyabilirsiniz. Anlaşılan Expo'nun yeni sürümleri yukarda bahsettiğim sorunları çözüyor. Yakında yeniden denerim kendisini.) 35 | 36 | #### Typescript 37 | 38 | Evet bu [zımbırtı](https://www.typescriptlang.org/) olmadan ben artık uygulama _yazmıyorum_. İlerde anlatacağım çoğu şeyde Typescript 39 | yardımını kullandığım için Typescriptin olmaması kabul edilemez. Unutmayın amacımız kurşun geçirmeyen, 40 | sürdürülebilir bir uygulama oluşturmak. Typescript bu konuda baya kolaylik sağlıyor. 41 | bu yüzden React Native uygulamasını Typescript ile kurmanızı tavsiye ederim. 42 | 43 | ```shell 44 | $ npx react-native init BestAppEver --template react-native-template-typescript 45 | ``` 46 | 47 | Detaylı kurulumu tabii ki React Native'in [kendi sitesinden](https://reactnative.dev/docs/environment-setup) 48 | bulabilirsiniz. 49 | 50 | Ayrıca ilerde proje içi tipler için bir `*.d.ts` dosyanızı oluşturmanızı tavsiye ederim. 51 | 52 | ```ts filename="./src/types/index.d.ts" 53 | declare global { 54 | type MyType = { 55 | name: string; 56 | //... 57 | }; 58 | } 59 | ``` 60 | 61 | Bu dosyayı oluşturduktan sonra import etmeye gerek kalmadan tiplerinizi kullanabileceksiniz. Fazlalık 62 | importların oluşmasını önlemek için güzel bir yöntem. 63 | 64 | ### Dizin Yapısı 65 | import { FileTree } from 'nextra/components' 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | Genel olarak böyle bir dizin yapısı kullanıyorum. Kısaca bir özet geceyim, daha sonra daha detaylı inceleyelim. 128 | 129 | - `api` burada backend servisi ile haberleşmek için gerekli dosyalar bulunur. `Axios`, `Apollo` gibi kütühanelerin örneklemeleri burada bulunur 130 | - `assets` burada uygulamada kullanılacak resimler ve fontlar gibi static dosyalar bulunur 131 | - `components` burada uygulama bileşenleri bulunur 132 | > Tercihen ben bu dizin içinde bir de `index.ts` ekleyip tüm componentleri buradan exportluyorum. Bu, komponenti 133 | > kullanırken daha temiz bir kod sağlıyor 134 | > 135 | > ```ts 136 | > export { default as Page } from './Page'; 137 | > export { default as Text } from './Text'; 138 | > export { default as Card } from './Card'; 139 | > export { default as Button } from './Button'; 140 | > ``` 141 | > 142 | > kullanırken 143 | > 144 | > ```ts 145 | > import { Button } from '@/components'; 146 | > ``` 147 | - `icons` Burada svg tipinde iconlar bulunur. 148 | - `lang` Burada dil objeleri bulunur 149 | - `navigation` Burada sayfaların navigasyondaki yollarını belirleyen tanımlamalar bulunur 150 | - `store` Burada uygulamanın store dosyaları bulunur 151 | - `styles` Burada renkler, fontlar ve margin-padding değerleri bulunur. 152 | - `typings` Burada uygulamamız için tip tanımlamaları bulunur 153 | - `utils` Burada hooklar, yardımcı methodlar ve sabit değelerin olduğu dosyalar bulunur 154 | - `views` Burada uygulamada kullancağımız sayfalar bulunur 155 | - `bootstrap.tsx` Bu dosya uygulamamızın kök komponenti. Genelde burada uygulama açılmadan önce yüklencek 156 | asenkron işlemler(network requestleri, local databaseden inital verilerin çekilmesi vb.) yapılır. Provider'ları da 157 | bu dosyaya yazarım ve genelde şuna benzer: 158 | 159 | ![Provider Hell](./provider.jpg) 160 | 161 | Eğer SplashScreen kullancaksınız bütün asenkron işlemleri yaptıktan sonra Splash ekranını bu component içinde kaldırabilirsiniz. 162 | 163 | Örnek olarak: 164 | 165 | ```tsx filename="./src/bootstrap.tsx" 166 | import React, { useEffect } from 'react'; 167 | import SplashScreen from 'react-native-splash-screen'; 168 | 169 | import RootStackScreen from './navigation'; 170 | 171 | export default function Bootstrap() { 172 | useEffect(() => { 173 | setupApp().then(() => SplashScreen.hide()); 174 | }, []); 175 | 176 | return ; 177 | } 178 | ``` 179 | 180 | ### Iconlar ve SVG 181 | 182 | Yaptığım uygulamaların hiçbirinde tasarımı ben yapmadım elimde her zaman bir [Figma](https://www.figma.com) dosyası oldu. Figmada 183 | iconları `.svg` olarak export alabildiğimden kolayca React Native içerisinde kullanbiliyordum. 184 | 185 | Bunun için kullandığım araç [SVGR](https://react-svgr.com/). SVGR nin amacı, svg dosyalarını benim için React componentlerine 186 | dönüştürmek. 187 | 188 | #### SVGR kurulumu 189 | 190 | ```shell 191 | $ yarn add @svgr/cli --dev 192 | $ yarn add react-native-svg 193 | ``` 194 | 195 | Gerekli kütüphaneleri kurduktan sonra npm scriptleri yardımıyla icon ekleme işlemini otomatikleştiriyorum 196 | 197 | ```json filename="package.json" 198 | "scripts": { 199 | //... 200 | "svgr": "svgr ./src/icons -d ./src/components/icons --native --no-dimensions --typescript --svgo-config .svgorc.json", 201 | } 202 | ``` 203 | 204 | Kısaca yaptığı işi açıklamak gerekirse, svgr ye `./src/icon` dizinini okumasını `--native` parametresi ile `react-native-svg` 205 | e uygun işlem yapmasını, `--no-dimesion` ile _width_ ve _height_ değerlerini kaldırmasını, `--typescript` ile Typescript uyumlu 206 | olmasını, `--svgo-config` config dosyasını yolunu ve sonunda oluşturduğu çıktıları `./src/components/icons` dizine 207 | çıkartmasını istediğimizi belirttik. Config dosyasında ise basitçe: 208 | 209 | ```json filename="./scgorc.json" 210 | { "plugins": [{ "removeViewBox": false }] } 211 | // viewboxu kaldırmak için plugin kullandık 212 | ``` 213 | 214 | Şimdi scripti çalıştırdığımızda 215 | 216 | ```shell 217 | ❯ yarn svgr 218 | yarn run v1.22.10 219 | $ svgr ./src/icons -d ./src/components/icons --native --no-dimensions --typescript --svgo-config .svgorc.json 220 | src/icons/diamond.svg -> src/components/icons/Diamond.tsx 221 | src/icons/button-refresh.svg -> src/components/icons/ButtonRefresh.tsx 222 | src/icons/arrow-left.svg -> src/components/icons/ArrowLeft.tsx 223 | src/icons/heart.svg -> src/components/icons/Heart.tsx 224 | src/icons/reload.svg -> src/components/icons/Reload.tsx 225 | src/icons/office-file.svg -> src/components/icons/OfficeFile.tsx 226 | src/icons/support-human.svg -> src/components/icons/SupportHuman.tsx 227 | 228 | ✨ Done in 0.98s. 229 | ``` 230 | 231 | Daha sonra istediğim iconu herhangi bir yerde kullanmak çok kolay 232 | 233 | ```tsx 234 | import * as Icons from '@/components/icons/'; 235 | 236 | ; 237 | ``` 238 | 239 | ### Boyutlar, Boşluklar 240 | 241 | Elinizde bir tasarım varsa tasarımda elemente gelip width, height değerlerini pixel cinsiden olduğu gibi 242 | React Native'e vermek çok mantıklı değil. Çünkü faklı ekranlarda aynı pixel değeri istediğimiz sonuçları vermeyebilir. 243 | Bunu çözmek için kullandığım yöntem ise şöyle: 244 | 245 | Diyelim ki referans alıdğınız bir tasarım var 246 | 247 | ![Örnek dizayn](./design1.png) 248 | 249 | Görüldüğü üzere tasarımın frame width değeri **375px**. Dolayısıyla ekrana yerlemiş olan elementler de bu genişlik 250 | değeri baz alınarak yapılmış. Biz de tasarımı uygulamaya geçirirken buna benzer bir yöntem kullanacağız. 251 | Bunun için React Native tarafında `PixelRatio` kullanarak ekranın genişliğine göre şöyle bir 252 | _utility_ fonksiyon yazabiliriz 253 | 254 | ```ts filename="./src/utils/index.ts" 255 | import { Dimensions, PixelRatio } from 'react-native'; 256 | 257 | let screenWidth = Dimensions.get('window').width; 258 | 259 | // https://www.npmjs.com/package/react-native-responsive-screen 260 | function widthPercentageToDP(widthPercent) { 261 | // Parse string percentage input and convert it to number. 262 | const elemWidth = 263 | typeof widthPercent === 'number' ? widthPercent : parseFloat(widthPercent); 264 | 265 | // Use PixelRatio.roundToNearestPixel method in order to round the layout 266 | // size (dp) to the nearest one that correspons to an integer number of pixels. 267 | return PixelRatio.roundToNearestPixel((screenWidth * elemWidth) / 100); 268 | } 269 | 270 | /** 271 | * @param size size given in the design 272 | * @returns responsive size of the text 273 | */ 274 | export function responsiveSize(size: number) { 275 | return widthPercentageToDP((size / 375) * 100); 276 | } 277 | ``` 278 | 279 | Daha sonra bu `responsiveSize` methodunu fontlarda, spacing değerlerinde kullanabilirsiniz. 280 | 281 | ```ts filename="./src/style/fonts.ts" 282 | import { responsiveSize as rs } from '@/utils'; 283 | 284 | export const size = { 285 | h1: rs(30), 286 | h2: rs(24), 287 | h3: rs(20), 288 | medium: rs(16), 289 | normal: rs(14), 290 | input: rs(16), 291 | regular: rs(14), 292 | small: rs(12), 293 | mini: rs(8), 294 | }; 295 | ``` 296 | 297 | ### API dizaynı 298 | 299 | Api dizaynı projeden projeye değişebileceği gibi bir REST API ile çalışıyorsanız endpointleri servis dosyaları şeklinde yazmak mantıklı oluyor. 300 | 301 | ```ts filename="/api/HTTPClient.ts" 302 | import Axios from 'axios'; 303 | 304 | let baseURL = 'https://api.product.com/'; 305 | const TOKEN = ''; 306 | 307 | const client = Axios.create({ 308 | baseURL, 309 | headers: { 310 | Accept: 'application/json, text/plain, */*', 311 | Authorization: `TOKEN ${TOKEN}`, 312 | }, 313 | }); 314 | 315 | export default client; 316 | ``` 317 | 318 | Diyelim ki API servisiniz size databasede olan araçları veriyor 319 | 320 | ```ts filename="./api/services/Cars.ts" 321 | import HTTPClient from '../HTTPClient'; 322 | 323 | export type Car = { 324 | name: string; 325 | model: string; 326 | }; 327 | 328 | export async function fetch(limit?: number) { 329 | const { data } = await HTTPClient.get('cars/'); 330 | return data; 331 | } 332 | ``` 333 | 334 | Bunun gibi bir servis dosyası yazmak, hem düzeni sağlıyor hem de okunabilirliği artırıyor. 335 | 336 | #### Side Effects 337 | 338 | Bir API isteği yapacağımız zaman _loading_, _error_ gibi durumları da 339 | yakalamamız ve kullanıcıya bildirmemiz gerekir. 340 | 341 | Backend servisini basit bir istek yaptığımızı düşünelim. Sayfa açıldığı zaman 342 | verileri çeksin 343 | 344 | ```tsx filename="./views/Cars/index.tsx" 345 | import React, { useEffect, useState } from 'react'; 346 | import { View, Text, FlatList } from 'react-native'; 347 | 348 | import * as CarService from '@/api/services/Cars'; 349 | 350 | export default function Index() { 351 | const [loading, setLoading] = useState(false); 352 | const [error, setError] = useState(null); 353 | const [data, setData] = useState([]); 354 | 355 | useEffect(() => { 356 | setLoading(true); 357 | CarService.fetch() 358 | .then(cars => { 359 | setData(cars); 360 | }) 361 | .catch(e => setError(e)) 362 | .finally(() => setLoading(false)); 363 | }, []); 364 | 365 | return ( 366 | 367 | {loading && Loading...} 368 | {error && Error!} 369 | {data && } 370 | 371 | ); 372 | } 373 | ``` 374 | 375 | 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 376 | bu şekilde tanımlamalar yapmak işkence olacaktır. Bunu daha efektif bir hale getirebiliriz. 377 | 378 | React tarafında backend servisinden veri çekerken [React Query](https://react-query.tanstack.com/) kullanıyorum. 379 | Basit olarak şöyle 380 | 381 | ```tsx filename="./views/Cars/index.tsx" 382 | import React from 'react'; 383 | import { View, Text, FlatList } from 'react-native'; 384 | import { useQuery } from 'react-query'; 385 | 386 | import * as CarService from '@/api/services/Cars'; 387 | 388 | export default function Index() { 389 | const { isLoading, isError, data } = useQuery('cars', () => 390 | CarService.fetch(), 391 | ); 392 | 393 | return ( 394 | 395 | {isLoading && Loading...} 396 | {isError && Error!} 397 | {data && } 398 | 399 | ); 400 | } 401 | ``` 402 | 403 | Yazması gerçekten keyifli oluyor tabi. Fakat React Query sadece "kodu kısaltmak" ile kalmıyor. 404 | Caching, Memorizing, Performance Optimization gibi pek çok farklı sorunu çözüyor. 405 | 406 | ### Store Yapısı 407 | 408 | Ben global state için uygulamarımda [Redux](https://redux.js.org/) ve [Redux-Toolkit](https://redux-toolkit.js.org/) kullanıyorum. Reduxdan önce [Mobx](https://mobx.js.org/README.html), 409 | [Mobx-State-Tree](https://mobx-state-tree.js.org/intro/welcome) gibi kütüphaneler de kullandım. Redux o zamanlar bana bir sürü reducer yazdığın, 410 | bir ton _boilerplate_ içeren kod yazmamın gerektiği, kötü bir kütüphane gibi geliyordu. Son zamanlarda 411 | Redux Toolkit ile birlikte Redux yazmak olukça keyifli hale geldi. Redux Toolkit ile 412 | daha az ve anlaşılır kod ile state'i yönetebildiğimi görünce artık projelerimde Redux kullanmaya 413 | başladım. 414 | 415 | Store yazarken dikkat ettiğim 2 şey var. Biri store'u typescript ile doğru ayarlamak. 416 | 417 | ```ts filename="./store/user.slice.ts" 418 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 419 | 420 | expot type User = { 421 | name: string 422 | } 423 | 424 | interface IUserState { 425 | user: User | null; 426 | } 427 | 428 | const initialState: IUserState = { 429 | user: null 430 | }; 431 | 432 | export const userSlice = createSlice({ 433 | name: "user", 434 | initialState, 435 | reducers: { 436 | setUser: (state, action: PayloadAction) => { 437 | state.user = action.payload; 438 | }, 439 | }, 440 | }); 441 | 442 | export const { setUser } = appSlice.actions; 443 | export default userSlice.reducer; 444 | ``` 445 | 446 | ```ts filename="./store/index.ts" 447 | import { combineReducers, configureStore } from '@reduxjs/toolkit'; 448 | import UserSlice from './user.slice'; 449 | 450 | const reducers = combineReducers({ 451 | user: UserSlice, 452 | }); 453 | 454 | const store = configureStore({ 455 | reducer: reducers, 456 | }); 457 | 458 | export default store; 459 | ``` 460 | 461 | Store tipine doğrudan erişmek için `index.d.ts` ye yazmak mantıklı. 462 | 463 | ```ts 464 | import store from '@/store'; 465 | 466 | declare global { 467 | // Oher Types... 468 | 469 | // Store Types 470 | type RootState = ReturnType; 471 | type AppDispatch = typeof store.dispatch; 472 | } 473 | ``` 474 | 475 | Ardından React uygulamasına özel store için iki basit hook yazabiliriz. 476 | 477 | ```ts 478 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 479 | 480 | export const useAppDispatch = () => useDispatch(); 481 | export const useAppSelector: TypedUseSelectorHook = useSelector; 482 | ``` 483 | 484 | Diğer dikkat ettiğim şey ise. Redux slice'larına özel hooklar yazmak. Mesela yukardaki `user` 485 | için söyle bir hook yazılabilir. 486 | 487 | ```tsx 488 | import { useContext } from "react"; 489 | 490 | import { useAppDispatch, useAppSelector, User } from "./useStore"; 491 | import { setUser as setUserReducer } from "@/store/user.slice"; 492 | 493 | export default function useUser() { 494 | const dispatch = useAppDispatch(); 495 | 496 | const user = useAppSelector((state) => state.user); 497 | 498 | const setUser = (user: User) => { 499 | dispatch(setUserReducer(user)); 500 | }; 501 | 502 | return { user, setUser }; 503 | } 504 | 505 | 506 | ... 507 | 508 | // Component içinde 509 | function Index(){ 510 | const user = useUser(); // Storedaki user artık burada 511 | 512 | return ... 513 | } 514 | 515 | ``` 516 | 517 | 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, 518 | uygulama içi satın alma ve abonelik gibi konulara gireceğim. 519 | -------------------------------------------------------------------------------- /pages/posts/building-bulletproof-react-native-applications/provider.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/building-bulletproof-react-native-applications/provider.jpg -------------------------------------------------------------------------------- /pages/posts/gece-sikilmasi-redux-kaynak-kodundan-ognrediklerim/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gece sıkılması, Redux kaynak kodundan öğrendiklerim 3 | date: 2022-10-31 4 | tag: 5 | - javascript 6 | - redux 7 | --- 8 | 9 | Redux gercekten hicbir sey yapmiyor. 10 | 11 | Pazar gecesi yaklasik 2 saat [Redux](https://github.com/reduxjs/redux) kaynak kodu okuma 12 | maceramdan cikardigim sonuc bu oldu iste. 13 | 14 | Yani bi'seyler yaptigi kesin, belki "_yaptigi isi cok basit sekilde hallediyor_" da denebilir 15 | 16 | --- 17 | 18 | Her seyden once, Redux nedir?... 19 | 20 | diye baslamayacagim tabi. Buraya kadar geldiyseniz Redux ne biliyorsunuzdur 21 | diye tahmin ediyorum. 22 | 23 | ## Simdi 24 | 25 | Bilginiz uzere `reducer` dedigimiz fonksiyonlar state'i degistirir. Ve bunu 26 | en yalin haliyle size yaptirir. Redux size onceki state'i ve bir action'i 27 | verir. Siz de bu action'a gore yeni state'i dondurursunuz. Bu islemlerin hepsini 28 | siz kendiniz yaparsiniz. Redux sadece uygulamanizdaki bazi yerlere "_state degisti_" 29 | sinyalini gonderir. Peki nasil yapar bunu? 30 | 31 | `createStore` [implementasyonuna](https://github.com/reduxjs/redux/blob/4ebc2bcfb7c8631656ab9dd400c2578afd1b0c41/src/createStore.ts#L42) bu bakalim. Edge caseleri, type checkleri ve 32 | typescript ile ilgili kisimlari gecip asil isleme bakarsak 33 | 34 | [ilgili kisim](https://github.com/reduxjs/redux/blob/4ebc2bcfb7c8631656ab9dd400c2578afd1b0c41/src/createStore.ts#L110-L112) 35 | 36 | ```js 37 | export default function createStore(reducer, preloadedState, enhancer) { 38 | let currentReducer = reducer; 39 | let currentState = preloadedState; 40 | let listeners = []; 41 | 42 | // ... 43 | } 44 | ``` 45 | 46 | `reducer` bildidigimiz gibi state'i degistiren fonksiyon. `preloadedState` ise 47 | uygulamanizin baslangic state'i. `enhancer` zimbirtisi middleware 48 | olaylarini sagliyor. Bu yazida onu ele almayacagim. 49 | 50 | `listeners` array'i dikkatimizi cekiyor. Vardir bir hikmeti deyip devam edelim 51 | 52 | kodun ilerleyen kisimlarinda storun kendisini donduren bir `getState` fonksiyonu 53 | var. 54 | 55 | [ilgili kisim](https://github.com/reduxjs/redux/blob/4ebc2bcfb7c8631656ab9dd400c2578afd1b0c41/src/createStore.ts#L134-L144) 56 | 57 | ```js 58 | export default function createStore(/* ... */) { 59 | // ... 60 | 61 | function getState() { 62 | return currentState; 63 | } 64 | 65 | // ... 66 | } 67 | ``` 68 | 69 | O anki state'i almamiza yarayan bir fonksiyon. Guzel 70 | 71 | kod `subscribe` methodu ile devam ediyor 72 | 73 | [ilgili kisim](https://github.com/reduxjs/redux/blob/4ebc2bcfb7c8631656ab9dd400c2578afd1b0c41/src/createStore.ts#L190-L209) 74 | 75 | ```js 76 | export default function createStore(/* ... */) { 77 | // ... 78 | 79 | function subscribe(listener) { 80 | listeners.push(listener); 81 | return function unsubscribe() { 82 | const index = listeners.indexOf(listener); 83 | listeners.splice(index, 1); 84 | }; 85 | } 86 | 87 | // ... 88 | } 89 | ``` 90 | 91 | gorunuse gore bu fonksiyon `listeners` array'ine bi'seyler ekliyor. Ve 92 | `unsubscribe` fonksiyonunu donduruyor. Bu fonksiyonu cagirdigimizda 93 | `listeners` array'inden listener'i cikariyor. Hmm, biraz garip bir islem 94 | 95 | Vakit kaybetmeden devam ediyorum ki buyuk resmi gorelim. Asagida meshur 96 | `dispatch` fonksiyonu var 97 | 98 | [ilgili kisim](https://github.com/reduxjs/redux/blob/4ebc2bcfb7c8631656ab9dd400c2578afd1b0c41/src/createStore.ts#L259-L268) 99 | 100 | ```js 101 | export default function createStore(/* ... */) { 102 | // ... 103 | 104 | function dispatch(action) { 105 | currentState = currentReducer(currentState, action); 106 | listeners.forEach((listener) => listener()); 107 | return action; 108 | } 109 | 110 | // ... 111 | } 112 | ``` 113 | 114 | Olayin koptugu yer burasi. `dispatch` fonksiyonu bir action aliyor. 115 | Bu action'i `reducer` fonksiyonuna veriyor. `reducer` fonksiyonu yeni state'i 116 | donduruyor. Bu yeni state'i `currentState` degiskenine atiyor. Ve `listeners` 117 | array'indeki tum listenerlar cagiriliyor. 118 | 119 | Buradan cikaracagimiz iki sonuc var. Ilk olarak `dispatch` fonksiyonu `reducer` i 120 | kullanarak state'i degistiriyor ve yeni state olusuyor. Ikinci olarak `listeners` array'i 121 | icinde fonksiyonlar tutuluyor. Bu fonskiyonlarin hepsi `dispatch` calistigi 122 | anda cagiriliyorlar. Bu sayede "state degisti" sinyalini tum 123 | listenerlara (subscribe olmus butun fonskiyonlara) gonderilmis oluyor. Bir onceki adimda 124 | state, `reducer` ile degistigi icin butun listenerlar yeni state ile calisiyor. 125 | 126 | Son olarak gerekli olan degerleri ve fonskiyonlari `createStore` fonksiyonu donduruyor. 127 | 128 | [ilgili kisim](https://github.com/reduxjs/redux/blob/4ebc2bcfb7c8631656ab9dd400c2578afd1b0c41/src/createStore.ts#L359-L368) 129 | 130 | ```js 131 | export default function createStore(/* ... */) { 132 | // ... 133 | 134 | dispatch({ type: '@@redux/INIT' }); 135 | return { 136 | dispatch, 137 | subscribe, 138 | getState 139 | }; 140 | } 141 | ``` 142 | 143 | Goruldugu gibi fonskiyon isini bitirmeden once bir `dispatch` cagiriyor. 144 | Bunun amaci `reducer` fonksiyonu ilk calistiginda baslangic state'i dondurmek. 145 | Boylece verilen `reducer` a gore initial state olusmus oluyor. Burada action 146 | type `@@redux/INIT` olmasi onemli. Cunku `reducer` fonksiyonu bu action 147 | type'i goremezse baslangic state'i dondurmeli. 148 | 149 | Tum kaynak kod sadelestirilmis hali ile su sekilde 150 | 151 | ```js 152 | export default function createStore(reducer, preloadedState, enhancer) { 153 | let currentReducer = reducer; 154 | let currentState = preloadedState; 155 | let listeners = []; 156 | 157 | function getState() { 158 | return currentState; 159 | } 160 | 161 | function subscribe(listener) { 162 | listeners.push(listener); 163 | return function unsubscribe() { 164 | const index = listeners.indexOf(listener); 165 | listeners.splice(index, 1); 166 | }; 167 | } 168 | 169 | function dispatch(action) { 170 | currentState = currentReducer(currentState, action); 171 | listeners.forEach((listener) => listener()); 172 | return action; 173 | } 174 | 175 | dispatch({ type: '@@redux/INIT' }); 176 | return { 177 | dispatch, 178 | subscribe, 179 | getState 180 | }; 181 | } 182 | ``` 183 | 184 | Bu kadar. Nasil calisitgina bakalim. 185 | 186 | ```js 187 | const counterReducer = (state = 0, action) => { 188 | switch (action.type) { 189 | case 'INCREMENT': 190 | return state + 1; 191 | case 'DECREMENT': 192 | return state - 1; 193 | default: 194 | return state; 195 | } 196 | }; 197 | 198 | const store = createStore(counterReducer, 0); 199 | 200 | store.subscribe(() => { 201 | console.log('state changed', store.getState()); 202 | }); 203 | 204 | store.dispatch({ type: 'INCREMENT' }); 205 | // state changed 1 206 | store.dispatch({ type: 'INCREMENT' }); 207 | // state changed 2 208 | store.dispatch({ type: 'DECREMENT' }); 209 | // state changed 1 210 | ``` 211 | 212 | [Kendin dene](https://codesandbox.io/s/distracted-tharp-ykcm55?file=/src/index.js) 213 | 214 | Evet bu kadardi. Redux'un kaynak kodu gercekten cok kisa ama suan NPM de haftalik 215 | indirme sayisi 8 milyon u gecmis durumda. React olmasa bu kadar populer olmazdi fakat 216 | isini guzel yapan bir kutuphane. 217 | 218 | > Ben de basit bir implementasyon yaptim 219 | > [Burada](https://codesandbox.io/s/admiring-mclean-tyfmhk?file=/src/index.js) 220 | -------------------------------------------------------------------------------- /pages/posts/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | type: posts 3 | title: Posts 4 | --- 5 | 6 | # Posts 7 | -------------------------------------------------------------------------------- /pages/posts/python-dataclasses/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Python Dataclasses 3 | date: 2020-11-01 4 | tags: 5 | - python 6 | --- 7 | 8 | ## Nedir? 9 | 10 | 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. 11 | 12 | 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. 13 | 14 | > Unutmayın `dataclass`'lar Python'un 3.7 sürümünde bulunur. 15 | 16 | ```python 17 | from dataclasses import dataclass 18 | 19 | @dataclass 20 | class Person: 21 | name: str 22 | age: int 23 | ``` 24 | 25 | Görüldüğü gibi `dataclass`'lar `@dataclass` decoratoru ile tanımlanır. 26 | 27 | > Sınıf değişkenlerinin yanında tipinin yazıldığını fark etmişsinizdir. Bu Python'da [Type Annotations](https://docs.python.org/3/library/typing.html) olarak geçer. `dataclass`'lar bunu kullandığından dolayı bilmek önemlidir. [Şurada](https://realpython.com/python-type-checking/) type annotations ile ilgili cok güzel bir makale var. 28 | 29 | Bir `dataclass`, bazı [special method](https://docs.python.org/3/reference/datamodel.html#special-method-names)'ları sizin için değiştirir. Mesela `__str__()` methodu otomatik olarak sınıf içindeki değerleri bastıracaktır. 30 | 31 | ```python 32 | >>> p = Person("Ömer", 19) 33 | >>> p 34 | Person(name='Ömer', age=19) 35 | >>> p.name = "Ahmet" 36 | >>> p.age = 30 37 | >>> p 38 | Person(name='Ahmet', age=30) 39 | ``` 40 | 41 | Eğer biz böyle bir çıktı isteseydik şöyle bir şey yazmamız gerekirdi; 42 | 43 | ```python 44 | class NPerson: 45 | def __init__(self, name, age): 46 | self.name = name 47 | self.age = age 48 | 49 | def __repr__(self): 50 | return f"{self.__class__.__name__}(name={self.name!r}, age={self.age})" 51 | 52 | def __str__(self): 53 | return self.__repr__() 54 | ``` 55 | 56 | Görüldüğü gibi bir değişkeni `__init__`'den alıp nesne değişkeni yapmak için 3 kere yazmamız gerekiyor. 57 | 58 | Ayrıca dikkatli bakarsanız objelerin tam olarak _tanımlanmadığını_ göreceksiniz. 59 | 60 | ```python 61 | >>> p = Person("Ömer", 19) 62 | >>> p == Person("Ömer", 19) 63 | True 64 | >>> np = NPerson("Ahmet", 30) 65 | >>> np == NPerson("Ahmet", 30) 66 | False 67 | ``` 68 | 69 | 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; 70 | 71 | ```python 72 | class NPerson: 73 | [...] 74 | def __eq__(self, other): 75 | if other.__class__ is not self.__class__: 76 | return NotImplemented 77 | return (self.name, self.age) == (other.name, other.age) 78 | ``` 79 | 80 | `dataclass`'lar bunu bizim için yapar. 81 | 82 | `dataclass`'lara default değerler de verebiliriz; 83 | 84 | ```python 85 | @dataclass 86 | class Person: 87 | name: str 88 | age: int = 18 89 | 90 | >>> p = Person("Ahmet") 91 | >>> p 92 | Person(name='Ahmet', age=18) 93 | ``` 94 | 95 | > **Not:** `dataclass`'lar (ve Python) aslında değişkenlerin tipine dikkat etmez. Type annotations sadece okunabilirliği artırır. 96 | 97 | ```python 98 | from dataclasses import dataclass 99 | from typing import Any 100 | 101 | @dataclass 102 | class Person: 103 | name: Any 104 | age: str 105 | 106 | >>> p = Person(14, "Foo") 107 | >>> p 108 | Person(name=14, age='Foo') 109 | ``` 110 | 111 | Şu ana kadar hiç fonksiyon yazmadık ama `dataclass`'da fonksiyon yazmak ile normalde yazmak arasında hiç fark yoktur. 112 | 113 | ```python 114 | from dataclasses import dataclass 115 | from typing import List 116 | 117 | @dataclass 118 | class Student: 119 | name: str 120 | age: int 121 | results: List[int] 122 | 123 | def average(self): 124 | return sum(self.results) / len(self.results) 125 | ``` 126 | 127 | ```python 128 | >>> st = Student("Ömer", 19, [85,97,67]) 129 | >>> st.average() 130 | 83.0 131 | ``` 132 | 133 | Biraz `@dataclass` decoratoru hakkında konuşalım. `@dataclass`decoratoru birçok parametre alabilir. 134 | 135 | ```python 136 | @dataclass 137 | class Foo: 138 | [...] 139 | 140 | # Aslında buna eşittir. 141 | 142 | @dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False) 143 | class Foo: 144 | [...] 145 | 146 | ``` 147 | 148 | - `init`: eğer `True` ise `__init__` fonksiyonunu override eder. 149 | 150 | - `repr`: eğer `True` ise `__repr__` fonksiyonunu override eder. 151 | 152 | - `eq`: eğer `True` ise `__eq__` fonksiyonunu override eder. Bu konuya yukarda değinmiştik. 153 | 154 | - `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. 155 | 156 | - `frozen`: eğer `True` ise nesne oluşturduktan sonra gelen değer atamaları `FrozenInstanceError` hatasını raise edecektir. 157 | 158 | ```python 159 | from dataclasses import dataclass 160 | from typing import List 161 | 162 | @dataclass(frozen=True) 163 | class Student: 164 | name: str 165 | age: int 166 | results: List[int] 167 | 168 | def average(self): 169 | return sum(self.results) / len(self.results) 170 | ``` 171 | 172 | ```python 173 | >>> st = Student("Ömer", 19, [85,97,67]) 174 | >>> st.name = "Ahmet" 175 | --------------------------------------------------------------------------- 176 | FrozenInstanceError Traceback (most recent call last) 177 | in 178 | ----> 1 st.name = "Ahmet" 179 | 180 | in __setattr__(self, name, value) 181 | 182 | FrozenInstanceError: cannot assign to field 'name' 183 | ``` 184 | 185 | `dataclass` ile kalıtım da yapabiliriz. 186 | 187 | ```python 188 | from dataclasses import dataclass 189 | from typing import List 190 | 191 | @dataclass 192 | class Person: 193 | name: str 194 | age: int 195 | 196 | def say(self, s): 197 | print(f"{self.name}: {s}") 198 | 199 | @dataclass 200 | class Student(Person): 201 | results: List[int] 202 | 203 | def average(self): 204 | return sum(self.results) / len(self.results) 205 | ``` 206 | 207 | ```python 208 | >>> st = Student("Ömer", 19, [85,97,67]) 209 | >>> st.say("Hello world") 210 | Ömer: Hello world 211 | ``` 212 | 213 | ## Alternatifler 214 | 215 | `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. 216 | 217 | ```python 218 | person_tuple = (19, "Ömer") # Tuple 219 | person_dict = {'age': 19, 'name': 'Ömer'} # Dict 220 | ``` 221 | 222 | 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. 223 | 224 | ```python 225 | person_dict['name'] ## person_dict.name desek daha hoş olmaz mı? 226 | ``` 227 | 228 | > Aslında `dict` veri tipindeki objelerin verilerine nokta(.) notasyonu ile erişebiliriz. Şöyle: 229 | 230 | > Konu ile ilgili içerik [Sözlük Veri Tipini Python Nesnesine Dönüştürme](https://www.coogger.com/@hakancelik96/sozluk-veri-tipini-python-nesnesine-donusturme/) 231 | 232 | ```python 233 | class NDict(dict): 234 | def __init__(self, *arg, **kwargs): 235 | for key, value in kwargs.items(): 236 | setattr(self, key, value) 237 | super().__init__(*arg, **kwargs) 238 | 239 | person = NDict(name="Ömer", age=19) 240 | person.age # 19 241 | person.name # Ömer 242 | ``` 243 | 244 | 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` 245 | 246 | ```python 247 | from collections import namedtuple 248 | 249 | Person = namedtuple("Person", ['age', 'name']) 250 | person = Person(16, "Ömer") 251 | 252 | person 253 | # Person(age=16, name='Ömer') 254 | person.name 255 | # Ömer 256 | ``` 257 | 258 | E `dataclass`'lardan farkı ne bunun? 259 | Ö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 260 | 261 | ```python 262 | >>> person == (16, "Ömer") 263 | True 264 | ``` 265 | 266 | İ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. 267 | 268 | Ayırca `namedtuple` obje oluştuktan sonra verilerin değişmesine izin vermeyecektir. 269 | 270 | ```python 271 | Person = namedtuple("Person", ['age', 'name']) 272 | person = Person(16, "Ömer") 273 | person.age = 22 274 | ---------------------------------------- 275 | AttributeError Traceback (most recent call last) 276 | in 277 | 3 Person = namedtuple("Person", ['age', 'name']) 278 | 4 person = Person(16, "Ömer") 279 | ----> 5 person.age = 22 280 | 281 | AttributeError: can't set attribute 282 | ``` 283 | 284 | ## field() 285 | 286 | Bir seneryo üzerinden devam edelim. 287 | 288 | ```python 289 | from dataclasses import dataclass 290 | from typing import List 291 | 292 | @dataclass 293 | class Student: 294 | id: int 295 | name: str 296 | 297 | @dataclass 298 | class Lesson: 299 | students: List[Student] 300 | ``` 301 | 302 | Buradan yeni nesneler üretelim 303 | 304 | ```python 305 | omer = Student(1, "Ömer") 306 | bersu = Student(2, "Bersu") 307 | math = Lesson([omer, bersu]) 308 | print(math) 309 | # Lesson(students=[Student(id=1, name='Ömer'), Student(id=2, name='Bersu')]) 310 | ``` 311 | 312 | Şimdi `Lesson` sınıfına default deger vermeyi deneyelim. Bunu yaparken bir factory fonksiyon yazalım. 313 | 314 | ```python 315 | NAMES = ["Ömer", "Ahmet", "Cem", "Zehra", "Büşra", "Bersu"] 316 | 317 | def collect_students(): 318 | return [Student(i + 1, v) for i, v in enumerate(NAMES)] 319 | 320 | collect_students() 321 | 322 | # [Student(id=1, name='Ömer'), 323 | # Student(id=2, name='Ahmet'), 324 | # Student(id=3, name='Cem'), 325 | # Student(id=4, name='Zehra'), 326 | # Student(id=5, name='Büşra'), 327 | # Student(id=6, name='Bersu')] 328 | ``` 329 | 330 | Teoride `Lesson`a varsayılan değer vermek için şöyle yaparsınız. 331 | 332 | ```python 333 | @dataclass 334 | class Lesson: 335 | students: List[Student] = collect_students() 336 | ``` 337 | 338 | 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. 339 | 340 | ```python 341 | --------------------------------------------------- 342 | ValueError Traceback (most recent call last) 343 | in 344 | 12 name: str 345 | 13 346 | ---> 14 @dataclass 347 | 15 class Lesson: 348 | 16 students: List[Student] = collect_students() 349 | 350 | \python\python37-32\lib\dataclasses.py in _get_field(cls, a_name, a_type) 351 | 725 # For real fields, disallow mutable defaults for known types. 352 | 726 if f._field_type is _FIELD and isinstance(f.default, (list, dict, set)): 353 | --> 727 raise ValueError(f'mutable default {type(f.default)} for field ' 354 | 728 f'{f.name} is not allowed: use default_factory') 355 | 729 356 | 357 | ValueError: mutable default for field students is not allowed: use default_factory 358 | ``` 359 | 360 | Bunun önüne geçmek için `field` methodunun `default_factory` diye bir parametresi var. 361 | 362 | ```python 363 | from datacasses import dataclass, field 364 | 365 | @dataclass 366 | class Lesson: 367 | students: List[Student] = field(default_factory=collect_students) 368 | 369 | Lesson() 370 | # 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')]) 371 | ``` 372 | 373 | `field`, sadece `default_factory` ile sınırlı değil. [Bu bağlantıdan](https://docs.python.org/3/library/dataclasses.html#dataclasses.field) diğer parametrelere ve ne işe yaradıklarına ulaşabilirsiniz. 374 | 375 | ## Optimizasyon 376 | 377 | 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](https://medium.com/@mazlumagar/python-tricks-1-slots-e0c9b04f4c5a) daha fazla bilgiye ulaşabilirsiniz. 378 | 379 | `dataclass`'larda `__slots__` kullanımı normal classlardaki gibidir. 380 | 381 | ```python 382 | from dataclasses import dataclass, field 383 | 384 | @dataclass 385 | class NormalPerson: 386 | name: str 387 | age: int 388 | salary: int 389 | 390 | @dataclass 391 | class SlotPerson: 392 | __slots__ = ['name', 'age', 'salary'] 393 | name: str 394 | age: int 395 | salary: int 396 | ``` 397 | 398 | Hafızada sahip olduğu büyüklüğe bakalım. 399 | 400 | ```python 401 | from sys import getsizeof 402 | 403 | getsizeof(NormalPerson("Ahmet", 33, 3000)), getsizeof(SlotPerson("Ahmet", 33, 3000)) 404 | # (32, 36) 405 | ``` 406 | 407 | Ayırca Python'un veriye erişmesi de normal class'lara göre daha hızlıdır. 408 | 409 | ```python 410 | from timeit import timeit 411 | 412 | timeit(setup="slot_p = SlotPerson('Ahmet', 33, 3000)", globals=globals()) 413 | # 0.012656699999752163 414 | timeit(setup="normal_p = NormalPerson('Ahmet', 33, 3000)", globals=globals()) 415 | # 0.012095599999156548 416 | ``` 417 | 418 | 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. 419 | -------------------------------------------------------------------------------- /pages/posts/use-native-driver/Fixed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/use-native-driver/Fixed.gif -------------------------------------------------------------------------------- /pages/posts/use-native-driver/Panic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/use-native-driver/Panic.gif -------------------------------------------------------------------------------- /pages/posts/use-native-driver/SmoothAnimation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bufgix/website/6a87825ac1d187f85e0b90ee198e48c28182b667/pages/posts/use-native-driver/SmoothAnimation.gif -------------------------------------------------------------------------------- /pages/posts/use-native-driver/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: useNativeDriver üzerine 3 | date: 2021-11-01 4 | tag: 5 | - react-native 6 | - animation 7 | --- 8 | 9 | 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? 10 | 11 | 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. 12 | 13 | ### Animasyonları JS'ye vermek 14 | 15 | Bir animasyon javascript tarafında kabaca şu adımları takip ederek oluşturulur. 16 | 17 | - Animasyon başlar 18 | - JS `requestAnimationFrame` fonksiyonunu çalıştırır. (Bu fonksiyon her saniye 60 kere çalıştırılmaya çalışılır.) 19 | - JS değiştirilmek istenen şeyin bir sonraki konumunu/opaklığını/transformunu hesaplar 20 | - JS hesapladığı değişikliği `bridge` aracılığıyla işletim sistemine gönderir. 21 | - Köprünün diğer ucunda bulunan native kod, (Android için Java, IOS için Obj-C) belirtilen transform işlemini uygular. 22 | - Ekranda yeni değişiklik güncellenir. 23 | 24 | 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. 25 | 26 | Her şey güzel JS animasyonlarımızı güzel güzel oluşturuyor. Fakat... 27 | 28 | 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. 29 | 30 | Bir örnek bu gecikmeyi daha iyi anlamanıza yardımcı olabilir. 31 | 32 | ```jsx {11} 33 | import React, { useRef } from 'react'; 34 | import { SafeAreaView, Animated, TouchableOpacity, Text } from 'react-native'; 35 | 36 | const App = () => { 37 | const scale = useRef(new Animated.Value(1)).current; 38 | 39 | const startAnimation = () => { 40 | Animated.timing(scale, { 41 | toValue: 2, 42 | duration: 600, 43 | useNativeDriver: false // Native dirver devre dışı 44 | }).start(); 45 | }; 46 | 47 | return ( 48 | 49 | 58 | 59 | Press Me 60 | 61 | 62 | ); 63 | }; 64 | 65 | export default App; 66 | ``` 67 | 68 | ![Smooth animation](./SmoothAnimation.gif) 69 | 70 | Evet JS güzel bir şekilde animasyonu çalıştırıyor. Biraz daha zorlayalım 71 | 72 | ```js {8-13} 73 | const startAnimation = () => { 74 | Animated.timing(scale, { 75 | toValue: 2, 76 | duration: 600, 77 | useNativeDriver: false, 78 | }).start(); 79 | 80 | setTimeout(() => { 81 | let r = 0 82 | for(let i = 0; i < 1000000000; i++) { 83 | r = i ** i 84 | } 85 | }, 10) 86 | ``` 87 | 88 | ![Panic!](./Panic.gif) 89 | 90 | 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. 91 | 92 | Biraz daha açarsak, animasyon başladığı anda `requestAnimationFrame`saniyede 60 kere çalıştırılmaya çalışılacak. JS her defasında bir işlemi yapabileceği için 93 | 94 | ```js 95 | setTimeout(() => { 96 | let r = 0; 97 | for (let i = 0; i < 1000000000; i++) { 98 | r = i ** i; 99 | } 100 | }, 10); 101 | ``` 102 | 103 | bu kod JS threadi _meşgul_ edecek bir bir sonraki animasyon karesi atlanacak. Sonuç olarak "smooth" olmayan animsayonlar oluşacak. 104 | 105 | Neyse ki React Native, native tarafta animasyonları oluşturmamıza izin verir. 106 | 107 | ### Animasyonları native tarafta çalıştırmak 108 | 109 | Yukardaki problemi basitçe `useNativeDriver` parametresini `true` yaparak çözebilirsiniz 110 | 111 | ```js {4} 112 | Animated.timing(scale, { 113 | toValue: 2, 114 | duration: 600, 115 | useNativeDriver: true 116 | }).start(); 117 | ``` 118 | 119 | ![Fixed](./Fixed.gif) 120 | 121 | Peki bu nasıl oluyor? Kabaca şöyle; 122 | 123 | - Animasyon başlar 124 | - JS animasyon için gerekli olan önbilgiyi "bridge" aracılığıyla aktarır 125 | - **Native tarafta** animasyon oluşturulur. 126 | 127 | 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. 128 | 129 | Bu noktada aklınıza şu gelebilir; "E o zaman `useNativeDriver`'i her zaman açalım" 130 | 131 | Animasyonları native tarafta çalıştırmanın bazi **eksileri** vardır. 132 | 133 | - Animasyonlar üstünde daha az yetkiye sahip olursunuz. (JS, animasyon devam ederken olan biteni gözlemleyemez.) 134 | - Daha az özeliği manipüle edebilirsiniz. Mesela `height` veya `width` özeliklerini native olarak animate edemezsiniz. 135 | 136 | ### Sonuç olarak 137 | 138 | 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. 139 | -------------------------------------------------------------------------------- /pages/tags/[tag].mdx: -------------------------------------------------------------------------------- 1 | --- 2 | type: tag 3 | title: Tagged Posts 4 | --- 5 | 6 | import { useRouter } from 'next/router'; 7 | 8 | export const TagName = () => { 9 | const { tag } = useRouter().query; 10 | return tag || null; 11 | }; 12 | 13 | # Posts Tagged with “” 14 | -------------------------------------------------------------------------------- /theme.config.jsx: -------------------------------------------------------------------------------- 1 | const currentYear = new Date().getFullYear(); 2 | 3 | export default { 4 | footer:

{currentYear} © Faruk

, 5 | head: ({ meta }) => ( 6 | <> 7 | {meta.description && ( 8 | 9 | )} 10 | {meta.tag && } 11 | {meta.author && } 12 | 13 | ), 14 | readMore: 'Read More →', 15 | postFooter: null, 16 | darkMode: true, 17 | navs: [], 18 | }; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "ES2022" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "strict": true, 13 | "noEmit": false, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "Bundler", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "moduleDetection": "force", 20 | "incremental": true, 21 | "noUncheckedIndexedAccess": true, 22 | "jsx": "preserve" 23 | }, 24 | "exclude": [ 25 | "node_modules", 26 | "build", 27 | "dist", 28 | ".next", 29 | ".expo" 30 | ], 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx" 35 | ] 36 | } 37 | --------------------------------------------------------------------------------