perf: deterministik dekor memo'ları ve küçük render süpürmeleri (Faz 7)
Some checks failed
Deploy / deploy (push) Has been cancelled

- pixel-decor: PixelField/PixelDivider piksel üretimi useMemo([seed]) —
  504 iterasyon + ~150 element çekmece reorder'ının her karesinde
  yeniden üretiliyordu
- rapor-listesi: acikSira effect'i önceki-prop desenine döndü (çift render
  ve eslint-disable kalktı), Set lazy init, tekIl useMemo
- program-tablosu: 3 dilimin spread'i useMemo
- typing-animation: displayedText state yerine türetme; kaynak reset'i
  render sırasında — tick başına state yazımı ve effect bağımlılığı azaldı
- site-top-banner: saniyelik geri sayım tiki startTransition'da
- program-liste-verileri: sparkline 6 dizi geçişi tek döngüde
- secimlerim-paneli: SecimSatiri profili prop'tan alır + memo (24 ayrı
  store aboneliği kalktı)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-08-08 14:29:14 +03:00
parent f3d8bf9b94
commit 78d7c51a7a
7 changed files with 129 additions and 86 deletions

View File

@@ -8,7 +8,7 @@
// açık; Yapay Zeka katmanı kilitliBaslangic sonrasında ücretlidir (metin
// sunucuda zaten maskelidir).
import { Fragment, useEffect, useRef, useState } from "react";
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { ChevronDown, Lock, MapPin, Rocket, Scale, ShieldCheck } from "lucide-react";
import { UniLogo } from "@/components/uni-logo";
@@ -121,14 +121,20 @@ export function RaporListesi({
/** Detaydaki "Haritada gör" — il DB biçiminde */
onHaritadaGor?: (il: string | null, sira: number) => void;
}) {
const [acik, setAcik] = useState<Set<number>>(new Set());
// Lazy init: boş Set her render'da boşuna allocate edilmesin
const [acik, setAcik] = useState<Set<number>>(() => new Set());
const satirRefs = useRef(new Map<number, HTMLTableRowElement>());
// Haritadan gelen seçim: satırı aç ve görünür alana kaydır
// Haritadan gelen seçim: satırın açılması render sırasındaki önceki-prop
// deseniyle (effect'te setState çift render yaratıyordu); yalnızca DOM işi
// olan scrollIntoView effect'te kalır.
const [oncekiAcikSira, setOncekiAcikSira] = useState(acikSira);
if (acikSira !== oncekiAcikSira) {
setOncekiAcikSira(acikSira);
if (acikSira != null) setAcik((mevcut) => new Set(mevcut).add(acikSira));
}
useEffect(() => {
if (acikSira == null) return;
// eslint-disable-next-line react-hooks/set-state-in-effect
setAcik((mevcut) => new Set(mevcut).add(acikSira));
satirRefs.current
.get(acikSira)
?.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -145,12 +151,14 @@ export function RaporListesi({
const kilitIdx = kilitliBaslangic ?? rapor.tercihler.length;
const iller = new Set(
rapor.tercihler
.map((t) => rapor.programlar[t.programId]?.il)
.filter((il): il is string => Boolean(il)),
);
const tekIl = iller.size === 1 ? [...iller][0] : null;
const tekIl = useMemo(() => {
const iller = new Set<string>();
for (const t of rapor.tercihler) {
const il = rapor.programlar[t.programId]?.il;
if (il) iller.add(il);
}
return iller.size === 1 ? [...iller][0] : null;
}, [rapor]);
const satir = (t: RaporSonuc["tercihler"][number], index: number) => {
const p = rapor.programlar[t.programId];