การตอบสนองตามความต้องการใน Vue 3

เผยแพร่แล้ว: 2022-03-11

นอกเหนือจากการปรับปรุงประสิทธิภาพที่น่าชื่นชม Vue 3 ที่เพิ่งเปิดตัวยังนำเสนอคุณสมบัติใหม่หลายอย่าง การแนะนำที่สำคัญที่สุดคือ Composition API ในส่วนแรกของบทความนี้ เราจะสรุปแรงจูงใจมาตรฐานสำหรับ API ใหม่ นั่นคือ การจัดระเบียบโค้ดที่ดีขึ้นและการนำกลับมาใช้ใหม่ ในส่วนที่สอง เราจะมุ่งเน้นไปที่แง่มุมต่างๆ ที่ไม่ค่อยมีใครพูดถึงของการใช้ API ใหม่ เช่น การใช้คุณลักษณะที่อิงตามปฏิกิริยาที่อธิบายไม่ได้ในระบบการเกิดปฏิกิริยาของ Vue 2

เราจะเรียกสิ่งนี้ว่า การเกิดปฏิกิริยาตามความต้องการ หลังจากแนะนำคุณสมบัติใหม่ที่เกี่ยวข้อง เราจะสร้างแอปพลิเคชันสเปรดชีตอย่างง่ายเพื่อแสดงให้เห็นถึงความหมายใหม่ของระบบปฏิกิริยาของ Vue ในตอนท้าย เราจะหารือเกี่ยวกับสิ่งที่ใช้งานจริงในการปรับปรุงปฏิกิริยาตอบสนองตามความต้องการนี้

มีอะไรใหม่ใน Vue 3 และเหตุใดจึงสำคัญ

Vue 3 เป็นการเขียนใหม่ครั้งใหญ่ของ Vue 2 โดยนำเสนอการปรับปรุงมากมายในขณะที่ยังคงความเข้ากันได้แบบย้อนหลังกับ API แบบเก่าได้เกือบทั้งหมด

หนึ่งในคุณสมบัติใหม่ที่สำคัญที่สุดใน Vue 3 คือ Composition API การแนะนำนี้ก่อให้เกิดความขัดแย้งอย่างมากเมื่อมีการพูดคุยในที่สาธารณะครั้งแรก ในกรณีที่คุณยังไม่คุ้นเคยกับ API ใหม่ เราจะอธิบายแรงจูงใจเบื้องหลังก่อน

หน่วยปกติของการจัดระเบียบโค้ดคือออบเจ็กต์ JavaScript ที่มีคีย์แสดงถึงประเภทที่เป็นไปได้ต่างๆ ของชิ้นส่วนของส่วนประกอบ ดังนั้นวัตถุอาจมีส่วนหนึ่งสำหรับข้อมูลปฏิกิริยา ( data ) ส่วนอื่นสำหรับคุณสมบัติที่คำนวณ ( computed ) อีกส่วนหนึ่งสำหรับวิธีการส่วนประกอบ ( methods ) เป็นต้น

ภายใต้กระบวนทัศน์นี้ ส่วนประกอบสามารถมีฟังก์ชันการทำงานที่ไม่เกี่ยวข้องหรือเกี่ยวข้องกันอย่างหลวมๆ ได้หลายฟังก์ชัน ซึ่งงานภายในจะถูกกระจายไปยังส่วนต่างๆ ที่กล่าวถึงข้างต้น ตัวอย่างเช่น เราอาจมีองค์ประกอบสำหรับการอัปโหลดไฟล์ที่ใช้ฟังก์ชันการทำงานสองอย่างแยกจากกัน: การจัดการไฟล์และระบบที่ควบคุมภาพเคลื่อนไหวสถานะการอัปโหลด

ส่วน <script> อาจมีสิ่งต่อไปนี้:

 export default { data () { return { animation_state: 'playing', animation_duration: 10, upload_filenames: [], upload_params: { target_directory: 'media', visibility: 'private', } } }, computed: { long_animation () { return this.animation_duration > 5; }, upload_requested () { return this.upload_filenames.length > 0; }, }, ... }

มีประโยชน์สำหรับแนวทางดั้งเดิมในการจัดระเบียบโค้ดนี้ โดยส่วนใหญ่แล้วในนักพัฒนาซอฟต์แวร์โดยไม่ต้องกังวลว่าจะเขียนโค้ดใหม่จากที่ใด หากเรากำลังเพิ่มตัวแปรปฏิกิริยา เราจะแทรกลงในส่วน data หากเรากำลังมองหาตัวแปรที่มีอยู่ เรารู้ว่าตัวแปรนั้นต้องอยู่ในส่วน data

วิธีการดั้งเดิมในการแบ่งการใช้งานฟังก์ชันออกเป็นส่วนๆ ( data , computed แล้ว ฯลฯ ) ไม่เหมาะกับทุกสถานการณ์

