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

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

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