ในปีที่ผ่านมา Angular ได้รับฟีเจอร์ใหม่ๆ มากมาย เช่น ปริมาณน้ำและการดูที่ช้าลง เพื่อช่วยให้นักพัฒนาซอฟต์แวร์ปรับปรุง Core Web Vitals และดูแลให้ผู้ใช้ปลายทางได้รับประสบการณ์ที่ยอดเยี่ยม การวิจัยเกี่ยวกับฟีเจอร์อื่นๆ ที่เกี่ยวข้องกับการแสดงผลฝั่งเซิร์ฟเวอร์ซึ่งต่อยอดฟังก์ชันการทำงานนี้ก็กำลังอยู่ในระหว่างการพัฒนา เช่น การสตรีมและการดื่มน้ำบางส่วน
ขออภัย มีรูปแบบหนึ่งที่อาจขัดขวางไม่ให้แอปพลิเคชันหรือไลบรารีของคุณใช้ประโยชน์อย่างเต็มที่จากฟีเจอร์ใหม่และที่กำลังจะเปิดตัวทั้งหมด ซึ่งก็คือการควบคุมโครงสร้าง DOM เบื้องหลังด้วยตนเอง Angular กำหนดให้โครงสร้างของ DOM ยังคงสอดคล้องกันตั้งแต่เวลาที่เซิร์ฟเวอร์สร้างคอมโพเนนต์ให้เป็นแบบต่อเนื่องจนกระทั่งได้รับน้ำในร่างกายในเบราว์เซอร์ การใช้ ElementRef
, Renderer2
หรือ DOM API เพื่อเพิ่ม ย้าย หรือนำโหนดออกจาก DOM ด้วยตนเองก่อนที่น้ำในร่างกายอาจทำให้เกิดความไม่สอดคล้องกันที่ทำให้ฟีเจอร์เหล่านี้ทำงานไม่ได้
อย่างไรก็ตาม การจัดการและการเข้าถึง DOM ด้วยตนเองบางส่วนอาจไม่เป็นปัญหา และบางครั้งก็เป็นเรื่องจำเป็น กุญแจสำคัญในการใช้ DOM อย่างปลอดภัยคือการลดความจำเป็นในการใช้ DOM ให้ได้มากที่สุด แล้วเลื่อนเวลาการใช้งานให้นานที่สุด คำแนะนำต่อไปนี้อธิบายวิธีที่คุณจะสามารถบรรลุวัตถุประสงค์ดังกล่าว และสร้างองค์ประกอบของ Angular ที่ใช้ได้ทั่วโลกและรองรับอนาคตอย่างแท้จริง ซึ่งจะใช้ประโยชน์จากฟีเจอร์ใหม่และที่กำลังจะเปิดตัวทั้งหมดของ Angular ได้อย่างเต็มที่
หลีกเลี่ยงการจัดการ DOM ด้วยตนเอง
วิธีที่ดีที่สุดในการหลีกเลี่ยงปัญหาที่เกิดจากการบิดเบือน DOM ด้วยตนเองคือการหลีกเลี่ยงเรื่องนี้ทั้งหมดเท่าที่ทำได้อย่างน่าประหลาดใจ Angular มี API และรูปแบบในตัวที่สามารถจัดการ DOM ได้เกือบทุกด้าน จึงควรใช้แทนการเข้าถึง DOM โดยตรง
เปลี่ยนแปลงองค์ประกอบ DOM ของคอมโพเนนต์เอง
ขณะเขียนคอมโพเนนต์หรือคำสั่ง คุณอาจต้องแก้ไของค์ประกอบโฮสต์ (ซึ่งก็คือองค์ประกอบ DOM ที่ตรงกับตัวเลือกของคอมโพเนนต์หรือคำสั่ง) เพื่อเพิ่มคลาส รูปแบบ หรือแอตทริบิวต์ เช่น เพิ่มการกำหนดเป้าหมายหรือแนะนำองค์ประกอบ Wrapper คุณอาจอยากเข้าถึง ElementRef
เพื่อเปลี่ยนองค์ประกอบ DOM ที่เกี่ยวข้อง คุณควรใช้การเชื่อมโยงโฮสต์เพื่อเชื่อมโยงค่ากับนิพจน์อย่างประกาศ ดังนี้
@Component({
selector: 'my-component',
template: `...`,
host: {
'[class.foo]': 'true'
},
})
export class MyComponent {
/* ... */
}
เช่นเดียวกับการเชื่อมโยงข้อมูลใน HTML ตัวอย่างเช่น คุณสามารถเชื่อมโยงกับแอตทริบิวต์และรูปแบบ แล้วเปลี่ยน 'true'
เป็นนิพจน์อื่นที่ Angular จะใช้เพื่อเพิ่มหรือนำค่าออกโดยอัตโนมัติตามที่ต้องการ
ในบางกรณี อาจต้องมีการคํานวณคีย์แบบไดนามิก นอกจากนี้ คุณยังเชื่อมโยงกับสัญญาณหรือฟังก์ชันที่ส่งคืนชุดหรือแมปค่าได้ด้วย โดยทำดังนี้
@Component({
selector: 'my-component',
template: `...`,
host: {
'[class.foo]': 'true',
'[class]': 'classes()'
},
})
export class MyComponent {
size = signal('large');
classes = computed(() => {
return [`size-${this.size()}`];
});
}
ในแอปพลิเคชันที่ซับซ้อนมากขึ้น คุณอาจอยากเข้าถึงการควบคุม DOM ด้วยตนเองเพื่อหลีกเลี่ยง ExpressionChangedAfterItHasBeenCheckedError
แต่คุณสามารถเชื่อมโยงค่ากับสัญญาณเหมือนในตัวอย่างก่อนหน้านี้ได้ ซึ่งทําได้ตามที่ต้องการและไม่จําเป็นต้องใช้สัญญาณในฐานของโค้ดทั้งหมด
เปลี่ยนแปลงองค์ประกอบ DOM นอกเทมเพลต
คุณอาจอยากลองใช้ DOM เพื่อเข้าถึงองค์ประกอบที่ปกติแล้วไม่สามารถเข้าถึงได้ เช่น องค์ประกอบที่เป็นของคอมโพเนนต์ระดับบนสุดหรือคอมโพเนนต์ย่อยอื่นๆ แต่เนื่องจากผู้ใช้มีแนวโน้มที่จะเกิดข้อผิดพลาด การห่อหุ้มข้อมูล และทำให้เปลี่ยนแปลงหรืออัปเกรดองค์ประกอบเหล่านั้นได้ยากในอนาคต
แต่ควรพิจารณาคอมโพเนนต์อื่นทั้งหมดว่าเป็นกล่องดำแทน ลองใช้เวลาและตำแหน่งที่คอมโพเนนต์อื่นๆ (แม้จะอยู่ในแอปพลิเคชันหรือไลบรารีเดียวกัน) อาจต้องโต้ตอบหรือปรับแต่งลักษณะการทำงานหรือรูปลักษณ์ของคอมโพเนนต์ แล้วจึงเปิดเผยวิธีการที่ปลอดภัยและมีการบันทึกไว้ ใช้ฟีเจอร์ เช่น การแทรกทรัพยากร Dependency ตามลําดับชั้นเพื่อให้ API พร้อมใช้งานสำหรับแผนผังย่อยเมื่อพร็อพเพอร์ตี้ @Input
และ @Output
แบบง่ายไม่เพียงพอ
ก่อนหน้านี้ มักมีการใช้งานฟีเจอร์ต่างๆ เช่น กล่องโต้ตอบแบบโมดัลหรือเคล็ดลับเครื่องมือด้วยการเพิ่มองค์ประกอบต่อท้าย <body>
หรือองค์ประกอบโฮสต์อื่นๆ แล้วย้ายหรือฉายเนื้อหาไปที่นั่น อย่างไรก็ตาม คุณอาจแสดงผลองค์ประกอบ <dialog>
แบบง่ายๆ ในเทมเพลตแทนได้ในช่วงนี้
@Component({
selector: 'my-component',
template: `<dialog #dialog>Hello World</dialog>`,
})
export class MyComponent {
@ViewChild('dialog') dialogRef!: ElementRef;
constructor() {
afterNextRender(() => {
this.dialogRef.nativeElement.showModal();
});
}
}
เลื่อนการจัดการ DOM ด้วยตนเอง
หลังจากใช้หลักเกณฑ์ก่อนหน้านี้เพื่อลดการจัดการ DOM โดยตรงและเข้าถึง DOM ให้น้อยที่สุดแล้ว อาจมีส่วนที่เหลือที่หลีกเลี่ยงไม่ได้ ในกรณีเช่นนี้ คุณจำเป็นต้องเลื่อนเวลาออกไปให้นานที่สุด การเรียกกลับ afterRender
และ afterNextRender
เป็นวิธีที่ยอดเยี่ยมเนื่องจากจะเรียกใช้เฉพาะในเบราว์เซอร์ หลังจากที่ Angular ตรวจสอบการเปลี่ยนแปลงและดำเนินการกับ DOM แล้ว
เรียกใช้ JavaScript เฉพาะเบราว์เซอร์
ในบางกรณี คุณจะมีไลบรารีหรือ API ที่ทำงานเฉพาะในเบราว์เซอร์ (เช่น ไลบรารีแผนภูมิ การใช้งาน IntersectionObserver
บางส่วน ฯลฯ) แทนที่จะต้องตรวจสอบแบบมีเงื่อนไขว่าคุณกำลังเรียกใช้เบราว์เซอร์ หรือแยกการทำงานบนเซิร์ฟเวอร์ คุณก็ใช้ afterNextRender
ได้โดยทำดังนี้
@Component({
/* ... */
})
export class MyComponent {
@ViewChild('chart') chartRef: ElementRef;
myChart: MyChart|null = null;
constructor() {
afterNextRender(() => {
this.myChart = new MyChart(this.chartRef.nativeElement);
});
}
}
สร้างเลย์เอาต์ที่กำหนดเอง
บางครั้งคุณอาจต้องอ่านหรือเขียนลงใน DOM เพื่อทำเลย์เอาต์บางอย่างที่เบราว์เซอร์เป้าหมายยังไม่รองรับ เช่น การวางตำแหน่งเคล็ดลับเครื่องมือ afterRender
เป็นตัวเลือกที่ดีในกรณีนี้ เนื่องจากคุณสามารถมั่นใจได้ว่า DOM อยู่ในสถานะที่สม่ำเสมอ afterRender
และ afterNextRender
ยอมรับค่า phase
เป็น EarlyRead
, Read
หรือ Write
การอ่านการจัดวาง DOM หลังจากเขียนจะบังคับให้เบราว์เซอร์คำนวณการจัดวางใหม่แบบซิงโครนัส ซึ่งอาจส่งผลต่อประสิทธิภาพการทำงานได้อย่างมาก (ดู: การข้ามการจัดวาง) ดังนั้น การแยกตรรกะของคุณออกเป็นระยะที่ถูกต้องจึงเป็นสิ่งสำคัญ
ตัวอย่างเช่น คอมโพเนนต์เคล็ดลับเครื่องมือที่ต้องการแสดงเคล็ดลับเครื่องมือที่สัมพันธ์กับองค์ประกอบอื่นในหน้ามักจะใช้ 2 ระยะ ระบบจะใช้ระยะ EarlyRead
ก่อนเพื่อให้ได้ขนาดและตำแหน่งขององค์ประกอบ ดังนี้
afterRender(() => {
targetRect = targetEl.getBoundingClientRect();
tooltipRect = tooltipEl.getBoundingClientRect();
}, { phase: AfterRenderPhase.EarlyRead },
);
จากนั้นเฟส Write
จะใช้ค่าที่อ่านแล้วก่อนหน้านี้เพื่อเปลี่ยนตำแหน่งเคล็ดลับเครื่องมือจริงๆ
afterRender(() => {
tooltipEl.style.setProperty('left', `${targetRect.left + targetRect.width / 2 - tooltipRect.width / 2}px`);
tooltipEl.style.setProperty('top', `${targetRect.bottom - 4}px`);
}, { phase: AfterRenderPhase.Write },
);
การแยกตรรกะของเราออกเป็นช่วงๆ ที่ถูกต้องทำให้ Angular สามารถจัดกลุ่มการจัดการ DOM ในคอมโพเนนต์อื่นๆ ทั้งหมดในแอปพลิเคชันได้อย่างมีประสิทธิภาพ ซึ่งช่วยให้เกิดผลกระทบต่อประสิทธิภาพน้อยที่สุด
บทสรุป
การแสดงผลฝั่งเซิร์ฟเวอร์ของ Angular ในด้านต่างๆ ของ Angular ได้รับการปรับปรุงใหม่ที่น่าสนใจมากมาย โดยมีเป้าหมายที่จะช่วยให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ยอดเยี่ยมได้ง่ายขึ้น เราหวังว่าเคล็ดลับก่อนหน้านี้จะเป็นประโยชน์ในการช่วยให้คุณได้รับประโยชน์สูงสุดจากเคล็ดลับเหล่านี้ในแอปพลิเคชันและคลังของคุณ!