มีการอ้างถึงข้อยกเว้นต่อไปนี้บ่อยครั้ง:

  1. การจัดการกับส่วนประกอบที่มีฟังก์ชันจำนวนมาก หากเราต้องการอัปเกรดโค้ดแอนิเมชันของเราด้วยความสามารถในการชะลอการเริ่มต้นของแอนิเมชัน เราจะต้องเลื่อน/ข้ามระหว่างส่วนที่เกี่ยวข้องทั้งหมดของคอมโพเนนต์ในตัวแก้ไขโค้ด ในกรณีขององค์ประกอบการอัปโหลดไฟล์ ส่วนประกอบนั้นมีขนาดเล็กและจำนวนฟังก์ชันที่ใช้งานก็น้อยเช่นกัน ดังนั้น ในกรณีนี้ การข้ามไปมาระหว่างส่วนต่างๆ จึงไม่เป็นปัญหาจริงๆ ปัญหาการกระจายตัวของรหัสนี้มีความเกี่ยวข้องเมื่อเราจัดการกับส่วนประกอบขนาดใหญ่
  2. อีกสถานการณ์หนึ่งที่แนวทางดั้งเดิมขาดหายไปคือการใช้รหัสซ้ำ บ่อยครั้งเราจำเป็นต้องสร้างการผสมผสานเฉพาะของข้อมูลปฏิกิริยา คุณสมบัติที่คำนวณ วิธีการ ฯลฯ ที่มีอยู่ในองค์ประกอบมากกว่าหนึ่งองค์ประกอบ

Vue 2 (และ Vue 3) ที่เข้ากันได้แบบย้อนกลับเสนอวิธีแก้ปัญหาสำหรับการจัดระเบียบโค้ดส่วนใหญ่และปัญหาการนำกลับมาใช้ใหม่: mixins

ข้อดีและข้อเสียของ Mixins ใน Vue 3

มิกซ์อินอนุญาตให้แยกฟังก์ชันการทำงานของคอมโพเนนต์ในหน่วยโค้ดแยกต่างหาก แต่ละฟังก์ชันจะถูกใส่ในมิกซ์อินแยกกัน และทุกส่วนประกอบสามารถใช้มิกซ์อินได้ตั้งแต่หนึ่งรายการขึ้นไป ชิ้นที่กำหนดในมิกซ์อินสามารถใช้ในส่วนประกอบได้ราวกับว่าถูกกำหนดไว้ในส่วนประกอบเอง มิกซ์อินนั้นคล้ายกับคลาสในภาษาเชิงวัตถุ โดยที่พวกเขารวบรวมโค้ดที่เกี่ยวข้องกับฟังก์ชันที่กำหนด เช่นเดียวกับคลาส มิกซ์อินสามารถสืบทอด (ใช้) ในหน่วยโค้ดอื่นได้

อย่างไรก็ตาม การให้เหตุผลกับมิกซ์อินนั้นยากกว่า เนื่องจากไม่จำเป็นต้องออกแบบมิกซ์อินโดยคำนึงถึงการห่อหุ้ม มิกซ์อินได้รับอนุญาตให้เป็นคอลเล็กชันของโค้ดที่ผูกมัดอย่างหลวมๆ โดยไม่มีอินเทอร์เฟซที่กำหนดไว้อย่างดีไปยังโลกภายนอก การใช้มิกซ์อินมากกว่าหนึ่งครั้งในส่วนประกอบเดียวกันอาจส่งผลให้ส่วนประกอบที่เข้าใจและใช้งานยาก

