การใช้การเลื่อนแบบไม่มีที่สิ้นสุดและการโหลดภาพแบบขี้เกียจใน React
เผยแพร่แล้ว: 2022-03-10HTML Intersection Observer API เพื่อใช้งานการเลื่อนแบบไม่สิ้นสุดและการโหลดรูปภาพแบบ Lazy Loading ในองค์ประกอบการทำงาน React ในกระบวนการนี้ เราจะเรียนรู้วิธีใช้ hooks ของ React และวิธีสร้าง Custom Hooksหากคุณกำลังมองหาทางเลือกอื่นนอกเหนือจากการแบ่งหน้า การเลื่อนแบบไม่สิ้นสุดถือเป็นการพิจารณาที่ดี ในบทความนี้ เราจะมาสำรวจกรณีการใช้งานบางอย่างสำหรับ Intersection Observer API ในบริบทของส่วนประกอบที่ทำงานของ React ผู้อ่านควรมีความรู้เกี่ยวกับการทำงานของส่วนประกอบที่ทำหน้าที่ตอบสนอง ความคุ้นเคยกับ React hooks บางอย่างจะเป็นประโยชน์ แต่ไม่จำเป็น เนื่องจากเราจะมาดูบางส่วนกัน
เป้าหมายของเราคือในตอนท้ายของบทความนี้ เราจะใช้การเลื่อนและการโหลดรูปภาพแบบไร้ขีดจำกัดโดยใช้ HTML API ดั้งเดิม นอกจากนี้เรายังได้เรียนรู้เพิ่มเติมเกี่ยวกับ React Hooks ด้วยสิ่งนี้ คุณสามารถใช้การเลื่อนแบบไม่สิ้นสุดและการโหลดรูปภาพแบบสันหลังยาวในแอปพลิเคชัน React ของคุณเมื่อจำเป็น
มาเริ่มกันเลย.
การสร้างแผนที่ด้วย React และ Leaflet
การรับข้อมูลจากไฟล์ CSV หรือ JSON ไม่เพียงแต่ซับซ้อน แต่ยังเป็นเรื่องน่าเบื่ออีกด้วย การแสดงข้อมูลเดียวกันในรูปแบบของเครื่องช่วยการมองเห็นทำได้ง่ายกว่า Shajia Abidi อธิบายว่า Leaflet เครื่องมือมีประสิทธิภาพเพียงใด และสามารถสร้างแผนที่ประเภทต่างๆ ได้มากมายเพียงใด อ่านบทความที่เกี่ยวข้อง →
The Intersection Observer API
ตามเอกสารของ MDN "Intersection Observer API มีวิธีสังเกตการเปลี่ยนแปลงในส่วนตัดขององค์ประกอบเป้าหมายที่มีองค์ประกอบระดับบนสุดหรือวิวพอร์ตของเอกสารระดับบนสุดแบบอะซิงโครนัส"
API นี้ช่วยให้เรานำฟีเจอร์เจ๋งๆ ไปใช้ เช่น การเลื่อนแบบไม่สิ้นสุดและการโหลดรูปภาพแบบ Lazy Loading ผู้สังเกตการณ์ทางแยกถูกสร้างขึ้นโดยการเรียกตัวสร้างและส่งการเรียกกลับและวัตถุตัวเลือก การเรียกกลับถูกเรียกใช้เมื่อใดก็ตามที่องค์ประกอบหนึ่ง เรียกว่า target ตัดกับวิวพอร์ตของอุปกรณ์หรือองค์ประกอบที่ระบุ เรียกว่า root ท เราสามารถระบุรูทแบบกำหนดเองในอาร์กิวเมนต์ options หรือใช้ค่าเริ่มต้น
let observer = new IntersectionObserver(callback, options);API นั้นใช้งานง่าย ตัวอย่างทั่วไปมีลักษณะดังนี้:
var intObserver = new IntersectionObserver(entries => { entries.forEach(entry => { console.log(entry) console.log(entry.isIntersecting) // returns true if the target intersects the root element }) }, { // default options } ); let target = document.querySelector('#targetId'); intObserver.observe(target); // start observation entries เป็นรายการของวัตถุ IntersectionObserverEntry อ็อบเจ็กต์ IntersectionObserverEntry อธิบายการเปลี่ยนแปลงทางแยกสำหรับองค์ประกอบเป้าหมายที่สังเกตได้หนึ่งรายการ โปรดทราบว่าการเรียกกลับไม่ควรจัดการกับงานที่ต้องใช้เวลามาก เนื่องจากทำงานบนเธรดหลัก
ขณะนี้ Intersection Observer API รองรับเบราว์เซอร์ในวงกว้าง ดังที่แสดงบน caniuse

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ API ได้ในลิงก์ที่ให้ไว้ในส่วนแหล่งข้อมูล
ตอนนี้ให้เราดูวิธีใช้งาน API นี้ในแอป React จริง เวอร์ชันสุดท้ายของแอปของเราจะเป็นหน้ารูปภาพที่เลื่อนไปเรื่อย ๆ และโหลดแต่ละภาพอย่างเกียจคร้าน
การเรียก API ด้วย useEffect Hook
ในการเริ่มต้น ให้โคลนโปรเจ็กต์เริ่มต้นจาก URL นี้ มีการตั้งค่าน้อยที่สุดและกำหนดรูปแบบบางอย่าง ฉันได้เพิ่มลิงก์ไปยัง CSS ของ Bootstrap ในไฟล์ public/index.html เนื่องจากฉันจะใช้คลาสของมันในการจัดแต่งทรงผม
รู้สึกอิสระที่จะสร้างโครงการใหม่หากคุณต้องการ ตรวจสอบให้แน่ใจว่าคุณได้ติดตั้งตัวจัดการแพ็คเกจ yarn หากคุณต้องการติดตามด้วย repo คุณสามารถดูคำแนะนำในการติดตั้งสำหรับระบบปฏิบัติการเฉพาะของคุณได้ที่นี่
สำหรับบทช่วยสอนนี้ เราจะนำรูปภาพจาก API สาธารณะมาแสดงบนหน้า เราจะใช้ Lorem Picsum API
สำหรับบทช่วยสอนนี้ เราจะใช้ปลายทาง https://picsum.photos/v2/list?page=0&limit=10 ซึ่งจะคืนค่าอาร์เรย์ของวัตถุรูปภาพ เพื่อให้ได้ภาพสิบภาพถัดไป เราเปลี่ยนค่าของหน้าเป็น 1 จากนั้นเป็น 2 และอื่นๆ
ตอนนี้เราจะสร้างองค์ประกอบแอพทีละส่วน
เปิด src/App.js และป้อนรหัสต่อไปนี้
import React, { useEffect, useReducer } from 'react'; import './index.css'; function App() { const imgReducer = (state, action) => { switch (action.type) { case 'STACK_IMAGES': return { ...state, images: state.images.concat(action.images) } case 'FETCHING_IMAGES': return { ...state, fetching: action.fetching } default: return state; } } const [imgData, imgDispatch] = useReducer(imgReducer,{ images:[], fetching: true}) // next code block goes here } ประการแรก เรากำหนดฟังก์ชันรี imgReducer ตัวลดนี้จัดการสองการกระทำ
- การดำเนินการ
STACK_IMAGESเชื่อมอาร์เรย์images - การดำเนินการ
FETCHING_IMAGESสลับค่าของตัวแปรfetchingระหว่างtrueและfalse
ขั้นตอนต่อไปคือการต่อสายลดนี้กับตะขอ useReducer เมื่อเสร็จแล้วเราจะได้สองสิ่งกลับมา:
-
imgDataซึ่งมีสองตัวแปร:imagesคืออาร์เรย์ของวัตถุรูปภาพ การfetchingเป็นบูลีนที่บอกเราว่าการเรียก API กำลังดำเนินการอยู่หรือไม่ -
imgDispatchซึ่งเป็นฟังก์ชันสำหรับอัปเดตอ็อบเจ็กต์ตัวลดขนาด
คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับ useReducer hook ได้ในเอกสารประกอบ React
ส่วนต่อไปของโค้ดคือจุดที่เราทำการเรียก API วางโค้ดต่อไปนี้ใต้บล็อคโค้ดก่อนหน้าใน App.js
// make API calls useEffect(() => { imgDispatch({ type: 'FETCHING_IMAGES', fetching: true }) fetch('https://picsum.photos/v2/list?page=0&limit=10') .then(data => data.json()) .then(images => { imgDispatch({ type: 'STACK_IMAGES', images }) imgDispatch({ type: 'FETCHING_IMAGES', fetching: false }) }) .catch(e => { // handle error imgDispatch({ type: 'FETCHING_IMAGES', fetching: false }) return e }) }, [ imgDispatch ]) // next code block goes here ภายใน useEffect hook เราทำการเรียกไปยังจุดปลาย API ด้วยการ fetch API จากนั้นเราอัปเดตอาร์เรย์รูปภาพด้วยผลลัพธ์ของการเรียก API โดยส่งการดำเนินการ STACK_IMAGES นอกจากนี้เรายังส่งการดำเนินการ FETCHING_IMAGES เมื่อการเรียก API เสร็จสิ้น
รหัสกลุ่มถัดไปกำหนดค่าตอบแทนของฟังก์ชัน ป้อนรหัสต่อไปนี้หลังจากเบ็ด useEffect
return ( <div className=""> <nav className="navbar bg-light"> <div className="container"> <a className="navbar-brand" href="/#"> <h2>Infinite scroll + image lazy loading</h2> </a> </div> </navv <div id='images' className="container"> <div className="row"> {imgData.images.map((image, index) => { const { author, download_url } = image return ( <div key={index} className="card"> <div className="card-body "> <img alt={author} className="card-img-top" src={download_url} /> </div> <div className="card-footer"> <p className="card-text text-center text-capitalize text-primary">Shot by: {author}</p> </div> </div> ) })} </div> </div> </div> ); ในการแสดงรูปภาพ เราแมปเหนืออาร์เรย์รูปภาพในอ็อบเจ็กต์ imgData
ตอนนี้เริ่มแอพและดูหน้าในเบราว์เซอร์ คุณควรเห็นภาพที่แสดงอย่างสวยงามในตารางตอบสนอง
บิตสุดท้ายคือการส่งออกส่วนประกอบแอป
export default App; 
สาขาที่เกี่ยวข้อง ณ จุดนี้คือ 01-make-api-calls
มาขยายขอบเขตนี้โดยแสดงรูปภาพเพิ่มเติมเมื่อเลื่อนหน้า
การใช้ Infinite Scroll
เราตั้งเป้าที่จะนำเสนอรูปภาพเพิ่มเติมเมื่อเลื่อนหน้า จาก URL ของปลายทาง API https://picsum.photos/v2/list?page=0&limit=10 เราทราบดีว่าการจะได้รูปภาพชุดใหม่ เราต้องเพิ่มค่าของ page เท่านั้น เรายังต้องทำสิ่งนี้เมื่อเราไม่มีรูปภาพที่จะแสดง เพื่อจุดประสงค์ของเราที่นี่ เราจะรู้ว่าเรามีภาพหมดแล้วเมื่อเราเข้าไปที่ด้านล่างสุดของหน้า ถึงเวลาดูว่า Intersection Observer API ช่วยให้เราบรรลุเป้าหมายได้อย่างไร
เปิด src/App.js และสร้างตัวลดใหม่ pageReducer ด้านล่าง imgReducer
// App.js const imgReducer = (state, action) => { ... } const pageReducer = (state, action) => { switch (action.type) { case 'ADVANCE_PAGE': return { ...state, page: state.page + 1 } default: return state; } } const [ pager, pagerDispatch ] = useReducer(pageReducer, { page: 0 }) เรากำหนดประเภทการดำเนินการเดียวเท่านั้น ทุกครั้งที่มีการทริกเกอร์การทำงาน ADVANCE_PAGE ค่าของ page จะเพิ่มขึ้น 1
อัปเดต URL ในฟังก์ชัน fetch เพื่อยอมรับหมายเลขหน้าแบบไดนามิกดังที่แสดงด้านล่าง
fetch(`https://picsum.photos/v2/list?page=${pager.page}&limit=10`) เพิ่ม pager.page ไปยังอาร์เรย์การพึ่งพาข้าง imgData การทำเช่นนี้ทำให้มั่นใจได้ว่าการเรียก API จะทำงานทุก pager.page
useEffect(() => { ... }, [ imgDispatch, pager.page ]) หลังจาก useEffect hook สำหรับการเรียก API ให้ป้อนรหัสด้านล่าง อัปเดตรายการนำเข้าของคุณด้วย
// App.js import React, { useEffect, useReducer, useCallback, useRef } from 'react'; useEffect(() => { ... }, [ imgDispatch, pager.page ]) // implement infinite scrolling with intersection observer let bottomBoundaryRef = useRef(null); const scrollObserver = useCallback( node => { new IntersectionObserver(entries => { entries.forEach(en => { if (en.intersectionRatio > 0) { pagerDispatch({ type: 'ADVANCE_PAGE' }); } }); }).observe(node); }, [pagerDispatch] ); useEffect(() => { if (bottomBoundaryRef.current) { scrollObserver(bottomBoundaryRef.current); } }, [scrollObserver, bottomBoundaryRef]); เรากำหนดตัวแปร bottomBoundaryRef และตั้งค่าเป็น useRef(null) useRef ให้ตัวแปรคงค่าไว้ระหว่างการแสดงผลส่วนประกอบ กล่าวคือ ค่า ปัจจุบัน ของตัวแปรจะคงอยู่เมื่อองค์ประกอบที่มีอยู่แสดงผลซ้ำ วิธีเดียวที่จะเปลี่ยนค่าคือการกำหนดคุณสมบัติ .current ให้กับตัวแปรนั้น
ในกรณีของเรา bottomBoundaryRef.current เริ่มต้นด้วยค่า null เมื่อรอบการแสดงหน้าดำเนินไป เราตั้งค่าคุณสมบัติปัจจุบันให้เป็น node <div id='page-bottom-boundary'>
เราใช้คำสั่งการมอบหมาย ref={bottomBoundaryRef} เพื่อบอกให้ React ตั้งค่า bottomBoundaryRef.current ให้เป็น div ที่ประกาศการมอบหมายนี้
ดังนั้น,
bottomBoundaryRef.current = nullเมื่อสิ้นสุดรอบการเรนเดอร์ จะกลายเป็น:
bottomBoundaryRef.current = <div></div>เราจะดูว่างานนี้เสร็จสิ้นในนาทีที่

ต่อไป เรากำหนดฟังก์ชัน scrollObserver ซึ่งจะตั้งค่าผู้สังเกตการณ์ ฟังก์ชันนี้ยอมรับโหนด DOM เพื่อสังเกต ประเด็นหลักที่ควรทราบในที่นี้คือ เมื่อใดก็ตามที่เราไปถึงทางแยกภายใต้การสังเกต เราจะดำเนินการ ADVANCE_PAGE ผลที่ได้คือการเพิ่มค่าของ pager.page โดย 1 เมื่อสิ่งนี้เกิดขึ้น useEffect hook ที่มีการพึ่งพาจะถูกเรียกใช้อีกครั้ง ในทางกลับกัน การเรียกใช้ซ้ำนี้จะเรียกเรียกการดึงข้อมูลด้วยหมายเลขหน้าใหม่
ขบวนเหตุการณ์มีลักษณะเช่นนี้
กดปุ่มแยกภายใต้การสังเกต → เรียกการกระทำADVANCE_PAGE→ ค่าที่เพิ่มขึ้นของpager.pageทีละ 1 →useEffecthook สำหรับการเรียกใช้การเรียกการเรียก → เรียกใช้การfetch→ ภาพที่ส่งคืนจะถูกต่อเข้ากับอาร์เรย์images
เราเรียกใช้ scrollObserver ใน useEffect hook เพื่อให้ฟังก์ชันทำงานก็ต่อเมื่อการพึ่งพาของ hook เปลี่ยนไปเท่านั้น หากเราไม่ได้เรียกใช้ฟังก์ชันภายใน useEffect hook ฟังก์ชันจะทำงานในทุกการแสดงผลหน้า
จำได้ว่า bottomBoundaryRef.current หมายถึง <div id="page-bottom-boundary" style="border: 1px solid red;"></div> เราตรวจสอบว่าค่าของมันไม่เป็นค่าว่างก่อนที่จะส่งต่อไปยัง scrollObserver มิฉะนั้น คอนสตรัคเตอร์ IntersectionObserver จะส่งคืนข้อผิดพลาด
เนื่องจากเราใช้ scrollObserver ใน useEffect hook เราจึงต้องห่อมันใน useCallback hook เพื่อป้องกันไม่ให้ส่วนประกอบไม่สิ้นสุดการเรนเดอร์ซ้ำ คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับ useCallback ได้ในเอกสาร React
ป้อนโค้ดด้านล่างต่อจาก <div id='images'> div
// App.js <div id='image'> ... </div> {imgData.fetching && ( <div className="text-center bg-secondary m-auto p-3"> <p className="m-0 text-white">Getting images</p> </div> )} <div id='page-bottom-boundary' style={{ border: '1px solid red' }} ref={bottomBoundaryRef}></div> เมื่อการเรียก API เริ่มต้น เราตั้งค่าการ fetching true และข้อความ Getting images จะปรากฏให้เห็น ทันทีที่เสร็จสิ้น เราตั้งค่าการ fetching false และข้อความจะถูกซ่อน เรายังทริกเกอร์การเรียก API ก่อนกดที่ขอบเขตได้อย่างแม่นยำด้วยการตั้งค่า threshold ที่แตกต่างกันในอ็อบเจ็กต์ตัวเลือกคอนสตรัคเตอร์ เส้นสีแดงที่ส่วนท้ายช่วยให้เรามองเห็นได้อย่างแม่นยำเมื่อเราไปถึงขอบเขตหน้า
สาขาที่เกี่ยวข้อง ณ จุดนี้คือ 02-infinite-scroll
ตอนนี้เราจะใช้การโหลดรูปภาพแบบ Lazy Loading
การนำรูปภาพไปใช้โหลดแบบขี้เกียจ
หากคุณตรวจสอบแท็บเครือข่ายขณะที่คุณเลื่อนลงมา คุณจะเห็นว่าทันทีที่คุณแตะเส้นสีแดง (ขอบล่าง) การเรียก API จะเกิดขึ้น และรูปภาพทั้งหมดเริ่มโหลดแม้ว่าคุณจะไม่ได้ดู พวกเขา. มีสาเหตุหลายประการที่ทำให้พฤติกรรมนี้ไม่พึงปรารถนา เราอาจต้องการบันทึกการโทรในเครือข่ายจนกว่าผู้ใช้จะต้องการเห็นภาพ ในกรณีเช่นนี้ เราสามารถเลือกที่จะโหลดภาพ อย่างเกียจคร้าน กล่าวคือ เราจะไม่โหลดภาพจนกว่าจะเลื่อนเข้าสู่มุมมอง
เปิด src/App.js ด้านล่างฟังก์ชันการเลื่อนแบบอนันต์ ให้ป้อนรหัสต่อไปนี้
// App.js // lazy loads images with intersection observer // only swap out the image source if the new url exists const imagesRef = useRef(null); const imgObserver = useCallback(node => { const intObs = new IntersectionObserver(entries => { entries.forEach(en => { if (en.intersectionRatio > 0) { const currentImg = en.target; const newImgSrc = currentImg.dataset.src; // only swap out the image source if the new url exists if (!newImgSrc) { console.error('Image source is invalid'); } else { currentImg.src = newImgSrc; } intObs.unobserve(node); // detach the observer when done } }); }) intObs.observe(node); }, []); useEffect(() => { imagesRef.current = document.querySelectorAll('.card-img-top'); if (imagesRef.current) { imagesRef.current.forEach(img => imgObserver(img)); } }, [imgObserver, imagesRef, imgData.images]); เช่นเดียวกับ scrollObserver เรากำหนดฟังก์ชัน imgObserver ซึ่งยอมรับโหนดเพื่อสังเกต เมื่อหน้าถึงทางแยก ตามที่กำหนดโดย en.intersectionRatio > 0 เราจะสลับแหล่งที่มาของรูปภาพในองค์ประกอบ ให้สังเกตว่าในขั้นแรกเราจะตรวจสอบว่ามีแหล่งที่มาของภาพใหม่หรือไม่ก่อนที่จะทำการสลับ เช่นเดียวกับฟังก์ชัน scrollObserver เรารวม imgObserver ไว้ในเบ็ด useCallback เพื่อป้องกันการแสดงผลซ้ำของคอมโพเนนต์ที่ไม่มีวันสิ้นสุด
โปรดทราบด้วยว่าเราหยุดสังเกตองค์ประกอบ img เมื่อเราทำการแทนที่เสร็จแล้ว เราทำสิ่งนี้ด้วยวิธีที่ unobserve
ใน useEffect hook ต่อไปนี้ เราจะดึงรูปภาพทั้งหมดที่มีคลาส .card-img-top บนหน้าด้วย document.querySelectorAll จากนั้นเราวนซ้ำในแต่ละภาพและตั้งผู้สังเกตการณ์ไว้
โปรดทราบว่าเราได้เพิ่ม imgData.images เป็นการขึ้นต่อกันของ useEffect hook เมื่อการเปลี่ยนแปลงนี้จะทริกเกอร์ useEffect hook และในทางกลับกัน imgObserver จะถูกเรียกด้วย <img className='card-img-top'> แต่ละองค์ประกอบ
อัปเดตองค์ประกอบ <img className='card-img-top'/> ดังที่แสดงด้านล่าง
<img alt={author} data-src={download_url} className="card-img-top" src={'https://picsum.photos/id/870/300/300?grayscale&blur=2'} /> เราตั้งค่าแหล่งที่มาเริ่มต้นสำหรับทุก <img className='card-img-top'/> และเก็บภาพที่เราต้องการแสดงบนคุณสมบัติ data-src รูปภาพเริ่มต้นมักจะมีขนาดที่เล็ก เราจึงดาวน์โหลดให้น้อยที่สุด เมื่อองค์ประกอบ <img/> ปรากฏขึ้น ค่าในคุณสมบัติ data-src จะแทนที่รูปภาพเริ่มต้น
ในภาพด้านล่าง เราจะเห็นรูปประภาคารเริ่มต้นที่ยังคงแสดงอยู่ในพื้นที่บางส่วน

สาขาที่เกี่ยวข้อง ณ จุดนี้คือ 03-lazy-loading
ตอนนี้เรามาดูกันว่าเราจะสรุปฟังก์ชันเหล่านี้ทั้งหมดอย่างไรเพื่อให้กลับมาใช้ใหม่ได้
Abstracting Fetch, Infinite Scroll และ Lazy Loading Into Custom Hooks
เราได้นำการดึงข้อมูล การเลื่อนไม่สิ้นสุด และการโหลดภาพแบบ Lazy Loading มาใช้สำเร็จแล้ว เราอาจมีส่วนประกอบอื่นในแอปพลิเคชันของเราที่ต้องการฟังก์ชันที่คล้ายคลึงกัน ในกรณีนั้น เราสามารถสรุปและนำฟังก์ชันเหล่านี้กลับมาใช้ใหม่ได้ สิ่งที่เราต้องทำคือย้ายไฟล์เหล่านั้นไปไว้ในไฟล์แยกต่างหากและนำเข้าในที่ที่เราต้องการ เราต้องการเปลี่ยนให้เป็น Custom Hooks
เอกสาร React กำหนด Custom Hook เป็นฟังก์ชัน JavaScript ที่มีชื่อขึ้นต้นด้วย "use" และอาจเรียก hook อื่น ๆ ในกรณีของเรา เราต้องการสร้าง hooks สามตัว useFetch useInfiniteScroll useLazyLoading
สร้างไฟล์ภายในโฟลเดอร์ src/ customHooks.js และวางโค้ดด้านล่างด้านใน
// customHooks.js import { useEffect, useCallback, useRef } from 'react'; // make API calls and pass the returned data via dispatch export const useFetch = (data, dispatch) => { useEffect(() => { dispatch({ type: 'FETCHING_IMAGES', fetching: true }); fetch(`https://picsum.photos/v2/list?page=${data.page}&limit=10`) .then(data => data.json()) .then(images => { dispatch({ type: 'STACK_IMAGES', images }); dispatch({ type: 'FETCHING_IMAGES', fetching: false }); }) .catch(e => { dispatch({ type: 'FETCHING_IMAGES', fetching: false }); return e; }) }, [dispatch, data.page]) } // next code block here ตะขอ useFetch ยอมรับฟังก์ชันการจัดส่งและออบเจ็กต์ข้อมูล ฟังก์ชันการจัดส่งจะส่งข้อมูลจากการเรียก API ไปยังคอมโพเนนต์ของ App ในขณะที่อ็อบเจ็กต์ข้อมูลช่วยให้เราอัปเดต URL ปลายทางของ API
// infinite scrolling with intersection observer export const useInfiniteScroll = (scrollRef, dispatch) => { const scrollObserver = useCallback( node => { new IntersectionObserver(entries => { entries.forEach(en => { if (en.intersectionRatio > 0) { dispatch({ type: 'ADVANCE_PAGE' }); } }); }).observe(node); }, [dispatch] ); useEffect(() => { if (scrollRef.current) { scrollObserver(scrollRef.current); } }, [scrollObserver, scrollRef]); } // next code block here ตะขอ useInfiniteScroll ยอมรับ scrollRef และฟังก์ชันการสั่ง dispatch scrollRef ช่วยเราตั้งค่าผู้สังเกตการณ์ ตามที่กล่าวไปแล้วในส่วนที่เราใช้งาน ฟังก์ชันการจัดส่งช่วยให้สามารถทริกเกอร์การดำเนินการที่อัปเดตหมายเลขหน้าใน URL ปลายทางของ API
// lazy load images with intersection observer export const useLazyLoading = (imgSelector, items) => { const imgObserver = useCallback(node => { const intObs = new IntersectionObserver(entries => { entries.forEach(en => { if (en.intersectionRatio > 0) { const currentImg = en.target; const newImgSrc = currentImg.dataset.src; // only swap out the image source if the new url exists if (!newImgSrc) { console.error('Image source is invalid'); } else { currentImg.src = newImgSrc; } intObs.unobserve(node); // detach the observer when done } }); }) intObs.observe(node); }, []); const imagesRef = useRef(null); useEffect(() => { imagesRef.current = document.querySelectorAll(imgSelector); if (imagesRef.current) { imagesRef.current.forEach(img => imgObserver(img)); } }, [imgObserver, imagesRef, imgSelector, items]) } เบ็ด useLazyLoading ได้รับตัวเลือกและอาร์เรย์ ตัวเลือกใช้สำหรับค้นหาภาพ การเปลี่ยนแปลงใดๆ ในอาร์เรย์จะทริกเกอร์ useEffect hook ที่ตั้งค่าผู้สังเกตการณ์ในแต่ละภาพ
เราจะเห็นว่ามันเป็นฟังก์ชันเดียวกันกับที่เรามีใน src/App.js ที่เราได้แตกไฟล์ไปยังไฟล์ใหม่ สิ่งที่ดีในตอนนี้คือเราสามารถส่งผ่านอาร์กิวเมนต์แบบไดนามิกได้ ลองใช้ hooks แบบกำหนดเองเหล่านี้ในองค์ประกอบแอปกัน
เปิด src/App.js นำเข้า hooks ที่กำหนดเองและลบฟังก์ชันที่เรากำหนดไว้สำหรับการดึงข้อมูล การเลื่อนแบบไม่จำกัด และการโหลดรูปภาพแบบ Lazy Loading ปล่อยให้ตัวลดขนาดและส่วนที่เราใช้ประโยชน์จาก useReducer วางในรหัสด้านล่าง
// App.js // import custom hooks import { useFetch, useInfiniteScroll, useLazyLoading } from './customHooks' const imgReducer = (state, action) => { ... } // retain this const pageReducer = (state, action) => { ... } // retain this const [pager, pagerDispatch] = useReducer(pageReducer, { page: 0 }) // retain this const [imgData, imgDispatch] = useReducer(imgReducer,{ images:[], fetching: true }) // retain this let bottomBoundaryRef = useRef(null); useFetch(pager, imgDispatch); useLazyLoading('.card-img-top', imgData.images) useInfiniteScroll(bottomBoundaryRef, pagerDispatch); // retain the return block return ( ... ) เราได้พูดคุยเกี่ยวกับ bottomBoundaryRef ในส่วนที่เลื่อนไม่สิ้นสุด เราส่งวัตถุ pager และฟังก์ชัน imgDispatch เพื่อ useFetch useLazyLoading ยอมรับชื่อคลาส .card-img-top หมายเหตุ . รวมอยู่ในชื่อคลาส โดยการทำเช่นนี้ เราไม่จำเป็นต้องระบุ document.querySelectorAll useInfiniteScroll ยอมรับทั้ง ref และฟังก์ชัน dispatch เพื่อเพิ่มค่าของ page
สาขาที่เกี่ยวข้อง ณ จุดนี้คือ 04-custom-hooks
บทสรุป
HTML เริ่มดีขึ้นในการจัดหา API ที่ดีสำหรับการนำคุณสมบัติเจ๋ง ๆ ไปใช้ ในโพสต์นี้ เราได้เห็นแล้วว่าการใช้ตัวสังเกตทางแยกในองค์ประกอบการทำงาน React นั้นง่ายเพียงใด ในกระบวนการนี้ เราได้เรียนรู้วิธีการใช้ hooks ของ React และวิธีเขียน hook ของเราเอง
ทรัพยากร
- “Infinite Scroll + Image Lazy Loading” Orji Chidi Matthew, GitHub
- “การเลื่อนแบบไม่มีที่สิ้นสุด การแบ่งหน้า หรือปุ่ม “โหลดเพิ่มเติม”? การค้นพบความสามารถในการใช้งานในอีคอมเมิร์ซ” Christian Holst, Smashing Magazine
- “Lorem Picsum,” David Marby & Nijiko Yonskai
- “IntersectionObserver's Coming View” Surma ข้อมูลพื้นฐานเกี่ยวกับเว็บ
- ฉันขอใช้
IntersectionObserver - “Intersection Observer API,” เอกสารเว็บ MDN
- “ส่วนประกอบและอุปกรณ์ประกอบฉาก” React
- “
useCallback” ตอบสนอง - “
useReducer” ตอบโต้
