From 27d97372eef8566da647e906521dd51674b94e27 Mon Sep 17 00:00:00 2001 From: Abdussamed Date: Wed, 17 May 2023 23:39:01 +0300 Subject: [PATCH] Beta --- readme.md | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ wire.js | 177 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 readme.md create mode 100644 wire.js diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..273fba4 --- /dev/null +++ b/readme.md @@ -0,0 +1,241 @@ +# WireJS | Değişken değil, kablo kullanın ! + +## Açıklama + +WireJS, ReactJS kütüphanesinin useState ve useEffect kancalarından ilham alınarak browser ortamı için tekrar yazılmış bir kütüphanedir. +Verileri getter/setter fonksiyonlarıyla sararak gerçek değişikleri algılayan özelliği sayesinde gereksiz güncellemeleri göz ardı eder. + +Veri ilişkilerini önceden belirleyerek değişiklik olduğunda okunacak ve yazılacak verileri koşullara bağlayabilir, okuma ve yazma öncesi ara katmanlar ekleyebilirsiniz + +## Kullanım örneği + +### Kablo okuma ve değiştirme + +Bir değişkeni `get` ile okuyabilir, `set` ile değiştirebilirsiniz. `get` fonksiyonu veriyi olduğu gibi döner. `set` fonksiyonuna bir fonksiyon verirseniz hemen çalıştıracak ve return edilen veriyi kabul edecektir + +```javascript +// Yeni kablo oluşturun +let currency = é(+0.00); + +// Değerini okuyun +currency.get(); // --> 0 + +// Değerini değiştirin +currency.set(5); + +currency.get(); // --> 5 + +// Değerini fonksiyon ile değiştirin +currency.set(oldValue => { + // Eski verinin üzerine 20 ekle + return oldValue + 20; +}); + +currency.get(); // --> 25 +``` + +## Değişimler + +Bir kablonun değerinin değiştiğini `useEffect` özelliğiyle dinleyebilirsiniz veya birden fazla kablonun değişimini aynı anda dinlemek için `é.useEffect` fonksiyonunu kullanabilirsiniz + +```javascript +let a = é(0); +let b = é(0); +a.useEffect(()=>{ + // a kablosu değiştiğinde burası çalışacak +}) +b.useEffect(()=>{ + // b kablosu değiştiğinde burası çalışacak +}) +é.useEffect(()=>{ + // a veya b değiştiğinde burası çalışacak +},[a, b]) +``` +Örnek +```javascript +let toplayan = é(0); +let toplanan = é(0); +let sonuc = é(0); + +é.useEffect(()=>{ + sonuc.set( toplayan.get() + toplanan.get() ) +},[toplayan, toplanan]) + +toplayan.useEffect(()=>{ + console.log("Toplayan: ",toplayan.get()) +}) +toplanan.useEffect(()=>{ + console.log("Toplanan: ",toplanan.get()) +}) +sonuc.useEffect(()=>{ + console.log("Değişen Sonuç: ",sonuc.get()) +}) + +toplayan.set(7); +toplanan.set(2); + +console.log("Sonuç",sonuc.get()) + +``` + +Konsol çıktısı aşağıdaki gibi olacaktır + +``` +Toplayan: 0 +Toplanan: 0 +Değişen Sonuç: 0 +Toplayan: 7 +Değişen Sonuç: 7 +Toplanan: 2 +Değişen Sonuç: 9 +Sonuç: 9 + +``` + +## Ara katmanlar + +### Okuma ara katmanı + +Bazen orjinal değişkeni değiştirmeden her seferinde daha gelişmiş bir veri tipiyle okumak istersiniz + +```javascript +// Yeni kablo oluşturun +let user = é({ + name: "John", + surname: "Wattson" +}); + +// Normalde okuma katmanı eklemezseniz veri düz bir şekilde verecektir +user.get(); // --> {"name":"John",surname:"Wattson"} + +// Verinin nasıl okunacağını belirleyin +user.readLayer((value, update)=>{ + // value: orjinal veridir + // değiştirmek istediğinizde update fonksiyonunda verirsiniz + update( + `İsim: ${value.name}, soyisim: ${value.surname}` + ) +}) + +// `get` fonksiyonu veriyi nasıl okuyacağını readLayer fonksiyonunda öğrenecektir +user.get(); // --> "İsim: John, soyisim: Wattson" +``` + +### Yazma ara katmanı + +Bazen orjinal değişkeni değiştirmeden her seferinde daha gelişmiş bir veri tipiyle okumak istersiniz + +```javascript +// Yeni kablo oluşturun +let user = é([2,4,6,8,10]); + +user.writeLayer((value, update)=>{ + // Verinin sadece ilk 5 öğesini alıyoruz + update( + value.slice(0, 5) + ) +}) +user.writeLayer((value, update)=>{ + // Sadece çift sayıları alıyoruz + update( + value.filter(e => e % 2 == 0) + ) +}) + +// Orjinal veri +user.get(); // --> [1,2,3,4,5] + +// set fonksiyonumuz gerçek veriyi değiştirmeden önce yukarıdaki iki yazma katmanını çalıştırıyorlar +// set fonksiyonumuz artık sadece ilk 5 çift sayıyı kaydediyor +user.set([10,11,12,13,14,15,16,17,18,19,20,21,22,23]) + +user.get(); // --> [10,12,14,16,18] +``` + +## Fark bulma algoritması + +WireJS eşitlenmek istenen değeri zaten var olan değer ile karşılaştırarak farklı olup olmadığını algılayan özel bir algoritmaya sahiptir. Bu şekilde tekrarlı eşitlemelerde veya bellekte aynı şekilde tutulan veriler için yazma işlemi gerçekleştirmez + +### Basit değişkenler + +```javascript +let variable = é(0); +variable.set(false); // --> Değişecek +variable.set(7); // --> Değişecek +variable.set(7); // --> Değişmeyecek +variable.set(7 + 0); // --> Değişmeyecek +variable.set(-7); // --> Değişecek +``` +### Objeler ve listeler + +```javascript +let variable = é([1,2,3]); +variable.set([1,2,3,4]) // --> Değişecek +variable.set([1,2,3,4]) // --> Değişmeyecek +variable.set([1,0,3,4]) // --> Değişecek +variable.set() // --> Değişecek (undefined olarak) +variable.set(null) // --> Değişmeyecek + +let variable = é({ + name: null, + surname: "Wattson", + numbers: [1,2,3,4] +}); + +// İsmini null iken John olarak değiştiği için değişecek +variable.set(oldValue => { + return { + name: "John", + surname: "Wattson", + numbers: [1,2,3,4] + } +}) + +// isim ve soyisim bir öncekiyle aynı olduğu için hiç bir şey yapılmaz +variable.set({ + name: "John", + surname: "Wattson", + numbers: [1,2,3,4] +}) + +// Ayrıca fark bulmak algoritması sonsuz derinlikte tarama yapıp farkı anlayabilir +variable.set({ + name: "John", + surname: "Wattson", + numbers: [1,2,3,4,"Hello !"] +}) + +``` + +### Sonsuz derinlikteki objeler + +Javascriptte bir objenin içindeki bir değer yine kendisini işaret ediyor olabilir, böyle bir durumda objenin içini tarayan algoritma aynı objeyi defalarca taramaması için (Maximum call stack hatası) önlem koyulmuştur + +```javascript +let infinite = { + one: 1, + two: 2, + three: 3, + onetwothree: undefined +}; +// Objenin içine yine kendisini yerleştiriyoruz +infinite.onetwothree = a; + +let variable = é(infinite); + +/** + * Aşağıdaki kodda WireJS neyin değiştiğini anlamak için infinite + * objesinin içine girdiğinde, içine girdiği objeleri bellekte + * tutarak aynı objeyi tekrar taramasını engelleyen yapıya + * sahip olur + **/ + +// Sonsuz döngüde dönerek [Maximum call stack] hatası vereceğine three değerini başarılı bir şekilde değiştirir +variable.set(oldValue => { + // sadece three değerini 3 ile çarpıyoruz + return { + ...oldValue, + three: oldValue.three * 3 + } +}); +``` \ No newline at end of file diff --git a/wire.js b/wire.js new file mode 100644 index 0000000..2790cb2 --- /dev/null +++ b/wire.js @@ -0,0 +1,177 @@ +function é(defaultValue) +{ + let value = undefined; + let effects = []; + let piping = é.pipeLine(); + let flag = 0; + let version = 0; + let diff = é.diff; + let get = () => { + return (flag & 2) ? piping.get(value) : value; + }; + let set = (newValue) => { + newValue = é.extract(newValue, value); + (flag & 4) && (newValue = piping.set(newValue)); + if(diff(newValue,value)) + { + (flag & 1) && effects.filter(e => e.o).forEach(k => k()); + value = newValue; + version++; + (flag & 1) && effects.map(e => e.f()); + } + }; + let useEffect = (e) => { + flag = flag | 1; + let k = { + f:e, + o:undefined + }; + requestAnimationFrame(()=> { + k.o = e(); + }) + effects.push(k); + } + value = é.extract(defaultValue, value); + return { + get, + set, + useEffect, + fp: é.fingerPrint, + getVersion: () => version, + equalTo: n => é.isSame(n, value), + readLayer: (a,b) => { + flag = flag | 2; + piping.read(a,b) + }, + writeLayer: (a,b) => { + flag = flag | 4; + piping.write(a,b) + } + } +} +é.isWire = n => n?.fp == é.fingerPrint; +é.extract = (e,v) => typeof e=='function'?e(v):e; +é.isSame = (a,b) => !é.diff(a,b); +é.fingerPrint = Symbol("wire"); +é.useEffect = (fn, assoc) => +{ + for (const wireVar of assoc) + { + if(é.isWire(wireVar)) + { + wireVar.useEffect(fn); + } + } +}; +é.diff = (a,b,c) => { + let cursoryDiffResult = é.cDiff(a,b); + if(cursoryDiffResult == é.cDiff.adiff) + { + return é.adiff(a,b,c||[]) + }else{ + return cursoryDiffResult == é.cDiff.some ? false : true; + } +} +é.adiff = (a,b,c) => { + if(c.includes(a) && c.includes(b)) + { + console.error("Circular object detected !") + return é.cObjectDiff(a,b); + } + let typea = a instanceof Array ? 0 : a.byteLength ? 1 : 2; + let typeb = b instanceof Array ? 0 : b.byteLength ? 1 : 2; + if(typea != typeb) + { + return true; + } + if(typea==0) + { + if(a.length != b.length) + { + return true + } + for(let k = 0; k < a.length; k++) + { + if(é.diff(a[k], b[k])) + { + return true + } + } + return false + } + if(a instanceof Object) + { + if(Object.keys(a).length != Object.keys(b).length) + { + return true + } + c.push(a); + c.push(b); + for (const key in a) + { + if (Object.hasOwnProperty.call(a, key)) + { + if(é.diff(a[key], b[key], c)) + { + return true + } + } + } + return false + } +} +é.cArrDiff = (a,b) => { + return a.length != b.length || !a.every((v,i) => a[i] === v) +}; +é.cObjectDiff = (a,b) => { + return é.cArrDiff( + Object.keys(a), + Object.keys(b) + ); +}; +é.cDiff = (a,b) => { + switch(typeof a) + { + case "undefined": + case "function":{ + return typeof a == typeof b ? é.cDiff.some : é.cDiff.different + } + case "symbol": + case "bigint": + case "boolean": + case "number": + case "string":{ + return a === b ? é.cDiff.some : é.cDiff.different; + } + case "object":{ + return é.cDiff.adiff; + } + }; +} +é.cDiff.adiff = -1; +é.cDiff.some = 0; +é.cDiff.different = 1; +é.pipeLine = () => { + let k = {}; + let reads = []; + let writes = []; + k.read = (fn,pr) => reads.push({fn,pr}); + k.write = (fn,pr) => writes.push({fn,pr}); + k.get = (val) => { + let fns = reads.sort((a,b) => (a?.pr|0) - (b?.pr|0)), + real = val; + for (const { fn } of fns) { + fn( real, e => real = e ); + }; + return real; + }; + k.set = (val) => { + let fns = writes.sort((a,b) => (a?.pr|0) - (b?.pr|0)), + real = val; + for (const { fn } of fns) { + fn( real, e => real = e ); + }; + return real; + }; + return k; +} \ No newline at end of file