React, Redux ve Sanity.io ile Bir Web Uygulaması Oluşturma

Yayınlanan: 2022-03-10
Hızlı özet ↬ Headless CMS, içeriği yönetmenin ve API'ye erişmenin güçlü ve kolay bir yoludur. React üzerine kurulu Sanity.io, esnek içerik yönetimi için kusursuz bir araçtır. Temelden karmaşık uygulamalara kadar basit uygulamalar oluşturmak için kullanılabilir. Bu makalede Ifeanyi, Sanity.io ve React ile basit bir listeleme uygulamasının nasıl oluşturulacağını açıklıyor. Global durumlar Redux ile yönetilecek ve uygulama stil bileşenleri ile şekillendirilecektir.

Dijital platformların hızlı gelişimi, Wordpress gibi geleneksel CMS'ye ciddi sınırlamalar getirdi. Bu platformlar birbirine bağlıdır, esnek değildir ve üründen çok projeye odaklıdır. Neyse ki, bu zorlukların ve daha fazlasının üstesinden gelmek için birkaç başsız CMS geliştirilmiştir.

Geleneksel CMS'den farklı olarak, Hizmet Olarak Yazılım (SaaS) olarak tanımlanabilecek başsız CMS, web siteleri, mobil uygulamalar, dijital ekranlar ve daha fazlasını geliştirmek için kullanılabilir. Sınırsız platformlarda kullanılabilirler. Platformdan bağımsız, geliştirici öncelikli ve çapraz platform desteği sunan bir CMS arıyorsanız, başsız CMS'den daha uzağa bakmanıza gerek yok.

Başsız bir CMS, basitçe kafasız bir CMS'dir. Buradaki head , ön uca veya sunum katmanına atıfta bulunurken, body arka uca veya içerik deposuna atıfta bulunur. Bu, birçok ilginç fayda sağlar. Örneğin, geliştiricinin istediği herhangi bir ön ucu seçmesine izin verir ve ayrıca sunum katmanını istediğiniz gibi tasarlayabilirsiniz.

Dışarıda çok sayıda başsız CMS var, en popüler olanlardan bazıları Strapi, Contentful, Contentstack, Sanity, Butter CMS, Prismic, Storyblok, Directus vb. Bu başsız CMS, API tabanlıdır ve bireysel güçlü noktaları vardır. Örneğin, Sanity, Strapi, Contentful ve Storyblok gibi CMS küçük projeler için ücretsizdir.

Bu başsız CMS'ler de farklı teknoloji yığınlarına dayanmaktadır. Sanity.io, React.js'yi temel alırken, Storyblok Vue.js'yi temel alır. Bir React geliştiricisi olarak, Sanity'ye hızla ilgi duymamın ana nedeni budur. Bununla birlikte, başsız bir CMS olarak, bu platformların her biri, Angular, Vue veya React olsun, herhangi bir ön uçta takılabilir.

Bu başsız CMS'lerin her biri, önemli fiyat artışını temsil eden hem ücretsiz hem de ücretli planlara sahiptir. Bu ücretli planlar daha fazla özellik sunsa da, küçük ve orta ölçekli bir proje için bu kadar fazla ödeme yapmak istemezsiniz. Sanity, kullandığın kadar öde seçeneklerini sunarak bu sorunu çözmeye çalışır. Bu seçeneklerle, kullandığınız kadar ödeme yapabilecek ve fiyat atlamasını önleyebileceksiniz.

Sanity.io'yu seçmemin bir başka nedeni de GROQ dili. Benim için Sanity, bu aracı sunarak kalabalığın arasından sıyrılıyor. Grafik-İlişkisel Nesne Sorguları (GROQ), geliştirme süresini kısaltır, ihtiyacınız olan içeriği ihtiyacınız olan formda almanıza yardımcı olur ve ayrıca geliştiricinin kod değişikliği olmadan yeni bir içerik modeliyle belge oluşturmasına yardımcı olur.

Ayrıca, geliştiriciler GROQ diliyle sınırlı değildir. Ayrıca, arka ucu sorgulamak için React uygulamanızda axios ve hatta geleneksel eksenleri kullanabilir ve fetch . Diğer birçok başsız CMS gibi, Sanity de platformda oluşturmaya yönelik yararlı ipuçları içeren kapsamlı belgelere sahiptir.

Not: Bu makale, temel bir React, Redux ve CSS bilgisi gerektirir.

Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

Sanity.io'ya Başlarken

Sanity'yi makinenizde kullanmak için Sanity CLI aracını yüklemeniz gerekir. Bu, projenize yerel olarak kurulabilirken, gelecekteki uygulamalar için erişilebilir hale getirmek için küresel olarak kurulması tercih edilir.

Bunu yapmak için terminalinize aşağıdaki komutları girin.

 npm install -g @sanity/cli

Yukarıdaki -g bayrağı, genel kurulumu etkinleştirir.

