
วิธีเริ่มต้นใช้งาน VUE3.0 อย่างรวดเร็ว: เข้าสู่และเรียนรู้
ในโปรเจ็กต์ React มีหลายสถานการณ์ที่จำเป็นต้องมี Ref ตัวอย่างเช่น ใช้แอตทริบิวต์ ref เพื่อรับโหนด DOM และรับอินสแตนซ์อ็อบเจ็กต์ ClassComponent; ใช้ useRef Hook เพื่อสร้างอ็อบเจ็กต์ Ref เพื่อแก้ไขปัญหาของ setInterval ที่ไม่สามารถรับสถานะล่าสุดได้ คุณยังสามารถเรียก React.createRef ได้ วิธีการสร้างวัตถุ Ref ด้วยตนเอง
แม้ว่า Ref จะใช้งานง่ายมาก แต่ก็ยังหลีกเลี่ยงไม่ได้ที่จะประสบปัญหาในโครงการจริง บทความนี้จะแยกแยะปัญหาต่างๆ ที่เกี่ยวข้องกับ Ref จากมุมมองของซอร์สโค้ด และชี้แจงสิ่งที่ทำเบื้องหลัง API ที่เกี่ยวข้องกับ ref หลังจากอ่านบทความนี้แล้ว คุณอาจมีความเข้าใจที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับ Ref
ประการแรก ref คือตัวย่อของ reference ซึ่งเป็นการอ้างอิง ในไฟล์การประกาศประเภท react คุณสามารถค้นหาประเภทที่เกี่ยวข้องกับการอ้างอิงได้หลายประเภท และแสดงไว้ที่นี่
RefObject<T> { อ่านอย่างเดียวปัจจุบัน: T | .
อินเทอร์เฟซ MutableRefObject<T> { current: T; } RefObject ใช้ useRef Hook, RefObject / MutableRefObejct จะถูกส่งคืน ทั้งสองประเภทกำหนดโครงสร้างออบเจ็กต์ของ { current: T } , Typescript จะเตือน ⚠️ หากมีการแก้ไข refObject.current
const อ้างอิง = useRef<สตริง>(null) ref.current = '' // ข้อผิดพลาด
TS: ไม่สามารถกำหนดให้กับ "ปัจจุบัน" ได้เนื่องจากเป็นคุณสมบัติแบบอ่านอย่างเดียว

ดูคำจำกัดความของเมธอด useRef ฟังก์ชันโอเวอร์โหลด ถูกใช้ที่นี่ เมื่อพารามิเตอร์ทั่วไปที่เข้ามา T ไม่มี null RefObject<T> จะถูกส่งคืน เมื่อมี null MutableRefObject<T>
ฟังก์ชั่น useRef<T>(ค่าเริ่มต้น: T): MutableRefObject<T>; ฟังก์ชั่น useRef<T>(initialValue: T | null): RefObject<T>;
ดังนั้นหากคุณต้องการให้คุณสมบัติปัจจุบันของวัตถุอ้างอิงที่สร้างขึ้นสามารถแก้ไขได้คุณต้องเพิ่ม | null
const อ้างอิง = useRef<สตริง | null>(null) ref.current = '' // ตกลง
เมื่อเรียกใช้เมธอด React.createRef() RefObject ก็จะถูกส่งคืนเช่นกัน
createRef
createRef(): RefObject {
const refObject = {
ปัจจุบัน: โมฆะ,
-
ถ้า (__DEV__) {
Object.seal(refObject);
-
กลับ refObject;
} RefObject/MutableRefObject ถูกเพิ่มในเวอร์ชัน 16.3 หากคุณใช้เวอร์ชันก่อนหน้า คุณต้องใช้ Ref Callback
การใช้ Ref Callback คือการส่งผ่านฟังก์ชันการโทรกลับ เมื่อตอบสนองการโทรกลับ อินสแตนซ์ที่เกี่ยวข้องจะถูกส่งกลับ และสามารถบันทึกได้ด้วยตัวเองสำหรับการโทร ประเภทของฟังก์ชันการโทรกลับนี้คือ RefCallback
พิมพ์ RefCallback<T> = (อินสแตนซ์: T | null) => void;
ตัวอย่างการใช้ RefCallback :
นำเข้า React จาก 'react'
คลาสส่งออก CustomTextInput ขยาย React.Component {
ข้อความอินพุต: HTMLInputElement | .
saveInputRef = (องค์ประกอบ: HTMLInputElement | null) => {
this.textInput = องค์ประกอบ;
-
แสดงผล() {
กลับ (
<input type="text" ref={this.saveInputRef} />
-
-
} ในการประกาศประเภท ยังมีประเภท Ref/LegacyRef ซึ่งใช้เพื่ออ้างอิงถึงประเภท Ref โดยทั่วไป LegacyRef เป็นเวอร์ชันที่เข้ากันได้ ในเวอร์ชันเก่าก่อนหน้านี้ ref อาจเป็นสตริงก็ได้
พิมพ์ Ref<T> = RefCallback<T> |. RefObject<T> |. type LegacyRef<T> = string |. Ref<T>;
เพียงทำความเข้าใจประเภทที่เกี่ยวข้องกับ Ref เท่านั้น คุณจึงจะเขียน Typescript ได้อย่างสะดวกสบายมากขึ้น
การส่งผ่านเมื่อใช้ ref บนส่วนประกอบ JSX เราจะตั้ง Ref ให้กับแอตทริบิวต์ ref เราทุกคนรู้ดีว่าไวยากรณ์ของ jsx จะถูกรวบรวมเป็นรูปแบบของ createElement โดยเครื่องมือเช่น Babel
//jsx
<อ้างอิงแอป={ref} id="my-app" ></แอป>
//เรียบเรียงเป็น
React.createElement (แอป {
อ้างอิง: อ้างอิง,
รหัส: "แอปของฉัน"
}); ดูเหมือนว่า ref จะไม่แตกต่างจากอุปกรณ์ประกอบฉากอื่น ๆ แต่ถ้าคุณพยายามพิมพ์ props.ref ภายในส่วนประกอบ มันจะ undefined และคอนโซลสภาพแวดล้อม dev จะแจ้งพร้อมท์
การพยายามเข้าถึงจะส่งผลให้
undefinedว่าถูกส่งคืน หากคุณต้องการเข้าถึงค่าเดียวกันภายในคอมโพเนนต์ลูก คุณควรส่งผ่านเป็นเสาอื่น
React ทำอะไรกับการอ้างอิง ดังที่คุณเห็นในซอร์สโค้ด ReactElement ref คือ RESERVED_PROPS key จะได้รับการประมวลผลและแยกจากอุปกรณ์ประกอบฉากเป็นพิเศษและส่งผ่านไปยัง Element
const RESERVED_PROPS = {
คีย์: จริง,
อ้างอิง: จริง,
__ตนเอง: จริง,
__ที่มา: จริง,
}; ดังนั้น ref จึงเป็น “props“ ที่จะได้รับการปฏิบัติเป็นพิเศษ
ก่อนเวอร์ชัน 16.8.0 Function Component ไม่มีสถานะและจะเรนเดอร์ตามอุปกรณ์ประกอบฉากที่เข้ามาเท่านั้น ด้วย Hook คุณไม่เพียงแต่จะมีสถานะภายในเท่านั้น แต่ยังเปิดเผยวิธีการสำหรับการโทรภายนอกด้วย (ต้องใช้ forwardRef และ useImperativeHandle )
หากคุณใช้ ref โดยตรงสำหรับ Function Component คอนโซลในสภาพแวดล้อม dev จะเตือนคุณว่าคุณต้องล้อมมันด้วย forwardRef
ฟังก์ชั่นอินพุต () {
กลับ <อินพุต />
-
const อ้างอิง = useRef()
<Input ref={ref} /> ส่วนประกอบของฟังก์ชันไม่สามารถให้การอ้างอิงได้ ความพยายามในการเข้าถึงการอ้างอิงนี้จะล้มเหลว คุณหมายถึงการใช้ React.forwardRef()
forwardRef อะไร ดูซอร์สโค้ด ReactForwardRef.js พับโค้ดที่เกี่ยวข้องกับ __DEV__ มันเป็นเพียงส่วนประกอบที่มีลำดับสูงที่ง่ายมาก รับ FunctionComponent ที่แสดงผล ล้อมมันและกำหนด $$typeof เป็น REACT_FORWARD_REF_TYPE แล้ว return

ติดตามโค้ดและค้นหา solveLazyComponentTag โดยที่ $$typeof จะถูกแยกวิเคราะห์เป็น WorkTag ที่เกี่ยวข้อง

WorkTag ที่สอดคล้องกับ REACT_FORWARD_REF_TYPE คือ ForwardRef จากนั้น ForwardRef จะเข้าสู่ตรรกะของ updateForwardRef
กรณี ForwardRef: {
เด็ก = updateForwardRef (
โมฆะ,
อยู่ระหว่างดำเนินการ,
ส่วนประกอบ,
อุปกรณ์ประกอบฉากที่ได้รับการแก้ไข,
เรนเดอร์เลน,
-
คืนลูก;
} วิธีนี้จะเรียกเมธอด renderWithHooks และส่งผ่าน ref ในพารามิเตอร์ที่ห้า
nextChildren = renderWithHooks ( ปัจจุบัน, อยู่ระหว่างดำเนินการ, แสดงผล, อุปกรณ์ประกอบฉากถัดไป, อ้างอิง // ที่นี่ renderLanes )
ติดตามโค้ดต่อไปและป้อนเมธอด renderWithHooks คุณจะเห็นว่า ref นั้นจะถูกส่งผ่านเป็นพารามิเตอร์ตัวที่สองของ Component ณ จุดนี้เราสามารถเข้าใจได้ว่า ref ตัวที่สองของ FuncitonComponent ที่ห่อด้วย forwardRef มาจากไหน (เทียบกับพารามิเตอร์ตัวที่สองของตัวสร้าง ClassComponent ซึ่งเป็น Context)

เมื่อรู้วิธีส่งผ่านผู้อ้างอิงแล้ว คำถามต่อไปคือจะมอบหมายผู้อ้างอิงอย่างไร
(กำหนด RefCallback เพื่ออ้างอิงและทำลายจุดในการโทรกลับ) ติดตามโค้ด commitAttachRef ในวิธีนี้ มันจะถูกตัดสินว่าการอ้างอิงของโหนดไฟเบอร์เป็น function หรือ RefObject และอินสแตนซ์ จะถูกประมวลผลตามประเภท หากโหนด Fiber เป็น HostComponent ( tag = 5 ) ซึ่งเป็นโหนด DOM อินสแตนซ์ก็คือโหนด DOM และหากโหนด Fiber เป็น ClassComponent ( tag = 1 ) อินสแตนซ์ก็คืออินสแตนซ์ของวัตถุ
ฟังก์ชั่น commitAttachRef (งานเสร็จแล้ว) {
var ref = เสร็จสิ้นการทำงาน.ref;
ถ้า (อ้างอิง !== null) {
var instanceToUse = เสร็จงาน.stateNode;
ถ้า (ประเภทของการอ้างอิง === 'ฟังก์ชั่น') {
อ้างอิง (อินสแตนซ์ ToUse);
} อื่น {
ref.current = instanceToUse;
-
-
} ข้างต้นเป็นตรรกะการกำหนดการอ้างอิงใน HostComponent และ ClassComponent สำหรับส่วนประกอบประเภท ForwardRef จะใช้โค้ดที่แตกต่างกัน แต่โดยพื้นฐานแล้วลักษณะการทำงานจะเหมือนกัน คุณสามารถดู imperativeHandleEffect ได้ที่นี่
ต่อไป เราจะเจาะลึกซอร์สโค้ด React ต่อไปเพื่อดูว่า useRef ถูกนำไปใช้อย่างไร
จะค้นหาโค้ดรันไทม์ useRef ReactFiberHooks โดยการติดตามโค้ด

มีสองวิธีที่นี่ mountRef และ updateRef ตามชื่อที่แนะนำ พวกเขาสอดคล้องกับการดำเนินการใน ref เมื่อ mount และ update โหนด Fiber
ฟังก์ชั่น updateRef<T>(ค่าเริ่มต้น: T): {|ปัจจุบัน: T|} {
const hook = updateWorkInProgressHook();
กลับ hook.memoizedState;
-
ฟังก์ชั่น mountRef<T>(ค่าเริ่มต้น: T): {|ปัจจุบัน: T|} {
const hook = mountWorkInProgressHook();
const อ้างอิง = {ปัจจุบัน: defaultValue};
hook.memoizedState = อ้างอิง;
ส่งคืนผู้อ้างอิง;
} คุณจะเห็นว่าเมื่อ mount , useRef จะสร้าง RefObject และกำหนดให้กับ memoizedState ของ hook เมื่อ update มันจะถูกนำออกมาและส่งคืนโดยตรง
Hook memoizedState ที่แตกต่างกันจะบันทึกเนื้อหาที่แตกต่างกัน useState บันทึกข้อมูล state useEffect บันทึกวัตถุ effect useRef บันทึกวัตถุ ref ...
เมธอด mountWorkInProgressHook และ updateWorkInProgressHook ได้รับการสนับสนุนโดยรายการเชื่อมโยงของ Hooks ดึงวัตถุ memoizedState เดียวกันทุกครั้งที่คุณแสดงผล useRef มันง่ายมาก
ณ จุดนี้ เราเข้าใจตรรกะของการส่งและมอบหมาย ref ใน React รวมถึงซอร์สโค้ดที่เกี่ยวข้องกับ useRef ใช้คำถามของแอปพลิเคชันเพื่อรวบรวมประเด็นความรู้ข้างต้น: มีส่วนประกอบอินพุต ภายในส่วนประกอบนั้น ต้องใช้ innerRef HTMLInputElement เพื่อเข้าถึงโหนด DOM ในเวลาเดียวกัน ยังอนุญาตให้ส่วนประกอบภายนอกอ้างอิงโหนดได้อย่างไร ที่จะนำไปใช้?
อินพุต const = forwardRef (( อุปกรณ์ประกอบฉาก อ้างอิง) => {
const innerRef = useRef<HTMLInputElement>(null)
กลับ (
<อินพุต {...อุปกรณ์ประกอบฉาก} อ้างอิง={???} />
-
}) พิจารณาว่าควรเขียน ??? ในโค้ดข้างต้นอย่างไร
============ เส้นแบ่งคำตอบ ==============
โดยการทำความเข้าใจการใช้งานภายในที่เกี่ยวข้องกับ Ref เห็นได้ชัดว่าเราสามารถสร้าง RefCallback ที่นี่ซึ่ง สามารถจัดการได้หลายรายการ เพียงกำหนด ref
ฟังก์ชันการส่งออก CombineRefs<T = ใดๆ>(
อ้างอิง: Array<MutableRefObject<T |.null> |
): React.RefCallback<T> {
คืนค่า => {
refs.forEach(อ้างอิง => {
ถ้า (ประเภทของการอ้างอิง === 'ฟังก์ชั่น') {
อ้างอิง (ค่า);
} อื่นถ้า (อ้างอิง !== null) {
ref.current = ค่า;
-
-
-
-
อินพุต const = forwardRef (( อุปกรณ์ประกอบฉาก อ้างอิง) => {
const innerRef = useRef<HTMLInputElement>(null)
กลับ (
<input {...อุปกรณ์ประกอบฉาก} ref={combineRefs(ref, innerRef)} />
-
-