ภาษาเชิงวัตถุส่วนใหญ่ (เช่น C# และ Java) กีดกันหรือไม่อนุญาตการสืบทอดหลายรายการ แม้ว่าข้อเท็จจริงที่ว่ากระบวนทัศน์การเขียนโปรแกรมเชิงวัตถุมีเครื่องมือในการจัดการกับความซับซ้อนดังกล่าว (บางภาษาอนุญาตให้มีการสืบทอดได้หลายแบบ เช่น C++ แต่การเรียบเรียงยังคงดีกว่าการสืบทอด)

ปัญหาในทางปฏิบัติที่อาจเกิดขึ้นเมื่อใช้มิกซ์อินใน Vue คือ การชนกันของชื่อ ซึ่งเกิดขึ้นเมื่อใช้มิกซ์อินตั้งแต่สองรายการขึ้นไปเพื่อประกาศชื่อทั่วไป ควรสังเกตว่าหากกลยุทธ์เริ่มต้นของ Vue ในการจัดการกับการชนกันของชื่อไม่เหมาะในสถานการณ์ที่กำหนด นักพัฒนาสามารถปรับกลยุทธ์ได้ ซึ่งทำให้ต้องเสียค่าใช้จ่ายในการทำให้เกิดความซับซ้อนมากขึ้น

อีกปัญหาหนึ่งคือมิกซ์อินไม่มีสิ่งที่คล้ายกับตัวสร้างคลาส นี่เป็นปัญหาเพราะบ่อยครั้งที่เราต้องการฟังก์ชันที่คล้ายคลึงกันมาก แต่ไม่ เหมือนกันทั้งหมด เพื่อที่จะแสดงในส่วนประกอบต่างๆ สิ่งนี้สามารถหลีกเลี่ยงได้ในบางกรณีง่ายๆ ด้วยการใช้โรงงานผสม

ดังนั้นมิกซ์อินจึงไม่ใช่วิธีแก้ปัญหาในอุดมคติสำหรับการจัดระเบียบโค้ดและการนำกลับมาใช้ใหม่ และยิ่งโครงการใหญ่ขึ้น ปัญหาก็จะยิ่งร้ายแรงมากขึ้นเท่านั้น Vue 3 นำเสนอวิธีใหม่ในการแก้ปัญหาเดียวกันเกี่ยวกับการจัดระเบียบโค้ดและการนำกลับมาใช้ใหม่

องค์ประกอบ API: คำตอบของ Vue 3 ต่อ Code Organization และ Reuse

Composition API ช่วยให้เรา (แต่ ไม่ ต้องการให้เรา) สามารถแยกชิ้นส่วนของส่วนประกอบออกได้อย่างสมบูรณ์ โค้ดทุกชิ้น—ตัวแปร, คุณสมบัติที่คำนวณ, นาฬิกา ฯลฯ—สามารถกำหนดได้อย่างอิสระ

ตัวอย่างเช่น แทนที่จะมีออบเจ็กต์ที่มีส่วน data ที่มีคีย์ animation_state ที่มีค่า (ค่าเริ่มต้น) "กำลังเล่น" ตอนนี้เราสามารถเขียน (ที่ใดก็ได้ในโค้ด JavaScript ของเรา):

 const animation_state = ref('playing');

ผลที่ได้เกือบจะเหมือนกับการประกาศตัวแปรนี้ในส่วน data ขององค์ประกอบบางอย่าง ความแตกต่างที่สำคัญเพียงอย่างเดียวคือเราต้องทำให้ผู้ ref กำหนดไว้ภายนอกส่วนประกอบที่มีอยู่ในส่วนประกอบที่เราตั้งใจจะใช้ เราทำสิ่งนี้โดยนำเข้าโมดูลไปยังตำแหน่งที่มีการกำหนดส่วนประกอบและส่งคืนการ ref จากส่วน setup ของส่วนประกอบ เราจะข้ามขั้นตอนนี้ไปก่อนและเน้นที่ API ใหม่สักครู่ ปฏิกิริยาใน Vue 3 ไม่ต้องการส่วนประกอบ แท้จริงแล้วมันเป็นระบบที่มีอยู่ในตัวเอง

เราสามารถใช้ตัวแปร animation_state ใน ทุก ขอบเขตที่เรานำเข้าตัวแปรนี้ไป หลังจากสร้าง ref เราได้รับและตั้งค่าจริงโดยใช้ ref.value ตัวอย่างเช่น:

 animation_state.value = 'paused'; console.log(animation_state.value);

เราต้องการส่วนต่อท้าย '.value' เนื่องจากตัวดำเนินการกำหนดจะกำหนดค่า (ไม่ตอบสนอง) "หยุดชั่วคราว" ให้กับตัวแปร animation_state การเกิดปฏิกิริยาใน JavaScript (ทั้งเมื่อมีการใช้งานผ่าน defineProperty เช่นเดียวกับใน Vue 2 และเมื่ออิงตาม Proxy เช่นเดียวกับใน Vue 3) ต้องใช้วัตถุที่มีคีย์ที่เราสามารถทำงานด้วยปฏิกิริยาได้

โปรดทราบว่านี่เป็นกรณีใน Vue 2 เช่นกัน ที่นั่น เรามีส่วนประกอบเป็นคำนำหน้าสมาชิกข้อมูลปฏิกิริยา ( component.data_member ) เว้นแต่และจนกว่ามาตรฐานภาษา JavaScript จะแนะนำความสามารถในการโอเวอร์โหลดโอเปอเรเตอร์การกำหนด นิพจน์เชิงโต้ตอบจะต้องใช้อ็อบเจกต์และคีย์ (เช่น animation_state และ value ตามด้านบน) ให้ปรากฏทางด้านซ้ายมือของการดำเนินการมอบหมายใดๆ ที่เราต้องการ รักษาปฏิกิริยา

ในเทมเพลต เราสามารถละเว้น .value ได้ เนื่องจาก Vue ต้องประมวลผลโค้ดเทมเพลตล่วงหน้า และสามารถตรวจหาการอ้างอิงได้โดยอัตโนมัติ:

 <animation :state='animation_state' />

ตามทฤษฎีแล้ว คอมไพเลอร์ Vue สามารถประมวลผลส่วน <script> ล่วงหน้าของ Single File Component (SFC) ในลักษณะเดียวกันได้เช่นกัน โดยแทรก .value หากจำเป็น อย่างไรก็ตาม การใช้ผู้ refs จะแตกต่างกันไปขึ้นอยู่กับว่าเราใช้ SFC หรือไม่ ดังนั้นคุณสมบัติดังกล่าวอาจไม่เป็นที่ต้องการด้วยซ้ำ

บางครั้ง เรามีเอนทิตี (เช่น เป็นออบเจ็กต์ Javascript หรืออาร์เรย์) ที่เราไม่ได้ตั้งใจจะแทนที่ด้วยอินสแตนซ์ที่ต่างไปจากเดิมอย่างสิ้นเชิง เราอาจสนใจเฉพาะการแก้ไขฟิลด์คีย์แทน มีการจดชวเลขในกรณีนี้: การใช้ reactive แทนการ ref ช่วยให้เราสามารถจ่ายด้วย .value :

 const upload_params = reactive({ target_directory: 'media', visibility: 'private', }); upload_params.visibility = 'public'; // no `.value` needed here // if we did not make `upload_params` constant, the following code would compile but we would lose reactivity after the assignment; it is thus a good idea to make reactive variables ```const``` explicitly: upload_params = { target_directory: 'static', visibility: 'public', };

ปฏิกิริยาที่แยกจากกันกับการ ref และ reactive ไม่ใช่คุณสมบัติใหม่อย่างสมบูรณ์ของ Vue 3 แต่ได้รับการแนะนำบางส่วนใน Vue 2.6 โดยที่อินสแตนซ์แยกกันของข้อมูลปฏิกิริยาเรียกว่า "สังเกตได้" ส่วนใหญ่ เราสามารถแทนที่ Vue.observable ด้วย reactive ความแตกต่างประการหนึ่งคือการเข้าถึงและการกลายพันธุ์ของวัตถุที่ส่งผ่านไปยัง Vue.observable โดยตรงนั้นเป็นปฏิกิริยา ในขณะที่ API ใหม่จะส่งคืนวัตถุพร็อกซี ดังนั้นการกลายพันธุ์ของวัตถุดั้งเดิมจะไม่มีผลตอบสนอง

การเปรียบเทียบ: ตัวเลือก API กับ องค์ประกอบ API

สิ่งใหม่ทั้งหมดใน Vue 3 คือตอนนี้ชิ้นส่วนปฏิกิริยาอื่น ๆ ของส่วนประกอบสามารถกำหนดได้อย่างอิสระเช่นกัน นอกเหนือจากข้อมูลปฏิกิริยา คุณสมบัติที่คำนวณได้ถูกนำมาใช้ในลักษณะที่คาดไว้:

 const x = ref(5); const x_squared = computed(() => x.value * x.value); console.log(x_squared.value); // outputs 25

ในทำนองเดียวกัน เราสามารถใช้นาฬิกาประเภทต่างๆ วิธีวงจรชีวิต และการฉีดพึ่งพาได้หลายประเภท เพื่อความกระชับ เราจะไม่กล่าวถึงสิ่งเหล่านั้นที่นี่

สมมติว่าเราใช้แนวทาง SFC มาตรฐานในการพัฒนา Vue เราอาจใช้ API แบบดั้งเดิมด้วยซ้ำ โดยมีส่วนแยกสำหรับข้อมูล คุณสมบัติที่คำนวณได้ ฯลฯ เราจะผสานรวมปฏิกิริยาเล็กๆ น้อยๆ ของ Composition API กับ SFC ได้อย่างไร Vue 3 แนะนำส่วนอื่นสำหรับสิ่งนี้: setup ส่วนใหม่ถือได้ว่าเป็นวิธีการวงจรชีวิตใหม่ (ซึ่งดำเนินการก่อนเบ็ดอื่น ๆ โดยเฉพาะก่อนที่จะ created )

ต่อไปนี้คือตัวอย่างขององค์ประกอบที่สมบูรณ์ซึ่งรวมวิธีการดั้งเดิมเข้ากับ Composition API:

 <template> <input v-model="x" /> <div>Squared: {{ x_squared }}, negative: {{ x_negative }}</div> </template> <script> import { ref, computed } from 'vue'; export default { name: "Demo", computed: { x_negative() { return -this.x; } }, setup() { const x = ref(0); const x_squared = computed(() => x.value * x.value); return {x, x_squared}; } } </script>

สิ่งที่ต้องนำออกไปจากตัวอย่างนี้:

  • ตอนนี้โค้ด Composition API ทั้งหมดอยู่ใน setup คุณอาจต้องการสร้างไฟล์แยกต่างหากสำหรับแต่ละฟังก์ชัน นำเข้าไฟล์นี้ใน SFC และคืนค่าบิตการเกิดปฏิกิริยาที่ต้องการจาก setup (เพื่อให้พร้อมใช้งานสำหรับส่วนประกอบที่เหลือ)
  • คุณสามารถผสมผสานวิธีการแบบใหม่และแบบเดิมในไฟล์เดียวกันได้ โปรดสังเกตว่า x แม้ว่าจะเป็นข้อมูลอ้างอิง แต่ก็ไม่ต้องการ .value เมื่ออ้างถึงในโค้ดเทมเพลตหรือในส่วนดั้งเดิมของส่วนประกอบ เช่น computed
  • สุดท้ายแต่ไม่ท้ายสุด สังเกตว่าเรามีโหนด DOM รูทสองโหนดในเทมเพลตของเรา ความสามารถในการมีโหนดรูทหลายโหนดเป็นคุณสมบัติใหม่อีกอย่างของ Vue 3

ปฏิกิริยาจะแสดงออกมากขึ้นใน Vue 3

ในส่วนแรกของบทความนี้ เราได้กล่าวถึงแรงจูงใจมาตรฐานสำหรับ Composition API ซึ่งเป็นการจัดระเบียบโค้ดที่ปรับปรุงและนำมาใช้ใหม่ แท้จริงแล้ว จุดขายหลักของ API ใหม่ไม่ใช่พลัง แต่ความสะดวกขององค์กรที่นำมา: ความสามารถในการจัดโครงสร้างโค้ดให้ชัดเจนยิ่งขึ้น ดูเหมือนว่านั่นคือทั้งหมด—ที่ Composition API เปิดใช้งานวิธีการปรับใช้ส่วนประกอบที่หลีกเลี่ยงข้อจำกัดของโซลูชันที่มีอยู่แล้ว เช่น มิกซ์อิน

อย่างไรก็ตาม ยังมี API ใหม่อีกมาก Composition API ไม่เพียงแต่เปิดใช้งานระบบปฏิกิริยาที่ดีขึ้นเท่านั้น แต่ยังมีประสิทธิภาพมากขึ้นอีกด้วย ส่วนประกอบสำคัญคือความสามารถในการเพิ่มการตอบสนองต่อแอปพลิเคชัน แบบไดนามิก ก่อนหน้านี้ เราต้องกำหนดข้อมูลทั้งหมด คุณสมบัติที่คำนวณทั้งหมด ฯลฯ ก่อนที่จะโหลดส่วนประกอบ เหตุใดการเพิ่มวัตถุปฏิกิริยาในระยะหลังจึงมีประโยชน์ ในส่วนที่เหลือ เราจะมาดูตัวอย่างที่ซับซ้อนยิ่งขึ้น: สเปรดชีต

การสร้างสเปรดชีตใน Vue 2

เครื่องมือสเปรดชีต เช่น Microsoft Excel, LibreOffice Calc และ Google ชีตล้วนมีระบบการเกิดปฏิกิริยา เครื่องมือเหล่านี้นำเสนอตารางให้กับผู้ใช้ โดยมีคอลัมน์ที่จัดทำดัชนีโดย A–Z, AA–ZZ, AAA–ZZZ ฯลฯ และแถวที่จัดทำดัชนีเป็นตัวเลข

แต่ละเซลล์อาจมีค่าธรรมดาหรือสูตร เซลล์ที่มีสูตรเป็นคุณสมบัติที่คำนวณโดยพื้นฐานแล้ว ซึ่งอาจขึ้นอยู่กับค่าหรือคุณสมบัติอื่นๆ ที่คำนวณได้ ด้วยสเปรดชีตมาตรฐาน (และไม่เหมือนกับระบบการเกิดปฏิกิริยาใน Vue) คุณสมบัติที่คำนวณได้เหล่านี้จึงอนุญาตให้พึ่งพาตัวเองได้! การอ้างอิงตนเองดังกล่าวมีประโยชน์ในบางสถานการณ์ซึ่งได้ค่าที่ต้องการโดยการประมาณซ้ำ

เมื่อเนื้อหาของเซลล์เปลี่ยนแปลง เซลล์ทั้งหมดที่ขึ้นอยู่กับเซลล์ที่เป็นปัญหาจะทริกเกอร์การอัปเดต หากมีการเปลี่ยนแปลงเพิ่มเติม อาจมีกำหนดการอัพเดตเพิ่มเติม

หากเราต้องสร้างแอปพลิเคชันสเปรดชีตด้วย Vue เป็นเรื่องปกติที่จะถามว่าเราจะนำระบบปฏิกิริยาของ Vue ไปใช้และทำให้ Vue เป็นกลไกของแอปสเปรดชีตได้หรือไม่ สำหรับแต่ละเซลล์ เราสามารถจดจำค่าดิบที่แก้ไขได้ เช่นเดียวกับค่าที่คำนวณได้ที่สอดคล้องกัน ค่าที่คำนวณได้จะสะท้อนถึงค่าดิบหากเป็นค่าธรรมดา มิฉะนั้น ค่าที่คำนวณจะเป็นผลลัพธ์ของนิพจน์ (สูตร) ​​ที่เขียนแทนค่าธรรมดา

ด้วย Vue 2 วิธีการใช้สเปรดชีตคือการมี raw_values สองมิติของสตริง และ computed_values ​​อาร์เรย์สองมิติ (ที่คำนวณแล้ว) ของค่าเซลล์

หากจำนวนเซลล์มีน้อยและคงที่ก่อนที่จะโหลดคอมโพเนนต์ Vue ที่เหมาะสม เราอาจมีค่าดิบหนึ่งค่าและค่าที่คำนวณได้หนึ่งค่าสำหรับทุกเซลล์ของตารางในข้อกำหนดคอมโพเนนต์ของเรา นอกเหนือจากความสวยงามมโหฬารที่เกิดจากการใช้งานดังกล่าวแล้ว ตารางที่มีจำนวนเซลล์ตายตัว ณ เวลารวบรวมอาจไม่นับเป็นสเปรดชีต

มีปัญหากับอาร์เรย์สองมิติ computed_values ​​เช่นกัน คุณสมบัติที่คำนวณได้นั้นเป็นฟังก์ชันที่การประเมิน ในกรณีนี้ ขึ้นอยู่กับตัวมันเองเสมอ (โดยทั่วไป การคำนวณค่าของเซลล์จะต้องใช้ค่าอื่นเพื่อคำนวณอยู่แล้ว) แม้ว่า Vue จะอนุญาตให้ใช้คุณสมบัติการคำนวณแบบอ้างอิงตัวเอง การอัปเดตเซลล์เดียวจะทำให้เซลล์ทั้งหมดถูกคำนวณใหม่ (ไม่ว่าจะมีการขึ้นต่อกันหรือไม่ก็ตาม) สิ่งนี้จะไม่มีประสิทธิภาพอย่างยิ่ง ดังนั้น เราอาจลงเอยด้วยการใช้ปฏิกิริยาเพื่อตรวจจับการเปลี่ยนแปลงในข้อมูลดิบด้วย Vue 2 แต่ทุกอย่างอื่นที่ชาญฉลาดจะต้องดำเนินการตั้งแต่เริ่มต้น

การสร้างแบบจำลองค่าที่คำนวณได้ใน Vue 3

ด้วย Vue 3 เราสามารถแนะนำคุณสมบัติการคำนวณใหม่สำหรับทุกเซลล์ หากตารางเติบโตขึ้น จะมีการแนะนำคุณสมบัติที่คำนวณใหม่

สมมติว่าเรามีเซลล์ A1 และ A2 และเราต้องการให้ A2 แสดงกำลังสองของ A1 ซึ่งมีค่าเท่ากับ 5 ภาพร่างของสถานการณ์นี้:

 let A1 = computed(() => 5); let A2 = computed(() => A1.value * A1.value); console.log(A2.value); // outputs 25

สมมติว่าเราอยู่ในสถานการณ์ง่ายๆ นี้สักครู่ มีปัญหาที่นี่ ถ้าเราต้องการเปลี่ยน A1 เพื่อให้มีเลข 6 ล่ะ? สมมติว่าเราเขียนสิ่งนี้:

 A1 = computed(() => 6); console.log(A2.value); // outputs 25 if we already ran the code above

สิ่งนี้ไม่เพียงเปลี่ยนค่า 5 เป็น 6 ใน A1 ตัวแปร A1 มีเอกลักษณ์ที่แตกต่างกันโดยสิ้นเชิงในขณะนี้: คุณสมบัติที่คำนวณได้ซึ่งแก้ไขเป็นหมายเลข 6 อย่างไรก็ตาม ตัวแปร A2 ยังคงตอบสนองต่อการเปลี่ยนแปลงของเอกลักษณ์เดิมของตัวแปร A1 ดังนั้น A2 ไม่ควรอ้างถึง A1 โดยตรง แต่ควรกล่าวถึงวัตถุพิเศษบางอย่างที่จะพร้อมใช้งานในบริบทเสมอ และจะบอกเราว่า A1 คืออะไรในขณะนี้ กล่าวอีกนัยหนึ่งเราต้องการระดับของทางอ้อมก่อนที่จะเข้าถึง A1 บางอย่างเช่นตัวชี้ ไม่มีตัวชี้เป็นเอนทิตีระดับเฟิร์สคลาสใน Javascript แต่ง่ายต่อการจำลอง หากเราต้องการให้ pointer ชี้ไปที่ value เราสามารถสร้าง pointer = {points_to: value} การเปลี่ยนเส้นทางของตัวชี้เป็นการกำหนดให้กับ pointer.points_to และการลดการอ้างอิง (การเข้าถึงค่าที่ชี้ไปที่) จะเป็นการดึงค่าของ pointer.points_to ในกรณีของเราเราดำเนินการดังนี้:

 let A1 = reactive({points_to: computed(() => 5)}); let A2 = reactive({points_to: computed(() => A1.points_to * A1.points_to)}); console.log(A2.points_to); // outputs 25

ตอนนี้เราสามารถแทนที่ 5 ด้วย 6

 A1.points_to = computed(() => 6); console.log(A2.points_to); // outputs 36

บนเซิร์ฟเวอร์ Discord ของ Vue ผู้ใช้ redblobgames ได้ เสนอแนวทางอื่นที่น่าสนใจ: แทนที่จะใช้ค่าที่คำนวณ ให้ใช้การอ้างอิงที่รวมฟังก์ชันปกติไว้ ด้วยวิธีนี้ เราสามารถสลับฟังก์ชันในทำนองเดียวกันโดยไม่ต้องเปลี่ยนเอกลักษณ์ของข้อมูลอ้างอิง

การใช้สเปรดชีตของเราจะมีเซลล์ที่อ้างอิงถึงโดยคีย์ของอาร์เรย์สองมิติ อาร์เรย์นี้สามารถให้ระดับทางอ้อมที่เราต้องการได้ ดังนั้น ในกรณีของเรา เราไม่ต้องการการจำลองตัวชี้เพิ่มเติมใดๆ เราอาจมีอาร์เรย์เดียวที่ไม่แยกความแตกต่างระหว่างค่าดิบและค่าที่คำนวณได้ ทุกอย่างสามารถเป็นค่าที่คำนวณได้:

 const cells = reactive([ computed(() => 5), computed(() => cells[0].value * cells[0].value) ]); cells[0] = computed(() => 6); console.log(cells[1].value); // outputs 36

อย่างไรก็ตาม เราต้องการแยกแยะระหว่างค่า raw และค่าที่คำนวณได้ เนื่องจากเราต้องการผูกค่า raw กับองค์ประกอบอินพุต HTML นอกจากนี้ หากเรามีอาร์เรย์แยกต่างหากสำหรับค่าดิบ เราไม่ต้องเปลี่ยนคำจำกัดความของคุณสมบัติที่คำนวณ พวกเขาจะอัปเดตโดยอัตโนมัติตามข้อมูลดิบ

การใช้สเปรดชีต

เริ่มต้นด้วยคำจำกัดความพื้นฐาน ซึ่งส่วนใหญ่อธิบายตนเองได้ชัดเจน

 const rows = ref(30), cols = ref(26); /* if a string codes a number, return the number, else return a string */ const as_number = raw_cell => /^[0-9]+(\.[0-9]+)?$/.test(raw_cell) ? Number.parseFloat(raw_cell) : raw_cell; const make_table = (val = '', _rows = rows.value, _cols = cols.value) => Array(_rows).fill(null).map(() => Array(_cols).fill(val)); const raw_values = reactive(make_table('', rows.value, cols.value)); const computed_values = reactive(make_table(undefined, rows.value, cols.value)); /* a useful metric for debugging: how many times did cell (re)computations occur? */ const calculations = ref(0);

แผนมีไว้สำหรับการ computed_values[row][column] ที่คำนวณได้ดังนี้ หาก raw_values[row][column] ไม่ได้เริ่มต้นด้วย = ให้ส่งคืน raw_values[row][column] มิฉะนั้น ให้แยกวิเคราะห์สูตร คอมไพล์เป็น JavaScript ประเมินโค้ดที่คอมไพล์แล้วส่งคืนค่า เพื่อให้สั้นลง เราจะโกงเล็กน้อยด้วยการแยกวิเคราะห์สูตร และเราจะไม่ทำการเพิ่มประสิทธิภาพที่ชัดเจนบางอย่างที่นี่ เช่น แคชการคอมไพล์

เราจะถือว่าผู้ใช้สามารถป้อนนิพจน์ JavaScript ที่ถูกต้องเป็นสูตรได้ เราสามารถแทนที่การอ้างอิงไปยังชื่อเซลล์ที่ปรากฏในนิพจน์ของผู้ใช้ เช่น A1, B5 เป็นต้น โดยอ้างอิงถึงค่าเซลล์จริง (คำนวณ) ฟังก์ชันต่อไปนี้ทำงานนี้ โดยสมมติว่าสตริงที่คล้ายกับชื่อเซลล์จริงๆ จะระบุเซลล์ (และไม่ได้เป็นส่วนหนึ่งของนิพจน์ JavaScript ที่ไม่เกี่ยวข้อง) เพื่อความง่าย เราจะถือว่าดัชนีคอลัมน์ประกอบด้วยตัวอักษรตัวเดียว

 const letters = Array(26).fill(0) .map((_, i) => String.fromCharCode("A".charCodeAt(0) + i)); const transpile = str => { let cell_replacer = (match, prepend, col, row) => { col = letters.indexOf(col); row = Number.parseInt(row) - 1; return prepend + ` computed_values[${row}][${col}].value `; }; return str.replace(/(^|[^AZ])([AZ])([0-9]+)/g, cell_replacer); };

การใช้ฟังก์ชัน transpile เราสามารถรับนิพจน์ JavaScript แท้จากนิพจน์ที่เขียนใน "ส่วนขยาย" เล็กๆ ของ JavaScript ที่มีการอ้างอิงเซลล์

ขั้นตอนต่อไปคือการสร้างคุณสมบัติที่คำนวณได้สำหรับทุกเซลล์ ขั้นตอนนี้จะเกิดขึ้นครั้งเดียวในชีวิตของทุกเซลล์ เราสามารถสร้างโรงงานที่จะคืนคุณสมบัติการคำนวณที่ต้องการ:

 const computed_cell_generator = (i, j) => { const computed_cell = computed(() => { // we don't want Vue to think that the value of a computed_cell depends on the value of `calculations` nextTick(() => ++calculations.value); let raw_cell = raw_values[i][j].trim(); if (!raw_cell || raw_cell[0] != '=') return as_number(raw_cell); let user_code = raw_cell.substring(1); let code = transpile(user_code); try { // the constructor of a Function receives the body of a function as a string let fn = new Function(['computed_values'], `return ${code};`); return fn(computed_values); } catch (e) { return "ERROR"; } }); return computed_cell; }; for (let i = 0; i < rows.value; ++i) for (let j = 0; j < cols.value; ++j) computed_values[i][j] = computed_cell_generator(i, j);

หากเราใส่รหัสทั้งหมดข้างต้นในวิธีการ setup เราจำเป็นต้องส่งคืน {raw_values, computed_values, rows, cols, letters, calculations}

ด้านล่างนี้ เรานำเสนอส่วนประกอบทั้งหมด พร้อมด้วยส่วนต่อประสานผู้ใช้พื้นฐาน

รหัสนี้มีอยู่ใน GitHub และคุณยังสามารถตรวจสอบการสาธิตสดได้อีกด้วย

 <template> <div> <div>Calculations: {{ calculations }}</div> <table class="table" border="0"> <tr class="row"> <td></td> <td class="column" v-for="(_, j) in cols" :key="'header' + j" > {{ letters[j] }} </td> </tr> <tr class="row" v-for="(_, i) in rows" :key="i" > <td class="column"> {{ i + 1 }} </td> <td class="column" v-for="(__, j) in cols" :key="i + '-' + j" :class="{ column_selected: active(i, j), column_inactive: !active(i, j), }" @click="activate(i, j)" > <div v-if="active(i, j)"> <input :ref="'input' + i + '-' + j" v-model="raw_values[i][j]" @keydown.enter.prevent="ui_enter()" @keydown.esc="ui_esc()" /> </div> <div v-else v-html="computed_value_formatter(computed_values[i][j].value)"/> </td> </tr> </table> </div> </template> <script> import {ref, reactive, computed, watchEffect, toRefs, nextTick, onUpdated} from "vue"; export default { name: 'App', components: {}, data() { return { ui_editing_i: null, ui_editing_j: null, } }, methods: { get_dom_input(i, j) { return this.$refs['input' + i + '-' + j]; }, activate(i, j) { this.ui_editing_i = i; this.ui_editing_j = j; nextTick(() => this.get_dom_input(i, j).focus()); }, active(i, j) { return this.ui_editing_i === i && this.ui_editing_j === j; }, unselect() { this.ui_editing_i = null; this.ui_editing_j = null; }, computed_value_formatter(str) { if (str === undefined || str === null) return 'none'; return str; }, ui_enter() { if (this.ui_editing_i < this.rows - 1) this.activate(this.ui_editing_i + 1, this.ui_editing_j); else this.unselect(); }, ui_esc() { this.unselect(); }, }, setup() { /*** All the code we wrote above goes here. ***/ return {raw_values, computed_values, rows, cols, letters, calculations}; }, } </script> <style> .table { margin-left: auto; margin-right: auto; margin-top: 1ex; border-collapse: collapse; } .column { box-sizing: border-box; border: 1px lightgray solid; } .column:first-child { background: #f6f6f6; min-width: 3em; } .column:not(:first-child) { min-width: 4em; } .row:first-child { background: #f6f6f6; } #empty_first_cell { background: white; } .column_selected { border: 2px cornflowerblue solid !important; padding: 0px; } .column_selected input, .column_selected input:active, .column_selected input:focus { outline: none; border: none; } </style>

แล้วการใช้งานในโลกแห่งความเป็นจริงล่ะ?

เราเห็นว่าระบบปฏิกิริยาที่แยกจากกันของ Vue 3 ไม่เพียงแต่เปิดใช้งานโค้ดที่สะอาดขึ้นเท่านั้น แต่ยังช่วยให้ระบบปฏิกิริยาที่ซับซ้อนมากขึ้นตามกลไกการเกิดปฏิกิริยาใหม่ของ Vue ผ่านไปประมาณเจ็ดปีนับตั้งแต่เปิดตัว Vue และการแสดงออกที่เพิ่มขึ้นนั้นไม่เป็นที่ต้องการอย่างมาก

ตัวอย่างสเปรดชีตเป็นการสาธิตอย่างตรงไปตรงมาว่า Vue ทำอะไรได้บ้าง และคุณยังสามารถดูการสาธิตสดได้อีกด้วย

แต่สำหรับตัวอย่างคำจริง มันค่อนข้างเฉพาะเจาะจง ระบบใหม่อาจมีประโยชน์ในสถานการณ์ใดบ้าง? กรณีการใช้งานที่ชัดเจนที่สุดสำหรับการเกิดปฏิกิริยาตามความต้องการอาจอยู่ที่ประสิทธิภาพที่เพิ่มขึ้นสำหรับแอปพลิเคชันที่ซับซ้อน

การเปรียบเทียบช่องทาง Vue 2 กับ Vue 3

ในแอปพลิเคชันฟรอนต์เอนด์ที่ทำงานกับข้อมูลจำนวนมาก ค่าใช้จ่ายของการใช้ปฏิกิริยาที่คิดอย่างรอบคอบอาจส่งผลกระทบด้านลบต่อประสิทธิภาพการทำงาน สมมติว่าเรามีแอปพลิเคชันแดชบอร์ดธุรกิจที่สร้างรายงานเชิงโต้ตอบของกิจกรรมทางธุรกิจของบริษัท ผู้ใช้สามารถเลือกช่วงเวลาและเพิ่มหรือลบตัวบ่งชี้ประสิทธิภาพในรายงาน ตัวบ่งชี้บางตัวอาจแสดงค่าที่ขึ้นอยู่กับตัวบ่งชี้อื่นๆ

วิธีหนึ่งในการสร้างรายงานคือการใช้โครงสร้างแบบเสาหิน เมื่อผู้ใช้เปลี่ยนพารามิเตอร์อินพุตในอินเทอร์เฟซ คุณสมบัติที่คำนวณได้เพียงรายการเดียว เช่น report_data จะได้รับการอัปเดต การคำนวณของคุณสมบัติที่คำนวณนี้เกิดขึ้นตามแผนฮาร์ดโค้ด: ขั้นแรก คำนวณตัวบ่งชี้ประสิทธิภาพอิสระทั้งหมด จากนั้นตัวที่ขึ้นอยู่กับตัวบ่งชี้อิสระเหล่านี้เท่านั้น ฯลฯ

การใช้งานที่ดีขึ้นจะแยกส่วนต่างๆ ของรายงานและคำนวณแยกกัน มีประโยชน์บางประการสำหรับสิ่งนี้:

  • นักพัฒนาไม่จำเป็นต้องฮาร์ดโค้ดแผนการดำเนินการซึ่งน่าเบื่อและมีแนวโน้มว่าจะเกิดข้อผิดพลาด ระบบการเกิดปฏิกิริยาของ Vue จะตรวจจับการพึ่งพาโดยอัตโนมัติ
  • ขึ้นอยู่กับปริมาณข้อมูลที่เกี่ยวข้อง เราอาจได้รับประสิทธิภาพที่เพิ่มขึ้นอย่างมาก เนื่องจากเราอัปเดตเฉพาะข้อมูลรายงานที่ขึ้นอยู่กับตรรกะของพารามิเตอร์อินพุตที่แก้ไขเท่านั้น

หากทราบตัวบ่งชี้ประสิทธิภาพทั้งหมดที่อาจเป็นส่วนหนึ่งของรายงานขั้นสุดท้ายก่อนที่จะโหลดคอมโพเนนต์ Vue เราอาจนำการดีคัปปลิ้งที่เสนอไปใช้แม้จะใช้ Vue 2 มิฉะนั้น หากแบ็กเอนด์เป็นแหล่งความจริงเพียงแหล่งเดียว (ซึ่งก็คือ โดยปกติในกรณีของแอปพลิเคชันที่ขับเคลื่อนด้วยข้อมูล) หรือหากมีผู้ให้บริการข้อมูลภายนอก เราสามารถสร้างคุณสมบัติที่คำนวณได้ตามความต้องการสำหรับรายงานทุกชิ้น

ต้องขอบคุณ Vue 3 ที่ตอนนี้ไม่เพียงแต่เป็นไปได้แต่ยังทำได้ง่ายอีกด้วย