Ardından, uygulamamızda Sanity'yi başlatmamız gerekiyor. Bu, ayrı bir proje olarak kurulabilmesine rağmen, genellikle onu ön uç uygulamanızın (bu durumda React) içine kurmanız tercih edilir.

Kapehe, blogunda Sanity'nin React ile nasıl entegre edileceğini ayrıntılı olarak açıkladı. Bu eğitime devam etmeden önce makaleyi gözden geçirmek faydalı olacaktır.

Sanity'yi React uygulamanızda başlatmak için aşağıdaki komutları girin.

 sanity init

Sanity CLI aracını kurduğumuzda sanity komutu bizim için kullanılabilir hale gelir. Terminalinizde akıl sanity veya sanity help yazarak mevcut Akıl sağlığı komutlarının bir listesini görüntüleyebilirsiniz.

Projenizi kurarken veya başlatırken, onu özelleştirmek için komutları izlemeniz gerekir. Ayrıca bir veri kümesi oluşturmanız istenecek ve hatta verilerle doldurulmuş özel veri kümelerini bile seçebilirsiniz. Bu listeleme uygulaması için Sanity'nin özel bilim kurgu filmleri veri setini kullanacağız. Bu bizi verileri kendimiz girmekten kurtaracaktır.

Veri kümenizi görüntülemek ve düzenlemek için terminalinizdeki Sanity alt dizinine cd girin ve sanity start yazın. Bu genellikle https://localhost:3333/ üzerinde çalışır. Arayüze erişmek için oturum açmanız gerekebilir (projeyi başlatırken kullandığınız hesapla oturum açtığınızdan emin olun). Aşağıda ortamın bir ekran görüntüsü gösterilmektedir.

Sanity sunucusuna genel bakış
Bilim kurgu filmi veri seti için akıl sağlığı sunucusuna genel bakış. (Büyük önizleme)

Sanity-React İki Yönlü İletişim

Sanity ve React'in tamamen işlevsel bir uygulama için birbirleriyle iletişim kurması gerekir.

Akıl Sağlığı Yöneticisinde CORS Origins Ayarı

Önce React uygulamamızı Sanity'ye bağlayacağız. Bunu yapmak için https://manage.sanity.io/ oturum açın ve Settings sekmesindeki API Settings altında CORS origins bulun. Burada, ön uç kaynağınızı Sanity arka ucuna bağlamanız gerekecek. React uygulamamız varsayılan olarak https://localhost:3000/ üzerinde çalışıyor, bu yüzden bunu CORS'a eklememiz gerekiyor.

Bu, aşağıdaki şekilde gösterilmiştir.

CORS başlangıç ​​ayarları
Sanity.io Manager'da CORS kaynağının ayarlanması. (Büyük önizleme)

Akıl Sağlığını Tepki Vermek İçin Bağlamak

Sanity, oluşturduğunuz her projeyle bir project ID ilişkilendirir. Bu kimlik, onu ön uç uygulamanıza bağlarken gereklidir. Proje kimliğini Akıl Yöneticinizde bulabilirsiniz.

Arka uç, sanity client olarak bilinen bir kitaplık kullanarak React ile iletişim kurar. Sanity projenize aşağıdaki komutları girerek bu kütüphaneyi kurmanız gerekmektedir.

 npm install @sanity/client

Proje src klasörünüzde bir sanitySetup.js dosyası (dosya adı önemli değil) oluşturun ve Sanity ile React arasında bir bağlantı kurmak için aşağıdaki React kodlarını girin.

 import sanityClient from "@sanity/client" export default sanityClient({ projectId: PROJECT_ID, dataset: DATASET_NAME, useCdn: true });

projectId , dataset name adımızı ve bir boolean useCdn @sanity/client öğesinden içe aktarılan akıl sağlığı istemcisinin örneğine ilettik. Bu, sihri çalıştırır ve uygulamamızı arka uca bağlar.

Şimdi iki yönlü bağlantıyı tamamladığımıza göre, hemen projemizi oluşturmaya geçelim.

Redux'u Kurma ve Uygulamamıza Bağlama

React uygulamamızda Redux ile çalışmak için birkaç bağımlılığa ihtiyacımız olacak. Terminalinizi React ortamınızda açın ve aşağıdaki bash komutlarını girin.

 npm install redux react-redux redux-thunk

Redux, React gibi çoğu ön uç çerçeve ve kitaplıkla kullanılabilen küresel bir durum yönetimi kitaplığıdır. Ancak Redux mağazamız ve React uygulamamız arasındaki iletişimi sağlamak için bir aracı araca react-redux ihtiyacımız var. Redux thunk , Redux'tan bir eylem nesnesi yerine bir işlev döndürmemize yardımcı olacaktır.

Redux iş akışının tamamını tek bir dosyaya yazabilsek de, endişelerimizi ayırmak genellikle daha düzenli ve daha iyidir. Bunun için iş akışımızı actions , reducers ve ardından store olmak üzere üç dosyaya böleceğiz. Ancak, constants olarak da bilinen action types depolamak için ayrı bir dosyaya da ihtiyacımız var.

