membuat carousel di react menggunakan hooks

Membuat Carousel di React Menggunakan Hooks

Pada artikel ini, kita akan melihat implementasi carousel yang mencoba menyederhanakan keterikatan dengan menggunakan React Hooks.

Salah satu masalah dalam pengembangan web saat ini adalah keterikatan berbagai lapisan. Kita tidak hanya menghadapi keterikatan yang kuat pada beberapa ketergantungan, tetapi kita juga menyambungkan kode logika secara langsung ke beberapa lapisan gaya atau presentasi.

Hasil akhirnya mungkin masih lebih mudah untuk digunakan kembali daripada kode serupa beberapa tahun yang lalu, tetapi jelas lebih sulit untuk digunakan kembali daripada yang seharusnya.

Implementasi Carousel di React

Melihat situasi implementasi carousel yang tersedia di dalam React dapat menjadi hal yang menakutkan. Ada cukup banyak, dan masing-masing memberikan janji yang berbeda. Ada banyak yang sudah cukup tua, sementara yang lain sangat populer, dan ada juga yang memiliki banyak dependensi. Namun, kesamaan yang mereka miliki adalah bahwa mereka memiliki pendapat tentang presentasi dan gaya.

Dalam kasus kami, kami tidak menginginkan hal itu. Kami sudah memiliki gaya dalam pikiran, dan kami ingin menggunakan kembali carousel yang sama dengan gaya yang berbeda – tidak hanya memilih, misalnya, warna yang berbeda untuk beberapa anak panah, tetapi pada kenyataannya, kami memilih apakah kami menggunakan anak panah atau tidak. Idealnya, seluruh penggunaan komponen terserah kepada pengguna. Pada akhirnya, kami memutuskan untuk melakukan implementasi carousel sendiri dengan menggunakan React Hooks.

Apa itu React Hooks?

React Hooks telah diperkenalkan untuk menyederhanakan penggunaan ulang kode. Salah satu alasan mengapa tim React memperkenalkan Hooks adalah untuk menyingkirkan komponen kelas, yang membutuhkan tingkat pengetahuan yang lebih tinggi dalam JavaScript, ditambah lagi dengan risiko bug yang lebih tinggi. Alasan utamanya adalah pemahaman yang benar tentang hal ini dalam JavaScript, yang sama sekali tidak intuitif bagi orang-orang yang berasal dari bahasa lain.

Dalam JavaScript, hal ini terikat konteks dan bukan terikat pada contoh. Jika, misalnya, sebuah metode diteruskan sebagai pemanggilan balik, maka metode tersebut akan kehilangan konteksnya. Jika metode tersebut kemudian dipanggil seperti fungsi, konteksnya tidak akan terdefinisi.

Oleh karena itu, untuk menghindari skenario ini, konteks ini harus ditangkap di dalam metode. Hal ini dapat dilakukan dengan membungkus metode (() => f()), menggunakan field dengan fungsi panah sebagai gantinya (f = () => {}), atau menggunakan versi terikat dari metode tersebut dengan menggunakan bind (f = f.bind(this)).

Alasan lain untuk memperkenalkan Hooks adalah kemampuan untuk menggunakan kembali kode yang berhubungan dengan state dan lifecycle komponen dengan lebih mudah. Sebelumnya, kita memiliki mixins untuk komponen kelas React, tetapi mereka memiliki beberapa masalah dan lebih banyak merugikan daripada menguntungkan.

Masalah utama di sini adalah mixin masih beroperasi pada fungsi lifecycle yang berbeda secara individual. Mixin juga hanya beroperasi di dalam instance komponen kelas, yang berarti probabilitas bahwa mixin yang berbeda menginjak satu sama lain (misalnya, dengan menimpa variabel) cukup tinggi.

Dengan menggunakan React Hooks, kita dapat memisahkan perilaku yang rumit dari representasinya dengan mudah. Hasilnya, kode akan terlihat seperti ini:

