ด่าน 1 (คำอธิบาย)
ผู้ชนะข้อเสนอ TC39: Daniel Ehrenberg, Yehuda Katz, Jatin Ramanathan, Shay Lewis, Kristen Hewell Garrett, Dominic Gannaway, Preston Sego, Milo M, Rob Eisenberg
ผู้เขียนต้นฉบับ: Rob Eisenberg และ Daniel Ehrenberg
เอกสารนี้อธิบายทิศทางทั่วไปในช่วงเริ่มต้นสำหรับสัญญาณใน JavaScript คล้ายกับความพยายามของ Promises/A+ ซึ่งนำหน้า Promises ที่เป็นมาตรฐานโดย TC39 ใน ES2015 ลองด้วยตัวคุณเองโดยใช้โพลีฟิล
เช่นเดียวกับ Promises/A+ ความพยายามนี้มุ่งเน้นไปที่การปรับระบบนิเวศของ JavaScript หากการจัดแนวนี้ประสบความสำเร็จ มาตรฐานก็อาจเกิดขึ้นได้ขึ้นอยู่กับประสบการณ์นั้น ผู้เขียนเฟรมเวิร์กหลายคนกำลังทำงานร่วมกันที่นี่เกี่ยวกับโมเดลทั่วไปซึ่งสามารถสนับสนุนแกนปฏิกิริยาของพวกเขาได้ แบบร่างปัจจุบันอิงตามข้อมูลการออกแบบจากผู้เขียน/ผู้ดูแล Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz และอื่นๆ...
แตกต่างจาก Promises/A+ เราไม่ได้พยายามแก้ปัญหาสำหรับ Surface API ทั่วไปที่นักพัฒนาต้องเผชิญ แต่เป็นการแก้ปัญหาหลักที่แม่นยำของกราฟสัญญาณที่ซ่อนอยู่ ข้อเสนอนี้รวม API ที่เป็นรูปธรรมอย่างสมบูรณ์ แต่ API ไม่ได้กำหนดเป้าหมายไปที่นักพัฒนาแอปพลิเคชันส่วนใหญ่ แต่สัญญาณ API ที่นี่เหมาะกว่าสำหรับเฟรมเวิร์กที่จะสร้างต่อยอด โดยให้ความสามารถในการทำงานร่วมกันผ่านกราฟสัญญาณทั่วไปและกลไกการติดตามอัตโนมัติ
แผนสำหรับข้อเสนอนี้คือการสร้างต้นแบบที่สำคัญตั้งแต่เนิ่นๆ รวมถึงการบูรณาการเข้ากับเฟรมเวิร์กต่างๆ ก่อนที่จะก้าวไปไกลกว่าขั้นที่ 1 เราสนใจเฉพาะในการกำหนดมาตรฐานสัญญาณหากสัญญาณเหล่านั้นเหมาะสำหรับใช้ในทางปฏิบัติในหลายเฟรมเวิร์ก และให้ประโยชน์ที่แท้จริงเหนือเฟรมเวิร์ก- สัญญาณที่ให้มา เราหวังว่าการสร้างต้นแบบที่สำคัญในช่วงแรกๆ จะให้ข้อมูลนี้แก่เรา ดู "สถานะและแผนการพัฒนา" ด้านล่างสำหรับรายละเอียดเพิ่มเติม
ในการพัฒนาอินเทอร์เฟซผู้ใช้ (UI) ที่ซับซ้อน นักพัฒนาแอปพลิเคชัน JavaScript จำเป็นต้องจัดเก็บ คำนวณ ทำให้ใช้งานไม่ได้ ซิงค์ และพุชสถานะไปยังเลเยอร์มุมมองของแอปพลิเคชันอย่างมีประสิทธิภาพ โดยทั่วไปแล้ว UI จะเกี่ยวข้องมากกว่าการจัดการค่าธรรมดา แต่มักจะเกี่ยวข้องกับการเรนเดอร์สถานะที่คำนวณซึ่งขึ้นอยู่กับแผนผังที่ซับซ้อนของค่าอื่นหรือสถานะที่คำนวณด้วยตัวมันเอง เป้าหมายของ Signals คือการจัดหาโครงสร้างพื้นฐานสำหรับการจัดการสถานะแอปพลิเคชันดังกล่าว เพื่อให้นักพัฒนาสามารถมุ่งเน้นไปที่ตรรกะทางธุรกิจมากกว่ารายละเอียดที่ซ้ำซากเหล่านี้
โครงสร้างที่คล้ายสัญญาณพบว่ามีประโยชน์ในบริบทที่ไม่ใช่ UI เช่นกัน โดยเฉพาะอย่างยิ่งในระบบการสร้างเพื่อหลีกเลี่ยงการสร้างใหม่โดยไม่จำเป็น
สัญญาณถูกใช้ในการเขียนโปรแกรมเชิงโต้ตอบเพื่อขจัดความจำเป็นในการจัดการการอัปเดตในแอปพลิเคชัน
โมเดลการเขียนโปรแกรมที่ประกาศสำหรับการอัพเดตตามการเปลี่ยนแปลงสถานะ
จาก ปฏิกิริยาคืออะไร? -
เมื่อพิจารณาตัวแปร counter
คุณต้องการแสดงผลใน DOM ไม่ว่าตัวนับจะเป็นเลขคู่หรือคี่ เมื่อใดก็ตามที่ counter
เปลี่ยนแปลง คุณต้องการอัปเดต DOM ด้วยความเท่าเทียมกันล่าสุด ใน Vanilla JS คุณอาจมีสิ่งนี้:
ให้เคาน์เตอร์ = 0;const setCounter = (ค่า) => { ตัวนับ = ค่า; render();};const isEven = () => (ตัวนับ & 1) == 0;const parity = () => isEven() ? "even" : "odd";const render = () => element.innerText = parity();// จำลองการอัปเดตภายนอกเพื่อ counter...setInterval(() => setCounter(counter + 1), 1000);
เรื่องนี้มีปัญหาหลายอย่าง...
การตั้ง counter
มีเสียงดังและมีน้ำหนักมาก
สถานะ counter
เชื่อมโยงกับระบบการเรนเดอร์อย่างแน่นหนา
หาก counter
เปลี่ยนแปลงแต่ parity
ไม่ (เช่นตัวนับเปลี่ยนจาก 2 เป็น 4) เราจะทำการคำนวณความเท่าเทียมกันและการเรนเดอร์ที่ไม่จำเป็นโดยไม่จำเป็น
จะเกิดอะไรขึ้นถ้าส่วนอื่นของ UI ของเราเพียงต้องการเรนเดอร์เมื่อมีการอัพเดต counter
?
จะเกิดอะไรขึ้นถ้าส่วนอื่นของ UI ของเราขึ้นอยู่กับ isEven
หรือ parity
เพียงอย่างเดียว?
แม้ในสถานการณ์ที่ค่อนข้างง่ายนี้ ปัญหาหลายประการก็เกิดขึ้นอย่างรวดเร็ว เราอาจลองแก้ไขสิ่งเหล่านี้โดยแนะนำ pub/sub สำหรับ counter
สิ่งนี้จะช่วยให้ผู้บริโภค counter
เพิ่มเติมสามารถสมัครสมาชิกเพื่อเพิ่มปฏิกิริยาของตนเองต่อการเปลี่ยนแปลงสถานะ
อย่างไรก็ตาม เรายังคงติดอยู่กับปัญหาต่อไปนี้:
ฟังก์ชั่นการเรนเดอร์ซึ่งขึ้นอยู่กับ parity
เท่านั้นจะต้อง "รู้" แทนว่าจำเป็นต้องสมัครรับ counter
ไม่สามารถอัปเดต UI ตาม isEven
หรือ parity
เพียงอย่างเดียว โดยไม่มีการโต้ตอบกับ counter
โดยตรง
เราได้เพิ่มรูปแบบสำเร็จรูปของเราแล้ว ทุกครั้งที่คุณใช้งานบางอย่าง มันไม่ใช่แค่เรื่องการเรียกใช้ฟังก์ชันหรือการอ่านตัวแปรเท่านั้น แต่เป็นการสมัครสมาชิกและอัพเดตที่นั่นแทน การจัดการการยกเลิกการสมัครสมาชิกก็ซับซ้อนเป็นพิเศษเช่นกัน
ตอนนี้ เราสามารถแก้ไขปัญหาสองสามข้อได้โดยการเพิ่ม pub/sub ไม่ใช่แค่เพื่อ counter
แต่ยังรวมไปถึง isEven
และ parity
ด้วย จากนั้นเราจะต้องสมัครสมาชิก isEven
เพื่อ counter
, parity
กับ isEven
และ render
to parity
น่าเสียดายที่ไม่เพียงแต่โค้ดสำเร็จรูปของเราระเบิดเท่านั้น แต่เรายังติดอยู่กับการสมัครสมาชิกจำนวนมาก และอาจเกิดภัยพิบัติหน่วยความจำรั่วไหลหากเราไม่ได้ทำความสะอาดทุกอย่างอย่างเหมาะสมด้วยวิธีที่ถูกต้อง ดังนั้นเราจึงได้แก้ไขปัญหาบางอย่างแล้ว แต่ได้สร้างหมวดหมู่ปัญหาใหม่และโค้ดจำนวนมาก ที่แย่กว่านั้นคือ เราต้องผ่านกระบวนการทั้งหมดนี้สำหรับทุกส่วนของรัฐในระบบของเรา
นามธรรมการเชื่อมโยงข้อมูลใน UI สำหรับโมเดลและมุมมองเป็นแกนหลักของเฟรมเวิร์ก UI ในภาษาการเขียนโปรแกรมหลายภาษามายาวนาน แม้ว่าจะไม่มีกลไกดังกล่าวใน JS หรือแพลตฟอร์มเว็บก็ตาม ภายในเฟรมเวิร์กและไลบรารี JS มีการทดลองจำนวนมากในวิธีต่างๆ เพื่อแสดงการเชื่อมโยงนี้ และประสบการณ์ได้แสดงให้เห็นถึงพลังของการไหลของข้อมูลทางเดียวร่วมกับประเภทข้อมูลชั้นหนึ่งที่แสดงถึงเซลล์สถานะหรือการคำนวณ ได้มาจากข้อมูลอื่น ซึ่งปัจจุบันมักเรียกว่า "สัญญาณ" แนวทางการตอบสนองค่าระดับเฟิร์สคลาสนี้ดูเหมือนว่าจะได้รับความนิยมเป็นครั้งแรกในเฟรมเวิร์กเว็บ JavaScript แบบโอเพ่นซอร์สพร้อมระบบ Knockout ในปี 2010 ในช่วงหลายปีที่ผ่านมา มีการสร้างรูปแบบและการใช้งานมากมาย ภายใน 3-4 ปีที่ผ่านมา Signal primitive และวิธีการที่เกี่ยวข้องได้รับแรงผลักดันมากขึ้น โดยไลบรารีหรือเฟรมเวิร์ก JavaScript สมัยใหม่เกือบทุกแห่งมีสิ่งที่คล้ายกันภายใต้ชื่อเดียวหรือชื่ออื่น
เพื่อให้เข้าใจถึงสัญญาณ เรามาดูตัวอย่างด้านบนที่ปรับโฉมใหม่ด้วย Signal API ที่จะอธิบายเพิ่มเติมด้านล่าง
const counter = new Signal.State(0);const isEven = new Signal.Computed(() => (counter.get() & 1) == 0);const parity = new Signal.Computed(() => isEven .get() ? "even" : "odd");// ไลบรารีหรือเฟรมเวิร์กกำหนดเอฟเฟกต์ตามเอฟเฟกต์ฟังก์ชัน Signal primitivesdeclare อื่น ๆ (cb: () => void): (() => เป็นโมฆะ);เอฟเฟกต์(() => element.innerText = parity.get());// จำลองการอัปเดตภายนอกเพื่อ counter...setInterval(() => counter.set(counter.get() + 1), 1000 );
มีบางสิ่งที่เราเห็นได้ทันที:
เราได้กำจัดรูปแบบสำเร็จรูปที่มีเสียงดังรอบๆ ตัวแปร counter
ออกจากตัวอย่างก่อนหน้านี้แล้ว
มี API แบบรวมเพื่อจัดการค่า การคำนวณ และผลข้างเคียง
ไม่มีปัญหาการอ้างอิงแบบวงกลมหรือการพึ่งพาแบบกลับหัวระหว่าง counter
และ render
ไม่มีการสมัครสมาชิกด้วยตนเอง และไม่จำเป็นต้องทำบัญชีใดๆ
มีวิธีการควบคุมเวลา/การกำหนดเวลาผลข้างเคียง
สัญญาณให้มากกว่าสิ่งที่เห็นได้บนพื้นผิวของ API มาก:
การติดตามการพึ่งพาอัตโนมัติ - สัญญาณที่คำนวณจะค้นหาสัญญาณอื่น ๆ ที่ต้องพึ่งพาโดยอัตโนมัติ ไม่ว่าสัญญาณเหล่านั้นจะเป็นค่าธรรมดาหรือการคำนวณอื่น ๆ
การประเมินแบบ Lazy - การคำนวณไม่ได้รับการประเมินอย่างกระตือรือร้นเมื่อมีการประกาศ และจะไม่ประเมินทันทีเมื่อการพึ่งพาเปลี่ยนแปลง พวกเขาจะได้รับการประเมินเมื่อมีการร้องขอค่าอย่างชัดเจนเท่านั้น
การจดจำ - สัญญาณที่คำนวณจะแคชค่าสุดท้ายไว้ ดังนั้นการคำนวณที่ไม่มีการเปลี่ยนแปลงการขึ้นต่อกันจึงไม่จำเป็นต้องได้รับการประเมินใหม่ ไม่ว่าจะเข้าถึงกี่ครั้งก็ตาม
การใช้งานสัญญาณแต่ละรายการมีกลไกการติดตามอัตโนมัติของตัวเอง เพื่อติดตามแหล่งที่มาที่พบเมื่อประเมินสัญญาณที่คำนวณ สิ่งนี้ทำให้ยากต่อการแชร์โมเดล ส่วนประกอบ และไลบรารีระหว่างเฟรมเวิร์กที่แตกต่างกัน พวกมันมักจะมาพร้อมกับการเชื่อมต่อที่ผิดพลาดกับเอ็นจิ้นการดู (เนื่องจากโดยปกติแล้ว Signals จะถูกนำไปใช้เป็นส่วนหนึ่งของเฟรมเวิร์ก JS)
เป้าหมายของข้อเสนอนี้คือการแยกโมเดลเชิงโต้ตอบออกจากมุมมองการเรนเดอร์โดยสมบูรณ์ ช่วยให้นักพัฒนาสามารถโยกย้ายไปยังเทคโนโลยีการเรนเดอร์ใหม่โดยไม่ต้องเขียนโค้ดที่ไม่ใช่ UI ใหม่ หรือพัฒนาโมเดลเชิงโต้ตอบที่ใช้ร่วมกันใน JS เพื่อนำไปใช้ในบริบทที่แตกต่างกัน น่าเสียดาย เนื่องจากการกำหนดเวอร์ชันและการทำซ้ำ จึงไม่สามารถทำได้ในการเข้าถึงระดับการแชร์ที่แข็งแกร่งผ่านไลบรารีระดับ JS ซึ่งบิวท์อินให้การรับประกันการแชร์ที่แข็งแกร่งกว่า
เป็นการเพิ่มประสิทธิภาพที่อาจเกิดขึ้นเล็กน้อยในการจัดส่งโค้ดน้อยลงเสมอเนื่องจากมีไลบรารี่ที่ใช้กันทั่วไปติดตั้งอยู่ภายใน แต่การใช้งาน Signals โดยทั่วไปนั้นค่อนข้างเล็ก ดังนั้นเราจึงไม่คิดว่าผลกระทบนี้จะมีขนาดใหญ่มาก
เราสงสัยว่าการใช้งาน C ++ ดั้งเดิมของโครงสร้างข้อมูลและอัลกอริธึมที่เกี่ยวข้องกับสัญญาณจะมีประสิทธิภาพมากกว่าสิ่งที่ทำได้ใน JS เล็กน้อยด้วยปัจจัยคงที่ อย่างไรก็ตาม คาดว่าจะไม่มีการเปลี่ยนแปลงอัลกอริทึมเทียบกับสิ่งที่จะมีอยู่ในโพลีฟิล เครื่องยนต์ไม่คาดว่าจะมีเวทย์มนตร์ที่นี่ และอัลกอริธึมการเกิดปฏิกิริยาเองก็มีการกำหนดไว้ชัดเจนและไม่คลุมเครือ
กลุ่มแชมป์เปี้ยนคาดว่าจะพัฒนาการใช้งาน Signals ต่างๆ และใช้สิ่งเหล่านี้เพื่อตรวจสอบความเป็นไปได้ด้านประสิทธิภาพเหล่านี้
ด้วยไลบรารี Signal ภาษา JS ที่มีอยู่ การติดตามสิ่งต่างๆ เช่น:
Callstack ข้ามสายโซ่ของสัญญาณที่คำนวณ ซึ่งแสดงสายโซ่สาเหตุสำหรับข้อผิดพลาด
กราฟอ้างอิงระหว่างสัญญาณต่างๆ เมื่อสัญญาณหนึ่งขึ้นอยู่กับสัญญาณอื่น ซึ่งมีความสำคัญในการแก้ไขจุดบกพร่องการใช้งานหน่วยความจำ
สัญญาณในตัวช่วยให้รันไทม์ของ JS และ DevTools มีโอกาสปรับปรุงการรองรับการตรวจสอบสัญญาณ โดยเฉพาะอย่างยิ่งสำหรับการแก้ไขจุดบกพร่องหรือการวิเคราะห์ประสิทธิภาพ ไม่ว่าจะมีอยู่ในเบราว์เซอร์หรือผ่านส่วนขยายที่ใช้ร่วมกัน เครื่องมือที่มีอยู่ เช่น ตัวตรวจสอบองค์ประกอบ สแน็ปช็อตประสิทธิภาพ และตัวสร้างโปรไฟล์หน่วยความจำ สามารถอัปเดตเพื่อเน้นสัญญาณในการนำเสนอข้อมูลโดยเฉพาะได้
โดยทั่วไป JavaScript มีไลบรารีมาตรฐานที่ค่อนข้างน้อย แต่แนวโน้มใน TC39 คือการทำให้ JS เป็นภาษา "รวมแบตเตอรี่" มากขึ้น โดยมีชุดฟังก์ชันการทำงานในตัวคุณภาพสูงที่พร้อมใช้งาน ตัวอย่างเช่น Temporal กำลังแทนที่ Moment.js และฟีเจอร์เล็กๆ จำนวนหนึ่ง เช่น Array.prototype.flat
และ Object.groupBy
กำลังแทนที่กรณีการใช้งาน Lodash จำนวนมาก ข้อดีต่างๆ ได้แก่ ขนาดบันเดิลที่เล็กลง ความเสถียรและคุณภาพที่ดีขึ้น การเรียนรู้น้อยลงเมื่อเข้าร่วมโปรเจ็กต์ใหม่ และคำศัพท์ทั่วไปสำหรับนักพัฒนา JS
งานปัจจุบันใน W3C และโดยผู้ใช้งานเบราว์เซอร์กำลังมองหาการนำเทมเพลตดั้งเดิมมาสู่ HTML (DOM Parts และ Template Instantiation) นอกจากนี้ W3C Web Components CG กำลังสำรวจความเป็นไปได้ในการขยาย Web Components เพื่อเสนอ HTML API ที่เปิดเผยอย่างสมบูรณ์ เพื่อให้บรรลุเป้าหมายทั้งสองนี้ ในที่สุด HTML จำเป็นต้องมีปฏิกิริยาดั้งเดิม นอกจากนี้ การปรับปรุงตามหลักสรีระศาสตร์หลายอย่างของ DOM ผ่านการบูรณาการสัญญาณสามารถจินตนาการได้และได้รับการร้องขอจากชุมชน
โปรดทราบว่าการบูรณาการนี้จะเป็นความพยายามแยกต่างหากในภายหลัง ไม่ใช่ส่วนหนึ่งของข้อเสนอนี้
บางครั้งความพยายามในการกำหนดมาตรฐานอาจมีประโยชน์ในระดับ "ชุมชน" เท่านั้น แม้ว่าจะไม่มีการเปลี่ยนแปลงในเบราว์เซอร์ก็ตาม ความพยายามของ Signals กำลังรวบรวมผู้เขียนเฟรมเวิร์กต่างๆ มากมายเพื่ออภิปรายเชิงลึกเกี่ยวกับธรรมชาติของปฏิกิริยา อัลกอริธึม และความสามารถในการทำงานร่วมกัน สิ่งนี้มีประโยชน์อยู่แล้ว และไม่ได้รวมไว้ในกลไก JS และเบราว์เซอร์ ควรเพิ่มสัญญาณลงในมาตรฐาน JavaScript หากมีประโยชน์ที่สำคัญ นอกเหนือจาก การเปิดใช้งานการแลกเปลี่ยนข้อมูลระบบนิเวศเท่านั้น
ปรากฎว่าไลบรารี Signal ที่มีอยู่ไม่ได้มีความแตกต่างกันมากนักโดยเป็นแกนหลัก ข้อเสนอนี้มีจุดมุ่งหมายเพื่อสร้างความสำเร็จโดยการนำคุณสมบัติที่สำคัญของห้องสมุดเหล่านั้นไปใช้
ประเภทสัญญาณที่แสดงถึงสถานะ เช่น สัญญาณที่เขียนได้ นี่คือค่าที่ผู้อื่นสามารถอ่านได้
ประเภทสัญญาณที่คำนวณ/บันทึก/ได้มา ซึ่งขึ้นอยู่กับสัญญาณอื่นๆ และมีการคำนวณและแคชอย่างเกียจคร้าน
การคำนวณเป็นแบบขี้เกียจ หมายความว่าสัญญาณที่คำนวณจะไม่ถูกคำนวณอีกครั้งตามค่าเริ่มต้น เมื่อการขึ้นต่อกันอย่างใดอย่างหนึ่งเปลี่ยนแปลงไป แต่จะทำงานเฉพาะเมื่อมีผู้อ่านจริงๆ เท่านั้น
การคำนวณนั้น "ปราศจากข้อผิดพลาด" ซึ่งหมายความว่าจะไม่มีการคำนวณที่ไม่จำเป็นเลย นี่หมายความว่าเมื่อแอปพลิเคชันอ่านสัญญาณที่คำนวณแล้ว จะมีการเรียงลำดับโทโพโลยีของส่วนที่อาจสกปรกของกราฟที่จะรัน เพื่อกำจัดรายการที่ซ้ำกัน
การคำนวณถูกแคชไว้ ซึ่งหมายความว่าหากหลังจากครั้งสุดท้ายที่การขึ้นต่อกันเปลี่ยนแปลง แต่ไม่มีการเปลี่ยนแปลงการขึ้นต่อกัน สัญญาณที่คำนวณจะ ไม่ ถูกคำนวณใหม่เมื่อมีการเข้าถึง
การเปรียบเทียบแบบกำหนดเองเป็นไปได้สำหรับสัญญาณที่คำนวณและสัญญาณสถานะ เพื่อทราบว่าเมื่อใดควรอัปเดตสัญญาณที่คำนวณเพิ่มเติมซึ่งขึ้นอยู่กับสัญญาณเหล่านั้น
การตอบสนองต่อเงื่อนไขที่สัญญาณที่คำนวณมีการขึ้นต่อกันอย่างใดอย่างหนึ่ง (หรือการขึ้นต่อกันแบบซ้อน) กลายเป็น "สกปรก" และเปลี่ยนแปลง ซึ่งหมายความว่าค่าของสัญญาณอาจล้าสมัย
ปฏิกิริยานี้มีไว้เพื่อกำหนดเวลางานที่สำคัญกว่าให้ดำเนินการในภายหลัง
ผลกระทบจะถูกนำไปใช้ในแง่ของปฏิกิริยาเหล่านี้ บวกกับการจัดกำหนดการระดับกรอบงาน
สัญญาณที่คำนวณแล้วจำเป็นต้องมีความสามารถในการตอบสนองต่อว่าสัญญาณเหล่านั้นได้รับการลงทะเบียนเป็นการพึ่งพา (ซ้อนกัน) ของปฏิกิริยาอย่างใดอย่างหนึ่งเหล่านี้หรือไม่
เปิดใช้งานเฟรมเวิร์ก JS เพื่อกำหนดเวลาของตนเอง ไม่มีการจัดกำหนดการแบบบังคับในตัวแบบสัญญา
จำเป็นต้องมีปฏิกิริยาแบบซิงโครนัสเพื่อให้สามารถจัดกำหนดการงานในภายหลังตามลอจิกของเฟรมเวิร์ก
การเขียนเป็นแบบซิงโครนัสและมีผลทันที (เฟรมเวิร์กที่การเขียนแบบแบตช์สามารถทำได้ด้านบน)
เป็นไปได้ที่จะแยกการตรวจสอบว่าเอฟเฟกต์อาจ "สกปรก" จากการรันเอฟเฟกต์จริงหรือไม่ (เปิดใช้งานตัวกำหนดเวลาเอฟเฟกต์แบบสองขั้นตอน)
ความสามารถในการอ่านสัญญาณ โดยไม่ กระตุ้นให้มีการบันทึกการขึ้นต่อกัน ( untrack
)
เปิดใช้งานการจัดองค์ประกอบของโค้ดเบสต่างๆ ซึ่งใช้สัญญาณ/ปฏิกิริยา เช่น
การใช้หลายเฟรมเวิร์กร่วมกันเท่าที่การติดตาม/ปฏิกิริยาดำเนินไป (การละเว้นแบบโมดูโล ดูด้านล่าง)
โครงสร้างข้อมูลเชิงโต้ตอบที่ไม่ขึ้นกับเฟรมเวิร์ก (เช่น พร็อกซีร้านค้าแบบโต้ตอบแบบเรียกซ้ำ แผนที่และการตั้งค่าและอาร์เรย์แบบโต้ตอบ ฯลฯ)
กีดกัน/ห้ามการใช้ปฏิกิริยาซิงโครนัสในทางที่ผิดโดยไร้เดียงสา
ความเสี่ยงด้านคุณภาพ: อาจทำให้เกิด "ข้อบกพร่อง" หากใช้งานไม่ถูกต้อง: หากการเรนเดอร์เสร็จสิ้นทันทีเมื่อมีการตั้งค่าสัญญาณ ผู้ใช้ปลายทางอาจเปิดเผยสถานะแอปพลิเคชันที่ไม่สมบูรณ์แก่ผู้ใช้ ดังนั้น คุณลักษณะนี้ควรใช้เพื่อจัดกำหนดการงานอย่างชาญฉลาดสำหรับภายหลังเท่านั้น เมื่อตรรกะของแอปพลิเคชันเสร็จสิ้น
วิธีแก้ไข: ไม่อนุญาตให้อ่านและเขียนสัญญาณใดๆ จากภายในปฏิกิริยาโต้ตอบแบบซิงโครนัส
กีดกันการ untrack
และทำเครื่องหมายลักษณะที่ไม่มั่นคงของมัน
ความเสี่ยงด้านความสมบูรณ์: อนุญาตให้สร้างสัญญาณที่คำนวณซึ่งค่าขึ้นอยู่กับสัญญาณอื่น แต่จะไม่อัปเดตเมื่อสัญญาณเหล่านั้นเปลี่ยนแปลง ควรใช้เมื่อการเข้าถึงที่ไม่ได้ติดตามจะไม่เปลี่ยนผลลัพธ์ของการคำนวณ
วิธีแก้ไข: API ถูกทำเครื่องหมายว่า "ไม่ปลอดภัย" ในชื่อ
หมายเหตุ: ข้อเสนอนี้อนุญาตให้ทั้งอ่านและเขียนสัญญาณจากสัญญาณที่คำนวณและเอฟเฟกต์ โดยไม่จำกัดการเขียนที่เกิดขึ้นหลังจากการอ่าน แม้จะมีความเสี่ยงด้านความสมบูรณ์ก็ตาม การตัดสินใจนี้ดำเนินการเพื่อรักษาความยืดหยุ่นและความเข้ากันได้ในการรวมเข้ากับเฟรมเวิร์ก
ต้องเป็นฐานที่มั่นคงสำหรับหลายเฟรมเวิร์กเพื่อใช้กลไกสัญญาณ/ปฏิกิริยา
ควรเป็นฐานที่ดีสำหรับพร็อกซีร้านค้าแบบเรียกซ้ำ ปฏิกิริยาของฟิลด์คลาสที่ใช้มัณฑนากร และทั้ง .value
และ [state, setState]
-style API
ความหมายสามารถแสดงรูปแบบที่ถูกต้องซึ่งเปิดใช้งานโดยกรอบงานที่แตกต่างกัน ตัวอย่างเช่น ควรเป็นไปได้ที่สัญญาณเหล่านี้จะเป็นพื้นฐานของการเขียนที่สะท้อนทันทีหรือการเขียนซึ่งจะถูกแบทช์และนำไปใช้ในภายหลัง
คงจะดีไม่น้อยถ้า API นี้สามารถใช้งานได้โดยตรงจากนักพัฒนา JavaScript
แนวคิด: จัดเตรียม hooks ทั้งหมด แต่รวมข้อผิดพลาดเมื่อใช้ในทางที่ผิดหากเป็นไปได้
แนวคิด: ใส่ API ที่ละเอียดอ่อนในเนมสเปซ subtle
ซึ่งคล้ายกับ crypto.subtle
เพื่อทำเครื่องหมายเส้นแบ่งระหว่าง API ที่จำเป็นสำหรับการใช้งานขั้นสูง เช่น การใช้เฟรมเวิร์กหรือการสร้างเครื่องมือ dev เทียบกับการใช้งานการพัฒนาแอปพลิเคชันในชีวิตประจำวันที่มากขึ้น เช่น การสร้างอินสแตนซ์สัญญาณเพื่อใช้กับ กรอบ.
อย่างไรก็ตาม สิ่งสำคัญคือต้องไม่ใช้ชื่อที่เหมือนกันทุกประการ!
หากคุณลักษณะตรงกับแนวคิดของระบบนิเวศ การใช้คำศัพท์ทั่วไปถือเป็นเรื่องดี
ความตึงเครียดระหว่าง "การใช้งานโดย JS devs" และ "มอบ hooks ทั้งหมดให้กับเฟรมเวิร์ก"
นำไปปฏิบัติและใช้งานได้ด้วยประสิทธิภาพที่ดี -- Surface API ไม่ทำให้เกิดโอเวอร์เฮดมากเกินไป
เปิดใช้งานคลาสย่อยเพื่อให้เฟรมเวิร์กสามารถเพิ่มวิธีการและฟิลด์ของตนเองได้ รวมถึงฟิลด์ส่วนตัวด้วย นี่เป็นสิ่งสำคัญเพื่อหลีกเลี่ยงความจำเป็นในการจัดสรรเพิ่มเติมในระดับกรอบงาน ดู "การจัดการหน่วยความจำ" ด้านล่าง
หากเป็นไปได้: สัญญาณที่คำนวณแล้วควรจะสามารถรวบรวมขยะได้ หากไม่มีสิ่งใดอยู่อ้างอิงถึงสัญญาณนั้นสำหรับการอ่านในอนาคต แม้ว่าจะเชื่อมโยงกับกราฟที่กว้างขึ้นซึ่งยังคงอยู่ (เช่น โดยการอ่านสถานะที่ยังคงมีอยู่)
โปรดทราบว่าเฟรมเวิร์กส่วนใหญ่ในปัจจุบันต้องการการกำจัดสัญญาณที่คำนวณอย่างชัดเจน หากมีการอ้างอิงหรือจากกราฟสัญญาณอื่นที่ยังมีชีวิตอยู่
สิ่งนี้จบลงได้ไม่เลวร้ายนักเมื่ออายุการใช้งานของมันเชื่อมโยงกับอายุการใช้งานของส่วนประกอบ UI และผลกระทบต่างๆ ก็ต้องถูกกำจัดทิ้งอยู่ดี
หากการดำเนินการด้วยซีแมนทิกส์เหล่านี้มีราคาแพงเกินไป เราควรเพิ่มการกำจัดสัญญาณที่คำนวณแล้วอย่างชัดเจน (หรือ "การยกเลิกการเชื่อมโยง") ไปยัง API ด้านล่าง ซึ่งในปัจจุบันยังขาดอยู่
เป้าหมายที่เกี่ยวข้องแยกต่างหาก: ลดจำนวนการจัดสรรให้เหลือน้อยที่สุด เช่น
เพื่อสร้างสัญญาณที่เขียนได้ (หลีกเลี่ยงการปิด + อาร์เรย์สองอันแยกกัน)
เพื่อใช้เอฟเฟกต์ (หลีกเลี่ยงการปิดสำหรับทุกปฏิกิริยา)
ใน API สำหรับการสังเกตการเปลี่ยนแปลงสัญญาณ ให้หลีกเลี่ยงการสร้างโครงสร้างข้อมูลชั่วคราวเพิ่มเติม
โซลูชัน: API แบบคลาสช่วยให้สามารถนำเมธอดและฟิลด์ที่กำหนดไว้ในคลาสย่อยมาใช้ซ้ำได้
แนวคิดเริ่มต้นของ Signal API อยู่ด้านล่างนี้ โปรดทราบว่านี่เป็นเพียงร่างเบื้องต้น และเราคาดว่าจะมีการเปลี่ยนแปลงเมื่อเวลาผ่านไป เรามาเริ่มด้วย .d.ts
ฉบับเต็มเพื่อทำความเข้าใจเกี่ยวกับรูปร่างโดยรวม จากนั้นเราจะพูดถึงรายละเอียดว่าทั้งหมดนี้หมายความว่าอย่างไร
interface Signal<T> {// รับค่าของ signalget(): T;}namespace Signal {// A read-write Signalclass State<T> ใช้งาน Signal<T> {// สร้าง state Signal ที่ขึ้นต้นด้วยค่า tconstructor(t: T, options?: SignalOptions<T>);// รับค่าของ signalget(): T;// ตั้งค่าสถานะ Signal เป็น tset(t: T): void;}// A Signal ซึ่งเป็นสูตรที่อิงจากคลาส Signals อื่นๆ คำนวณ<T = ไม่ทราบ> ใช้สัญญาณ<T> {// สร้างสัญญาณซึ่งประเมินเป็นค่าที่ส่งคืนโดยการโทรกลับ// การโทรกลับถูกเรียกด้วยสัญญาณนี้เป็นค่านี้ ตัวสร้าง (cb: (นี่คือ: คำนวณ<T >) => T, options?: SignalOptions<T>);// รับค่าของ signalget(): T;}// เนมสเปซนี้มีคุณสมบัติ "ขั้นสูง" ที่ดีกว่า // ปล่อยให้ผู้เขียนเฟรมเวิร์กมากกว่า แอปพลิเคชัน นักพัฒนา // คล้ายกับ `crypto.subtle`namespace บอบบาง {// เรียกใช้การโทรกลับโดยปิดการใช้งานการติดตามทั้งหมด untrack<T>(cb: () => T): T;// รับสัญญาณที่คำนวณในปัจจุบันซึ่งกำลังติดตามใด ๆ สัญญาณจะอ่านถ้ามีฟังก์ชัน currentComputed (): Computed | null;// ส่งคืนรายการเรียงลำดับของสัญญาณทั้งหมดที่สัญญาณนี้อ้างอิงถึง// ในระหว่างการประเมินครั้งล่าสุด// สำหรับ Watcher ให้แสดงรายการชุดของสัญญาณที่กำลังรับชมอยู่ ฟังก์ชั่น introspectSources (s: คำนวณ | Watcher): (สถานะ | คำนวณแล้ว)[];// ส่งคืน Watchers ที่มีสัญญาณนี้อยู่ บวกกับสัญญาณใด ๆ // คำนวณแล้วซึ่งอ่านสัญญาณนี้ในครั้งล่าสุดที่พวกเขาได้รับการประเมิน // หากสัญญาณที่คำนวณนั้นเป็น (เรียกซ้ำ) watch.function introspectSinks(s: State | Computed): (คำนวณ | Watcher)[];// True หากสัญญาณนี้เป็น "สด" โดยที่ Watcher เฝ้าดู // หรือถูกอ่านโดย สัญญาณที่คำนวณซึ่ง (เรียกซ้ำ) live.function hasSinks(s: State | Computed): boolean;// True หากองค์ประกอบนี้เป็น "ปฏิกิริยา" ซึ่งขึ้นอยู่กับ// กับองค์ประกอบอื่น ๆ สัญญาณ. Computed โดยที่ hasSources เป็นเท็จ// จะส่งคืนค่าคงที่เดียวกันเสมอ function hasSources(s: Computed | Watcher): boolean;class Watcher {// เมื่อมีการเขียนแหล่งที่มา (แบบเรียกซ้ำ) ของ Watcher ให้เรียกการโทรกลับนี้// หากยังไม่ได้ถูกเรียกตั้งแต่การโทร `watch` ครั้งล่าสุด// ไม่สามารถอ่านหรือเขียนสัญญาณได้ในระหว่าง notify.constructor(แจ้งเตือน: (นี่คือ: Watcher) => void);// เพิ่มสัญญาณเหล่านี้ไปยังชุดของ Watcher และตั้งค่าให้ Watcher ทำงาน // แจ้งเตือนการโทรกลับในครั้งต่อไปที่สัญญาณใด ๆ ในชุด (หรือหนึ่งในการอ้างอิงของมัน) เปลี่ยนแปลง // สามารถเรียกได้โดยไม่มีข้อโต้แย้งเพียงเพื่อรีเซ็ต "แจ้งเตือน" state ดังนั้น// การแจ้งเตือนการโทรกลับจะถูกเรียกใช้อีกครั้ง watch(...s: Signal[]): void;// ลบสัญญาณเหล่านี้ออกจากชุดที่เฝ้าดู (เช่น สำหรับเอฟเฟกต์ที่ถูกกำจัด)unwatch(. ..s: Signal[]): void;// ส่งคืนชุดของแหล่งที่มาในชุด Watcher ที่ยังคงสกปรกหรือเป็นสัญญาณที่คำนวณแล้ว // ด้วยแหล่งที่มาที่สกปรกหรือรอดำเนินการและยังไม่ได้รับการประเมินใหม่getPending(): Signal[];}// Hooks เพื่อสังเกตการดูหรือไม่ได้ดูอีกต่อไป var ดู: Symbol;var unwatched: Symbol;}อินเทอร์เฟซ SignalOptions<T> {// ฟังก์ชันการเปรียบเทียบแบบกำหนดเองระหว่างค่าเก่าและค่าใหม่ ค่าเริ่มต้น: Object.is.// สัญญาณถูกส่งผ่านเป็นค่านี้สำหรับ context.equals?: (นี่คือ: Signal<T>, t: T, t2: T) => boolean;// โทรกลับถูกเรียกเมื่อ isWatched กลายเป็น จริง หากก่อนหน้านี้เป็นเท็จ[Signal.subtle.watched]?: (นี่คือ: Signal<T>) => void;// โทรกลับที่ถูกเรียกเมื่อใดก็ตามที่ isWatched กลายเป็นเท็จ หากก่อนหน้านี้ จริง[Signal.subtle.unwatched]?: (นี่: สัญญาณ<T>) => เป็นโมฆะ;}}
สัญญาณแสดงถึงเซลล์ข้อมูลที่อาจเปลี่ยนแปลงเมื่อเวลาผ่านไป สัญญาณอาจเป็น "สถานะ" (เพียงค่าที่ตั้งค่าด้วยตนเอง) หรือ "คำนวณ" (สูตรที่อิงจากสัญญาณอื่น)
สัญญาณที่คำนวณทำงานโดยการติดตามสัญญาณอื่นที่ถูกอ่านโดยอัตโนมัติในระหว่างการประเมิน เมื่ออ่านข้อมูลที่คำนวณแล้ว ระบบจะตรวจสอบว่าการขึ้นต่อกันที่บันทึกไว้ก่อนหน้านี้มีการเปลี่ยนแปลงหรือไม่ และจะประเมินตัวเองอีกครั้งหากมีการเปลี่ยนแปลง เมื่อสัญญาณที่คำนวณหลายรายการซ้อนกัน การระบุแหล่งที่มาทั้งหมดของการติดตามจะไปอยู่ที่สัญญาณที่อยู่ด้านในสุด
สัญญาณที่คำนวณนั้นขี้เกียจ เช่น เป็นแบบดึง: จะมีการประเมินอีกครั้งเมื่อมีการเข้าถึงเท่านั้น แม้ว่าการขึ้นต่อกันอย่างใดอย่างหนึ่งจะเปลี่ยนไปก่อนหน้านี้ก็ตาม
การโทรกลับที่ส่งผ่านไปยังสัญญาณที่คำนวณโดยทั่วไปควรจะ "บริสุทธิ์" ในแง่ของการเป็นฟังก์ชันที่กำหนดขึ้นและไม่มีผลข้างเคียงของสัญญาณอื่น ๆ ที่เข้าถึง ในเวลาเดียวกัน ระยะเวลาของการเรียกกลับจะถูกกำหนดขึ้น โดยอนุญาตให้ใช้ผลข้างเคียงด้วยความระมัดระวัง
สัญญาณมีการแคช/บันทึกข้อมูลที่โดดเด่น: ทั้งสถานะและสัญญาณที่คำนวณจะจดจำค่าปัจจุบัน และทริกเกอร์เฉพาะสัญญาณที่คำนวณใหม่ซึ่งอ้างอิงถึงสัญญาณเหล่านั้นหากมีการเปลี่ยนแปลงจริง ไม่จำเป็นต้องเปรียบเทียบค่าเก่ากับใหม่ซ้ำๆ การเปรียบเทียบจะเกิดขึ้นครั้งเดียวเมื่อมีการรีเซ็ต/ประเมินสัญญาณต้นทาง และกลไกสัญญาณจะติดตามว่าสิ่งใดที่อ้างอิงถึงสัญญาณนั้นยังไม่ได้อัปเดตตามค่าใหม่ มูลค่ายัง โดยทั่วไปจะแสดงผ่าน "การระบายสีกราฟ" ตามที่อธิบายไว้ใน (โพสต์ในบล็อกของ Milo)
สัญญาณที่คำนวณจะติดตามการขึ้นต่อกันแบบไดนามิก ในแต่ละครั้งที่มีการเรียกใช้ สัญญาณเหล่านั้นอาจจบลงด้วยการขึ้นอยู่กับสิ่งต่าง ๆ และชุดการขึ้นต่อกันที่แม่นยำนั้นจะถูกเก็บไว้ใหม่ในกราฟสัญญาณ ซึ่งหมายความว่า หากคุณมีการพึ่งพาที่จำเป็นในสาขาเดียวเท่านั้น และการคำนวณก่อนหน้านี้ใช้สาขาอื่น การเปลี่ยนแปลงค่าที่ไม่ได้ใช้ชั่วคราวนั้นจะไม่ทำให้สัญญาณที่คำนวณถูกคำนวณใหม่ แม้ว่าจะดึงออกมาก็ตาม
ไม่เหมือนกับ JavaScript Promises ทุกอย่างใน Signals ทำงานพร้อมกัน:
การตั้งค่าสัญญาณเป็นค่าใหม่เป็นแบบซิงโครนัส และสิ่งนี้จะปรากฏทันทีเมื่ออ่านสัญญาณที่คำนวณซึ่งจะขึ้นอยู่กับสัญญาณนั้นในภายหลัง ไม่มีการรวมชุดของการกลายพันธุ์นี้
การอ่านสัญญาณที่คำนวณเป็นแบบซิงโครนัส - ค่าของสัญญาณจะพร้อมใช้งานเสมอ
การ notify
การโทรกลับใน Watchers ดังที่อธิบายไว้ด้านล่าง จะทำงานพร้อมกันระหว่างการเรียก .set()
ซึ่งทริกเกอร์ (แต่หลังจากการระบายสีกราฟเสร็จสิ้น)
เช่นเดียวกับสัญญา Signals สามารถแสดงสถานะข้อผิดพลาดได้: หากการเรียกกลับของสัญญาณที่คำนวณออกมา ข้อผิดพลาดนั้นจะถูกแคชเช่นเดียวกับค่าอื่น และถูกโยนใหม่ทุกครั้งที่อ่านสัญญาณ
Signal
แสดงถึงความสามารถในการอ่านค่าที่เปลี่ยนแปลงแบบไดนามิกซึ่งมีการติดตามการอัปเดตเมื่อเวลาผ่านไป นอกจากนี้ยังรวมถึงความสามารถในการสมัครรับสัญญาณโดยปริยาย โดยปริยายผ่านการเข้าถึงที่ติดตามจากสัญญาณคอมพิวเตอร์อื่น
API ที่นี่ได้รับการออกแบบเพื่อให้ตรงกับฉันทามติของระบบนิเวศคร่าวๆ ในกลุ่มไลบรารี Signal ส่วนใหญ่ที่ใช้ชื่อต่างๆ เช่น "signal", "computed" และ "state" อย่างไรก็ตาม การเข้าถึงสัญญาณจากคอมพิวเตอร์และสัญญาณสถานะทำได้ผ่าน .get()
ซึ่งไม่เห็นด้วยกับ Signal API ยอดนิยมทั้งหมด ซึ่งใช้ตัวเข้าถึง .value
-style หรือไวยากรณ์การเรียก signal()
API ได้รับการออกแบบมาเพื่อลดจำนวนการจัดสรร เพื่อให้สัญญาณเหมาะสำหรับการฝังในเฟรมเวิร์ก JavaScript ในขณะที่ได้รับประสิทธิภาพที่เท่ากันหรือดีกว่าสัญญาณที่ปรับแต่งเฟรมเวิร์กที่มีอยู่ นี่หมายถึง:
State Signals เป็นออบเจ็กต์ที่สามารถเขียนได้เพียงออบเจ็กต์เดียว ซึ่งสามารถเข้าถึงได้และตั้งค่าจากการอ้างอิงเดียวกัน (ดูความหมายด้านล่างในส่วน "การแยกความสามารถ")
ทั้งสัญญาณสถานะและสัญญาณคอมพิวเตอร์ได้รับการออกแบบให้เป็นคลาสย่อยได้ เพื่ออำนวยความสะดวกในความสามารถของเฟรมเวิร์กในการเพิ่มคุณสมบัติเพิ่มเติมผ่านฟิลด์คลาสสาธารณะและส่วนตัว (รวมถึงวิธีการใช้สถานะนั้น)
การเรียกกลับต่างๆ (เช่น equals
การเรียกกลับที่คำนวณแล้ว) จะถูกเรียกพร้อมกับสัญญาณที่เกี่ยวข้องเป็นค่า this
สำหรับบริบท ดังนั้นจึงไม่จำเป็นต้องปิดใหม่ต่อสัญญาณ แต่สามารถบันทึกบริบทไว้ในคุณสมบัติพิเศษของสัญญาณแทนได้
เงื่อนไขข้อผิดพลาดบางประการที่บังคับใช้โดย API นี้:
มันเป็นข้อผิดพลาดในการอ่านการคำนวณซ้ำ
การ notify
การโทรกลับของ Watcher ไม่สามารถอ่านหรือเขียนสัญญาณใดๆ ได้
หากการเรียกกลับของสัญญาณที่คำนวณเกิดขึ้น การเข้าถึงสัญญาณครั้งต่อๆ ไปจะจัดข้อผิดพลาดที่แคชไว้ใหม่ จนกว่าการขึ้นต่อกันอย่างใดอย่างหนึ่งจะเปลี่ยนแปลงและมีการคำนวณใหม่
เงื่อนไขบางประการที่ ไม่ได้ บังคับใช้:
สัญญาณที่คำนวณแล้วสามารถเขียนไปยังสัญญาณอื่นได้พร้อมกันภายในการโทรกลับ
งานที่จัดคิวโดยการโทรกลับ notify
ของ Watcher อาจอ่านหรือเขียนสัญญาณ ทำให้สามารถจำลองรูปแบบการต่อต้าน React แบบคลาสสิกในแง่ของสัญญาณได้!
อินเทอร์เฟซ Watcher
ที่กำหนดไว้ข้างต้นเป็นพื้นฐานสำหรับการนำ JS API ทั่วไปไปใช้สำหรับเอฟเฟกต์: การเรียกกลับซึ่งจะถูกเรียกใช้อีกครั้งเมื่อสัญญาณอื่นเปลี่ยนแปลง โดยมีวัตถุประสงค์เพื่อผลข้างเคียงเท่านั้น ฟังก์ชั่น effect
กต์ที่ใช้ในตัวอย่างเริ่มต้นสามารถกำหนดได้ดังนี้:
// โดยปกติฟังก์ชันนี้จะอยู่ในไลบรารี/เฟรมเวิร์ก ไม่ใช่โค้ดของแอปพลิเคชัน// หมายเหตุ: ตรรกะการกำหนดเวลานี้พื้นฐานเกินกว่าจะมีประโยชน์ อย่าคัดลอก/วาง.let pending = false;let w = new Signal.subtle.Watcher(() => {if (!pending) {pending = true;queueMicrotask(() => {pending = false;for (let s ของ w.getPending()) s.get();w.watch();});}});// เอฟเฟกต์เอฟเฟกต์ Signal ซึ่งประเมินเป็น cb ซึ่งกำหนดเวลาการอ่าน// ตัวเองในคิวไมโครทาสก์ เมื่อใดก็ตามที่อย่างใดอย่างหนึ่ง การพึ่งพาอาจเปลี่ยนแปลงเอฟเฟกต์ฟังก์ชันการส่งออก (cb) {let destructor;let c = new Signal.Computed(() => { destructor?.(); destructor = cb(); });w.watch(c);c.get ();return () => { ตัวทำลาย?.(); w.unwatch(c) };}
Signal API ไม่มีฟังก์ชันในตัวเช่น effect
เนื่องจากการกำหนดเอฟเฟกต์นั้นละเอียดอ่อนและมักจะเชื่อมโยงกับวงจรการเรนเดอร์เฟรมเวิร์กและสถานะหรือกลยุทธ์เฉพาะเฟรมเวิร์กระดับสูงอื่น ๆ ซึ่ง JS ไม่สามารถเข้าถึงได้
อธิบายการดำเนินการต่างๆ ที่ใช้ที่นี่: notify
callback ที่ส่งผ่านไปยัง Watcher
Constructor คือฟังก์ชันที่ถูกเรียกเมื่อสัญญาณเปลี่ยนจากสถานะ "สะอาด" (โดยที่เรารู้ว่าแคชถูกเตรียมใช้งานและถูกต้อง) ไปสู่สถานะ "ตรวจสอบ" หรือ "สกปรก" " สถานะ (โดยที่แคชอาจหรืออาจจะไม่ถูกต้อง เนื่องจากมีการเปลี่ยนแปลงอย่างน้อยหนึ่งสถานะซึ่งขึ้นอยู่กับการเรียกซ้ำนี้)
ในที่สุดการโทรเพื่อ notify
จะถูกกระตุ้นโดยการเรียก .set()
บนสัญญาณสถานะบางอย่าง การโทรนี้เป็นแบบซิงโครนัส: มันเกิดขึ้นก่อนที่ .set
จะกลับมา แต่ไม่จำเป็นต้องกังวลเกี่ยวกับการโทรกลับนี้โดยสังเกตกราฟสัญญาณในสถานะประมวลผลครึ่งหนึ่ง เนื่องจากในระหว่างการโทรกลับ notify
จะไม่สามารถอ่านหรือเขียนสัญญาณได้ แม้แต่ในการโทร untrack
เนื่องจาก notify
ถูกเรียกระหว่าง .set()
จึงเป็นการขัดจังหวะเธรดอื่นของตรรกะ ซึ่งอาจไม่สมบูรณ์ หากต้องการอ่านหรือเขียน Signals จาก notify
ให้กำหนดเวลาการทำงานให้ทำงานในภายหลัง เช่น โดยการเขียน Signal ลงในรายการเพื่อเข้าถึงได้ในภายหลัง หรือใช้ queueMicrotask
ตามที่กล่าวไว้ข้างต้น
โปรดทราบว่าเป็นไปได้อย่างสมบูรณ์แบบที่จะใช้สัญญาณอย่างมีประสิทธิภาพโดยไม่ต้องใช้ Symbol.subtle.Watcher
โดยการกำหนดเวลาการสำรวจสัญญาณที่คำนวณแล้ว เช่นเดียวกับที่ Glimmer ทำ อย่างไรก็ตาม เฟรมเวิร์กจำนวนมากพบว่าการให้ตรรกะการกำหนดเวลานี้ทำงานพร้อมกันมักจะมีประโยชน์มาก ดังนั้น Signals API จึงรวมไว้ด้วย
ทั้งสัญญาณที่คำนวณและสถานะจะถูกรวบรวมแบบขยะเหมือนกับค่า JS ใดๆ แต่ Watchers มีวิธีพิเศษในการทำให้สิ่งต่าง ๆ มีชีวิตอยู่: สัญญาณใด ๆ ที่ Watcher เฝ้าดูจะยังคงอยู่ตราบเท่าที่สถานะพื้นฐานใด ๆ สามารถเข้าถึงได้ เนื่องจากสิ่งเหล่านี้อาจทำให้เกิดการโทร notify
ในอนาคต (และจากนั้น .get()
ในอนาคต .get()
)) ด้วยเหตุนี้ อย่าลืมโทร Watcher.prototype.unwatch
เพื่อล้างเอฟเฟกต์
Signal.subtle.untrack
เป็นช่องทางหลบหนีที่ช่วยให้อ่านสัญญาณได้ โดยไม่ต้อง ติดตามการอ่านเหล่านั้น ความสามารถนี้ไม่ปลอดภัยเนื่องจากอนุญาตให้สร้างสัญญาณที่คำนวณซึ่งค่าขึ้นอยู่กับสัญญาณอื่น แต่จะไม่ได้รับการอัปเดตเมื่อสัญญาณเหล่านั้นเปลี่ยนแปลง ควรใช้เมื่อการเข้าถึงที่ไม่ได้ติดตามจะไม่เปลี่ยนผลลัพธ์ของการคำนวณ
คุณลักษณะเหล่านี้อาจถูกเพิ่มในภายหลัง แต่ไม่รวมอยู่ในฉบับร่างปัจจุบัน การละเลยของพวกเขาเกิดจากการขาดฉันทามติที่เป็นที่ยอมรับในพื้นที่การออกแบบระหว่างกรอบงาน เช่นเดียวกับความสามารถที่แสดงให้เห็นในการแก้ไขการขาดหายไปด้วยกลไกที่อยู่ด้านบนของแนวคิดสัญญาณที่อธิบายไว้ในเอกสารนี้ อย่างไรก็ตาม น่าเสียดายที่การละเว้นนี้จำกัดศักยภาพของการทำงานร่วมกันระหว่างกรอบงานต่างๆ เนื่องจากต้นแบบของสัญญาณตามที่อธิบายไว้ในเอกสารนี้ถูกสร้างขึ้น จึงมีความพยายามที่จะตรวจสอบอีกครั้งว่าการละเว้นเหล่านี้เป็นการตัดสินใจที่เหมาะสมหรือไม่
Async : สัญญาณจะพร้อมใช้งานสำหรับการประเมินพร้อมกันเสมอในโมเดลนี้ อย่างไรก็ตาม บ่อยครั้งมีประโยชน์ที่จะมีกระบวนการอะซิงโครนัสบางอย่างซึ่งนำไปสู่การตั้งค่าสัญญาณ และเพื่อให้เข้าใจเมื่อสัญญาณยังคง "โหลด" อยู่ วิธีง่ายๆ วิธีหนึ่งในการสร้างแบบจำลองสถานะการโหลดคือมีข้อยกเว้น และพฤติกรรมการแคชข้อยกเว้นของสัญญาณที่คำนวณจะประกอบขึ้นอย่างสมเหตุสมผลด้วยเทคนิคนี้ เทคนิคที่ได้รับการปรับปรุงจะกล่าวถึงในฉบับที่ 30
ธุรกรรม : สำหรับการเปลี่ยนระหว่างมุมมอง มักจะมีประโยชน์ในการรักษาสถานะปัจจุบันสำหรับทั้งสถานะ "จาก" และ "เป็น" สถานะ "เป็น" แสดงผลในพื้นหลัง จนกว่าจะพร้อมที่จะสลับ (ยอมรับธุรกรรม) ในขณะที่สถานะ "จาก" ยังคงโต้ตอบอยู่ การรักษาสถานะทั้งสองไว้พร้อมๆ กันนั้นจำเป็นต้อง "แยก" สถานะของกราฟสัญญาณ และอาจเป็นประโยชน์ในการรองรับการเปลี่ยนที่ค้างอยู่หลายครั้งในคราวเดียว การอภิปรายในฉบับ #73
วิธีการอำนวยความสะดวกที่เป็นไปได้บางอย่างก็ถูกละเว้นเช่นกัน
ข้อเสนอนี้อยู่ในวาระ TC39 เดือนเมษายน 2024 สำหรับระยะที่ 1 ปัจจุบันอาจถือเป็น "ระยะ 0"
มีโพลีฟิลสำหรับข้อเสนอนี้พร้อมการทดสอบพื้นฐานบางอย่าง ผู้เขียนเฟรมเวิร์กบางคนได้เริ่มทดลองใช้การทดแทนการใช้สัญญาณนี้ แต่การใช้งานนี้ยังอยู่ในช่วงเริ่มต้น
ผู้ทำงานร่วมกันในข้อเสนอ Signal ต้องการ ระมัดระวัง เป็นพิเศษในการผลักดันข้อเสนอนี้ไปข้างหน้า เพื่อที่เราจะไม่ติดกับดักของการขนส่งบางสิ่งซึ่งสุดท้ายแล้วเราจะเสียใจและไม่ได้ใช้จริง แผนของเราคือการทำงานเพิ่มเติมต่อไปนี้ ซึ่งไม่จำเป็นในกระบวนการ TC39 เพื่อให้แน่ใจว่าข้อเสนอนี้เป็นไปตามแผน:
ก่อนที่จะเสนอสำหรับระยะที่ 2 เราวางแผนที่จะ:
พัฒนาการใช้งานโพลีฟิลระดับการผลิตหลายรายการที่แข็งแกร่งและผ่านการทดสอบอย่างดี (เช่น ผ่านการทดสอบจากเฟรมเวิร์กต่างๆ รวมถึงการทดสอบสไตล์ test262) และแข่งขันได้ในแง่ของประสิทธิภาพ (ตามที่ตรวจสอบด้วยชุดเกณฑ์มาตรฐานสัญญาณ/เฟรมเวิร์กอย่างละเอียด)
ผสานรวม Signal API ที่เสนอเข้ากับเฟรมเวิร์ก JS จำนวนมากที่เราถือว่าค่อนข้างเป็นตัวแทน และแอปพลิเคชันขนาดใหญ่บางตัวก็ใช้งานได้บนพื้นฐานนี้ ทดสอบว่าทำงานได้อย่างมีประสิทธิภาพและถูกต้องในบริบทเหล่านี้
มีความเข้าใจอย่างถ่องแท้เกี่ยวกับพื้นที่ของส่วนขยายที่เป็นไปได้ของ API และได้ข้อสรุปว่าควรเพิ่มส่วนขยายใด (ถ้ามี) ลงในข้อเสนอนี้
ส่วนนี้อธิบายแต่ละ API ที่เปิดเผยต่อ JavaScript ในแง่ของอัลกอริทึมที่พวกเขานำไปใช้ สิ่งนี้ถือได้ว่าเป็นข้อกำหนดเฉพาะของโปรโต และถูกรวมไว้ในจุดเริ่มต้นนี้เพื่อตอกย้ำชุดซีแมนทิกส์ที่เป็นไปได้หนึ่งชุด ในขณะที่เปิดกว้างต่อการเปลี่ยนแปลงอย่างมาก
แง่มุมบางประการของอัลกอริทึม:
ลำดับการอ่านสัญญาณภายในการคำนวณมีความสำคัญ และสามารถสังเกตได้ตามลำดับที่การโทรกลับบางอย่าง (ซึ่ง Watcher
ถูกเรียกใช้ equals
พารามิเตอร์แรกของ new Signal.Computed
และการเรียกกลับ watched
/ unwatched
) จะถูกดำเนินการ ซึ่งหมายความว่าต้องจัดเก็บแหล่งที่มาของสัญญาณที่คำนวณไว้ตามลำดับ
การเรียกกลับทั้งสี่นี้อาจทำให้เกิดข้อยกเว้นทั้งหมด และข้อยกเว้นเหล่านี้ได้รับการเผยแพร่ในลักษณะที่คาดเดาได้ไปยังโค้ด JS ที่เรียกใช้ ข้อยกเว้น ไม่ ได้หยุดการดำเนินการของอัลกอริทึมนี้หรือปล่อยให้กราฟอยู่ในสถานะประมวลผลเพียงครึ่งเดียว สำหรับข้อผิดพลาดที่เกิดขึ้นในการ notify
การโทรกลับของ Watcher ข้อยกเว้นนั้นจะถูกส่งไปยังการเรียก .set()
ซึ่งทริกเกอร์ โดยใช้ AggregateError หากมีข้อยกเว้นหลายรายการเกิดขึ้น ส่วนที่เหลือ (รวมถึง watched
/ unwatched
?) จะถูกเก็บไว้ในค่าของสัญญาณ ซึ่งจะถูกโยนใหม่เมื่ออ่าน และสัญญาณการโยนใหม่ดังกล่าวสามารถทำเครื่องหมาย ~clean~
ได้เหมือนกับสัญญาณอื่น ๆ ที่มีค่าปกติ
มีการใช้ความระมัดระวังเพื่อหลีกเลี่ยงการหมุนเวียนในกรณีของสัญญาณที่คำนวณซึ่งไม่ได้ "เฝ้าดู" (ถูกสังเกตโดยผู้เฝ้าดูคนใดก็ตาม) เพื่อให้สามารถรวบรวมขยะได้โดยอิสระจากส่วนอื่น ๆ ของกราฟสัญญาณ ภายในสามารถนำไปใช้กับระบบหมายเลขรุ่นซึ่งจะถูกรวบรวมอยู่เสมอ โปรดทราบว่าการใช้งานที่ได้รับการปรับปรุงอาจรวมถึงหมายเลขการสร้างต่อโหนดในเครื่องด้วย หรือหลีกเลี่ยงการติดตามตัวเลขบางตัวในสัญญาณที่รับชม
อัลกอริธึมสัญญาณจำเป็นต้องอ้างอิงสถานะสากลบางอย่าง สถานะนี้เป็นสถานะส่วนกลางสำหรับเธรดทั้งหมด หรือ "ตัวแทน"
computing
: สัญญาณที่คำนวณหรือเอฟเฟกต์ที่อยู่ด้านในสุดกำลังได้รับการประเมินใหม่เนื่องจากการเรียก .get
หรือ .run
หรือ null
เริ่มแรกเป็น null
frozen
: บูลีนแสดงว่ามีการโทรกลับที่กำลังดำเนินการอยู่หรือไม่ ซึ่งต้องการให้กราฟไม่ได้รับการแก้ไข ตอนแรกเป็น false
generation
: จำนวนเต็มที่เพิ่มขึ้นเริ่มต้นที่ 0 ใช้เพื่อติดตามว่าค่าปัจจุบันเป็นอย่างไรในขณะที่หลีกเลี่ยงการหมุนเวียน
Signal
Signal
เป็นอ็อบเจ็กต์ธรรมดาที่ทำหน้าที่เป็นเนมสเปซสำหรับคลาสและฟังก์ชันที่เกี่ยวข้องกับสัญญาณ
Signal.subtle
เป็นวัตถุเนมสเปซภายในที่คล้ายกัน
Signal.State
Signal.State
ช่องภายใน value
: ค่าปัจจุบันของสัญญาณสถานะ
equals
: ฟังก์ชันการเปรียบเทียบที่ใช้เมื่อเปลี่ยนค่า
watched
: การโทรกลับที่จะถูกเรียกเมื่อสัญญาณถูกสังเกตโดยเอฟเฟกต์
unwatched
: การโทรกลับที่จะถูกเรียกเมื่อสัญญาณไม่ถูกสังเกตโดยเอฟเฟกต์อีกต่อไป
sinks
: ชุดของสัญญาณที่เฝ้าดูซึ่งขึ้นอยู่กับสัญญาณนี้
Signal.State(initialValue, options)
ตั้ง value
ของสัญญาณนี้เป็น initialValue
ตั้งค่าสัญญาณนี้ให้ equals
ตัวเลือก?.เท่ากับ
ตั้งค่าตัวเลือก watched
ของสัญญาณนี้หรือไม่.[Signal.subtle.watched]
ตั้งค่าตัวเลือก unwatched
ของสัญญาณนี้หรือไม่.[Signal.subtle.unwatched]
ตั้งค่า sinks
ของ Signal นี้เป็น set ว่าง
Signal.State.prototype.get()
หาก frozen
เป็นจริง ให้ส่งข้อยกเว้น
หาก computing
ไม่ได้ undefined
ให้เพิ่มสัญญาณนี้ไปยังชุด sources
ของ computing
หมายเหตุ: เราไม่เพิ่ม computing
ให้กับ sinks
สัญญาณนี้จนกว่าผู้เฝ้าดูจะเฝ้าดู
ส่งกลับ value
ของสัญญาณนี้
Signal.State.prototype.set(newValue)
หากบริบทการดำเนินการปัจจุบัน frozen
ส่งข้อยกเว้น
เรียกใช้อัลกอริทึม "ตั้งค่าสัญญาณ" ด้วยสัญญาณนี้และพารามิเตอร์แรกสำหรับค่า
หากอัลกอริทึมนั้นส่งคืน ~clean~
ให้ส่งคืนไม่ได้กำหนด
ตั้งค่า state
ของ sinks
ทั้งหมดของสัญญาณนี้เป็น (หากเป็นสัญญาณคอมพิวเตอร์) ~dirty~
หากก่อนหน้านี้สะอาด หรือ (หากเป็นผู้เฝ้าดู) ~pending~
หากก่อนหน้านี้ ~watching~
ตั้งค่า state
ของการพึ่งพาสัญญาณคอมพิวเตอร์ของซิงก์ทั้งหมด (แบบเรียกซ้ำ) เป็น ~checked~
หากก่อนหน้านี้ ~clean~
(นั่นคือ ทิ้งเครื่องหมายสกปรกไว้กับที่) หรือสำหรับ Watchers ~pending~
หากก่อนหน้านี้ ~watching~
สำหรับ ~watching~
แต่ละครั้งก่อนหน้านี้ ผู้ดูจะพบในการค้นหาแบบเรียกซ้ำ จากนั้นจึงเรียงลำดับเชิงลึกเป็นอันดับแรก
ตั้งค่า frozen
เป็นจริง
โทรแจ้งการโทร notify
(ยกเว้นข้อยกเว้นใด ๆ ที่เกิดขึ้น แต่ไม่สนใจค่าที่ส่งคืนของ notify
)
คืนค่า frozen
เป็นเท็จ
ตั้งค่า state
ของ Watcher เป็น ~waiting~
หากมีข้อยกเว้นใดๆ เกิดขึ้นจาก notify
การโทรกลับ ให้เผยแพร่ไปยังผู้โทรหลังจากที่ notify
การโทรกลับทั้งหมดได้ดำเนินการไปแล้ว หากมีข้อยกเว้นหลายข้อ ให้รวมเข้าด้วยกันเป็น AggregateError แล้วโยนสิ่งนั้นทิ้ง
กลับไม่ได้กำหนด
Signal.Computed
Signal.Computed
เครื่องคอมพิวเตอร์สถานะ state
ของสัญญาณคอมพิวเตอร์อาจเป็นอย่างใดอย่างหนึ่งต่อไปนี้:
~clean~
: ค่าของสัญญาณมีอยู่และทราบว่าไม่เก่า
~checked~
: แหล่งที่มา (ทางอ้อม) ของสัญญาณนี้มีการเปลี่ยนแปลง สัญญาณนี้มีค่าแต่ อาจ จะเก่าแล้ว ไม่ว่าจะเก่าหรือไม่นั้นเราจะทราบได้ก็ต่อเมื่อมีการประเมินแหล่งที่มาทันทีทั้งหมดแล้วเท่านั้น
~computing~
: ขณะนี้การโทรกลับของสัญญาณนี้กำลังถูกดำเนินการซึ่งเป็นผลข้างเคียงของการเรียก . .get()
~dirty~
: สัญญาณนี้มีค่าที่ทราบว่าเก่าหรือไม่เคยได้รับการประเมิน
กราฟการเปลี่ยนแปลงมีดังนี้:
stateDiagram-v2
[*] --> สกปรก
สกปรก -> คอมพิวเตอร์: [4]
คอมพิวเตอร์ --> สะอาด: [5]
สะอาด -> สกปรก: [2]
สะอาด -> ตรวจสอบแล้ว: [3]
ตรวจสอบแล้ว --> สะอาด: [6]
ตรวจสอบแล้ว -> สกปรก: [1]
กำลังโหลดการเปลี่ยนผ่านคือ:
ตัวเลข | จาก | ถึง | เงื่อนไข | อัลกอริทึม |
---|---|---|---|---|
1 | ~checked~ | ~dirty~ | แหล่งที่มาทันทีของสัญญาณนี้ซึ่งเป็นสัญญาณที่คำนวณได้รับการประเมินแล้ว และค่าของสัญญาณก็เปลี่ยนไป | อัลกอริทึม: คำนวณสัญญาณคำนวณที่สกปรกใหม่ |
2 | ~clean~ | ~dirty~ | แหล่งที่มาโดยตรงของสัญญาณนี้ซึ่งเป็นสถานะได้รับการตั้งค่าแล้ว โดยมีค่าไม่เท่ากับค่าก่อนหน้า | วิธีการ: Signal.State.prototype.set(newValue) |
3 | ~clean~ | ~checked~ | แหล่งที่มาของสัญญาณนี้แบบเรียกซ้ำแต่ไม่ได้เกิดขึ้นทันที ซึ่งเป็นสถานะได้รับการตั้งค่าแล้ว โดยมีค่าไม่เท่ากับค่าก่อนหน้า | วิธีการ: Signal.State.prototype.set(newValue) |
4 | ~dirty~ | ~computing~ | เรากำลังจะดำเนิน callback | อัลกอริทึม: คำนวณสัญญาณคำนวณที่สกปรกใหม่ |
5 | ~computing~ | ~clean~ | callback เสร็จสิ้นการประเมินและส่งคืนค่าหรือส่งข้อยกเว้น | อัลกอริทึม: คำนวณสัญญาณคำนวณที่สกปรกใหม่ |
6 | ~checked~ | ~clean~ | แหล่งที่มาของสัญญาณนี้ทันทีทั้งหมดได้รับการประเมินแล้ว และทั้งหมดถูกค้นพบว่าไม่มีการเปลี่ยนแปลง ดังนั้นเราจึงทราบแล้วว่าไม่ล้าสมัย | อัลกอริทึม: คำนวณสัญญาณคำนวณที่สกปรกใหม่ |
Signal.Computed
สล็อตภายในที่คำนวณแล้ว value
: ค่าแคชก่อนหน้าของสัญญาณ หรือ ~uninitialized~
สำหรับสัญญาณที่คำนวณไม่เคยอ่าน ค่าอาจเป็นข้อยกเว้นที่ได้รับการโยนใหม่เมื่อมีการอ่านค่า undefined
เสมอสำหรับสัญญาณเอฟเฟกต์
state
: อาจจะเป็น ~clean~
, ~checked~
, ~computing~
หรือ ~dirty~
sources
: ชุดสัญญาณที่ได้รับคำสั่งซึ่งสัญญาณนี้ขึ้นอยู่กับ
sinks
: ชุดสัญญาณที่ได้รับคำสั่งซึ่งขึ้นอยู่กับสัญญาณนี้
equals
: วิธีการเท่ากับที่ให้ไว้ในตัวเลือก
callback
: การโทรกลับซึ่งถูกเรียกเพื่อรับค่าของสัญญาณที่คำนวณได้ ตั้งค่าเป็นพารามิเตอร์แรกที่ส่งผ่านไปยังตัวสร้าง
Signal.Computed
ตัวสร้างคอมพิวเตอร์ชุดคอนสตรัคเตอร์
callback
ไปยังพารามิเตอร์แรก
equals
ขึ้นอยู่กับตัวเลือก โดยค่าเริ่มต้นเป็น Object.is
หากไม่มี
state
ว่า ~dirty~
value
เป็น ~uninitialized~
ด้วย AsyncContext การเรียกกลับจะส่งผ่านไปยัง new Signal.Computed
คำนวณแล้วจะปิดสแน็ปช็อตจากเวลาที่เรียกใช้ Constructor และกู้คืนสแน็ปช็อตนี้ระหว่างการดำเนินการ
Signal.Computed.prototype.get
หากบริบทการดำเนินการปัจจุบันถูก frozen
หรือหากสัญญาณนี้มีสถานะ ~computing~
หรือหากสัญญาณนี้เป็นเอฟเฟกต์และ computing
สัญญาณที่คำนวณแล้ว ให้ส่งข้อยกเว้น
หาก computing
ไม่เป็น null
ให้เพิ่มสัญญาณนี้ไปยังชุด sources
ของ computing
หมายเหตุ: เราไม่เพิ่ม computing
ให้กับสัญญาณนี้ที่ตั้ง sinks
ไว้จนกว่า/เว้นแต่จะมีผู้เฝ้าดู
หากสถานะของสัญญาณนี้เป็น ~dirty~
หรือ ~checked~
: ทำซ้ำขั้นตอนต่อไปนี้จนกว่าสัญญาณนี้จะ ~clean~
:
เรียกซ้ำผ่าน sources
เพื่อค้นหาแหล่งกำเนิดซ้ำที่ลึกที่สุด ซ้ายสุด (เช่น สังเกตเร็วที่สุด) ซึ่งเป็นสัญญาณคอมพิวเตอร์ที่ทำเครื่องหมายว่า ~dirty~
(ตัดการค้นหาออกเมื่อกดปุ่มสัญญาณคอมพิวเตอร์ ~clean~
และรวมสัญญาณคอมพิวเตอร์นี้เป็นสิ่งสุดท้าย เพื่อค้นหา)
ดำเนินการอัลกอริธึม "คำนวณสัญญาณคำนวณสกปรกใหม่" บนสัญญาณนั้น
ณ จุดนี้ สถานะของสัญญาณนี้จะเป็น ~clean~
และไม่มีแหล่งที่มาแบบเรียกซ้ำจะ ~dirty~
หรือ ~checked~
ส่งกลับ value
ของสัญญาณ ถ้าค่านั้นเป็นข้อยกเว้น ให้เขียนข้อยกเว้นนั้นใหม่
Signal.subtle.Watcher
Signal.subtle.Watcher
เครื่องสเตท state
ของผู้เฝ้าดูอาจเป็นอย่างใดอย่างหนึ่งต่อไปนี้:
~waiting~
: เรียกใช้ notify
การโทรกลับแล้ว หรือ Watcher ใหม่ แต่ไม่ได้เฝ้าดูสัญญาณใดๆ อยู่
~watching~
: ผู้เฝ้าดูกำลังดูสัญญาณอยู่ แต่ยังไม่มีการเปลี่ยนแปลงใดๆ เกิดขึ้น ซึ่งจำเป็นต้องมี notify
ให้โทรกลับ
~pending~
: การพึ่งพาของ Watcher มีการเปลี่ยนแปลง แต่การโทรกลับ notify
ยังไม่ได้ดำเนินการ
กราฟการเปลี่ยนแปลงมีดังนี้:
stateDiagram-v2
[*] --> กำลังรอ
รอ --> ดู: [1]
กำลังดู --> กำลังรอ: [2]
กำลังดู --> รอดำเนินการ: [3]
รอดำเนินการ --> กำลังรอ: [4]
กำลังโหลดการเปลี่ยนผ่านคือ:
ตัวเลข | จาก | ถึง | เงื่อนไข | อัลกอริทึม |
---|---|---|---|---|
1 | ~waiting~ | ~watching~ | มีการเรียกวิธี watch ของ Watcher | วิธีการ: Signal.subtle.Watcher.prototype.watch(...signals) |
2 | ~watching~ | ~waiting~ | มีการเรียกใช้วิธี unwatch ของ Watcher และสัญญาณการดูครั้งล่าสุดได้ถูกลบออก | วิธีการ: Signal.subtle.Watcher.prototype.unwatch(...signals) |
3 | ~watching~ | ~pending~ | สัญญาณที่รับชมอาจมีการเปลี่ยนแปลงค่า | วิธีการ: Signal.State.prototype.set(newValue) |
4 | ~pending~ | ~waiting~ | เรียกใช้ notify การโทรกลับแล้ว | วิธีการ: Signal.State.prototype.set(newValue) |
Signal.subtle.Watcher
ช่องภายใน state
: อาจจะเป็น ~watching~
, ~pending~
หรือ ~waiting~
signals
: ชุดสัญญาณที่ได้รับคำสั่งซึ่งผู้เฝ้าดูรายนี้กำลังดูอยู่
notifyCallback
: การโทรกลับซึ่งจะถูกเรียกเมื่อมีการเปลี่ยนแปลงบางอย่าง ตั้งค่าเป็นพารามิเตอร์แรกที่ส่งผ่านไปยังตัวสร้าง
new Signal.subtle.Watcher(callback)
state
ถูกตั้งค่าเป็น ~waiting~
เริ่มต้น signals
เป็นชุดว่าง
notifyCallback
ถูกตั้งค่าเป็นพารามิเตอร์การโทรกลับ
ด้วย AsyncContext การเรียกกลับที่ส่งผ่านไปยัง new Signal.subtle.Watcher
จะ ไม่ ปิดทับสแน็ปช็อตจากเวลาที่เรียกใช้ตัวสร้าง ดังนั้นข้อมูลเชิงบริบทรอบการเขียนจึงมองเห็นได้
Signal.subtle.Watcher.prototype.watch(...signals)
หาก frozen
เป็นจริง ให้ส่งข้อยกเว้น
หากข้อโต้แย้งข้อใดไม่ใช่สัญญาณ ให้ส่งข้อยกเว้น
เพิ่มข้อโต้แย้งทั้งหมดต่อท้าย signals
ของวัตถุนี้
สำหรับสัญญาณที่ดูใหม่แต่ละรายการ เรียงจากซ้ายไปขวา
เพิ่มผู้เฝ้าดูนี้เป็น sink
สัญญาณนั้น
หากนี่คือซิงก์แรก ให้เรียกซ้ำแหล่งที่มาเพื่อเพิ่มสัญญาณนั้นเป็นซิงก์
ตั้งค่า frozen
เป็นจริง
โทรติดต่อกลับ watched
หากมีอยู่
คืนค่า frozen
เป็นเท็จ
หาก state
ของ Signal คือ ~waiting~
ให้ตั้งค่าเป็น ~watching~
Signal.subtle.Watcher.prototype.unwatch(...signals)
หาก frozen
เป็นจริง ให้ส่งข้อยกเว้น
หากข้อโต้แย้งใดๆ ไม่ใช่สัญญาณ หรือไม่ได้ถูกจับตามองโดยผู้เฝ้าดูรายนี้ ให้ส่งข้อยกเว้น
สำหรับแต่ละสัญญาณในอาร์กิวเมนต์ เรียงจากซ้ายไปขวา
ลบสัญญาณนั้นออกจากชุด signals
ของ Watcher นี้
ลบ Watcher นี้ออกจากชุด sink
ของ Signal นั้น
หากชุด sink
ของสัญญาณนั้นว่างเปล่า ให้ลบสัญญาณนั้นออกจากแหล่งที่มาแต่ละแห่ง
ตั้งค่า frozen
เป็นจริง
โทรติดต่อกลับ unwatched
หากมีอยู่
คืนค่า frozen
เป็นเท็จ
หากตอนนี้ผู้ดูไม่มี signals
และ state
เป็น ~watching~
ให้ตั้งค่าเป็น ~waiting~
Signal.subtle.Watcher.prototype.getPending()
ส่งกลับอาร์เรย์ที่มีชุดย่อยของ signals
ซึ่งเป็นสัญญาณคอมพิวเตอร์ในสถานะ ~dirty~
หรือ ~pending~
Signal.subtle.untrack(cb)
ให้ c
เป็นสถานะ computing
ปัจจุบันของบริบทการดำเนินการ
ตั้งค่า computing
เป็นโมฆะ
โทรหา cb
.
คืนค่า computing
เป็น c
(แม้ว่า cb
จะมีข้อยกเว้นก็ตาม)
ส่งคืนค่าที่ส่งคืนของ cb
(สร้างข้อยกเว้นใหม่อีกครั้ง)
หมายเหตุ: การยกเลิกการติดตามไม่ได้ทำให้คุณออกจากสถานะ frozen
ซึ่งได้รับการดูแลรักษาอย่างเคร่งครัด
Signal.subtle.currentComputed()
ส่งคืนค่า computing
ปัจจุบัน
ล้างชุด sources
ของสัญญาณนี้ออก และลบออกจากชุด sinks
ของแหล่งที่มาเหล่านั้น
บันทึกค่า computing
ก่อนหน้าและตั้งค่า computing
เป็นสัญญาณนี้
ตั้งค่าสถานะของสัญญาณนี้เป็น ~computing~
เรียกใช้การเรียกกลับของสัญญาณที่คำนวณนี้ โดยใช้สัญญาณนี้เป็นค่านี้ บันทึกค่าที่ส่งคืน และหากการเรียกกลับทำให้เกิดข้อยกเว้น ให้เก็บค่านั้นไว้สำหรับการโยนใหม่
คืนค่า computing
ก่อนหน้า
ใช้อัลกอริธึม "set Signal value" กับค่าส่งคืนของการเรียกกลับ
ตั้งค่าสถานะของสัญญาณนี้เป็น ~clean~
หากอัลกอริทึมนั้นส่งคืน ~dirty~
: ทำเครื่องหมาย sinks ทั้งหมดของสัญญาณนี้เป็น ~dirty~
(ก่อนหน้านี้ sinks อาจมีทั้งการตรวจสอบและสกปรกผสมกัน) (หรือหากยังไม่ได้ดู ให้ใช้หมายเลขรุ่นใหม่เพื่อระบุความสกปรกหรืออะไรทำนองนั้น)
มิฉะนั้น อัลกอริธึมนั้นจะส่งคืน ~clean~
: ในกรณีนี้ สำหรับแต่ละ ~checked~
sink ของสัญญาณนี้ หากแหล่งที่มาของสัญญาณทั้งหมดนั้นสะอาดแล้ว ให้ทำเครื่องหมายสัญญาณนั้นเป็น ~clean~
เช่นกัน ใช้ขั้นตอนการล้างข้อมูลนี้เพื่อซิงก์เพิ่มเติมแบบวนซ้ำ กับสัญญาณที่เพิ่งล้างใหม่ซึ่งมีการตรวจสอบซิงก์แล้ว (หรือถ้าไม่ได้ดูก็ให้ระบุเหมือนกันเพื่อที่การล้างข้อมูลจะได้ดำเนินการอย่างเกียจคร้าน)
หากอัลกอริธึมนี้ถูกส่งผ่านค่า (ซึ่งตรงข้ามกับข้อยกเว้นสำหรับการสร้างใหม่ จากอัลกอริธึมสัญญาณที่คำนวณสกปรกที่คำนวณใหม่):
เรียกฟังก์ชัน equals
ของสัญญาณนี้ โดยส่งผ่านพารามิเตอร์เป็น value
ปัจจุบัน ค่าใหม่ และสัญญาณนี้ หากมีการโยนข้อยกเว้น ให้บันทึกข้อยกเว้นนั้น (สำหรับการเขียนใหม่เมื่ออ่าน) เป็นค่าของสัญญาณและดำเนินการต่อราวกับว่าการเรียกกลับได้ส่งคืนเท็จ
หากฟังก์ชันนั้นคืนค่าเป็นจริง ให้คืนค่า ~clean~
ตั้ง value
ของสัญญาณนี้เป็นพารามิเตอร์
กลับ ~dirty~
ถาม : อีกไม่นานก็จะสร้างมาตรฐานบางอย่างที่เกี่ยวข้องกับ Signals เมื่อมันเพิ่งเริ่มเป็นสิ่งใหม่ที่มาแรงในปี 2022? เราไม่ควรให้เวลาพวกเขามากขึ้นในการพัฒนาและรักษาเสถียรภาพใช่ไหม?
ตอบ : สถานะปัจจุบันของ Signals ใน Web Frameworks เป็นผลมาจากการพัฒนาอย่างต่อเนื่องมากว่า 10 ปี ในขณะที่การลงทุนก้าวสูงขึ้น เช่นเดียวกับในช่วงไม่กี่ปีที่ผ่านมา กรอบงานเว็บเกือบทั้งหมดกำลังเข้าใกล้โมเดลหลักของ Signals ที่คล้ายกันมาก ข้อเสนอนี้เป็นผลมาจากแบบฝึกหัดการออกแบบที่ใช้ร่วมกันระหว่างผู้นำปัจจุบันจำนวนมากในกรอบงานเว็บ และจะไม่ถูกผลักดันไปสู่การกำหนดมาตรฐานหากไม่มีการตรวจสอบความถูกต้องของกลุ่มผู้เชี่ยวชาญโดเมนนั้นในบริบทต่างๆ
ถาม : เฟรมเวิร์กสามารถใช้สัญญาณในตัวได้หรือไม่ เนื่องจากมีการผสานรวมอย่างแน่นหนากับการเรนเดอร์และการเป็นเจ้าของ
ตอบ : ส่วนที่เจาะจงกรอบงานมากกว่ามักจะอยู่ในขอบเขตของผลกระทบ กำหนดการ และความเป็นเจ้าของ/การกำจัด ซึ่งข้อเสนอนี้ไม่ได้พยายามแก้ไข สิ่งสำคัญอันดับแรกของเราในการสร้างต้นแบบสัญญาณติดตามมาตรฐานคือการตรวจสอบว่าสัญญาณเหล่านั้นสามารถอยู่ "ใต้" เฟรมเวิร์กที่มีอยู่ได้อย่างเข้ากันได้และมีประสิทธิภาพที่ดี
ถาม : Signal API มีไว้สำหรับนักพัฒนาแอปพลิเคชันโดยตรงหรือถูกห่อหุ้มโดยเฟรมเวิร์ก
ตอบ : แม้ว่านักพัฒนาแอปพลิเคชันจะสามารถใช้ API นี้ได้โดยตรง (อย่างน้อยส่วนที่ไม่อยู่ในเนมสเปซ Signal.subtle
) แต่ก็ไม่ได้ออกแบบมาให้เหมาะกับสรีระเป็นพิเศษ ความต้องการของผู้เขียนห้องสมุด/กรอบงานถือเป็นเรื่องสำคัญแทน เฟรมเวิร์กส่วนใหญ่ได้รับการคาดหวังให้ครอบคลุมแม้แต่ Signal.State
และ Signal.Computed
API ขั้นพื้นฐานที่มีบางสิ่งที่แสดงถึงความเอียงตามหลักสรีระศาสตร์ ในทางปฏิบัติ โดยทั่วไปแล้วจะเป็นการดีที่สุดที่จะใช้สัญญาณผ่านเฟรมเวิร์ก ซึ่งจะจัดการฟีเจอร์ที่ซับซ้อนกว่า (เช่น Watcher, untrack
) รวมถึงการจัดการความเป็นเจ้าของและการกำจัด (เช่น การหาว่าเมื่อใดที่ควรเพิ่มและลบสัญญาณออกจากผู้ดู) และ กำลังจัดกำหนดการการแสดงผลไปยัง DOM--ข้อเสนอนี้ไม่ได้พยายามแก้ไขปัญหาเหล่านั้น
ถาม : ฉันต้องรื้อสัญญาณที่เกี่ยวข้องกับวิดเจ็ตเมื่อวิดเจ็ตนั้นถูกทำลายหรือไม่? API สำหรับสิ่งนั้นคืออะไร?
ตอบ : การดำเนินการแยกส่วนที่เกี่ยวข้องที่นี่คือ Signal.subtle.Watcher.prototype.unwatch
เฉพาะสัญญาณที่รับชมเท่านั้นที่ต้องได้รับการทำความสะอาด (โดยการยกเลิกการรับชม) ในขณะที่สัญญาณที่ยังไม่ได้รับชมสามารถรวบรวมขยะได้โดยอัตโนมัติ
ถาม : Signals ทำงานร่วมกับ VDOM หรือทำงานกับ HTML DOM ที่เกี่ยวข้องโดยตรงได้หรือไม่
ก . ใช่! สัญญาณไม่ขึ้นอยู่กับเทคโนโลยีการเรนเดอร์ กรอบงาน JavaScript ที่มีอยู่ซึ่งใช้โครงสร้างคล้ายสัญญาณบูรณาการกับ VDOM (เช่น Preact), DOM ดั้งเดิม (เช่น Solid) และการรวมกัน (เช่น Vue) สิ่งเดียวกันนี้จะเป็นไปได้ด้วยสัญญาณในตัว
ถาม : การใช้ Signals ในบริบทของเฟรมเวิร์กแบบอิงคลาส เช่น Angular และ Lit จะเป็นไปตามหลักสรีระศาสตร์หรือไม่ แล้วเฟรมเวิร์กที่ใช้คอมไพเลอร์เช่น Svelte ล่ะ?
ตอบ : ฟิลด์คลาสสามารถสร้างตามสัญญาณด้วยตัวตกแต่งตัวเข้าถึงแบบง่าย ดังที่แสดงใน Signal polyfill readme สัญญาณมีความสอดคล้องอย่างใกล้ชิดกับรูนของ Svelte 5 เป็นเรื่องง่ายสำหรับคอมไพเลอร์ในการแปลงรูนเป็น Signal API ที่กำหนดไว้ที่นี่ และอันที่จริงนี่คือสิ่งที่ Svelte 5 ทำเป็นการภายใน (แต่มีไลบรารีสัญญาณของตัวเอง)
ถาม : สัญญาณใช้งานได้กับ SSR หรือไม่ ความชุ่มชื้น? ความสามารถในการกลับมาทำงานต่อ?
ก . ใช่. Qwik ใช้ Signals เพื่อให้เกิดผลดีกับคุณสมบัติทั้งสองนี้ และเฟรมเวิร์กอื่นๆ ก็มีแนวทางอื่นๆ ที่ได้รับการพัฒนาอย่างดีในการให้ความชุ่มชื้นด้วย Signals โดยมีข้อดีข้อเสียที่แตกต่างกัน เราคิดว่าเป็นไปได้ที่จะจำลองสัญญาณที่กลับมาทำงานต่อของ Qwik ได้โดยใช้สัญญาณสถานะและสัญญาณคอมพิวเตอร์ที่เชื่อมต่อเข้าด้วยกัน และวางแผนที่จะพิสูจน์สิ่งนี้ในรูปแบบโค้ด
ถาม : Signals ทำงานกับโฟลว์ข้อมูลทางเดียวเหมือนกับที่ React ทำหรือไม่
ตอบ : ใช่ สัญญาณเป็นกลไกสำหรับกระแสข้อมูลทางเดียว เฟรมเวิร์ก UI ที่ใช้สัญญาณช่วยให้คุณแสดงมุมมองของคุณในฐานะฟังก์ชันของโมเดล (โดยที่โมเดลรวมเอาสัญญาณ) กราฟสถานะและสัญญาณที่คำนวณจะไม่เป็นวงกลมโดยการสร้าง นอกจากนี้ยังเป็นไปได้ที่จะสร้างการต่อต้านรูปแบบ React ภายใน Signals (!) เช่น Signal ที่เทียบเท่ากับ setState
ภายใน useEffect
คือการใช้ Watcher เพื่อกำหนดเวลาการเขียนไปยังสัญญาณ State
ถาม : สัญญาณเกี่ยวข้องกับระบบการจัดการสถานะเช่น Redux อย่างไร สัญญาณส่งเสริมสถานะที่ไม่มีโครงสร้างหรือไม่?
ตอบ : สัญญาณสามารถสร้างพื้นฐานที่มีประสิทธิภาพสำหรับนามธรรมการจัดการสถานะที่เหมือนกับร้านค้า รูปแบบทั่วไปที่พบในหลายเฟรมเวิร์กคืออ็อบเจ็กต์ที่ใช้ Proxy ซึ่งแสดงคุณสมบัติภายในโดยใช้ Signals เช่น Vue reactive()
หรือ Solid stores ระบบเหล่านี้ช่วยให้สามารถจัดกลุ่มสถานะได้อย่างยืดหยุ่นในระดับนามธรรมที่เหมาะสมสำหรับแอปพลิเคชันเฉพาะ
ถาม : Signals ใดบ้างที่ Proxy
ไม่สามารถจัดการได้ในปัจจุบัน?
ตอบ : พรอกซีและสัญญาณเป็นส่วนเสริมและเข้ากันได้ดี พรอกซีช่วยให้คุณสามารถสกัดกั้นการดำเนินการของวัตถุตื้นและสัญญาณประสานกราฟการพึ่งพา (ของเซลล์) การสำรองข้อมูลพร็อกซีด้วยสัญญาณเป็นวิธีที่ดีเยี่ยมในการสร้างโครงสร้างรีแอกทีฟที่ซ้อนกันพร้อมหลักสรีรศาสตร์ที่ยอดเยี่ยม
ในตัวอย่างนี้ เราสามารถใช้พรอกซีเพื่อทำให้สัญญาณมีคุณสมบัติ getter และ setter แทนที่จะใช้เมธอด get
และ set
:
const a = new Signal.State (0); const b = new Proxy (a, { รับ (เป้าหมาย คุณสมบัติ ผู้รับ) {if (คุณสมบัติ === 'ค่า') { กลับ target.get ():} - set (เป้าหมาย คุณสมบัติ ค่า ผู้รับ) {if (property === 'value') { target.set (value)!} }});// การใช้งานในบริบทเชิงโต้ตอบเชิงสมมุติ:<template> {b.value} <ปุ่ม onclick={() => {b.value++; }}>เปลี่ยน</ปุ่ม></เทมเพลต>
เมื่อใช้ตัวเรนเดอร์ที่ได้รับการปรับให้เหมาะสมสำหรับปฏิกิริยาแบบละเอียด การคลิกปุ่มจะทำให้เซลล์ b.value
ได้รับการอัปเดต
ดู:
ตัวอย่างของโครงสร้างปฏิกิริยาแบบซ้อนที่สร้างขึ้นด้วยทั้งสัญญาณและพรอกซี: signal-utils
ตัวอย่างการใช้งานก่อนหน้านี้แสดงความสัมพันธ์ระหว่างข้อมูลปฏิกิริยาที่พร็อกซี: ติดตามในตัว
การอภิปราย.
ถาม : สัญญาณเป็นแบบพุชหรือแบบดึง
ตอบ : การประเมินสัญญาณที่คำนวณนั้นยึดตามการดึง: สัญญาณที่คำนวณแล้วจะได้รับการประเมินเมื่อมีการเรียกใช้ .get()
เท่านั้น แม้ว่าสถานะพื้นฐานจะเปลี่ยนแปลงเร็วกว่ามากก็ตาม ในเวลาเดียวกัน การเปลี่ยนสัญญาณสถานะอาจทำให้มีการโทรกลับของผู้เฝ้าดูทันที โดย "พุช" การแจ้งเตือน ดังนั้นสัญญาณอาจถูกมองว่าเป็นโครงสร้างแบบ "ผลัก-ดึง"
ถาม : Signals นำเสนอความไม่แน่นอนในการทำงานของ JavaScript หรือไม่
ตอบ : ไม่ ประการแรก การดำเนินการของสัญญาณทั้งหมดมีความหมายและการเรียงลำดับที่ชัดเจน และจะไม่แตกต่างกันระหว่างการใช้งานที่สอดคล้อง ในระดับที่สูงกว่า สัญญาณจะติดตามชุดของค่าคงที่บางชุด โดยพิจารณาว่าเป็น "เสียง" สัญญาณที่คำนวณจะสังเกตกราฟสัญญาณในสถานะที่สอดคล้องกันเสมอ และการดำเนินการจะไม่ถูกขัดจังหวะด้วยรหัสการกลายพันธุ์ของสัญญาณอื่นๆ (ยกเว้นสิ่งที่เรียกตัวเองว่า) ดูคำอธิบายด้านบน
ถาม : เมื่อฉันเขียนไปยัง State Signal การอัพเดตสัญญาณที่คำนวณจะมีกำหนดเมื่อใด
A : ยังไม่มีกำหนด! สัญญาณที่คำนวณจะคำนวณตัวเองใหม่ในครั้งถัดไปที่มีคนอ่าน พร้อมกันนั้น การโทรกลับ notify
ของ Watcher อาจถูกเรียก ทำให้เฟรมเวิร์กสามารถกำหนดเวลาการอ่านในเวลาที่พวกเขาเห็นว่าเหมาะสม
ถาม : การเขียนไปยังสถานะ Signals จะมีผลเมื่อใด ทันทีหรือเป็นชุด?
ตอบ : การเขียนไปยังสัญญาณสถานะจะสะท้อนให้เห็นทันที - ในครั้งถัดไปที่สัญญาณที่คำนวณซึ่งขึ้นอยู่กับสัญญาณสถานะถูกอ่าน มันจะคำนวณตัวเองใหม่หากจำเป็น แม้ว่าจะอยู่ในบรรทัดโค้ดต่อไปนี้ทันทีก็ตาม อย่างไรก็ตาม ความเกียจคร้านที่มีอยู่ในกลไกนี้ (สัญญาณที่คำนวณจะถูกคำนวณเมื่ออ่านเท่านั้น) หมายความว่า ในทางปฏิบัติ การคำนวณอาจเกิดขึ้นเป็นชุด
ถาม : Signals เปิดใช้งานการดำเนินการ "ปราศจากข้อผิดพลาด" หมายความว่าอย่างไร
ตอบ : โมเดลแบบพุชก่อนหน้านี้สำหรับการเกิดปฏิกิริยาประสบปัญหาการคำนวณซ้ำซ้อน: หากการอัปเดตสัญญาณสถานะทำให้สัญญาณที่คำนวณทำงานอย่างกระตือรือล้น ในที่สุดสิ่งนี้อาจผลักดันการอัปเดตไปยัง UI แต่การเขียนไปยัง UI นี้อาจเกิดก่อนเวลาอันควร หากจะมีการเปลี่ยนแปลงอีกครั้งกับสัญญาณสถานะเริ่มต้นก่อนเฟรมถัดไป บางครั้งค่ากลางที่ไม่ถูกต้องยังแสดงให้ผู้ใช้เห็นด้วยซ้ำเนื่องจากข้อบกพร่องดังกล่าว สัญญาณหลีกเลี่ยงไดนามิกนี้โดยเป็นแบบดึงมากกว่าแบบกด: ในขณะที่เฟรมเวิร์กกำหนดเวลาการเรนเดอร์ UI มันจะดึงการอัปเดตที่เหมาะสม หลีกเลี่ยงงานที่สูญเปล่าทั้งในด้านการคำนวณและการเขียนไปยัง DOM
ถาม : สัญญาณ "สูญเสีย" หมายความว่าอย่างไร
ตอบ : นี่คืออีกด้านหนึ่งของการดำเนินการที่ปราศจากข้อผิดพลาด: สัญญาณเป็นตัวแทนของเซลล์ข้อมูล - เพียงค่าปัจจุบันทันที (ซึ่งอาจเปลี่ยนแปลงได้) ไม่ใช่กระแสข้อมูลเมื่อเวลาผ่านไป ดังนั้น หากคุณเขียนไปยัง State Signal สองครั้งติดต่อกันโดยไม่ทำอะไรเลย การเขียนครั้งแรกจะ "สูญหาย" และจะไม่เคยเห็นสัญญาณหรือผลกระทบจากการคำนวณใดๆ เลย นี่เป็นที่เข้าใจกันว่าเป็นคุณลักษณะแทนที่จะเป็นจุดบกพร่อง โครงสร้างอื่นๆ (เช่น การทำซ้ำแบบอะซิงก์ การสังเกตได้) มีความเหมาะสมมากกว่าสำหรับสตรีม
ถาม : Native Signals จะเร็วกว่าการใช้งาน JS Signal ที่มีอยู่หรือไม่
ตอบ : เราหวังเช่นนั้น (ด้วยปัจจัยคงที่เล็กน้อย) แต่สิ่งนี้ยังคงต้องพิสูจน์ด้วยโค้ด เอ็นจิ้น JS ไม่ใช่เวทย์มนตร์ และท้ายที่สุดจะต้องใช้อัลกอริธึมประเภทเดียวกันกับการใช้งาน Signals ของ JS ดูส่วนด้านบนเกี่ยวกับประสิทธิภาพ
ถาม : เหตุใดข้อเสนอนี้จึงไม่รวมฟังก์ชัน effect()
ในเมื่อเอฟเฟกต์จำเป็นต่อการใช้งาน Signals ในทางปฏิบัติ
ตอบ : ผลกระทบโดยธรรมชาติแล้วจะเชื่อมโยงกับการจัดกำหนดการและการกำจัด ซึ่งได้รับการจัดการโดยกรอบการทำงานและอยู่นอกขอบเขตของข้อเสนอนี้ ข้อเสนอนี้กลับรวมพื้นฐานสำหรับการนำเอฟเฟกต์ไปใช้ผ่าน Signal.subtle.Watcher
API ระดับต่ำที่มากกว่าแทน
ถาม : เหตุใดการสมัครสมาชิกจึงเป็นอัตโนมัติแทนที่จะจัดให้มีอินเทอร์เฟซแบบแมนนวล
ตอบ : จากประสบการณ์แสดงให้เห็นว่าอินเทอร์เฟซการสมัครสมาชิกด้วยตนเองสำหรับปฏิกิริยานั้นไม่เป็นไปตามหลักสรีระศาสตร์และเกิดข้อผิดพลาดได้ง่าย การติดตามอัตโนมัติสามารถจัดวางได้มากกว่าและเป็นคุณลักษณะหลักของสัญญาณ
ถาม : เหตุใดการโทรกลับของ Watcher
จึงทำงานพร้อมกัน แทนที่จะกำหนดเวลาในไมโครทาสก์
ตอบ : เนื่องจาก callback ไม่สามารถอ่านหรือเขียนสัญญาณได้ จึงไม่เกิดอาการผิดปกติจากการเรียกพร้อมกัน การโทรกลับโดยทั่วไปจะเพิ่มสัญญาณให้กับอาร์เรย์เพื่ออ่านในภายหลังหรือทำเครื่องหมายไว้ที่ใดที่หนึ่ง การแยกงานย่อยสำหรับการดำเนินการทุกประเภทเหล่านี้ไม่จำเป็นและมีราคาแพงในทางปฏิบัติ
ถาม : API นี้ขาดสิ่งดีๆ บางอย่างที่เฟรมเวิร์กโปรดของฉันมอบให้ ซึ่งทำให้ง่ายต่อการเขียนโปรแกรมด้วยสัญญาณ สามารถเพิ่มเข้าไปในมาตรฐานด้วยได้หรือไม่?
ตอบ : อาจจะ. ส่วนการขยายเวลาต่างๆ ยังอยู่ระหว่างการพิจารณา โปรดแจ้งปัญหาเพื่อหารือเกี่ยวกับคุณลักษณะที่ขาดหายไปที่คุณพบว่ามีความสำคัญ
ถาม : API นี้สามารถลดขนาดหรือความซับซ้อนได้หรือไม่
ตอบ : เป็นเป้าหมายที่แน่นอนที่จะทำให้ API นี้น้อยที่สุด และเราได้พยายามที่จะทำเช่นนั้นกับสิ่งที่นำเสนอข้างต้น หากคุณมีแนวคิดเพิ่มเติมเกี่ยวกับสิ่งที่สามารถลบออกได้ โปรดยื่นประเด็นเพื่อหารือ
ถาม : เราไม่ควรเริ่มทำงานด้านมาตรฐานในพื้นที่นี้ด้วยแนวคิดดั้งเดิมมากกว่า เช่น สิ่งที่สังเกตได้ หรือไม่
ตอบ : สิ่งที่สังเกตได้อาจเป็นความคิดที่ดีสำหรับบางสิ่ง แต่ไม่ได้แก้ปัญหาที่ Signals มุ่งหวังที่จะแก้ไข ตามที่อธิบายไว้ข้างต้น กลไกที่สังเกตได้หรือกลไกการเผยแพร่/สมัครสมาชิกอื่นๆ ไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์สำหรับการเขียนโปรแกรม UI หลายประเภท เนื่องจากงานกำหนดค่าที่มีแนวโน้มเกิดข้อผิดพลาดมากเกินไปสำหรับนักพัฒนา และงานที่สิ้นเปลืองเนื่องจากขาดความเกียจคร้าน ท่ามกลางปัญหาอื่นๆ
ถาม : เหตุใดจึงเสนอสัญญาณใน TC39 แทนที่จะเป็น DOM เนื่องจากแอปพลิเคชันส่วนใหญ่เป็นแบบเว็บ
ตอบ : ผู้เขียนร่วมบางคนของข้อเสนอนี้สนใจสภาพแวดล้อม UI ที่ไม่ใช่เว็บเป็นเป้าหมาย แต่ในปัจจุบัน สถานที่แห่งใดแห่งหนึ่งอาจเหมาะสมกับสิ่งนั้น เนื่องจาก Web API มีการใช้งานนอกเว็บบ่อยมากขึ้น ท้ายที่สุดแล้ว สัญญาณไม่จำเป็นต้องขึ้นอยู่กับ DOM API ใดๆ ดังนั้นไม่ว่าจะด้วยวิธีใดก็ตาม หากใครมีเหตุผลที่ชัดเจนในการเปลี่ยนกลุ่มนี้ โปรดแจ้งให้เราทราบในประเด็นปัญหา ในตอนนี้ ผู้ร่วมให้ข้อมูลทุกคนได้ลงนามในข้อตกลงทรัพย์สินทางปัญญาของ TC39 แล้ว และมีแผนที่จะนำเสนอสิ่งนี้ต่อ TC39
ถาม : จะต้องใช้เวลานานเท่าใดกว่าฉันจะสามารถใช้สัญญาณมาตรฐานได้?
ตอบ : Polyfill มีอยู่แล้ว แต่ทางที่ดีที่สุดคือไม่ต้องพึ่งพาความเสถียร เนื่องจาก API นี้มีการพัฒนาในระหว่างกระบวนการตรวจสอบ ในบางเดือนหรือหนึ่งปี โพลีฟิลคุณภาพสูงและประสิทธิภาพสูงควรจะใช้งานได้ แต่ยังคงต้องมีการแก้ไขโดยคณะกรรมการและยังไม่ได้มาตรฐาน ตามแนวทางทั่วไปของข้อเสนอ TC39 คาดว่าจะต้องใช้เวลาอย่างน้อย 2-3 ปีเป็นอย่างน้อยที่สุดก่อนที่สัญญาณจะพร้อมใช้งานในเบราว์เซอร์ทุกตัวที่ย้อนกลับไปบางเวอร์ชัน โดยไม่จำเป็นต้องมีโพลีฟิล
ถาม : เราจะป้องกันการกำหนดมาตรฐานของสัญญาณที่ไม่ถูกต้องเร็วเกินไป เช่นเดียวกับ {{JS/web features that you don't like}} ได้อย่างไร
ตอบ : ผู้เขียนข้อเสนอนี้วางแผนที่จะก้าวไปอีกขั้นด้วยการสร้างต้นแบบและพิสูจน์สิ่งต่างๆ ก่อนที่จะขอเลื่อนขั้นที่ TC39 ดู "สถานะและแผนการพัฒนา" ด้านบน หากคุณเห็นช่องว่างในแผนนี้หรือโอกาสในการปรับปรุง โปรดยื่นเรื่องเพื่ออธิบาย