Mağazayı Kurma

Mağaza, Redux'daki en önemli dosyadır. Durumları organize eder, paketler ve React uygulamamıza gönderir.

Redux iş akışımızı bağlamak için gereken Redux mağazamızın ilk kurulumu.

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/"; export default createStore( reducers, applyMiddleware(thunk) );

Bu createStore işlevi üç parametre alır: reducer (gerekli), başlangıç ​​durumu ve geliştirici (bu durumda genellikle bir ara yazılım, applyMiddleware aracılığıyla sağlanan thunk ). Redüktörlerimiz bir reducers klasöründe saklanacak ve onları reducers klasöründeki bir index.js dosyasında birleştirip dışa aktaracağız. Bu, yukarıdaki kodda içe aktardığımız dosyadır. Bu dosyayı daha sonra tekrar gözden geçireceğiz.

Sanity'nin GROQ Diline Giriş

Sanity, GROQ'u tanıtarak JSON verilerini sorgulamayı bir adım öteye taşıyor. GROQ, Grafik İlişkisel Nesne Sorguları anlamına gelir. Sanity.io'ya göre GROQ, büyük ölçüde şemasız JSON belgelerinin koleksiyonlarını sorgulamak için tasarlanmış bildirimsel bir sorgu dilidir.

Sanity, geliştiricilerin dile aşina olmalarına yardımcı olmak için GROQ Playground'u bile sağlar. Ancak, oyun alanına erişmek için akıl sağlığı vizyonunu yüklemeniz gerekir. Yüklemek için terminalinizde sanity install @sanity/vision çalıştırın.

GROQ, GraphQL ile benzer bir sözdizimine sahiptir, ancak daha yoğun ve okunması daha kolaydır. Ayrıca, GraphQL'den farklı olarak GROQ, JSON verilerini sorgulamak için kullanılabilir.

Örneğin, film belgemizdeki her öğeyi almak için aşağıdaki GROQ sözdizimini kullanacağız.

 *[_type == "movie"]