const MyCarousel = ({ slideTime }) => {
  const carouselBehavior = useCarousel(slideTime);
  return <div className="my-carousel">...</div>;
};

Meskipun ada berbagai macam Hooks inti, yang paling menarik adalah useState (membuat atau mendapatkan state cell) dan useEffect (memberikan kita kemampuan untuk mengeksekusi efek samping tergantung pada beberapa kondisi). Ketika state menjadi rumit, useReducer mungkin juga berguna.

Aliran (atau siklus hidup) Hooks dapat diringkas dengan baik oleh diagram berikut:

react hook flow diagram

React Hooks adalah fungsi sederhana yang bekerja bersama dengan dispatcher React. Dengan demikian, fungsi-fungsi tersebut harus dipanggil pada saat rendering (dari komponen yang bersangkutan), dan harus muncul dalam urutan yang sama. Salah satu konsekuensinya adalah React Hooks tidak boleh berada di dalam sebuah kondisi atau perulangan. Selain itu, Hooks hanya dapat digunakan oleh komponen fungsional.

Memahami kondisi penting dari sebuah carousel

Carousel adalah komponen UI yang menggunakan satu tampilan untuk menampilkan beberapa item. Item ditampilkan dalam tampilan dengan rotasi. Beberapa carousel memungkinkan rotasi dipicu oleh waktu; yang lain memungkinkan interaksi pengguna dengan poin-poin (navigasi bebas) atau panah (maju atau mundur). Pada ponsel, pola yang populer adalah menggeser untuk maju atau mundur seperti berikut:

contoh carousel react

Dengan demikian, state Carousel dapat dituliskan sebagai:

const [current, setCurrent] = React.useState(0);

Hasil pemanggilan Hook useState dengan nilai awal adalah sebuah tuple (yaitu sebuah larik dengan jumlah item yang tetap) yang berisi nilai saat ini dan callback untuk mengubah nilai saat ini. Di sini, tuple menyederhanakan penamaan khusus untuk kita.

Jika kita ingin melakukan rotasi otomatis setelah waktu tertentu (waktu, diberikan dalam milidetik), kita dapat melakukannya dengan berikut ini:

React.useEffect(() => {
  const next = (current + 1) % slides.length;
  const id = setTimeout(() => setCurrent(next), time);
  return () => clearTimeout(id);
}, [current]);

Jumlah slide ditentukan oleh slides.length. Karena operasi modulo, kami memastikan bahwa slide saat ini selalu berada di antara 0 (inklusif) dan jumlah slide (eksklusif).

Menariknya, kita dapat menggunakan argumen kedua dari useEffect untuk menentukan kapan efek samping harus dipicu. Dengan mengatur sebuah array ke current, kita memberitahu React untuk membuang efek sebelumnya (secara efektif memanggil clearTimeout), jika ada, dan menjalankannya lagi.

Secara alami, kita mengatur ulang waktu pada interaksi pengguna secara manual (ke mana saja, misalnya, maju) dan memiliki efek yang mirip dengan setInterval, tetapi lebih mudah untuk dikontrol dan jauh lebih sesuai dengan ide inti dari React Hooks.

Perilaku Carousel yang diinginkan

Carousel kita harus mampu berputar otomatis. Untuk itu, kita memerlukan efek seperti yang telah diperkenalkan sebelumnya. Namun demikian, sebagai tambahan, pengguna harus dapat menyeret slide saat ini ke depan atau ke belakang. Ini semua harus berjalan dengan lancar, diberdayakan oleh beberapa animasi CSS. Apabila pengguna mulai menyeret, rotasi otomatis harus diatur ulang.

Untuk membedakan antara mode yang berbeda, kami memperkenalkan variabel status berikut ini, yang dalam banyak kasus ditetapkan secara bersama-sama:

const initialCarouselState = {
  offset: 0,
  desired: 0,
  active: 0
};

