Membangun Aplikasi Web Dengan React, Redux, dan Sanity.io
Diterbitkan: 2022-03-10Evolusi cepat platform digital telah menempatkan batasan serius pada CMS tradisional seperti Wordpress. Platform ini digabungkan, tidak fleksibel dan terfokus pada proyek, bukan produk. Untungnya, beberapa CMS tanpa kepala telah dikembangkan untuk mengatasi tantangan ini dan banyak lagi.
Tidak seperti CMS tradisional, CMS tanpa kepala, yang dapat digambarkan sebagai Software as a Service (SaaS), dapat digunakan untuk mengembangkan situs web, aplikasi seluler, tampilan digital, dan banyak lagi. Mereka dapat digunakan pada platform tanpa batas. Jika Anda mencari CMS yang tidak bergantung pada platform, mengutamakan pengembang, dan menawarkan dukungan lintas platform, Anda tidak perlu mencari lebih jauh dari CMS tanpa kepala.
CMS tanpa kepala hanyalah CMS tanpa kepala. head
di sini mengacu pada frontend atau lapisan presentasi sedangkan body
mengacu pada backend atau repositori konten. Ini menawarkan banyak manfaat menarik. Misalnya, ini memungkinkan pengembang untuk memilih frontend pilihannya dan Anda juga dapat mendesain lapisan presentasi sesuai keinginan.
Ada banyak CMS tanpa kepala di luar sana, beberapa yang paling populer termasuk Strapi, Contentful, Contentstack, Sanity, Butter CMS, Prismic, Storyblok, Directus, dll. CMS tanpa kepala ini berbasis API dan memiliki keunggulan masing-masing. Misalnya, CMS seperti Sanity, Strapi, Contentful, dan Storyblok gratis untuk proyek kecil.
CMS tanpa kepala ini juga didasarkan pada tumpukan teknologi yang berbeda. Sementara Sanity.io didasarkan pada React.js, Storyblok didasarkan pada Vue.js. Sebagai pengembang React, inilah alasan utama mengapa saya dengan cepat tertarik pada Sanity. Namun, sebagai CMS tanpa kepala, masing-masing platform ini dapat dipasang di frontend mana pun, baik Angular, Vue, atau React.
Masing-masing CMS tanpa kepala ini memiliki paket gratis dan berbayar yang mewakili lonjakan harga yang signifikan. Meskipun paket berbayar ini menawarkan lebih banyak fitur, Anda tidak ingin membayar terlalu banyak untuk proyek kecil hingga menengah. Sanity mencoba memecahkan masalah ini dengan memperkenalkan opsi bayar sesuai pemakaian. Dengan opsi ini, Anda akan dapat membayar untuk apa yang Anda gunakan dan menghindari lonjakan harga.
Alasan lain mengapa saya memilih Sanity.io adalah bahasa GROQ mereka. Bagi saya, Sanity menonjol dari yang lain dengan menawarkan alat ini. Graphical-Relational Object Query (GROQ) mengurangi waktu pengembangan, membantu Anda mendapatkan konten yang Anda butuhkan dalam bentuk yang Anda butuhkan, dan juga membantu pengembang membuat dokumen dengan model konten baru tanpa perubahan kode.
Selain itu, pengembang tidak dibatasi oleh bahasa GROQ. Anda juga dapat menggunakan axios
atau bahkan aksioma tradisional dan fetch
di aplikasi React Anda untuk menanyakan backend. Seperti kebanyakan CMS tanpa kepala lainnya, Sanity memiliki dokumentasi lengkap yang berisi tips bermanfaat untuk membangun platform.
Catatan: Artikel ini membutuhkan pemahaman dasar tentang React, Redux dan CSS.
Memulai Dengan Sanity.io
Untuk menggunakan Sanity di mesin Anda, Anda harus menginstal alat Sanity CLI. Meskipun ini dapat diinstal secara lokal di proyek Anda, lebih baik menginstalnya secara global agar dapat diakses oleh aplikasi apa pun di masa mendatang.
Untuk melakukan ini, masukkan perintah berikut di terminal Anda.
npm install -g @sanity/cli
Bendera -g
pada perintah di atas memungkinkan instalasi global.
Selanjutnya, kita perlu menginisialisasi Sanity di aplikasi kita. Meskipun ini dapat diinstal sebagai proyek terpisah, biasanya lebih baik untuk menginstalnya di dalam aplikasi frontend Anda (dalam hal ini Bereaksi).
Dalam blognya, Kapehe menjelaskan secara detail bagaimana mengintegrasikan Sanity dengan React. Akan sangat membantu untuk membaca artikel sebelum melanjutkan dengan tutorial ini.
Masukkan perintah berikut untuk menginisialisasi Sanity di aplikasi React Anda.
sanity init
Perintah sanity
menjadi tersedia bagi kami ketika kami menginstal alat Sanity CLI. Anda dapat melihat daftar perintah Sanity yang tersedia dengan mengetikkan sanity
atau sanity help
di terminal Anda.
Saat menyiapkan atau menginisialisasi proyek Anda, Anda harus mengikuti petunjuk untuk menyesuaikannya. Anda juga akan diminta untuk membuat kumpulan data dan Anda bahkan dapat memilih kumpulan data khusus yang diisi dengan data. Untuk aplikasi daftar ini, kami akan menggunakan dataset film fiksi ilmiah Sanity. Ini akan menyelamatkan kita dari memasukkan data sendiri.
Untuk melihat dan mengedit dataset Anda, cd
ke subdirektori Sanity di terminal Anda dan masukkan sanity start
. Ini biasanya berjalan di https://localhost:3333/
. Anda mungkin diminta untuk masuk untuk mengakses antarmuka (pastikan Anda masuk dengan akun yang sama yang Anda gunakan saat menginisialisasi proyek). Tangkapan layar lingkungan ditunjukkan di bawah ini.
Komunikasi Dua Arah Sanity-React
Sanity dan React perlu berkomunikasi satu sama lain untuk aplikasi yang berfungsi penuh.
Pengaturan Asal CORS Di Sanity Manager
Pertama-tama kita akan menghubungkan aplikasi React kita ke Sanity. Untuk melakukan ini, masuk ke https://manage.sanity.io/
dan temukan CORS origins
di bawah API Settings
di tab Settings
. Di sini, Anda harus menghubungkan asal frontend Anda ke backend Sanity. Aplikasi React kami berjalan di https://localhost:3000/
secara default, jadi kami perlu menambahkannya ke CORS.
Hal ini ditunjukkan pada gambar di bawah ini.
Menghubungkan Kewarasan Untuk Bereaksi
Sanity mengaitkan project ID
ke setiap proyek yang Anda buat. ID ini diperlukan saat menghubungkannya ke aplikasi frontend Anda. Anda dapat menemukan ID proyek di Manajer Sanity Anda.
Backend berkomunikasi dengan React menggunakan library yang dikenal sebagai sanity client
. Anda perlu menginstal perpustakaan ini di proyek Sanity Anda dengan memasukkan perintah berikut.
npm install @sanity/client
Buat file sanitySetup.js
(nama file tidak masalah), di folder src
proyek Anda dan masukkan kode React berikut untuk mengatur koneksi antara Sanity dan React.
import sanityClient from "@sanity/client" export default sanityClient({ projectId: PROJECT_ID, dataset: DATASET_NAME, useCdn: true });
Kami meneruskan projectId
kami, dataset name
set data, dan boolean useCdn
ke instance klien sanity yang diimpor dari @sanity/client
. Ini bekerja dengan ajaib dan menghubungkan aplikasi kami ke backend.
Sekarang setelah kita menyelesaikan koneksi dua arah, mari langsung masuk untuk membangun proyek kita.
Menyiapkan Dan Menghubungkan Redux Ke Aplikasi Kami
Kami memerlukan beberapa dependensi untuk bekerja dengan Redux di aplikasi React kami. Buka terminal Anda di lingkungan React Anda dan masukkan perintah bash berikut.
npm install redux react-redux redux-thunk
Redux adalah perpustakaan manajemen keadaan global yang dapat digunakan dengan sebagian besar kerangka kerja frontend dan perpustakaan seperti React. Namun, kami memerlukan alat perantara react-redux
untuk mengaktifkan komunikasi antara toko Redux kami dan aplikasi React kami. Redux thunk akan membantu kita mengembalikan fungsi alih-alih objek tindakan dari Redux.
Meskipun kita dapat menulis seluruh alur kerja Redux dalam satu file, seringkali lebih rapi dan lebih baik untuk memisahkan masalah kita. Untuk ini, kita akan membagi alur kerja kita menjadi tiga file yaitu, actions
, reducers
, dan kemudian store
. Namun, kami juga memerlukan file terpisah untuk menyimpan action types
, juga dikenal sebagai constants
.
Menyiapkan Toko
Toko adalah file terpenting di Redux. Ini mengatur dan mengemas status dan mengirimkannya ke aplikasi React kami.
Berikut adalah pengaturan awal toko Redux kami yang diperlukan untuk menghubungkan alur kerja Redux kami.
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/"; export default createStore( reducers, applyMiddleware(thunk) );
Fungsi createStore
dalam file ini mengambil tiga parameter: reducer
(diperlukan), status awal dan penambah (biasanya middleware, dalam hal ini, thunk
disediakan melalui applyMiddleware
). Pereduksi kami akan disimpan dalam folder reducers
dan kami akan menggabungkan dan mengekspornya dalam file index.js
di folder reducers
. Ini adalah file yang kami impor dalam kode di atas. Kami akan meninjau kembali file ini nanti.
Pengantar Bahasa GROQ Sanity
Sanity membuat kueri pada data JSON selangkah lebih maju dengan memperkenalkan GROQ. GROQ adalah singkatan dari Graph-Relational Object Query. Menurut Sanity.io, GROQ adalah bahasa kueri deklaratif yang dirancang untuk menanyakan koleksi sebagian besar dokumen JSON tanpa skema.
Sanity bahkan menyediakan GROQ Playground untuk membantu pengembang agar terbiasa dengan bahasa tersebut. Namun, untuk mengakses taman bermain, Anda perlu menginstal sanity vision . Jalankan sanity install @sanity/vision
di terminal Anda untuk menginstalnya.
GROQ memiliki sintaks yang mirip dengan GraphQL tetapi lebih ringkas dan lebih mudah dibaca. Selain itu, tidak seperti GraphQL, GROQ dapat digunakan untuk mengkueri data JSON.
Misalnya, untuk mengambil setiap item dalam dokumen film kami, kami akan menggunakan sintaks GROQ berikut.
*[_type == "movie"]
Namun, jika kami ingin mengambil hanya _ids
dan crewMembers
dalam dokumen film kami. Kita perlu menentukan bidang-bidang tersebut sebagai berikut.
`*[_type == 'movie']{ _id, crewMembers }
Di sini, kami menggunakan *
untuk memberi tahu GROQ bahwa kami menginginkan setiap dokumen _type
movie. _type
adalah atribut di bawah koleksi film. Kami juga dapat mengembalikan tipe seperti yang kami lakukan pada _id
dan crewMembers
sebagai berikut:
*[_type == 'movie']{ _id, _type, crewMembers }
Kami akan bekerja lebih banyak di GROQ dengan menerapkannya dalam tindakan Redux kami, tetapi Anda dapat memeriksa dokumentasi Sanity.io untuk GROQ untuk mempelajari lebih lanjut tentangnya. Lembar contekan kueri GROQ menyediakan banyak contoh untuk membantu Anda menguasai bahasa kueri.
Menyiapkan Konstanta
Kami membutuhkan konstanta untuk melacak jenis tindakan di setiap tahap alur kerja Redux. Konstanta membantu menentukan jenis tindakan yang dikirim pada setiap titik waktu. Misalnya, kami dapat melacak saat API sedang dimuat, terisi penuh, dan saat terjadi kesalahan.
Kami tidak perlu mendefinisikan konstanta dalam file terpisah tetapi untuk kesederhanaan dan kejelasan, ini biasanya merupakan praktik terbaik di Redux.
Dengan konvensi, konstanta dalam Javascript didefinisikan dengan huruf besar. Kami akan mengikuti praktik terbaik di sini untuk menentukan konstanta kami. Berikut adalah contoh konstanta untuk menunjukkan permintaan untuk pengambilan film bergerak.
export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST";
Di sini, kami membuat MOVIE_FETCH_REQUEST
konstan yang menunjukkan jenis tindakan MOVIE_FETCH_REQUEST
. Ini membantu kita untuk dengan mudah memanggil jenis tindakan ini tanpa menggunakan strings
dan menghindari bug. Kami juga mengekspor konstanta agar tersedia di mana saja di proyek kami.
Demikian pula, kita dapat membuat konstanta lain untuk mengambil jenis tindakan yang menunjukkan saat permintaan berhasil atau gagal. Kode lengkap untuk movieConstants.js
diberikan dalam kode di bawah ini.
export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST"; export const MOVIE_FETCH_SUCCESS = "MOVIE_FETCH_SUCCESS"; export const MOVIE_FETCH_FAIL = "MOVIE_FETCH_FAIL"; export const MOVIES_FETCH_REQUEST = "MOVIES_FETCH_REQUEST"; export const MOVIES_FETCH_SUCCESS = "MOVIES_FETCH_SUCCESS"; export const MOVIES_FETCH_FAIL = "MOVIES_FETCH_FAIL"; export const MOVIES_FETCH_RESET = "MOVIES_FETCH_RESET"; export const MOVIES_REF_FETCH_REQUEST = "MOVIES_REF_FETCH_REQUEST"; export const MOVIES_REF_FETCH_SUCCESS = "MOVIES_REF_FETCH_SUCCESS"; export const MOVIES_REF_FETCH_FAIL = "MOVIES_REF_FETCH_FAIL"; export const MOVIES_SORT_REQUEST = "MOVIES_SORT_REQUEST"; export const MOVIES_SORT_SUCCESS = "MOVIES_SORT_SUCCESS"; export const MOVIES_SORT_FAIL = "MOVIES_SORT_FAIL"; export const MOVIES_MOST_POPULAR_REQUEST = "MOVIES_MOST_POPULAR_REQUEST"; export const MOVIES_MOST_POPULAR_SUCCESS = "MOVIES_MOST_POPULAR_SUCCESS"; export const MOVIES_MOST_POPULAR_FAIL = "MOVIES_MOST_POPULAR_FAIL";
Di sini kami telah menetapkan beberapa konstanta untuk mengambil film atau daftar film, menyortir dan mengambil film paling populer. Perhatikan bahwa kita menetapkan konstanta untuk menentukan kapan permintaan loading
, successful
dan failed
.
Demikian pula, file personConstants.js
kami diberikan di bawah ini:
export const PERSONS_FETCH_REQUEST = "PERSONS_FETCH_REQUEST"; export const PERSONS_FETCH_SUCCESS = "PERSONS_FETCH_SUCCESS"; export const PERSONS_FETCH_FAIL = "PERSONS_FETCH_FAIL"; export const PERSON_FETCH_REQUEST = "PERSON_FETCH_REQUEST"; export const PERSON_FETCH_SUCCESS = "PERSON_FETCH_SUCCESS"; export const PERSON_FETCH_FAIL = "PERSON_FETCH_FAIL"; export const PERSONS_COUNT = "PERSONS_COUNT";
Seperti movieConstants.js
, kami menetapkan daftar konstanta untuk mengambil seseorang atau beberapa orang. Kami juga menetapkan konstanta untuk menghitung orang. Konstanta mengikuti konvensi yang dijelaskan untuk movieConstants.js
dan kami juga mengekspornya agar dapat diakses ke bagian lain dari aplikasi kami.
Terakhir, kami akan menerapkan mode terang dan gelap di aplikasi sehingga kami memiliki file konstanta lain globalConstants.js
. Mari kita lihat itu.
export const SET_LIGHT_THEME = "SET_LIGHT_THEME"; export const SET_DARK_THEME = "SET_DARK_THEME";
Di sini kami mengatur konstanta untuk menentukan kapan mode terang atau gelap dikirim. SET_LIGHT_THEME
menentukan kapan pengguna beralih ke tema terang dan SET_DARK_THEME
menentukan kapan tema gelap dipilih. Kami juga mengekspor konstanta kami seperti yang ditunjukkan.
Menyiapkan Tindakan
Dengan konvensi, tindakan kami disimpan dalam folder terpisah. Tindakan dikelompokkan menurut jenisnya. Misalnya, tindakan film kami disimpan di movieActions.js
sementara tindakan orang kami disimpan dalam file personActions.js
.
Kami juga memiliki globalActions.js
untuk menangani peralihan tema dari mode terang ke mode gelap.
Mari kita ambil semua film di moviesActions.js
.
import sanityAPI from "../../sanitySetup"; import { MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS } from "../constants/movieConstants"; const fetchAllMovies = () => async (dispatch) => { try { dispatch({ type: MOVIES_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster": poster.asset->url, } ` ); dispatch({ type: MOVIES_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_FETCH_FAIL, payload: error.message }); } };
Ingat ketika kita membuat file sanitySetup.js
untuk menghubungkan React ke backend Sanity kita? Di sini, kami mengimpor pengaturan untuk memungkinkan kami menanyakan backend kewarasan kami menggunakan GROQ. Kami juga mengimpor beberapa konstanta yang diekspor dari file movieConstants.js
di folder constants
.
Selanjutnya, kami membuat fungsi tindakan fetchAllMovies
untuk mengambil setiap film dalam koleksi kami. Sebagian besar aplikasi React tradisional menggunakan axios
atau fetch
untuk mengambil data dari backend. Tetapi sementara kami dapat menggunakan salah satu dari ini di sini, kami menggunakan GROQ
Sanity. Untuk masuk ke mode GROQ
, kita perlu memanggil fungsi sanityAPI.fetch()
seperti yang ditunjukkan pada kode di atas. Di sini, sanityAPI
adalah koneksi React-Sanity yang kita atur sebelumnya. Ini mengembalikan Promise
dan karenanya harus dipanggil secara tidak sinkron. Kami telah menggunakan sintaks async-await
di sini, tetapi kami juga dapat menggunakan sintaks .then
.
Karena kami menggunakan thunk
dalam aplikasi kami, kami dapat mengembalikan fungsi alih-alih objek tindakan. Namun, kami memilih untuk meneruskan pernyataan pengembalian dalam satu baris.
const fetchAllMovies = () => async (dispatch) => { ... }
Perhatikan bahwa kita juga dapat menulis fungsi dengan cara ini:
const fetchAllMovies = () => { return async (dispatch)=>{ ... } }
Secara umum, untuk mengambil semua film, pertama-tama kami mengirimkan jenis tindakan yang melacak saat permintaan masih dimuat. Kami kemudian menggunakan sintaks GROQ Sanity untuk menanyakan dokumen film secara asinkron. Kami mengambil _id
dan url poster dari data film. Kami kemudian mengembalikan muatan yang berisi data yang didapat dari API.
Demikian pula, kami dapat mengambil film berdasarkan _id
mereka, mengurutkan film, dan mendapatkan film paling populer.
Kami juga dapat mengambil film yang cocok dengan referensi orang tertentu. Kami melakukan ini di fungsi fetchMoviesByRef
.
const fetchMoviesByRef = (ref) => async (dispatch) => { try { dispatch({ type: MOVIES_REF_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && (castMembers[person._ref match '${ref}'] || crewMembers[person._ref match '${ref}']) ]{ _id, "poster" : poster.asset->url, title } ` ); dispatch({ type: MOVIES_REF_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_REF_FETCH_FAIL, payload: error.message }); } };
Fungsi ini mengambil argumen dan memeriksa apakah person._ref
baik di castMembers
atau crewMembers
cocok dengan argumen yang diteruskan. Kami mengembalikan film _id
, poster url
, dan title
bersama. Kami juga mengirimkan tindakan jenis MOVIES_REF_FETCH_SUCCESS
, melampirkan muatan data yang dikembalikan, dan jika terjadi kesalahan, kami mengirimkan tindakan jenis MOVIE_REF_FETCH_FAIL
, melampirkan muatan pesan kesalahan, berkat pembungkus try-catch
.
Dalam fungsi fetchMovieById
, kami menggunakan GROQ
untuk mengambil film yang cocok dengan id
tertentu yang diteruskan ke fungsi tersebut.
Sintaks GROQ
untuk fungsi tersebut ditunjukkan di bawah ini.
const data = await sanityAPI.fetch( `*[_type == 'movie' && _id == '${id}']{ _id, "cast" : castMembers[]{ "ref": person._ref, characterName, "name": person->name, "image": person->image.asset->url } , "crew" : crewMembers[]{ "ref": person._ref, department, job, "name": person->name, "image": person->image.asset->url } , "overview": { "text": overview[0].children[0].text }, popularity, "poster" : poster.asset->url, releaseDate, title }[0]` );
Seperti tindakan fetchAllMovies
, kami memulai dengan memilih semua dokumen dari jenis movie
tetapi kami melangkah lebih jauh untuk memilih hanya dokumen dengan id yang disediakan untuk fungsi tersebut. Karena kami bermaksud untuk menampilkan banyak detail untuk film, kami menentukan banyak atribut untuk diambil.
Kami mengambil id
film dan juga beberapa atribut dalam array castMembers
yaitu ref
, characterName
, nama orang, dan gambar orang. Kami juga mengubah alias dari castMembers
menjadi cast
.
Seperti castMembers
, kami memilih beberapa atribut dari array crewMembers
, yaitu ref
, department
, job
, nama orang dan citra orang tersebut. kami juga mengubah alias dari crewMembers
menjadi crew
.
Dengan cara yang sama, kami memilih teks ikhtisar, popularitas, url poster film, tanggal rilis dan judul film.
Bahasa GROQ Sanity juga memungkinkan kita untuk mengurutkan dokumen. Untuk mengurutkan item, kami melewati pesanan di sebelah operator pipa .
Misalnya, jika kita ingin mengurutkan film berdasarkan releaseDate
dalam urutan menaik, kita dapat melakukan hal berikut.
const data = await sanityAPI.fetch( `*[_type == 'movie']{ ... } | order(releaseDate, asc)` );
Kami menggunakan gagasan ini dalam fungsi sortMoviesBy
untuk mengurutkan menurut urutan naik atau turun.
Mari kita lihat fungsi ini di bawah ini.
const sortMoviesBy = (item, type) => async (dispatch) => { try { dispatch({ type: MOVIES_SORT_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, title } | order( ${item} ${type})` ); dispatch({ type: MOVIES_SORT_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_SORT_FAIL, payload: error.message }); } };
Kami mulai dengan mengirimkan tindakan jenis MOVIES_SORT_REQUEST
untuk menentukan kapan permintaan dimuat. Kami kemudian menggunakan sintaks GROQ
untuk mengurutkan dan mengambil data dari koleksi movie
. Item yang akan diurutkan disediakan dalam item
variabel dan mode penyortiran (naik atau turun) disediakan dalam type
variabel. Akibatnya, kami mengembalikan id
, url poster, dan judul. Setelah data dikembalikan, kami mengirimkan tindakan jenis MOVIES_SORT_SUCCESS
dan jika gagal, kami mengirimkan tindakan jenis MOVIES_SORT_FAIL
.
Konsep GROQ
serupa berlaku untuk fungsi getMostPopular
. Sintaks GROQ
ditunjukkan di bawah ini.
const data = await sanityAPI.fetch( ` *[_type == 'movie']{ _id, "overview": { "text": overview[0].children[0].text }, "poster" : poster.asset->url, title }| order(popularity desc) [0..2]` );
Satu-satunya perbedaan di sini adalah kami mengurutkan film berdasarkan popularitas dalam urutan menurun dan kemudian memilih hanya tiga yang pertama. Item dikembalikan dalam indeks berbasis nol sehingga tiga item pertama adalah item 0, 1 dan 2. Jika kita ingin mengambil sepuluh item pertama, kita dapat meneruskan [0..9]
ke fungsi tersebut.
Berikut kode lengkap untuk aksi film di file movieActions.js
.
import sanityAPI from "../../sanitySetup"; import { MOVIE_FETCH_FAIL, MOVIE_FETCH_REQUEST, MOVIE_FETCH_SUCCESS, MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, MOVIES_SORT_REQUEST, MOVIES_SORT_SUCCESS, MOVIES_SORT_FAIL, MOVIES_MOST_POPULAR_REQUEST, MOVIES_MOST_POPULAR_SUCCESS, MOVIES_MOST_POPULAR_FAIL, MOVIES_REF_FETCH_SUCCESS, MOVIES_REF_FETCH_FAIL, MOVIES_REF_FETCH_REQUEST } from "../constants/movieConstants"; const fetchAllMovies = () => async (dispatch) => { try { dispatch({ type: MOVIES_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, } ` ); dispatch({ type: MOVIES_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_FETCH_FAIL, payload: error.message }); } }; const fetchMoviesByRef = (ref) => async (dispatch) => { try { dispatch({ type: MOVIES_REF_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && (castMembers[person._ref match '${ref}'] || crewMembers[person._ref match '${ref}']) ]{ _id, "poster" : poster.asset->url, title }` ); dispatch({ type: MOVIES_REF_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_REF_FETCH_FAIL, payload: error.message }); } }; const fetchMovieById = (id) => async (dispatch) => { try { dispatch({ type: MOVIE_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && _id == '${id}']{ _id, "cast" : castMembers[]{ "ref": person._ref, characterName, "name": person->name, "image": person->image.asset->url } , "crew" : crewMembers[]{ "ref": person._ref, department, job, "name": person->name, "image": person->image.asset->url } , "overview": { "text": overview[0].children[0].text }, popularity, "poster" : poster.asset->url, releaseDate, title }[0]` ); dispatch({ type: MOVIE_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIE_FETCH_FAIL, payload: error.message }); } }; const sortMoviesBy = (item, type) => async (dispatch) => { try { dispatch({ type: MOVIES_MOST_POPULAR_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, title } | order( ${item} ${type})` ); dispatch({ type: MOVIES_SORT_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_SORT_FAIL, payload: error.message }); } }; const getMostPopular = () => async (dispatch) => { try { dispatch({ type: MOVIES_SORT_REQUEST }); const data = await sanityAPI.fetch( ` *[_type == 'movie']{ _id, "overview": { "text": overview[0].children[0].text }, "poster" : poster.asset->url, title }| order(popularity desc) [0..2]` ); dispatch({ type: MOVIES_MOST_POPULAR_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_MOST_POPULAR_FAIL, payload: error.message }); } }; export { fetchAllMovies, fetchMovieById, sortMoviesBy, getMostPopular, fetchMoviesByRef };
Menyiapkan Peredam
Reducer adalah salah satu konsep terpenting di Redux. Mereka mengambil keadaan sebelumnya dan menentukan perubahan keadaan.
Biasanya, kita akan menggunakan pernyataan switch untuk mengeksekusi kondisi untuk setiap jenis tindakan. Misalnya, kita dapat mengembalikan loading
saat jenis tindakan menunjukkan pemuatan, dan kemudian muatan saat menunjukkan keberhasilan atau kesalahan. Diharapkan untuk mengambil initial state
dan action
sebagai argumen.
File movieReducers.js
kami berisi berbagai reduksi untuk mencocokkan tindakan yang ditentukan dalam file movieActions.js
. Namun, masing-masing reduksi memiliki sintaks dan struktur yang serupa. Satu-satunya perbedaan adalah constants
yang mereka panggil dan nilai yang mereka kembalikan.
Mari kita mulai dengan melihat fetchAllMoviesReducer
di file movieReducers.js
.
import { MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, } from "../constants/movieConstants"; const fetchAllMoviesReducer = (state = {}, action) => { switch (action.type) { case MOVIES_FETCH_REQUEST: return { loading: true }; case MOVIES_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_FETCH_FAIL: return { loading: false, error: action.payload }; case MOVIES_FETCH_RESET: return {}; default: return state; } };
Seperti semua reduksi, fetchAllMoviesReducer
mengambil objek status awal ( state
) dan objek action
sebagai argumen. Kami menggunakan pernyataan switch untuk memeriksa jenis tindakan pada setiap titik waktu. Jika sesuai dengan MOVIES_FETCH_REQUEST
, kami mengembalikan pemuatan sebagai true untuk memungkinkan kami menampilkan indikator pemuatan kepada pengguna.
Jika sesuai dengan MOVIES_FETCH_SUCCESS
, kami mematikan indikator pemuatan dan kemudian mengembalikan muatan aksi dalam movies
variabel. Tetapi jika MOVIES_FETCH_FAIL
, kami juga mematikan pemuatan dan kemudian mengembalikan kesalahan. Kami juga menginginkan opsi untuk mengatur ulang film kami. Ini akan memungkinkan kita untuk menghapus status ketika kita perlu melakukannya.
Kami memiliki struktur yang sama untuk reduksi lainnya. movieReducers.js
lengkap ditunjukkan di bawah ini.
import { MOVIE_FETCH_FAIL, MOVIE_FETCH_REQUEST, MOVIE_FETCH_SUCCESS, MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, MOVIES_SORT_REQUEST, MOVIES_SORT_SUCCESS, MOVIES_SORT_FAIL, MOVIES_MOST_POPULAR_REQUEST, MOVIES_MOST_POPULAR_SUCCESS, MOVIES_MOST_POPULAR_FAIL, MOVIES_FETCH_RESET, MOVIES_REF_FETCH_REQUEST, MOVIES_REF_FETCH_SUCCESS, MOVIES_REF_FETCH_FAIL } from "../constants/movieConstants"; const fetchAllMoviesReducer = (state = {}, action) => { switch (action.type) { case MOVIES_FETCH_REQUEST: return { loading: true }; case MOVIES_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_FETCH_FAIL: return { loading: false, error: action.payload }; case MOVIES_FETCH_RESET: return {}; default: return state; } }; const fetchMoviesByRefReducer = (state = {}, action) => { switch (action.type) { case MOVIES_REF_FETCH_REQUEST: return { loading: true }; case MOVIES_REF_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_REF_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const fetchMovieByIdReducer = (state = {}, action) => { switch (action.type) { case MOVIE_FETCH_REQUEST: return { loading: true }; case MOVIE_FETCH_SUCCESS: return { loading: false, movie: action.payload }; case MOVIE_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const sortMoviesByReducer = (state = {}, action) => { switch (action.type) { case MOVIES_SORT_REQUEST: return { loading: true }; case MOVIES_SORT_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_SORT_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const getMostPopularReducer = (state = {}, action) => { switch (action.type) { case MOVIES_MOST_POPULAR_REQUEST: return { loading: true }; case MOVIES_MOST_POPULAR_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_MOST_POPULAR_FAIL: return { loading: false, error: action.payload }; default: return state; } }; export { fetchAllMoviesReducer, fetchMovieByIdReducer, sortMoviesByReducer, getMostPopularReducer, fetchMoviesByRefReducer };
Kami juga mengikuti struktur yang sama persis untuk personReducers.js
. Misalnya, fungsi fetchAllPersonsReducer
mendefinisikan status untuk mengambil semua orang dalam database.
Ini diberikan dalam kode di bawah ini.
import { PERSONS_FETCH_FAIL, PERSONS_FETCH_REQUEST, PERSONS_FETCH_SUCCESS, } from "../constants/personConstants"; const fetchAllPersonsReducer = (state = {}, action) => { switch (action.type) { case PERSONS_FETCH_REQUEST: return { loading: true }; case PERSONS_FETCH_SUCCESS: return { loading: false, persons: action.payload }; case PERSONS_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } };
Sama seperti fetchAllMoviesReducer
, kami mendefinisikan fetchAllPersonsReducer
dengan state
dan action
sebagai argumen. Ini adalah pengaturan standar untuk reduksi Redux. Kami kemudian menggunakan pernyataan switch untuk memeriksa jenis tindakan dan jika jenisnya PERSONS_FETCH_REQUEST
, kami mengembalikan pemuatan sebagai true. Jika PERSONS_FETCH_SUCCESS
, kami menonaktifkan pemuatan dan mengembalikan muatan, dan jika PERSONS_FETCH_FAIL
, kami mengembalikan kesalahan.
Menggabungkan Peredam
Fungsi combineReducers
Redux memungkinkan kita untuk menggabungkan lebih dari satu peredam dan meneruskannya ke toko. Kami akan menggabungkan reduksi film dan orang kami dalam file index.js
di dalam folder reducers
.
Mari kita lihat itu.
import { combineReducers } from "redux"; import { fetchAllMoviesReducer, fetchMovieByIdReducer, sortMoviesByReducer, getMostPopularReducer, fetchMoviesByRefReducer } from "./movieReducers"; import { fetchAllPersonsReducer, fetchPersonByIdReducer, countPersonsReducer } from "./personReducers"; import { toggleTheme } from "./globalReducers"; export default combineReducers({ fetchAllMoviesReducer, fetchMovieByIdReducer, fetchAllPersonsReducer, fetchPersonByIdReducer, sortMoviesByReducer, getMostPopularReducer, countPersonsReducer, fetchMoviesByRefReducer, toggleTheme });
Di sini kami mengimpor semua reduksi dari film, orang, dan file reduksi global dan meneruskannya ke fungsi combineReducers
. Fungsi combineReducers
mengambil objek yang memungkinkan kita melewatkan semua reduksi kita. Kami bahkan dapat menambahkan alias ke argumen dalam proses.
Kami akan mengerjakan globalReducers
nanti.
Sekarang kita dapat melewatkan reduksi di file Redux store.js
. Ini ditunjukkan di bawah ini.
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/index"; export default createStore(reducers, initialState, applyMiddleware(thunk));
Setelah mengatur alur kerja Redux kita, mari kita siapkan aplikasi React kita.
Menyiapkan Aplikasi React Kami
Aplikasi reaksi kami akan mencantumkan film dan pemeran serta anggota kru yang sesuai. Kami akan menggunakan react-router-dom
untuk perutean dan styled-components
gaya untuk menata aplikasi. Kami juga akan menggunakan Material UI untuk ikon dan beberapa komponen UI.
Masukkan perintah bash
berikut untuk menginstal dependensi.
npm install react-router-dom @material-ui/core @material-ui/icons query-string
Inilah yang akan kita bangun:
Menghubungkan Redux Ke Aplikasi React Kami
React-redux
dikirimkan dengan fungsi Provider yang memungkinkan kita untuk menghubungkan aplikasi kita ke toko Redux. Untuk melakukan ini, kita harus memberikan instance toko ke Penyedia. Kita bisa melakukannya di file index.js
atau App.js
kita.
Ini file index.js kami.
import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import { Provider } from "react-redux"; import store from "./redux/store"; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") );
Di sini, kami mengimpor Provider
dari react-redux
dan store
dari toko Redux kami. Kemudian kami membungkus seluruh pohon komponen kami dengan Penyedia, meneruskan toko ke sana.
Selanjutnya, kita membutuhkan react-router-dom
untuk perutean di aplikasi React kita. react-router-dom
hadir dengan BrowserRouter
, Switch
dan Route
yang dapat digunakan untuk menentukan jalur dan rute kita.
Kami melakukan ini di file App.js
kami. Ini ditunjukkan di bawah ini.
import React from "react"; import Header from "./components/Header"; import Footer from "./components/Footer"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import MoviesList from "./pages/MoviesListPage"; import PersonsList from "./pages/PersonsListPage"; function App() { return ( <Router> <main className="contentwrap"> <Header /> <Switch> <Route path="/persons/"> <PersonsList /> </Route> <Route path="/" exact> <MoviesList /> </Route> </Switch> </main> <Footer /> </Router> ); } export default App;
Ini adalah pengaturan standar untuk perutean dengan react-router-dom. Anda dapat memeriksanya di dokumentasi mereka. Kami mengimpor komponen kami Header
, Footer
, PersonsList
dan MovieList
. Kami kemudian mengatur react-router-dom
dengan membungkus semuanya di Router
dan Switch
.
Karena kami ingin halaman kami berbagi header dan footer yang sama, kami harus melewati komponen <Header />
dan <Footer />
sebelum membungkus struktur dengan Switch
. Kami juga melakukan hal serupa dengan elemen main
karena kami ingin membungkus seluruh aplikasi.
Kami melewati setiap komponen ke rute menggunakan Route
dari react-router-dom
.
Mendefinisikan Halaman dan Komponen Kami
Aplikasi kami diatur secara terstruktur. Komponen yang dapat digunakan kembali disimpan di folder components
sementara Halaman disimpan di folder pages
.
pages
kami terdiri dari movieListPage.js
, moviePage.js
, PersonListPage.js
dan PersonPage.js
. MovieListPage.js
mencantumkan semua film di backend Sanity.io kami serta film paling populer.
Untuk membuat daftar semua film, kami cukup dispatch
tindakan fetchAllMovies
yang ditentukan dalam file movieAction.js
kami. Karena kita perlu mengambil daftar segera setelah halaman dimuat, kita harus mendefinisikannya di useEffect
. Ini ditunjukkan di bawah ini.
import React, { useEffect } from "react"; import { fetchAllMovies } from "../redux/actions/movieActions"; import { useDispatch, useSelector } from "react-redux"; const MoviesListPage = () => { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchAllMovies()); }, [dispatch]); const { loading, error, movies } = useSelector( (state) => state.fetchAllMoviesReducer ); return ( ... ) }; export default MoviesListPage;
Berkat useDispatch
dan useSelector
Hooks, kami dapat mengirimkan tindakan Redux dan memilih status yang sesuai dari toko Redux. Perhatikan bahwa status loading
, error
dan movies
didefinisikan dalam fungsi Reducer kami dan di sini memilihnya menggunakan useSelector
Hook dari React Redux. Status ini yaitu loading
, error
, dan movies
menjadi tersedia segera kami mengirimkan tindakan fetchAllMovies()
.
Setelah kami mendapatkan daftar film, kami dapat menampilkannya di aplikasi kami menggunakan fungsi map
atau sesuka kami.
Berikut adalah kode lengkap untuk file moviesListPage.js
.
import React, {useState, useEffect} from 'react' import {fetchAllMovies, getMostPopular, sortMoviesBy} from "../redux/actions/movieActions" import {useDispatch, useSelector} from "react-redux" import Loader from "../components/BackdropLoader" import {MovieListContainer} from "../styles/MovieStyles.js" import SortIcon from '@material-ui/icons/Sort'; import SortModal from "../components/Modal" import {useLocation, Link} from "react-router-dom" import queryString from "query-string" import {MOVIES_FETCH_RESET} from "../redux/constants/movieConstants" const MoviesListPage = () => { const location = useLocation() const dispatch = useDispatch() const [openSort, setOpenSort] = useState(false) useEffect(()=>{ dispatch(getMostPopular()) const {order, type} = queryString.parse(location.search) if(order && type){ dispatch({ type: MOVIES_FETCH_RESET }) dispatch(sortMoviesBy(order, type)) }else{ dispatch(fetchAllMovies()) } }, [dispatch, location.search]) const {loading: popularLoading, error: popularError, movies: popularMovies } = useSelector(state => state.getMostPopularReducer) const { loading: moviesLoading, error: moviesError, movies } = useSelector(state => state.fetchAllMoviesReducer) const { loading: sortLoading, error: sortError, movies: sortMovies } = useSelector(state => state.sortMoviesByReducer) return ( <MovieListContainer> <div className="mostpopular"> { popularLoading ? <Loader /> : popularError ? popularError : popularMovies && popularMovies.map(movie => ( <Link to={`/movie?id=${movie._id}`} className="popular" key={movie._id} style={{backgroundImage: `url(${movie.poster})`}}> <div className="content"> <h2>{movie.title}</h2> <p>{movie.overview.text.substring(0, 50)}…</p> </div> </Link> )) } </div> <div className="moviespanel"> <div className="top"> <h2>All Movies</h2> <SortIcon onClick={()=> setOpenSort(true)} /> </div> <div className="movieslist"> { moviesLoading ? <Loader /> : moviesError ? moviesError : movies && movies.map(movie =>( <Link to={`/movie?id=${movie._id}`} key={movie._id}> <img className="movie" src={movie.poster} alt={movie.title} /> </Link> )) } { ( sortLoading ? !movies && <Loader /> : sortError ? sortError : sortMovies && sortMovies.map(movie =>( <Link to={`/movie?id=${movie._id}`} key={movie._id}> <img className="movie" src={movie.poster} alt={movie.title} /> </Link> )) ) } </div> </div> <SortModal open={openSort} setOpen={setOpenSort} /> </MovieListContainer> ) } export default MoviesListPage
Kami mulai dengan mengirimkan aksi film getMostPopular
(tindakan ini memilih film dengan popularitas tertinggi) di useEffect
Hook. Ini memungkinkan kami untuk mengambil film paling populer segera setelah halaman dimuat. Selain itu, kami mengizinkan pengguna untuk mengurutkan film berdasarkan releaseDate
dan popularity
. Ini ditangani oleh tindakan sortMoviesBy
yang dikirim dalam kode di atas. Selanjutnya, kami mengirimkan fetchAllMovies
tergantung pada parameter kueri.
Juga, kami menggunakan useSelector
Hook untuk memilih reduksi yang sesuai untuk setiap tindakan ini. Kami memilih status untuk loading
, error
, dan movies
untuk masing-masing reduksi.
Setelah mendapatkan movies
dari reduksi, sekarang kami dapat menampilkannya kepada pengguna. Di sini, kami telah menggunakan fungsi map
ES6 untuk melakukan ini. Kami pertama kali menampilkan pemuat setiap kali setiap status film sedang dimuat dan jika ada kesalahan, kami menampilkan pesan kesalahan. Akhirnya, jika kami mendapatkan film, kami menampilkan gambar film kepada pengguna menggunakan fungsi map
. Kami membungkus seluruh komponen dalam komponen MovieListContainer
.
<MovieListContainer> … </MovieListContainer>
adalah div
yang ditentukan menggunakan komponen gaya. Kami akan segera membahasnya.
Menata Aplikasi Kami Dengan Komponen yang Digayakan
Komponen yang ditata memungkinkan kita untuk menata halaman dan komponen kita secara individual. Ini juga menawarkan beberapa fitur menarik seperti inheritance
, Theming
, passing of props
, dll.
Meskipun kami selalu ingin menata halaman kami secara individual, terkadang gaya global mungkin diinginkan. Menariknya, komponen gaya menyediakan cara untuk melakukannya, berkat fungsi createGlobalStyle
.
Untuk menggunakan komponen gaya dalam aplikasi kita, kita perlu menginstalnya. Buka terminal Anda di proyek reaksi Anda dan masukkan perintah bash
berikut.
npm install styled-components
Setelah menginstal komponen gaya, Mari kita mulai dengan gaya global kita.
Mari buat folder terpisah di direktori src
bernama styles
. Ini akan menyimpan semua gaya kita. Mari kita juga membuat file globalStyles.js
di dalam folder gaya. Untuk membuat gaya global dalam komponen gaya, kita perlu mengimpor createGlobalStyle
.
import { createGlobalStyle } from "styled-components";
Kami kemudian dapat mendefinisikan gaya kami sebagai berikut:
export const GlobalStyle = createGlobalStyle` ... `
Komponen yang diberi gaya menggunakan literal template untuk mendefinisikan props. Dalam literal ini, kita dapat menulis kode CSS
tradisional kita.
Kami juga mengimpor deviceWidth
didefinisikan dalam file bernama definition.js
. deviceWidth
memegang definisi breakpoints untuk menyetel kueri media kita.
import { deviceWidth } from "./definition";
Kami mengatur overflow ke hidden untuk mengontrol aliran aplikasi kami.
html, body{ overflow-x: hidden; }
Kami juga mendefinisikan gaya header menggunakan pemilih gaya .header
.
.header{ z-index: 5; background-color: ${(props)=>props.theme.midDarkBlue}; display:flex; align-items:center; padding: 0 20px; height:50px; justify-content:space-between; position:fixed; top:0; width:100%; @media ${deviceWidth.laptop_lg} { width:97%; } ... }
Di sini, berbagai gaya seperti warna latar belakang, indeks-z, padding, dan banyak properti CSS tradisional lainnya ditentukan.
Kami telah menggunakan props
komponen gaya untuk mengatur warna latar belakang. Ini memungkinkan kita untuk mengatur variabel dinamis yang dapat diteruskan dari komponen kita. Selain itu, kami juga meneruskan variabel tema untuk memungkinkan kami memanfaatkan pengalihan tema secara maksimal.
Tema dimungkinkan di sini karena kami telah membungkus seluruh aplikasi kami dengan ThemeProvider
dari komponen gaya. Kami akan membicarakan ini sebentar lagi. Selanjutnya, kami menggunakan CSS flexbox
untuk menata tajuk dengan benar dan mengatur posisinya menjadi fixed
untuk memastikannya tetap terkait dengan browser. Kami juga mendefinisikan breakpoint untuk membuat header mobile friendly.
Berikut adalah kode lengkap untuk file globalStyles.js
kami.
import { createGlobalStyle } from "styled-components"; import { deviceWidth } from "./definition"; export const GlobalStyle = createGlobalStyle` html{ overflow-x: hidden; } body{ background-color: ${(props) => props.theme.lighter}; overflow-x: hidden; min-height: 100vh; display: grid; grid-template-rows: auto 1fr auto; } #root{ display: grid; flex-direction: column; } h1,h2,h3, label{ font-family: 'Aclonica', sans-serif; } h1, h2, h3, p, span:not(.MuiIconButton-label), div:not(.PrivateRadioButtonIcon-root-8), div:not(.tryingthis){ color: ${(props) => props.theme.bodyText} } p, span, div, input{ font-family: 'Jost', sans-serif; } .paginate button{ color: ${(props) => props.theme.bodyText} } .header{ z-index: 5; background-color: ${(props) => props.theme.midDarkBlue}; display: flex; align-items: center; padding: 0 20px; height: 50px; justify-content: space-between; position: fixed; top: 0; width: 100%; @media ${deviceWidth.laptop_lg}{ width: 97%; } @media ${deviceWidth.tablet}{ width: 100%; justify-content: space-around; } a{ text-decoration: none; } label{ cursor: pointer; color: ${(props) => props.theme.goldish}; font-size: 1.5rem; } .hamburger{ cursor: pointer; color: ${(props) => props.theme.white}; @media ${deviceWidth.desktop}{ display: none; } @media ${deviceWidth.tablet}{ display: block; } } } .mobileHeader{ z-index: 5; background-color: ${(props) => props.theme.darkBlue}; color: ${(props) => props.theme.white}; display: grid; place-items: center; width: 100%; @media ${deviceWidth.tablet}{ width: 100%; } height: calc(100% - 50px); transition: all 0.5s ease-in-out; position: fixed; right: 0; top: 50px; .menuitems{ display: flex; box-shadow: 0 0 5px ${(props) => props.theme.lightshadowtheme}; flex-direction: column; align-items: center; justify-content: space-around; height: 60%; width: 40%; a{ display: flex; flex-direction: column; align-items:center; cursor: pointer; color: ${(props) => props.theme.white}; text-decoration: none; &:hover{ border-bottom: 2px solid ${(props) => props.theme.goldish}; .MuiSvgIcon-root{ color: ${(props) => props.theme.lightred} } } } } } footer{ min-height: 30px; margin-top: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.875rem; background-color: ${(props) => props.theme.midDarkBlue}; color: ${(props) => props.theme.white}; } `;
Perhatikan bahwa kami menulis kode CSS murni di dalam literal tetapi ada beberapa pengecualian. Komponen gaya memungkinkan kita untuk melewatkan props. Anda dapat mempelajari lebih lanjut tentang ini di dokumentasi.
Selain mendefinisikan gaya global, kita dapat menentukan gaya untuk halaman individual.
Misalnya, berikut adalah gaya untuk PersonListPage.js
yang didefinisikan dalam PersonStyle.js
di folder styles
.
import styled from "styled-components"; import { deviceWidth, colors } from "./definition"; export const PersonsListContainer = styled.div` margin: 50px 80px; @media ${deviceWidth.tablet} { margin: 50px 10px; } a { text-decoration: none; } .top { display: flex; justify-content: flex-end; padding: 5px; .MuiSvgIcon-root { cursor: pointer; &:hover { color: ${colors.darkred}; } } } .personslist { margin-top: 20px; display: grid; place-items: center; grid-template-columns: repeat(5, 1fr); @media ${deviceWidth.laptop} { grid-template-columns: repeat(4, 1fr); } @media ${deviceWidth.tablet} { grid-template-columns: repeat(3, 1fr); } @media ${deviceWidth.tablet_md} { grid-template-columns: repeat(2, 1fr); } @media ${deviceWidth.mobile_lg} { grid-template-columns: repeat(1, 1fr); } grid-gap: 30px; .person { width: 200px; position: relative; img { width: 100%; } .content { position: absolute; bottom: 0; left: 8px; border-right: 2px solid ${colors.goldish}; border-left: 2px solid ${colors.goldish}; border-radius: 10px; width: 80%; margin: 20px auto; padding: 8px 10px; background-color: ${colors.transparentWhite}; color: ${colors.darkBlue}; h2 { font-size: 1.2rem; } } } } `;
Kami pertama-tama mengimpor styled
dari styled-components
dan deviceWidth
dari file definition
. Kami kemudian mendefinisikan PersonsListContainer
sebagai div
untuk menampung gaya kami. Menggunakan kueri media dan titik henti sementara yang ditetapkan, kami membuat halaman ramah seluler dengan menyetel berbagai titik henti sementara.
Di sini, kami hanya menggunakan breakpoint browser standar untuk layar kecil, besar, dan sangat besar. Kami juga memanfaatkan flexbox dan grid CSS untuk menata dan menampilkan konten kami dengan benar di halaman.
Untuk menggunakan gaya ini di file PersonListPage.js
kami, kami cukup mengimpornya dan menambahkannya ke halaman kami sebagai berikut.
import React from "react"; const PersonsListPage = () => { return ( <PersonsListContainer> ... </PersonsListContainer> ); }; export default PersonsListPage;
Wrapper akan menampilkan div
karena kami mendefinisikannya sebagai div dalam gaya kami.
Menambahkan Tema Dan Membungkusnya
Itu selalu merupakan fitur keren untuk menambahkan tema ke aplikasi kami. Untuk ini, kita membutuhkan yang berikut:
- Tema khusus kami ditentukan dalam file terpisah (dalam file
definition.js
kasus kami). - Logika yang ditentukan dalam tindakan dan reduksi Redux kami.
- Memanggil tema kita di aplikasi kita dan meneruskannya melalui pohon komponen.
Mari kita periksa ini.
Berikut adalah objek theme
kami dalam file definition.js
.
export const theme = { light: { dark: "#0B0C10", darkBlue: "#253858", midDarkBlue: "#42526e", lightBlue: "#0065ff", normal: "#dcdcdd", lighter: "#F4F5F7", white: "#FFFFFF", darkred: "#E85A4F", lightred: "#E98074", goldish: "#FFC400", bodyText: "#0B0C10", lightshadowtheme: "rgba(0, 0, 0, 0.1)" }, dark: { dark: "white", darkBlue: "#06090F", midDarkBlue: "#161B22", normal: "#dcdcdd", lighter: "#06090F", white: "white", darkred: "#E85A4F", lightred: "#E98074", goldish: "#FFC400", bodyText: "white", lightshadowtheme: "rgba(255, 255, 255, 0.9)" } };
Kami telah menambahkan berbagai properti warna untuk tema terang dan gelap. Warna dipilih dengan cermat untuk memungkinkan visibilitas baik dalam mode terang maupun gelap. Anda dapat menentukan tema sesuai keinginan. Ini bukan aturan yang keras dan cepat.
Selanjutnya, mari tambahkan fungsionalitas ke Redux.
Kami telah membuat globalActions.js
di folder tindakan Redux kami dan menambahkan kode berikut.
import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants"; import { theme } from "../../styles/definition"; export const switchToLightTheme = () => (dispatch) => { dispatch({ type: SET_LIGHT_THEME, payload: theme.light }); localStorage.setItem("theme", JSON.stringify(theme.light)); localStorage.setItem("light", JSON.stringify(true)); }; export const switchToDarkTheme = () => (dispatch) => { dispatch({ type: SET_DARK_THEME, payload: theme.dark }); localStorage.setItem("theme", JSON.stringify(theme.dark)); localStorage.setItem("light", JSON.stringify(false)); };
Di sini, kami hanya mengimpor tema yang kami tentukan. Mengirimkan tindakan yang sesuai, melewati muatan tema yang kami butuhkan. Hasil payload disimpan di penyimpanan lokal menggunakan kunci yang sama untuk tema terang dan gelap. Ini memungkinkan kami untuk mempertahankan status di browser.
Kita juga perlu mendefinisikan peredam kita untuk tema.
import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants"; export const toggleTheme = (state = {}, action) => { switch (action.type) { case SET_LIGHT_THEME: return { theme: action.payload, light: true }; case SET_DARK_THEME: return { theme: action.payload, light: false }; default: return state; } };
Ini sangat mirip dengan apa yang telah kami lakukan. Kami menggunakan pernyataan switch
untuk memeriksa jenis tindakan dan kemudian mengembalikan payload
yang sesuai. Kami juga mengembalikan status light
yang menentukan apakah tema terang atau gelap dipilih oleh pengguna. Kami akan menggunakan ini di komponen kami.
Kami juga perlu menambahkannya ke peredam dan penyimpanan root kami. Berikut adalah kode lengkap untuk store.js
kami.
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import { theme as initialTheme } from "../styles/definition"; import reducers from "./reducers/index"; const theme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : initialTheme.light; const light = localStorage.getItem("light") ? JSON.parse(localStorage.getItem("light")) : true; const initialState = { toggleTheme: { light, theme } }; export default createStore(reducers, initialState, applyMiddleware(thunk));
Karena kami perlu mempertahankan tema saat pengguna menyegarkan, kami harus mendapatkannya dari penyimpanan lokal menggunakan localStorage.getItem()
dan meneruskannya ke status awal kami.
Menambahkan Fungsionalitas Ke Aplikasi React Kami
Komponen bergaya memberi kami ThemeProvider
yang memungkinkan kami meneruskan tema melalui aplikasi kami. Kami dapat memodifikasi file App.js kami untuk menambahkan fungsi ini.
Mari kita lihat itu.
import React from "react"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import { useSelector } from "react-redux"; import { ThemeProvider } from "styled-components"; function App() { const { theme } = useSelector((state) => state.toggleTheme); let Theme = theme ? theme : {}; return ( <ThemeProvider theme={Theme}> <Router> ... </Router> </ThemeProvider> ); } export default App;
Dengan meneruskan tema melalui ThemeProvider
, kita dapat dengan mudah menggunakan props tema dalam gaya kita.
Misalnya, kita dapat mengatur warna ke warna kustom bodyText
kita sebagai berikut.
color: ${(props) => props.theme.bodyText};
Kami dapat menggunakan tema khusus di mana saja kami membutuhkan warna dalam aplikasi kami.
Misalnya, untuk mendefinisikan border-bottom
, kita lakukan hal berikut.
border-bottom: 2px solid ${(props) => props.theme.goldish};
Kesimpulan
Kami mulai dengan mempelajari Sanity.io, mengaturnya dan menghubungkannya ke aplikasi React kami. Kemudian kami mengatur Redux dan menggunakan bahasa GROQ untuk menanyakan API kami. Kami melihat bagaimana menghubungkan dan menggunakan Redux ke aplikasi React kami menggunakan react-redux
, menggunakan komponen gaya dan tema.
Namun, kami hanya menggores permukaan pada apa yang mungkin dengan teknologi ini. Saya mendorong Anda untuk membaca contoh kode di repo GitHub saya dan mencoba proyek yang sama sekali berbeda menggunakan teknologi ini untuk mempelajari dan menguasainya.
Sumber daya
- Dokumentasi Kewarasan
- Cara Membuat Blog dengan Sanity.io oleh Kapehe
- Dokumentasi Redux
- Dokumentasi Komponen Bergaya
- Lembar Cheat GROQ
- Dokumentasi UI Materi
- Redux Middleware dan Efek Samping
- Dokumentasi Redux Thunk