Ancak, film belgemizde yalnızca _ids ve crewMembers Üyelerini almak istiyorsak. Bu alanları aşağıdaki gibi belirtmemiz gerekiyor.

 `*[_type == 'movie']{ _id, crewMembers }

Burada, GROQ'a _type filminin her belgesini istediğimizi söylemek için * kullandık. _type , film koleksiyonunun altındaki bir özelliktir. Ayrıca türü _id ve crewMembers gibi aşağıdaki gibi döndürebiliriz:

 *[_type == 'movie']{ _id, _type, crewMembers }

Redux eylemlerimize uygulayarak GROQ üzerinde daha fazla çalışacağız, ancak bu konuda daha fazla bilgi edinmek için Sanity.io'nun GROQ belgelerine göz atabilirsiniz. GROQ sorgu hile sayfası, sorgu dilinde ustalaşmanıza yardımcı olacak birçok örnek sunar.

Sabitleri Ayarlama

Redux iş akışının her aşamasında eylem türlerini izlemek için sabitlere ihtiyacımız var. Sabitler, zamanın her noktasında gönderilen eylemin türünü belirlemeye yardımcı olur. Örneğin, API'nin ne zaman yüklendiğini, tam olarak yüklendiğini ve bir hata oluştuğunu takip edebiliyoruz.

Sabitleri ayrı bir dosyada tanımlamamız gerekmez, ancak basitlik ve netlik için bu genellikle Redux'daki en iyi uygulamadır.

Kural olarak, Javascript'teki sabitler büyük harfle tanımlanır. Sabitlerimizi tanımlamak için buradaki en iyi uygulamaları izleyeceğiz. İşte hareketli film getirme isteklerini belirtmek için bir sabit örneği.

 export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST";

Burada, MOVIE_FETCH_REQUEST eylem türünü belirten bir MOVIE_FETCH_REQUEST sabiti oluşturduk. Bu, strings kullanmadan bu eylem türünü kolayca çağırmamıza ve hataları önlememize yardımcı olur. Ayrıca sabiti projemizin herhangi bir yerinde mevcut olacak şekilde ihraç ettik.

Benzer şekilde, isteğin başarılı veya başarısız olduğunu gösteren eylem türlerini getirmek için başka sabitler de oluşturabiliriz. movieConstants.js için tam bir kod aşağıdaki kodda verilmiştir.

 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";

Burada bir film veya film listesi almak, en popüler filmleri sıralamak ve getirmek için birkaç sabit tanımladık. İsteğin ne zaman loading , successful ve failed olduğunu belirlemek için sabitler belirlediğimize dikkat edin.

Benzer şekilde personConstants.js dosyamız da aşağıda verilmiştir:

 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";

movieConstants.js gibi, bir kişiyi veya kişileri getirmek için bir sabitler listesi belirledik. Ayrıca kişileri saymak için bir sabit belirledik. Sabitler, movieConstants.js için açıklanan kuralı takip eder ve biz de bunları uygulamamızın diğer bölümlerine erişilebilmesi için dışa aktardık.

Son olarak, uygulamada açık ve koyu modu uygulayacağız ve böylece globalConstants.js başka bir sabit dosyamız olacak. Bir göz atalım.

 export const SET_LIGHT_THEME = "SET_LIGHT_THEME"; export const SET_DARK_THEME = "SET_DARK_THEME";

Burada açık veya koyu modun ne zaman gönderileceğini belirlemek için sabitler ayarlıyoruz. SET_LIGHT_THEME , kullanıcının açık temaya ne zaman geçeceğini ve SET_DARK_THEME , koyu temanın ne zaman seçileceğini belirler. Ayrıca sabitlerimizi gösterildiği gibi dışa aktardık.

Eylemleri Ayarlama

Kural olarak, eylemlerimiz ayrı bir klasörde saklanır. Eylemler türlerine göre gruplandırılmıştır. Örneğin, film eylemlerimiz personActions.js movieActions.js saklanır.

Ayrıca temayı açık moddan koyu moda geçirmeyle ilgilenmek için globalActions.js .

moviesActions.js tüm filmleri getirelim.

 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 }); } };

React'i Sanity arka ucumuza bağlamak için sanitySetup.js dosyasını oluşturduğumuzu hatırlıyor musunuz? Burada, GROQ kullanarak akıl sağlığı arka uçumuzu sorgulamamızı sağlamak için kurulumu içe aktardık. Ayrıca constants klasöründeki movieConstants.js dosyasından dışa aktarılan birkaç sabiti de içe aktardık.

Ardından, koleksiyonumuzdaki her filmi almak için fetchAllMovies eylem işlevini oluşturduk. Çoğu geleneksel React uygulaması, arka uçtan veri almak için axios veya fetch kullanır. Ancak bunlardan herhangi birini burada kullanabilirken, GROQ kullanıyoruz. GROQ moduna girmek için yukarıdaki kodda gösterildiği gibi sanityAPI.fetch() fonksiyonunu çağırmamız gerekiyor. Burada sanityAPI , daha önce kurduğumuz React-Sanity bağlantısıdır. Bu bir Promise döndürür ve bu nedenle eşzamansız olarak çağrılması gerekir. Burada async-await sözdizimini kullandık, ancak .then sözdizimini de kullanabiliriz.

Uygulamamızda thunk kullandığımız için action nesnesi yerine bir fonksiyon döndürebiliriz. Ancak, return ifadesini tek satırda geçirmeyi seçtik.

 const fetchAllMovies = () => async (dispatch) => { ... }

Fonksiyonu şu şekilde de yazabileceğimizi unutmayın:

 const fetchAllMovies = () => { return async (dispatch)=>{ ... } }

Genel olarak, tüm filmleri getirmek için önce istek yüklenmeye devam ederken izleyen bir eylem türü gönderdik. Daha sonra film belgesini eşzamansız olarak sorgulamak için Sanity'nin GROQ sözdizimini kullandık. Film verilerinin _id ve poster URL'sini aldık. Daha sonra API'den alınan verileri içeren bir yük döndürdük.

Benzer şekilde, filmleri _id göre alabilir, filmleri sıralayabilir ve en popüler filmleri alabiliriz.

Belirli bir kişinin referansıyla eşleşen filmleri de getirebiliriz. Bunu fetchMoviesByRef işlevinde yaptık.

 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 }); } };

Bu işlev bir argüman alır ve person._ref veya crewMembers içindeki castMembers , geçirilen argümanla eşleşip eşleşmediğini kontrol eder. Filmi _id , poster url ve title ile birlikte döndürürüz. Ayrıca, döndürülen verilerin bir yükünü ekleyerek MOVIES_REF_FETCH_SUCCESS türünde bir eylem göndeririz ve bir hata oluşursa, try-catch sarmalayıcı sayesinde hata mesajının bir yükünü ekleyerek MOVIE_REF_FETCH_FAIL türünde bir eylem göndeririz.

fetchMovieById işlevinde, işleve iletilen belirli bir id eşleşen bir filmi almak için GROQ kullandık.

İşlev için GROQ sözdizimi aşağıda gösterilmiştir.

 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]` );

fetchAllMovies eylemi gibi, movie türündeki tüm belgeleri seçerek başladık, ancak yalnızca işleve sağlanan kimliği olanları seçmek için daha da ileri gittik. Film için çok fazla ayrıntı göstermeyi planladığımız için, alınacak bir sürü öznitelik belirledik.

Film id ve ayrıca castMembers dizisindeki ref , characterName , kişinin adı ve kişinin resmi gibi birkaç özniteliği aldık. Ayrıca castMembers olan takma adı da cast olarak değiştirdik.

castMembers gibi, mürettebat crewMembers dizisinden ref , department , job , kişinin adı ve kişinin resmi gibi birkaç öznitelik seçtik. Ayrıca crew crewMembers değiştirdik.