Offset relevan untuk mengelola upaya penyeretan pengguna saat ini. Demikian juga, diinginkan dan aktif diperlukan untuk menunjukkan slide yang sedang aktif versus slide yang sebenarnya ingin kita tuju. Keduanya berbeda jika terjadi transisi yang sedang berlangsung.

Persyaratan kita dengan menyeret dan menggulir halus mengharuskan kita untuk tidak memiliki N slide (atau “gambar”) dalam rotasi, tetapi sebenarnya N + 2. Apa yang kita perlukan di bawah tenda akan terlihat seperti ini:

image rotation diagram

Meskipun kita mulai dari slide pertama yang biasa, kita harus menyisipkan satu slide sebelumnya (indeks nyata 0, mengacu pada slide ke-N terakhir). Slide semu ini akan digunakan saat kita menggeser ke kiri atau akan ke kiri. Namun perlu diperhatikan bahwa setelah kita mencapai slide ini, kita akan mengatur ulang offset ke slide yang sebenarnya (tanpa transisi).

Setelah kita berada “di dalam” deck slide, tidak ada masalah untuk maju atau mundur:

inside slide deck

Masalah yang sama seperti pada slide pertama, juga dapat dilihat pada slide terakhir. Dalam kasus ini, bukan mundur (menggeser ke kanan) yang menjadi masalah, tetapi maju (menggeser ke kiri). Sekali lagi, solusi kita adalah menyisipkan slide semu (indeks nyata N+1), kali ini mengacu pada slide pertama.

ending last slide

Perlu diingat bahwa wadah yang terlihat akan diatur ke overflow: hidden, wadah bagian dalam akan meluas melampaui layar. Dengan demikian, lebar wadah ini sebenarnya adalah (N + 2) * 100% sehubungan dengan wadah yang terlihat (carousel).

Namun demikian, transisi di dalam wadah bagian dalam mengacu ke lebar wadah bagian dalam. Dengan demikian, meskipun lebar wadah bagian dalam mungkin, misalnya, 500% (untuk tiga slide), terjemahan dari satu slide ke slide lainnya akan selalu kurang dari 100%. Karena jumlah minimum slide adalah tiga (satu slide nyata dengan dua slide semu – mengacu pada slide yang sama), maka ukuran maksimum terjemahan adalah 33 persen. Untuk delapan slide nyata (yaitu, total 10 slide), kita mendapatkan pergeseran di antara transisi sebesar 10 persen.

Implementasi State Carousel Menggunakan React Hooks

Karena variabel state digunakan secara bersama-sama, kita harus menggunakan Hook useReducer. Implementasi yang mungkin berdasarkan carousel state seperti yang dijelaskan sebelumnya terlihat seperti ini:

function carouselReducer(state, action) {
  switch (action.type) {
    case "jump":
      return {
        ...state,
        desired: action.desired
      };
    case "next":
      return {
        ...state,
        desired: next(action.length, state.active)
      };
    case "prev":
      return {
        ...state,
        desired: previous(action.length, state.active)
      };
    case "done":
      return {
        ...state,
        offset: NaN,
        active: state.desired
      };
    case "drag":
      return {
        ...state,
        offset: action.offset
      };
    default:
      return state;
  }
}

Selanjutnya, gunakan carouselReducer:

const [state, dispatch] = useReducer(carouselReducer, initialCarouselState);

Memperkenalkan gerakan sentuh tingkat lanjut (menggesek) dapat dilakukan melalui library (react-swipeable). Pustaka ini sudah memberi kita Hook:

const handlers = useSwipeable({
  onSwiping(e) {
    dispatch({
      type: "drag",
      offset: -e.deltaX
    });
  },
  onSwipedLeft(e) {
    const t = threshold(e.event.target);

    if (e.deltaX >= t) {
      dispatch({
        type: "next",
        length
      });
    } else {
      dispatch({
        type: "drag",
        offset: 0
      });
    }
  },
  onSwipedRight(e) {
    const t = threshold(e.event.target);

    if (-e.deltaX >= t) {
      dispatch({
        type: "prev",
        length
      });
    } else {
      dispatch({
        type: "drag",
        offset: 0
      });
    }
  },
  trackMouse: true,
  trackTouch: true
});

Nilai yang dikembalikan adalah penangan yang dapat dilampirkan ke wadah apa pun untuk mengikuti operasi seret. Nilai threshold dapat diatur ke nilai berapa pun. Dalam implementasi ini, kami menetapkannya menjadi sepertiga dari lebar kontainer (diperoleh melalui e.event.target).

Dengan kata lain, pada kode sebelumnya, kita membedakan antara kasus-kasus berikut:

  • Operasi drag sedang berlangsung dan kita perlu merefleksikan kemajuan saat ini dalam state
  • Operasi drag berhasil diselesaikan dan kita perlu pergi ke slide berikutnya atau sebelumnya
  • Operasi drag selesai tanpa berhasil – sekarang kita harus mengatur ulang offset

Seluruh mesin state dibantu oleh useEffect untuk mendapatkan waktu yang tepat:

useEffect(() => {
  const id = setTimeout(() => dispatch({ type: "next", length }), interval);
  return () => clearTimeout(id);
}, [state.offset, state.active]);

useEffect(() => {
  const id = setTimeout(() => dispatch({ type: "done" }), transitionTime);
  return () => clearTimeout(id);
}, [state.desired]);

Seperti yang telah disebutkan sebelumnya, useEffect pertama bertanggung jawab untuk rotasi otomatis. Satu-satunya perbedaan pada kode yang disajikan sebelumnya adalah penggunaan ketergantungan lain untuk memicu/membuang rotasi. Karena persyaratan kami, kami juga memperkenalkan offset. Dengan demikian, jika operasi menyeret sedang berlangsung, kita tidak akan memicu rotasi otomatis.

UseEffect kedua akan diperlukan untuk akhirnya mengatur status aktif ke status yang diinginkan. Karena kita menggunakan transisi CSS, kita tidak mengontrol transisi dari JS. Oleh karena itu, timeout dengan waktu yang sama perlu ada untuk membantu kita.

Untuk transisi, kita mengatur konstanta berikut:

const transitionTime = 400;
const elastic = `transform ${transitionTime}ms cubic-bezier(0.68, -0.55, 0.265, 1.55)`;
const smooth = `transform ${transitionTime}ms ease`;

Transisi elastis digunakan untuk mengindikasikan “bounce-back” (memantul kembali) apabila menyeret slide saat ini tidak mencukupi untuk bergerak maju atau mundur. Transisi yang mulus adalah preferensi kita apabila kita berpindah ke slide lainnya.

Terakhir, salah satu penggunaan useCarousel Hook dapat terlihat sebagai berikut:

export const Carousel = ({ slides, interval = 5000 }) => {
  const length = slides.length;
  const [active, setActive, handlers, style] = useCarousel(length, interval);

  return (
    length > 0 && (
      <div className="carousel">
        <ol className="carousel-indicators">
          {slides.map((_, index) => (
            <li
              onClick={() => setActive(index)}
              key={index}
              className={`${active === index ? "active" : ""}`}
            />
          ))}
        </ol>
        <div className="carousel-content" {...handlers} style={style}>
          <div className="carousel-item">{slides[slides.length - 1]}</div>
          {slides.map((slide, index) => (
            <div className="carousel-item" key={index}>
              {slide}
            </div>
          ))}
          <div className="carousel-item">{slides[0]}</div>
        </div>
      </div>
    )
  );
};

