วิธีเริ่มต้นใช้งาน 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)} /> - -