Aynı şekilde genel bakış metni, popülerlik, filmin afiş url'si, filmin çıkış tarihi ve başlığını seçtik.

Sanity'nin GROQ dili ayrıca bir belgeyi sıralamamıza da izin verir. Bir öğeyi sıralamak için, bir boru operatörünün yanına siparişi geçiyoruz.

Örneğin, filmleri çıkış tarihlerine göre artan düzende sıralamak releaseDate , aşağıdakileri yapabiliriz.

 const data = await sanityAPI.fetch( `*[_type == 'movie']{ ... } | order(releaseDate, asc)` );

Bu kavramı sortMoviesBy işlevinde artan veya azalan düzende sıralamak için kullandık.

Aşağıda bu fonksiyona bir göz atalım.

 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 }); } };

İsteğin ne zaman yüklendiğini belirlemek için MOVIES_SORT_REQUEST türünde bir eylem göndererek başladık. Ardından, movie koleksiyonundaki verileri sıralamak ve almak için GROQ sözdizimini kullandık. Sıralanacak öğe, değişken item sağlanır ve sıralama modu (artan veya azalan) değişken type sağlanır. Sonuç olarak, id , poster URL'sini ve başlığı döndürdük. Veriler döndürüldüğünde, MOVIES_SORT_SUCCESS türünde bir eylem gönderdik ve başarısız olursa, MOVIES_SORT_FAIL türünde bir eylem gönderdik.