Perhatikan bahwa kami memperkenalkan dua duplikat seperti yang dijelaskan di bagian perilaku; item carousel pertama (mengacu pada slide terakhir) dan item carousel terakhir (mengacu pada slide pertama) ada di sana untuk memungkinkan penarikan terus menerus, menghasilkan pengalaman berkala (seperti yang diharapkan oleh carousel, yaitu, objek bundar dengan periodisitas tertentu).

Gaya yang tepat – seperti di mana letak indikator, atau apakah kita menggunakan indikator sama sekali – sepenuhnya ditentukan oleh kita. Penyajiannya juga terpisah dari logika perilaku. Kami hanya menerima gaya yang mengatur atau menentukan logika tampilan transisi. Demikian juga, kami menerima penangan untuk dilampirkan di mana kami melihat titik interaksi.

use-carousel-hook

Tujuan utama dari artikel ini adalah untuk mengimplementasikan komponen carousel yang sangat mudah dikonfigurasi, tidak dapat diubah, dan dapat digunakan kembali yang memberikan semua kekuatan kepada pengguna untuk membuat carousel mereka sendiri. Pada saat pembaruan ini, sebuah hook baru telah diterbitkan yang memiliki semua itu dan mudah diintegrasikan dan digunakan. Pada bagian ini, kita akan melihat dengan cepat bagaimana cara menggunakan hook ini: use-carousel-hook.

use-carousel-hook adalah sebuah hook React baru yang digunakan untuk membuat carousel geser yang dapat dikonfigurasi. Sama seperti hook di atas, gaya sepenuhnya ditentukan oleh pengguna, dan hook ini juga mengembalikan fungsi-fungsi untuk diintegrasikan ke dalam slider Anda untuk memberikan fleksibilitas dan kontrol penuh terhadap carousel yang Anda buat.

Inilah fungsi-fungsi tersebut:

import { useCarousel } from 'use-carousel-hook';
const { ref, previous, next, setCurrent, reset } = useCarousel();

Ref harus dilampirkan ke wadah carousel yang berisi elemen carousel. Previous dan next untuk menavigasi ke elemen previous atau next dalam carousel. Anda juga dapat mengatur jumlah untuk mengurangi/menambah; standarnya adalah 1. setCurrent digunakan untuk melompat ke elemen tertentu dan dapat membantu ketika Anda ingin menampilkan beberapa elemen sekaligus. Dan tentu saja, reset digunakan untuk kembali ke awal carousel.

Kode di bawah ini menunjukkan fungsi-fungsi yang sedang bekerja:

 return (
        <div>
            <button onClick={() => previous()}>Previous</button>
            <button onClick={() => previous(2)}>Go back 2 items</button>
            <button onClick={() => next()}>Next</button>
            <button onClick={() => next(2)}>Go forward 2 items</button>
            <button onClick={() => reset()}>Reset</button>
            <button onClick={() => setCurrent(2)}>Set index to 2</button>
            <ul ref={ref} className="carousel__list">
                <li className="carousel__item">
                  <img src='https://picsum.photos/200' alt=''/>
                </li>
                <li className="carousel__item">
                  <img src='https://picsum.photos/201' alt=''/>
                </li>
                <li className="carousel__item">
                  <img src='https://picsum.photos/202' alt=''/>
                </li>
                <li className="carousel__item">
                  <img src='https://picsum.photos/203' alt=''/>
                </li>
            </ul>
        </div>
    );

Dan kita tambahkan styling untuk mempercantik tampilannya.

.carousel__list {
  display: flex;
  list-style: none;
  padding: 0;
  padding: 1rem 0 0;
  overflow: hidden;
  position: relative;
  width: 75vw;
  margin: 0 auto;
  max-width: 50rem;
}

.carousel__item {
  flex: 0 0 auto;
  width: 100%;
  padding: 0;
  margin: 0;
}

Seperti yang Anda lihat, library ini sangat mirip dengan apa yang kami terapkan dan memungkinkan pengguna untuk mengatur tampilan carousel sambil menyediakan fungsionalitas inti dari carousel.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top