การใช้การเลื่อนแบบไม่มีที่สิ้นสุดและการโหลดภาพแบบขี้เกียจใน 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 →useEffect
hook สำหรับการเรียกใช้การเรียกการเรียก → เรียกใช้การ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
” ตอบโต้