Benzer bir GROQ kavramı, getMostPopular işlevi için de geçerlidir. GROQ sözdizimi aşağıda gösterilmiştir.

 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]` );

Buradaki tek fark, filmleri popülaritesine göre azalan düzende sıralamamız ve ardından sadece ilk üçü seçmemizdir. Öğeler sıfır tabanlı bir dizinde döndürülür ve bu nedenle ilk üç öğe 0, 1 ve 2 öğeleridir. İlk on öğeyi almak istiyorsak, işleve [0..9] iletebiliriz.

movieActions.js dosyasındaki film eylemlerinin tam kodu burada.

 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 };

Redüktörlerin Ayarlanması

Redüktörler Redux'daki en önemli kavramlardan biridir. Önceki durumu alır ve durum değişikliklerini belirler.

Tipik olarak, her eylem türü için bir koşul yürütmek üzere switch ifadesini kullanacağız. Örneğin, eylem türü yüklemeyi ifade ettiğinde loading ve ardından başarıyı veya hatayı gösterdiğinde yükü geri döndürebiliriz. initial state ve action argüman olarak alması bekleniyor.

movieReducers.js dosyamız, movieActions.js dosyasında tanımlanan eylemlerle eşleşen çeşitli indirgeyiciler içerir. Ancak indirgeyicilerin her birinin benzer bir sözdizimi ve yapısı vardır. Tek fark, çağırdıkları constants ve döndürdükleri değerlerdir.

fetchAllMoviesReducer dosyasındaki movieReducers.js bir göz atarak başlayalım.

 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; } };

Tüm redüktörler gibi, fetchAllMoviesReducer da ilk durum nesnesini ( state ) ve action nesnesini argüman olarak alır. Zamanın her noktasında eylem türlerini kontrol etmek için switch ifadesini kullandık. MOVIES_FETCH_REQUEST karşılık geliyorsa, kullanıcıya bir yükleme göstergesi gösterebilmemiz için yüklemeyi true olarak döndürürüz.

MOVIES_FETCH_SUCCESS karşılık geliyorsa, yükleme göstergesini kapatıyoruz ve ardından eylem yükünü bir değişken movies döndürüyoruz. Ancak MOVIES_FETCH_FAIL ise, yüklemeyi de kapatıyoruz ve ardından hatayı döndürüyoruz. Ayrıca filmlerimizi sıfırlama seçeneğini de istiyoruz. Bu, yapmamız gerektiğinde durumları temizlememizi sağlayacaktır.

Diğer redüktörler için de aynı yapıya sahibiz. Tam movieReducers.js aşağıda gösterilmiştir.

 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 };

personReducers.js için de aynı yapıyı takip ettik. Örneğin, fetchAllPersonsReducer işlevi, veritabanındaki tüm kişileri getirme durumlarını tanımlar.

Bu, aşağıdaki kodda verilmiştir.

 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; } };

Tıpkı fetchAllMoviesReducer gibi, fetchAllPersonsReducer state ve action ile argüman olarak tanımladık. Bunlar Redux redüktörleri için standart kurulumdur. Daha sonra eylem türlerini kontrol etmek için switch ifadesini kullandık ve PERSONS_FETCH_REQUEST , yüklemeyi doğru olarak döndürürüz. PERSONS_FETCH_SUCCESS ise, yüklemeyi kapatıp yükü iade ediyoruz ve PERSONS_FETCH_FAIL ise hatayı döndürüyoruz.

Kombine Redüktörler

combineReducers işlevi, birden fazla redüktörü birleştirerek mağazaya iletmemizi sağlar. Filmlerimizi ve kişi redüktörlerimizi reducers klasörü içindeki bir index.js dosyasında birleştireceğiz.

Bir göz atalım.

 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 });

Burada filmler, kişiler ve global redüktörler dosyasındaki tüm redüktörleri içe aktardık ve bunları combineReducers işlevine aktardık. combineReducers işlevi, tüm redüktörlerimizi geçmemize izin veren bir nesne alır. Hatta süreçteki argümanlara bir takma ad bile ekleyebiliriz.

globalReducers üzerinde daha sonra çalışacağız.

Artık Redux store.js dosyasındaki redüktörleri geçebiliriz. Bu aşağıda gösterilmiştir.

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/index"; export default createStore(reducers, initialState, applyMiddleware(thunk));

Redux iş akışımızı kurduktan sonra React uygulamamızı kuralım.

React Uygulamamızı Kurma

Tepki uygulamamız, filmleri ve bunlara karşılık gelen oyuncuları ve ekip üyelerini listeleyecektir. Uygulamayı şekillendirmek için yönlendirme için react-router-dom ve styled-components kullanacağız. Ayrıca simgeler ve bazı UI bileşenleri için Material UI kullanacağız.

Bağımlılıkları yüklemek için aşağıdaki bash komutunu girin.

 npm install react-router-dom @material-ui/core @material-ui/icons query-string

İşte inşa edeceğimiz şey:

Redux'u React Uygulamamıza Bağlama

React-redux , uygulamamızı Redux mağazasına bağlamamıza izin veren bir Sağlayıcı işleviyle birlikte gelir. Bunu yapmak için, mağazanın bir örneğini Sağlayıcıya iletmeliyiz. Bunu index.js veya App.js dosyamızda yapabiliriz.

İşte index.js dosyamız.

 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") );

Burada Provider react-redux ve store Redux mağazamızdan içe aktardık. Ardından tüm bileşen ağacımızı Sağlayıcı ile sardık ve mağazayı ona aktardık.

Ardından, React uygulamamızda yönlendirme için react-router-dom ihtiyacımız var. react-router-dom , yolumuzu ve rotalarımızı tanımlamak için kullanılabilecek BrowserRouter , Switch ve Route ile birlikte gelir.

Bunu App.js dosyamızda yapıyoruz. Bu aşağıda gösterilmiştir.

 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;

Bu, tepki-yönlendirici-dom ile yönlendirme için standart bir kurulumdur. Belgelerinde kontrol edebilirsiniz. Header , Footer , PersonsList ve MovieList bileşenlerini içe aktardık . Daha sonra her şeyi Router ve Switch içine sararak react-router-dom kurduk.

Sayfalarımızın aynı üstbilgi ve altbilgiyi paylaşmasını istediğimiz için, yapıyı Switch ile sarmadan önce <Header /> ve <Footer /> bileşenlerini geçmek zorunda kaldık. Tüm uygulamayı sarmasını istediğimiz için main öğeyle de benzer bir şey yaptık.

Her bileşeni Route from react-router-dom kullanarak rotaya geçirdik.

Sayfalarımızı ve Bileşenlerimizi Tanımlama

Uygulamamız yapılandırılmış bir şekilde düzenlenmiştir. Yeniden kullanılabilir bileşenler, components klasöründe, Sayfalar ise pages klasöründe saklanır.

pages movieListPage.js , moviePage.js , PersonListPage.js ve PersonPage.js . MovieListPage.js , Sanity.io arka ucumuzdaki tüm filmleri ve en popüler filmleri listeler.

Tüm filmleri listelemek için movieAction.js dosyamızda tanımlanan fetchAllMovies eylemini dispatch yeterlidir. Listeyi sayfa yüklenir yüklenmez getirmemiz gerektiğinden, onu useEffect içinde tanımlamamız gerekiyor. Bu aşağıda gösterilmiştir.

 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;

useDispatch ve useSelector Hooks sayesinde Redux eylemlerini gönderebilir ve Redux deposundan uygun durumları seçebiliriz. loading , error ve movies durumlarının Redüktör işlevlerimizde tanımlandığına ve burada React useSelector Hook kullanılarak seçildiğine dikkat edin. Bu durumlar, yani loading , error ve movies , fetchAllMovies() eylemlerini gönderdiğimizde hemen kullanılabilir hale gelir.

Filmlerin listesini aldığımızda, map işlevini kullanarak veya istediğimiz şekilde uygulamamızda görüntüleyebiliriz.

İşte moviesListPage.js dosyasının tam kodu.

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

getMostPopular filmler eylemini (bu eylem en yüksek popülerliğe sahip filmleri seçer) useEffect . Bu, sayfa yüklenir yüklenmez en popüler filmleri almamızı sağlar. Ek olarak, kullanıcıların filmleri releaseDate ve popularity göre sıralamalarına izin verdik. Bu, yukarıdaki kodda gönderilen sortMoviesBy eylemi tarafından gerçekleştirilir. Ayrıca, sorgu parametrelerine bağlı olarak fetchAllMovies gönderdik.

Ayrıca, bu eylemlerin her biri için karşılık gelen redüktörleri seçmek için useSelector Hook'u kullandık. Redüktörlerin her biri için loading , error ve movies durumlarını seçtik.

movies redüktörlerden aldıktan sonra artık kullanıcıya gösterebiliyoruz. Burada, bunu yapmak için ES6 map işlevini kullandık. Film durumlarının her biri yüklenirken ilk önce bir yükleyici gösterdik ve bir hata varsa, hata mesajını gösteriyoruz. Son olarak bir film alırsak, map fonksiyonunu kullanarak film görüntüsünü kullanıcıya gösteriyoruz. Tüm bileşeni bir MovieListContainer bileşenine sardık.

<MovieListContainer> … </MovieListContainer> etiketi, stilli bileşenler kullanılarak tanımlanan bir div . Yakında buna kısa bir göz atacağız.

Uygulamamızı Tarz Bileşenlerle Şekillendirme

Tarzlanmış bileşenler, sayfalarımızı ve bileşenlerimizi ayrı ayrı stillendirmemize olanak tanır. Ayrıca inheritance , Theming oluşturma , passing of props vb. gibi bazı ilginç özellikler de sunar.

Sayfalarımızı her zaman bireysel olarak biçimlendirmek istesek de, bazen genel stil tercih edilebilir. İlginç bir şekilde, stilli bileşenler, createGlobalStyle işlevi sayesinde bunu yapmanın bir yolunu sunar.

Uygulamamızda stil bileşenleri kullanmak için onu yüklememiz gerekiyor. Tepki projenizde terminalinizi açın ve aşağıdaki bash komutunu girin.

 npm install styled-components

Tarz bileşenlerini yükledikten sonra, küresel stillerimizle başlayalım.

src dizinimizde styles adında ayrı bir klasör oluşturalım. Bu, tüm stillerimizi saklayacaktır. Ayrıca stiller klasörü içinde bir globalStyles.js dosyası oluşturalım. Tarz bileşenlerinde global stil oluşturmak için createGlobalStyle içe aktarmamız gerekir.

 import { createGlobalStyle } from "styled-components";

Daha sonra stillerimizi aşağıdaki gibi tanımlayabiliriz:

 export const GlobalStyle = createGlobalStyle` ... `

Tarz bileşenleri, sahne öğelerini tanımlamak için şablon değişmezini kullanır. Bu literal içinde geleneksel CSS kodlarımızı yazabiliriz.

Ayrıca define.js adlı bir dosyada definition.js deviceWidth de içe aktardık. deviceWidth , medya sorgularımızı ayarlamak için kesme noktalarının tanımını içerir.

 import { deviceWidth } from "./definition";

Uygulamamızın akışını kontrol etmek için overflow'u gizli olarak ayarladık.

 html, body{ overflow-x: hidden; }

Ayrıca başlık stilini .header stil seçicisini kullanarak tanımladık.

 .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%; } ... }

Burada, arka plan rengi, z-endeksi, dolgu ve diğer birçok geleneksel CSS özelliği gibi çeşitli stiller tanımlanır.

Arka plan rengini ayarlamak için styled-components props kullandık. Bu, bileşenimizden geçirilebilecek dinamik değişkenleri ayarlamamıza izin verir. Ayrıca, tema geçişimizden en iyi şekilde yararlanmamızı sağlamak için tema değişkenini de geçtik.

Tema oluşturma burada mümkündür çünkü tüm uygulamamızı stilize edilmiş bileşenlerden ThemeProvider ile sardık. Bunun hakkında birazdan konuşacağız. Ayrıca, CSS flexbox kullandık ve tarayıcıya göre sabit kalmasını sağlamak için konumu fixed olarak ayarladık. Başlıkları mobil uyumlu hale getirmek için kesme noktalarını da tanımladık.

İşte globalStyles.js dosyamızın tam kodu.

 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}; } `;

