การใช้การเลื่อนแบบไม่มีที่สิ้นสุดและการโหลดภาพแบบขี้เกียจใน React

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างย่อ ↬ ในบทช่วยสอนนี้ เราจะเรียนรู้วิธีใช้ HTML 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

รองรับเบราว์เซอร์ Intersection Observer (ตัวอย่างขนาดใหญ่)

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ 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 ตัวลดนี้จัดการสองการกระทำ

  1. การดำเนินการ STACK_IMAGES เชื่อมอาร์เรย์ images
  2. การดำเนินการ FETCHING_IMAGES สลับค่าของตัวแปร fetching ระหว่าง true และ false

ขั้นตอนต่อไปคือการต่อสายลดนี้กับตะขอ useReducer เมื่อเสร็จแล้วเราจะได้สองสิ่งกลับมา:

  1. imgData ซึ่งมีสองตัวแปร: images คืออาร์เรย์ของวัตถุรูปภาพ การ fetching เป็นบูลีนที่บอกเราว่าการเรียก API กำลังดำเนินการอยู่หรือไม่
  2. 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 ” ตอบโต้