Saf CSS kodunu değişmezin içinde yazdığımıza dikkat edin, ancak birkaç istisna var. Tarz bileşenleri, sahne öğelerini geçmemize izin verir. Bununla ilgili daha fazla bilgiyi belgelerde bulabilirsiniz.

Global stiller tanımlamanın yanı sıra, tek tek sayfalar için stiller tanımlayabiliriz.

Örneğin, styles klasöründeki PersonListPage.js tanımlanan PersonStyle.js stili buradadır.

 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; } } } } `;

İlk önce styled'den styled styled-components ve deviceWidth definition dosyasından içe aktardık. Daha sonra PersonsListContainer stillerimizi tutmak için bir div olarak tanımladık. Medya sorgularını ve oluşturulan kesme noktalarını kullanarak, çeşitli kesme noktaları ayarlayarak sayfayı mobil uyumlu hale getirdik.

Burada küçük, büyük ve çok büyük ekranlar için yalnızca standart tarayıcı kesme noktalarını kullandık. Ayrıca, içeriğimizi sayfada düzgün bir şekilde biçimlendirmek ve görüntülemek için CSS esnek kutusundan ve ızgarasından en iyi şekilde yararlandık.

Bu stili PersonListPage.js dosyamızda kullanmak için basitçe import ettik ve aşağıdaki gibi sayfamıza ekledik.

 import React from "react"; const PersonsListPage = () => { return ( <PersonsListContainer> ... </PersonsListContainer> ); }; export default PersonsListPage;

Sarmalayıcı bir div çıktısı verir çünkü onu stillerimizde div olarak tanımladık.

Tema Ekleme ve Tamamlama

Uygulamamıza tema eklemek her zaman harika bir özelliktir. Bunun için aşağıdakilere ihtiyacımız var:

  • Ayrı bir dosyada tanımlanan özel temalarımız (bizim durum definition.js dosyamızda).
  • Redux eylemlerimizde ve redüktörlerimizde tanımlanan mantık.
  • Temamızı uygulamamızda çağırmak ve bileşen ağacından geçirmek.

Bunu kontrol edelim.

İşte definition.js dosyasındaki theme nesnemiz.

 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)" } };

Açık ve koyu temalar için çeşitli renk özellikleri ekledik. Renkler, hem aydınlık hem de karanlık modda görünürlük sağlamak için özenle seçilmiştir. Temalarınızı istediğiniz gibi tanımlayabilirsiniz. Bu zor ve hızlı bir kural değildir.

Ardından, işlevselliği Redux'a ekleyelim.

Redux eylemleri klasörümüzde globalActions.js oluşturduk ve aşağıdaki kodları ekledik.

 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)); };

Burada, tanımlı temalarımızı basitçe içe aktardık. İhtiyacımız olan temaların yükünü ileterek ilgili eylemleri gönderdi. Yük sonuçları, hem açık hem de koyu temalar için aynı tuşlar kullanılarak yerel depolamada saklanır. Bu, tarayıcıdaki durumları kalıcı hale getirmemizi sağlar.

Ayrıca temalar için redüktörümüzü tanımlamamız gerekiyor.

 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; } };

Bu bizim yaptığımıza çok benziyor. Eylemin türünü kontrol etmek için switch ifadesini kullandık ve ardından uygun payload döndürdük. Ayrıca, kullanıcı tarafından açık veya koyu temanın seçildiğini belirleyen bir durum light döndürdük. Bunu bileşenlerimizde kullanacağız.

Ayrıca kök redüktörümüze ekleyip saklamamız gerekiyor. İşte store.js için tam kod.

 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));

Kullanıcı yenilendiğinde temayı sürdürmemiz gerektiğinden, onu localStorage.getItem() kullanarak yerel depodan almamız ve ilk durumumuza geçirmemiz gerekiyordu.

React Uygulamamıza İşlevsellik Ekleme

Tarz bileşenleri, temaları uygulamamız üzerinden geçirmemizi sağlayan ThemeProvider sağlar. Bu işlevi eklemek için App.js dosyamızı değiştirebiliriz.

Bir göz atalım.

 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;

Temaları ThemeProvider üzerinden geçirerek, tema aksesuarlarını stillerimizde kolayca kullanabiliriz.

Örneğin bodyText özel rengimize aşağıdaki gibi rengi ayarlayabiliriz.

 color: ${(props) => props.theme.bodyText};

Özel temaları uygulamamızda renge ihtiyacımız olan her yerde kullanabiliriz.

Örneğin border-bottom tanımlamak için aşağıdakileri yapıyoruz.

 border-bottom: 2px solid ${(props) => props.theme.goldish};

Çözüm

Sanity.io'yu inceleyerek, kurarak ve React uygulamamıza bağlayarak başladık. Ardından Redux'u kurduk ve API'mizi sorgulamak için GROQ dilini kullandık. React react-redux kullanarak Redux'u React uygulamamıza nasıl bağlayıp kullanacağımızı, stil bileşenlerini ve temaları nasıl kullanacağımızı gördük.

Ancak, bu teknolojilerle mümkün olanın sadece yüzeyini kazıdık. GitHub depomdaki kod örneklerini gözden geçirmenizi ve bu teknolojileri öğrenmek ve ustalaşmak için tamamen farklı bir proje üzerinde ellerinizi denemenizi tavsiye ederim.

Kaynaklar

  • akıl sağlığı belgeleri
  • Kapehe'den Sanity.io ile Blog Nasıl Oluşturulur
  • Redux Belgeleri
  • Tarz Bileşenleri Belgeleri
  • GROQ Hile Sayfası
  • Malzeme Kullanıcı Arayüzü Belgeleri
  • Redux Middleware ve Yan Etkiler
  • Redux Thunk Belgeleri