สร้างเว็บแอปด้วย React, Redux และ Sanity.io
เผยแพร่แล้ว: 2022-03-10วิวัฒนาการอย่างรวดเร็วของแพลตฟอร์มดิจิทัลได้วางข้อจำกัดที่ร้ายแรงบน CMS แบบเดิม เช่น Wordpress แพลตฟอร์มเหล่านี้ทำงานควบคู่กัน ไม่ยืดหยุ่น และมุ่งเน้นที่โครงการมากกว่าที่ผลิตภัณฑ์ โชคดีที่ CMS แบบไม่ใช้หัวหลายตัวได้รับการพัฒนาเพื่อรับมือกับความท้าทายเหล่านี้และอีกมากมาย
ต่างจาก CMS ทั่วไปตรงที่ CMS ที่ไม่มีส่วนหัว ซึ่งสามารถอธิบายได้ว่าเป็น Software as a Service (SaaS) สามารถใช้ในการพัฒนาเว็บไซต์ แอพมือถือ จอแสดงผลดิจิทัล และอื่นๆ อีกมากมาย สามารถใช้บนแพลตฟอร์มที่ไร้ขีดจำกัด หากคุณกำลังมองหา CMS ที่ไม่ขึ้นกับแพลตฟอร์ม นักพัฒนาต้องมาก่อน และให้การสนับสนุนข้ามแพลตฟอร์ม คุณไม่จำเป็นต้องมองไปไกลกว่า CMS ที่ไม่มีส่วนหัว
CMS ที่ไม่มีส่วนหัวเป็นเพียง CMS ที่ไม่มีส่วนหัว head
นี้หมายถึงส่วนหน้าหรือชั้นการนำเสนอในขณะที่ body
หมายถึงส่วนหลังหรือที่เก็บเนื้อหา นี้มีประโยชน์ที่น่าสนใจมากมาย ตัวอย่างเช่น ช่วยให้นักพัฒนาสามารถเลือกส่วนหน้าที่ต้องการได้ และคุณยังสามารถออกแบบเลเยอร์การนำเสนอได้ตามที่คุณต้องการ
มี CMS ที่ไม่มีส่วนหัวอยู่มากมาย บางตัวที่ได้รับความนิยมมากที่สุด ได้แก่ Strapi, Contentful, Contentstack, Sanity, Butter CMS, Prismic, Storyblok, Directus เป็นต้น CMS ที่ไม่มีส่วนหัวเหล่านี้ใช้ API และมีจุดแข็งเฉพาะตัว ตัวอย่างเช่น CMS เช่น Sanity, Strapi, Contentful และ Storyblok นั้นฟรีสำหรับโครงการขนาดเล็ก
CMS ที่ไม่มีส่วนหัวเหล่านี้อิงตามเทคโนโลยีสแต็คต่างๆ เช่นกัน แม้ว่า Sanity.io จะอิงจาก React.js แต่ Storyblok ก็อิงตาม Vue.js ในฐานะนักพัฒนา React นี่คือเหตุผลหลักว่าทำไมฉันจึงเลือกความสนใจใน Sanity อย่างรวดเร็ว อย่างไรก็ตาม เนื่องจากเป็น CMS ที่ไม่มีส่วนหัว แต่ละแพลตฟอร์มจึงสามารถเชื่อมต่อกับส่วนหน้าใดก็ได้ ไม่ว่าจะเป็น Angular, Vue หรือ React
CMS ที่ไม่มีส่วนหัวเหล่านี้แต่ละรายการมีทั้งแบบฟรีและแบบเสียเงินซึ่งแสดงถึงการกระโดดของราคาอย่างมีนัยสำคัญ แม้ว่าแผนบริการแบบชำระเงินเหล่านี้จะมีฟีเจอร์มากกว่า แต่คุณก็ไม่ต้องจ่ายมากขนาดนั้นสำหรับโปรเจ็กต์ขนาดเล็กถึงขนาดกลาง Sanity พยายามแก้ปัญหานี้โดยแนะนำตัวเลือกการจ่ายตามการใช้งาน ด้วยตัวเลือกเหล่านี้ คุณจะสามารถจ่ายสำหรับสิ่งที่คุณใช้และหลีกเลี่ยงการกระโดดของราคา
อีกเหตุผลที่ฉันเลือก Sanity.io ก็คือภาษา GROQ ของพวกเขา สำหรับฉัน Sanity นั้นโดดเด่นกว่าคนอื่นๆ ด้วยการนำเสนอเครื่องมือนี้ Graphical-Relational Object Queries (GROQ) ช่วยลดเวลาในการพัฒนา ช่วยให้คุณได้เนื้อหาที่ต้องการในรูปแบบที่คุณต้องการ และยังช่วยให้นักพัฒนาสร้างเอกสารด้วยโมเดลเนื้อหาใหม่โดยไม่ต้องเปลี่ยนโค้ด
ยิ่งไปกว่านั้น นักพัฒนาไม่จำกัดภาษา GROQ คุณยังสามารถใช้ GraphQL หรือแม้แต่ axios
ดั้งเดิมและ fetch
ในแอป React ของคุณเพื่อสอบถามแบ็กเอนด์ เช่นเดียวกับ CMS ที่ไม่มีส่วนหัวอื่นๆ ส่วนใหญ่ Sanity มีเอกสารประกอบที่ครอบคลุมซึ่งมีคำแนะนำที่เป็นประโยชน์ในการสร้างบนแพลตฟอร์ม
หมายเหตุ: บทความนี้ต้องการความเข้าใจพื้นฐานเกี่ยวกับ React, Redux และ CSS
เริ่มต้นใช้งาน Sanity.io
ในการใช้ Sanity ในเครื่องของคุณ คุณจะต้องติดตั้งเครื่องมือ Sanity CLI แม้ว่าจะสามารถติดตั้งได้เฉพาะในโปรเจ็กต์ของคุณ แต่ควรติดตั้งทั่วโลกเพื่อให้สามารถเข้าถึงได้จากแอปพลิเคชันในอนาคต
ในการดำเนินการนี้ ให้ป้อนคำสั่งต่อไปนี้ในเทอร์มินัลของคุณ
npm install -g @sanity/cli
แฟล็ก -g
ในคำสั่งด้านบนทำให้สามารถติดตั้งโกลบอลได้
ต่อไป เราต้องเริ่มต้น Sanity ในแอปพลิเคชันของเรา แม้ว่าจะสามารถติดตั้งเป็นโปรเจ็กต์แยกต่างหากได้ แต่โดยทั่วไปแล้วควรติดตั้งในแอปฟรอนต์เอนด์ของคุณ (ในกรณีนี้คือ React)
ในบล็อกของเธอ Kapehe ได้อธิบายรายละเอียดวิธีการผสานรวม Sanity เข้ากับ React การอ่านบทความก่อนดำเนินการต่อในบทช่วยสอนนี้จะเป็นประโยชน์
ป้อนคำสั่งต่อไปนี้เพื่อเริ่มต้น Sanity ในแอป React ของคุณ
sanity init
เราสามารถใช้คำสั่ง sanity
ได้เมื่อเราติดตั้งเครื่องมือ Sanity CLI คุณสามารถดูรายการคำสั่ง Sanity ที่มีได้โดยพิมพ์ sanity
หรือ sanity help
ในเทอร์มินัลของคุณ
เมื่อตั้งค่าหรือเริ่มต้นโครงการ คุณจะต้องปฏิบัติตามคำแนะนำเพื่อปรับแต่ง คุณจะต้องสร้างชุดข้อมูลและคุณยังสามารถเลือกชุดข้อมูลที่กำหนดเองซึ่งเติมข้อมูลได้ด้วย สำหรับแอปรายชื่อนี้ เราจะใช้ชุดข้อมูลภาพยนตร์ไซไฟที่กำหนดเองของ Sanity สิ่งนี้จะช่วยเราจากการป้อนข้อมูลด้วยตนเอง
ในการดูและแก้ไขชุดข้อมูลของคุณ cd
ไปที่ไดเร็กทอรีย่อย Sanity ในเทอร์มินัลของคุณและป้อน sanity start
ซึ่งมักจะทำงานบน https://localhost:3333/
คุณอาจจำเป็นต้องเข้าสู่ระบบเพื่อเข้าถึงอินเทอร์เฟซ (ตรวจสอบให้แน่ใจว่าคุณเข้าสู่ระบบด้วยบัญชีเดียวกับที่คุณใช้เมื่อเริ่มต้นโครงการ) ภาพหน้าจอของสภาพแวดล้อมแสดงอยู่ด้านล่าง
Sanity-React การสื่อสารสองทาง
Sanity และ React จำเป็นต้องสื่อสารกันเพื่อการใช้งานที่ครบถ้วน
CORS Origins การตั้งค่าใน Sanity Manager
ก่อนอื่นเราจะเชื่อมต่อแอป React กับ Sanity ในการดำเนินการนี้ ให้เข้าสู่ระบบ https://manage.sanity.io/
และค้นหา CORS origins
ภายใต้ API Settings
ในแท็บ Settings
ที่นี่ คุณจะต้องเชื่อมโยงต้นทางส่วนหน้าของคุณกับแบ็กเอนด์ Sanity แอป React ของเราทำงานบน https://localhost:3000/
โดยค่าเริ่มต้น ดังนั้นเราจึงจำเป็นต้องเพิ่มแอปนั้นใน CORS
ดังแสดงในรูปด้านล่าง
เชื่อมต่อสติกับปฏิกิริยา
Sanity เชื่อมโยง project ID
กับทุกโครงการที่คุณสร้าง รหัสนี้จำเป็นเมื่อเชื่อมต่อกับแอปพลิเคชันส่วนหน้าของคุณ คุณสามารถค้นหารหัสโครงการได้ใน Sanity Manager
แบ็กเอนด์สื่อสารกับ React โดยใช้ไลบรารีที่เรียกว่า sanity client
คุณต้องติดตั้งไลบรารีนี้ในโครงการ Sanity ของคุณโดยป้อนคำสั่งต่อไปนี้
npm install @sanity/client
สร้างไฟล์ sanitySetup.js
(ชื่อไฟล์ไม่สำคัญ) ในโฟลเดอร์ src
ของโปรเจ็กต์และป้อนรหัส React ต่อไปนี้เพื่อตั้งค่าการเชื่อมต่อระหว่าง Sanity และ React
import sanityClient from "@sanity/client" export default sanityClient({ projectId: PROJECT_ID, dataset: DATASET_NAME, useCdn: true });
เราส่ง projectId
dataset name
และ useCdn
บูลีนไปยังอินสแตนซ์ของไคลเอ็นต์ sanity ที่นำเข้าจาก @sanity/client
วิธีนี้ใช้งานได้ดีและเชื่อมต่อแอปของเรากับแบ็กเอนด์
ตอนนี้เราได้เสร็จสิ้นการเชื่อมต่อแบบสองทางแล้ว มาเริ่มกันเลยเพื่อสร้างโครงการของเรา
การตั้งค่าและเชื่อมต่อ Redux กับแอพของเรา
เราจำเป็นต้องมีการพึ่งพาบางอย่างเพื่อทำงานกับ Redux ในแอป React ของเรา เปิดเทอร์มินัลของคุณในสภาพแวดล้อม React และป้อนคำสั่ง bash ต่อไปนี้
npm install redux react-redux redux-thunk
Redux เป็นไลบรารีการจัดการสถานะส่วนกลางที่สามารถใช้กับเฟรมเวิร์กส่วนหน้าและไลบรารีส่วนใหญ่ เช่น React อย่างไรก็ตาม เราจำเป็นต้องมีเครื่องมือตัวกลาง react-redux
เพื่อเปิดใช้งานการสื่อสารระหว่าง ร้าน Redux และแอปพลิเคชัน React ของเรา Redux thunk จะช่วยให้เราคืนฟังก์ชันแทนวัตถุการกระทำจาก Redux
แม้ว่าเราจะเขียนเวิร์กโฟลว์ Redux ทั้งหมดในไฟล์เดียวได้ แต่การแยกข้อกังวลของเราแยกจากกันจะดีกว่าและดีกว่า สำหรับสิ่งนี้ เราจะแบ่งเวิร์กโฟลว์ของเราออกเป็นสามไฟล์ ได้แก่ actions
, reducers
และ store
อย่างไรก็ตาม เรายังต้องการไฟล์แยกต่างหากเพื่อจัดเก็บ action types
หรือที่เรียกว่า constants
การตั้งค่าร้านค้า
ร้านค้าเป็นไฟล์ที่สำคัญที่สุดใน Redux มันจัดระเบียบและบรรจุสถานะและส่งไปยังแอปพลิเคชัน React ของเรา
นี่คือการตั้งค่าเริ่มต้นของร้าน Redux ที่จำเป็นในการเชื่อมต่อเวิร์กโฟลว์ Redux ของเรา
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/"; export default createStore( reducers, applyMiddleware(thunk) );
ฟังก์ชัน createStore
ในไฟล์นี้ใช้พารามิเตอร์สามตัว: ตัว reducer
(จำเป็น) สถานะเริ่มต้นและตัวเพิ่มประสิทธิภาพ (โดยปกติคือมิดเดิลแวร์ ในกรณีนี้ applyMiddleware
thunk
ตัวลดขนาดของเราจะถูกเก็บไว้ในโฟลเดอร์ตัวลดและเราจะรวมและส่งออกใน index.js
reducers
โฟลเดอร์ reducers
ลด นี่คือไฟล์ที่เรานำเข้าในโค้ดด้านบน เราจะกลับมาดูไฟล์นี้อีกครั้งในภายหลัง
บทนำสู่ภาษา GROQ ของ Sanity
Sanity ยกระดับการสืบค้นข้อมูล JSON ไปอีกขั้นด้วยการแนะนำ GROQ GROQ ย่อมาจาก Graph-Relational Object Queries ตาม Sanity.io GROQ เป็นภาษาการสืบค้นแบบประกาศที่ออกแบบมาเพื่อสืบค้นคอลเลกชั่นของเอกสาร JSON ที่ไม่มีสคีมาเป็นส่วนใหญ่
Sanity ยังมอบ GROQ Playground เพื่อช่วยให้นักพัฒนาคุ้นเคยกับภาษา อย่างไรก็ตาม ในการเข้าถึงสนามเด็กเล่น คุณต้องติดตั้ง sanity vision เรียกใช้ sanity install @sanity/vision
บนเทอร์มินัลของคุณเพื่อติดตั้ง
GROQ มีรูปแบบคล้ายกับ GraphQL แต่ย่อและอ่านง่ายกว่า นอกจากนี้ GROQ ต่างจาก GraphQL ตรงที่สามารถใช้สืบค้นข้อมูล JSON ได้
ตัวอย่างเช่น ในการดึงทุกรายการในเอกสารภาพยนตร์ของเรา เราจะใช้ไวยากรณ์ GROQ ต่อไปนี้
*[_type == "movie"]
อย่างไรก็ตาม หากเราต้องการดึงเฉพาะ _ids
และ crewMembers
ในเอกสารภาพยนตร์ของเรา เราจำเป็นต้องระบุฟิลด์เหล่านั้นดังนี้
`*[_type == 'movie']{ _id, crewMembers }
ในที่นี้ เราใช้ *
เพื่อบอก GROQ ว่าเราต้องการเอกสารของภาพยนตร์ _type
ทุกฉบับ _type
เป็นแอตทริบิวต์ภายใต้คอลเลคชันภาพยนตร์ เราสามารถส่งคืนประเภทเหมือนที่เราทำกับ _id
และ crewMembers
ได้ดังนี้:
*[_type == 'movie']{ _id, _type, crewMembers }
เราจะทำงานเพิ่มเติมใน GROQ โดยนำไปใช้ในการดำเนินการ Redux ของเรา แต่คุณสามารถตรวจสอบเอกสารของ Sanity.io สำหรับ GROQ เพื่อเรียนรู้เพิ่มเติม ตารางสรุปข้อความค้นหาของ GROQ มีตัวอย่างมากมายที่จะช่วยให้คุณเชี่ยวชาญภาษาการสืบค้น
การตั้งค่าค่าคงที่
เราต้องการค่าคงที่เพื่อติดตามประเภทการดำเนินการในทุกขั้นตอนของเวิร์กโฟลว์ Redux ค่าคงที่ช่วยในการกำหนดประเภทของการดำเนินการที่ส่งในแต่ละจุดในเวลา ตัวอย่างเช่น เราสามารถติดตามได้เมื่อ API กำลังโหลด โหลดเต็มที่ และเมื่อเกิดข้อผิดพลาด
เราไม่จำเป็นต้องกำหนดค่าคงที่ในไฟล์แยกต่างหาก แต่เพื่อความเรียบง่ายและชัดเจน นี่จึงเป็นแนวทางปฏิบัติที่ดีที่สุดใน Redux
ตามแบบแผน ค่าคงที่ใน Javascript ถูกกำหนดด้วยตัวพิมพ์ใหญ่ เราจะปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดที่นี่เพื่อกำหนดค่าคงที่ของเรา ต่อไปนี้คือตัวอย่างค่าคงที่สำหรับแสดงคำขอเรียกภาพยนตร์ที่เคลื่อนไหว
export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST";
ที่นี่ เราได้สร้าง MOVIE_FETCH_REQUEST
คงที่ซึ่งระบุประเภทการกระทำของ MOVIE_FETCH_REQUEST
ซึ่งช่วยให้เราเรียกประเภทการกระทำนี้ได้อย่างง่ายดายโดยไม่ต้องใช้ strings
และหลีกเลี่ยงจุดบกพร่อง นอกจากนี้เรายังส่งออกค่าคงที่เพื่อให้สามารถใช้ได้ทุกที่ในโครงการของเรา
ในทำนองเดียวกัน เราสามารถสร้างค่าคงที่อื่นๆ สำหรับการดึงประเภทการดำเนินการที่แสดงว่าคำขอสำเร็จหรือล้มเหลวเมื่อใด รหัสที่สมบูรณ์สำหรับ movieConstants.js
มีอยู่ในรหัสด้านล่าง
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";
ในที่นี้ เราได้กำหนดค่าคงที่หลายค่าสำหรับการดึงข้อมูลภาพยนตร์หรือรายการภาพยนตร์ การจัดเรียงและการดึงข้อมูลภาพยนตร์ยอดนิยม ขอให้สังเกตว่าเราตั้งค่าคงที่เพื่อกำหนดว่าคำขอกำลัง loading
successful
และ failed
เมื่อใด
ในทำนองเดียวกัน ไฟล์ personConstants.js
ของเรามีดังต่อไปนี้:
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
เราตั้งค่ารายการค่าคงที่สำหรับการดึงข้อมูลบุคคลหรือบุคคล เรายังตั้งค่าคงที่สำหรับการนับคน ค่าคงที่เป็นไปตามแบบแผนที่อธิบายไว้สำหรับ movieConstants.js
และเรายังส่งออกค่าคงที่เพื่อให้สามารถเข้าถึงได้ในส่วนอื่นๆ ของแอปพลิเคชันของเรา
สุดท้าย เราจะใช้โหมดสว่างและมืดในแอป ดังนั้นเราจึงมีไฟล์ค่าคงที่ globalConstants.js
ลองมาดูที่มัน
export const SET_LIGHT_THEME = "SET_LIGHT_THEME"; export const SET_DARK_THEME = "SET_DARK_THEME";
ที่นี่เราตั้งค่าคงที่เพื่อกำหนดเมื่อส่งโหมดสว่างหรือมืด SET_LIGHT_THEME
กำหนดเมื่อผู้ใช้เปลี่ยนไปใช้ธีมสว่าง และ SET_DARK_THEME
กำหนดเมื่อเลือกธีมสีเข้ม นอกจากนี้เรายังส่งออกค่าคงที่ของเราตามที่แสดง
การตั้งค่าการดำเนินการ
ตามแบบแผน การกระทำของเราจะถูกเก็บไว้ในโฟลเดอร์แยกต่างหาก การดำเนินการจะถูกจัดกลุ่มตามประเภท ตัวอย่างเช่น การกระทำของภาพยนตร์ของเราจะถูกเก็บไว้ใน movieActions.js
ในขณะที่การกระทำของบุคคลของเราจะถูกเก็บไว้ในไฟล์ personActions.js
เรายังมี globalActions.js
เพื่อดูแลการสลับธีมจากโหมดสว่างเป็นโหมดมืด
มาดึงหนังทั้งหมดใน 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 }); } };
จำได้ไหมว่าเมื่อเราสร้างไฟล์ sanitySetup.js
เพื่อเชื่อมต่อ React กับแบ็กเอนด์ Sanity ของเรา ที่นี่ เรานำเข้าการตั้งค่าเพื่อให้เราสามารถสอบถามแบ็กเอนด์สติของเราโดยใช้ GROQ นอกจากนี้เรายังนำเข้าค่าคงที่สองสามตัวที่ส่งออกจากไฟล์ movieConstants.js
ในโฟลเดอร์ constants
ต่อไป เราได้สร้างฟังก์ชันการทำงาน fetchAllMovies
เพื่อดึงข้อมูลภาพยนตร์ทุกเรื่องในคอลเล็กชันของเรา แอปพลิเคชั่น React แบบดั้งเดิมส่วนใหญ่ใช้ axios
หรือ fetch
เพื่อดึงข้อมูลจากแบ็กเอนด์ แต่ในขณะที่เราใช้สิ่งใดก็ได้ที่นี่ เรากำลังใช้ GROQ
ของ Sanity ในการเข้าสู่โหมด GROQ
เราต้องเรียกใช้ sanityAPI.fetch()
ตามที่แสดงในโค้ดด้านบน ที่นี่ sanityAPI
คือการเชื่อมต่อ React-Sanity ที่เราตั้งค่าไว้ก่อนหน้านี้ ส่งคืน Promise
ดังนั้นจึงต้องเรียกแบบอะซิงโครนัส เราใช้ไวยากรณ์ async-await
ที่นี่ แต่เรายังสามารถใช้ไวยากรณ์ .then
ได้
เนื่องจากเราใช้ thunk
ในแอปพลิเคชันของเรา เราจึงสามารถส่งคืนฟังก์ชันแทนอ็อบเจ็กต์การกระทำได้ อย่างไรก็ตาม เราเลือกที่จะส่งคำสั่ง return ในหนึ่งบรรทัด
const fetchAllMovies = () => async (dispatch) => { ... }
โปรดทราบว่าเราสามารถเขียนฟังก์ชันด้วยวิธีนี้:
const fetchAllMovies = () => { return async (dispatch)=>{ ... } }
โดยทั่วไป ในการดึงข้อมูลภาพยนตร์ทั้งหมด ก่อนอื่นเราได้ส่งประเภทการดำเนินการที่ติดตามเมื่อคำขอยังคงโหลดอยู่ จากนั้นเราใช้ไวยากรณ์ GROQ ของ Sanity เพื่อสืบค้นเอกสารภาพยนตร์แบบอะซิงโครนัส เราดึงข้อมูล _id
และ URL โปสเตอร์ของข้อมูลภาพยนตร์ จากนั้นเราส่งคืนเพย์โหลดที่มีข้อมูลที่ได้รับจาก API
ในทำนองเดียวกัน เราสามารถเรียกภาพยนตร์ตาม _id
ของพวกเขา เรียงลำดับภาพยนตร์ และรับภาพยนตร์ยอดนิยมที่สุด
นอกจากนี้เรายังสามารถดึงภาพยนตร์ที่ตรงกับการอ้างอิงของบุคคลใดบุคคลหนึ่ง เราทำสิ่งนี้ในฟังก์ชัน 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 }); } };
ฟังก์ชันนี้รับอาร์กิวเมนต์และตรวจสอบว่า person._ref
ใน castMembers
หรือ crewMembers
ตรงกับอาร์กิวเมนต์ที่ส่งผ่านหรือไม่ เราส่งคืนภาพยนตร์ _id
, poster url
และ title
ข้างๆ นอกจากนี้เรายังส่งการดำเนินการประเภท MOVIES_REF_FETCH_SUCCESS
โดยแนบเพย์โหลดของข้อมูลที่ส่งคืน และหากเกิดข้อผิดพลาด เราจะส่งการดำเนินการประเภท MOVIE_REF_FETCH_FAIL
โดยแนบเพย์โหลดของข้อความแสดงข้อผิดพลาดด้วยตัวห่อตัว try-catch
ในฟังก์ชัน fetchMovieById
เราใช้ GROQ
เพื่อดึงข้อมูลภาพยนตร์ที่ตรงกับ id
เฉพาะที่ส่งไปยังฟังก์ชัน
ไวยากรณ์ GROQ
สำหรับฟังก์ชันแสดงอยู่ด้านล่าง
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
เราเริ่มต้นด้วยการเลือกเอกสารประเภท movie
ทั้งหมด แต่เราดำเนินการต่อไปเพื่อเลือกเฉพาะเอกสารที่มี id ที่ให้มากับฟังก์ชันเท่านั้น เนื่องจากเราตั้งใจที่จะแสดงรายละเอียดมากมายสำหรับภาพยนตร์ เราจึงระบุแอตทริบิวต์จำนวนหนึ่งเพื่อดึงข้อมูล
เราดึง id
ภาพยนตร์และคุณลักษณะบางอย่างในอาร์เรย์ castMembers
ได้แก่ ref
, characterName
, ชื่อบุคคล และรูปภาพของบุคคล นอกจากนี้เรายังเปลี่ยนนามแฝงจาก castMembers
เป็น cast
เช่นเดียวกับ castMembers
เราเลือกคุณลักษณะบางอย่างจากอาร์เรย์ของ crewMembers
ได้แก่ ref
department
job
ชื่อบุคคลและภาพของบุคคล เรายังเปลี่ยนนามแฝงจาก crewMembers
เป็น crew
ในทำนองเดียวกัน เราเลือกข้อความภาพรวม ความนิยม URL โปสเตอร์ของภาพยนตร์ วันที่ออกฉายและชื่อภาพยนตร์
ภาษา GROQ ของ Sanity ช่วยให้เราจัดเรียงเอกสารได้ ในการเรียงลำดับรายการ เราส่ง คำสั่งซื้อ ถัดจากตัวดำเนินการ ไปป์
ตัวอย่างเช่น หากเราต้องการจัดเรียงภาพยนตร์ตาม releaseDate
โดยเรียงลำดับจากน้อยไปมาก เราสามารถดำเนินการดังต่อไปนี้
const data = await sanityAPI.fetch( `*[_type == 'movie']{ ... } | order(releaseDate, asc)` );
เราใช้แนวคิดนี้ในฟังก์ชัน sortMoviesBy
เพื่อเรียงลำดับจากน้อยไปมากหรือมากไปหาน้อย
มาดูฟังก์ชันด้านล่างนี้กัน
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 }); } };
เราเริ่มต้นด้วยการส่งการดำเนินการประเภท MOVIES_SORT_REQUEST
เพื่อพิจารณาว่าคำขอกำลังโหลดเมื่อใด จากนั้นเราใช้ไวยากรณ์ GROQ
เพื่อจัดเรียงและดึงข้อมูลจากคอลเลคชัน movie
รายการที่จะจัดเรียงตามมีอยู่ใน item
ตัวแปรและโหมดการจัดเรียง (จากน้อยไปมากหรือมากไปหาน้อย) จะถูกจัดเตรียมใน type
ตัวแปร ดังนั้นเราจึงส่งคืน id
, โปสเตอร์ url และชื่อ เมื่อข้อมูลถูกส่งกลับ เราได้ส่งการกระทำประเภท MOVIES_SORT_SUCCESS
และหากล้มเหลว เราจะส่งการดำเนินการประเภท MOVIES_SORT_FAIL
แนวคิด GROQ
ที่คล้ายกันใช้กับฟังก์ชัน getMostPopular
ไวยากรณ์ GROQ
แสดงอยู่ด้านล่าง
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]` );
ข้อแตกต่างเพียงอย่างเดียวที่นี่คือ เราจัดเรียงภาพยนตร์ตามความนิยมในลำดับจากมากไปน้อย จากนั้นเลือกเฉพาะสามเรื่องแรกเท่านั้น รายการจะถูกส่งคืนในดัชนีแบบอิงศูนย์ ดังนั้นสามรายการแรกคือรายการที่ 0, 1 และ 2 หากเราต้องการดึงข้อมูลสิบรายการแรก เราสามารถส่ง [0..9]
ไปยังฟังก์ชันได้
นี่คือรหัสที่สมบูรณ์สำหรับการกระทำของภาพยนตร์ในไฟล์ 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 };
การตั้งค่าตัวลดขนาด
Reducers เป็นหนึ่งในแนวคิดที่สำคัญที่สุดใน Redux พวกเขาใช้สถานะก่อนหน้าและกำหนดการเปลี่ยนแปลงสถานะ
โดยทั่วไป เราจะใช้คำสั่ง switch เพื่อดำเนินการเงื่อนไขสำหรับการดำเนินการแต่ละประเภท ตัวอย่างเช่น เราสามารถส่งคืน loading
เมื่อประเภทการดำเนินการหมายถึงการโหลด และจากนั้นจึงกลับมาโหลดเมื่อระบุว่าสำเร็จหรือผิดพลาด คาดว่าจะใช้ใน initial state
และการ action
เป็นอาร์กิวเมนต์
ไฟล์ movieReducers.js
ของเรามีตัวลดขนาดต่างๆ เพื่อให้ตรงกับการดำเนินการที่กำหนดไว้ในไฟล์ movieActions.js
อย่างไรก็ตาม ตัวลดแต่ละตัวมีรูปแบบและโครงสร้างที่คล้ายคลึงกัน ความแตกต่างเพียงอย่างเดียวคือ constants
เรียกใช้และค่าที่ส่งคืน
เริ่มต้นด้วยการดู fetchAllMoviesReducer
ในไฟล์ 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; } };
เช่นเดียวกับตัวลดทั้งหมด fetchAllMoviesReducer
ใช้ออบเจ็กต์สถานะเริ่มต้น ( state
) และอ็อบเจ็กต์ action
เป็นอาร์กิวเมนต์ เราใช้คำสั่ง switch เพื่อตรวจสอบประเภทการดำเนินการในแต่ละช่วงเวลา หากสอดคล้องกับ MOVIES_FETCH_REQUEST
เราจะคืนค่าการโหลดเป็น true เพื่อให้เราสามารถแสดงตัวบ่งชี้การโหลดแก่ผู้ใช้
ถ้ามันสอดคล้องกับ MOVIES_FETCH_SUCCESS
เราจะปิดตัวแสดงการโหลด จากนั้นส่งคืนเพย์โหลดการดำเนินการใน movies
ตัวแปร แต่ถ้าเป็น MOVIES_FETCH_FAIL
เราก็ปิดการโหลดแล้วส่งคืนข้อผิดพลาด เราต้องการตัวเลือกในการรีเซ็ตภาพยนตร์ของเราด้วย ซึ่งจะทำให้เราสามารถล้างสถานะต่างๆ เมื่อเราจำเป็นต้องทำเช่นนั้น
เรามีโครงสร้างเดียวกันสำหรับตัวลดขนาดอื่นๆ movieReducers.js
ฉบับสมบูรณ์แสดงอยู่ด้านล่าง
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
ตัวอย่างเช่น ฟังก์ชัน fetchAllPersonsReducer
กำหนดสถานะสำหรับการดึงข้อมูลบุคคลทั้งหมดในฐานข้อมูล

นี้ได้รับในรหัสด้านล่าง
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; } };
เช่นเดียวกับ fetchAllMoviesReducer
เรากำหนด fetchAllPersonsReducer
ด้วย state
และ action
เป็นอาร์กิวเมนต์ สิ่งเหล่านี้คือการตั้งค่ามาตรฐานสำหรับตัวลด Redux จากนั้นเราใช้คำสั่ง switch เพื่อตรวจสอบประเภทการกระทำ และหากเป็นประเภท PERSONS_FETCH_REQUEST
เราจะคืนค่าการโหลดเป็น true หากเป็น PERSONS_FETCH_SUCCESS
เราจะปิดการโหลดและส่งคืนเพย์โหลด และหากเป็น PERSONS_FETCH_FAIL
เราจะส่งคืนข้อผิดพลาด
รวมตัวลด
ฟังก์ชัน combineReducers
ของ Redux ช่วยให้เราสามารถรวมตัวลดขนาดได้มากกว่าหนึ่งตัวและส่งผ่านไปยังร้านค้า เราจะรวมภาพยนตร์และตัวลดขนาดบุคคลของเราไว้ในไฟล์ index.js
ภายในโฟลเดอร์ reducers
ลด
ลองมาดูที่มัน
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 });
ที่นี่เรานำเข้าตัวลดขนาดทั้งหมดจากภาพยนตร์ บุคคล และไฟล์ตัวลดทั่วโลก และส่งผ่านไป combineReducers
ฟังก์ชันรวมตัวลด ฟังก์ชัน combineReducers
รับออบเจ็กต์ที่ช่วยให้เราผ่านตัวลดขนาดทั้งหมดได้ เรายังเพิ่มนามแฝงให้กับอาร์กิวเมนต์ในกระบวนการได้อีกด้วย
เราจะดำเนินการกับ globalReducers
ในภายหลัง
ตอนนี้เราสามารถส่งตัวลดขนาดในไฟล์ Redux store.js
ได้แล้ว นี้แสดงอยู่ด้านล่าง
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/index"; export default createStore(reducers, initialState, applyMiddleware(thunk));
หลังจากตั้งค่าเวิร์กโฟลว์ Redux แล้ว มาตั้งค่าแอปพลิเคชัน React ของเรากัน
การตั้งค่าแอปพลิเคชัน React ของเรา
แอปพลิเคชั่นตอบโต้ของเราจะแสดงรายการภาพยนตร์และนักแสดงและทีมงานที่เกี่ยวข้อง เราจะใช้ react-router-dom
สำหรับการกำหนดเส้นทางและ styled-components
สำหรับการกำหนดสไตล์แอพ เรายังจะใช้ Material UI สำหรับไอคอนและส่วนประกอบ UI บางอย่างอีกด้วย
ป้อนคำสั่ง bash
ต่อไปนี้เพื่อติดตั้งการพึ่งพา
npm install react-router-dom @material-ui/core @material-ui/icons query-string
นี่คือสิ่งที่เรากำลังจะสร้าง:
เชื่อมต่อ Redux กับแอป React ของเรา
React-redux
มาพร้อมกับฟังก์ชัน ผู้ให้บริการ ที่ช่วยให้เราเชื่อมต่อแอปพลิเคชันของเรากับร้าน Redux ในการดำเนินการนี้ เราต้องส่งอินสแตนซ์ของร้านค้าไปยังผู้ให้บริการ เราสามารถทำได้ทั้งในไฟล์ index.js
หรือ App.js
นี่คือไฟล์ index.js ของเรา
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") );
ที่นี่ เรานำเข้า Provider
จาก react-redux
และ store
จากร้าน Redux ของเรา จากนั้นเรารวมทรีส่วนประกอบทั้งหมดของเรากับผู้ให้บริการ โดยส่งร้านค้าไปให้
ต่อไป เราต้องการ react-router-dom
สำหรับการกำหนดเส้นทางในแอปพลิเคชัน React ของเรา react-router-dom
มาพร้อมกับ BrowserRouter
, Switch
และ Route
ที่สามารถใช้เพื่อกำหนดเส้นทางและเส้นทางของเรา
เราทำสิ่งนี้ในไฟล์ App.js
ของเรา นี้แสดงอยู่ด้านล่าง
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;
นี่คือการตั้งค่ามาตรฐานสำหรับการกำหนดเส้นทางด้วย react-router-dom คุณสามารถตรวจสอบได้ในเอกสารประกอบ เรานำเข้าส่วนประกอบของเรา Header
, Footer
, PersonsList
และ MovieList
จากนั้นเราตั้งค่า react-router-dom
โดยห่อทุกอย่างใน Router
และ Switch
เนื่องจากเราต้องการให้เพจของเราใช้ส่วนหัวและส่วนท้ายร่วมกัน เราจึงต้องส่งองค์ประกอบ <Header />
และ <Footer />
ก่อนที่จะปิดโครงสร้างด้วย Switch
เรายังทำสิ่งที่คล้ายกันกับองค์ประกอบ main
เนื่องจากเราต้องการรวมแอปพลิเคชันทั้งหมด
เราส่งแต่ละองค์ประกอบไปยังเส้นทางโดยใช้ Route
จาก react-router-dom
การกำหนดหน้าและส่วนประกอบของเรา
แอปพลิเคชันของเราได้รับการจัดระเบียบอย่างมีโครงสร้าง ส่วนประกอบที่ใช้ซ้ำได้จะถูกเก็บไว้ในโฟลเดอร์ components
ในขณะที่เพจถูกเก็บไว้ในโฟลเดอร์ pages
pages
ของเราประกอบด้วย movieListPage.js
, moviePage.js
, PersonListPage.js
และ PersonPage.js
MovieListPage.js
แสดงรายการภาพยนตร์ทั้งหมดในแบ็กเอนด์ Sanity.io ของเรา รวมถึงภาพยนตร์ยอดนิยมที่สุด
ในการแสดงรายการภาพยนตร์ทั้งหมด เราเพียงแค่ dispatch
การกระทำ fetchAllMovies
ที่กำหนดไว้ในไฟล์ movieAction.js
ของเรา เนื่องจากเราจำเป็นต้องดึงรายการทันทีที่หน้าโหลด เราจึงต้องกำหนดรายการใน useEffect
นี้แสดงอยู่ด้านล่าง
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
และ useSelector
Hooks เราสามารถส่งการกระทำของ Redux และเลือกสถานะที่เหมาะสมจากร้าน Redux โปรดสังเกตว่าสถานะ loading
error
และ movies
ถูกกำหนดไว้ในฟังก์ชัน Reducer ของเรา และเลือกที่นี่โดยใช้ useSelector
Hook จาก React Redux สถานะเหล่านี้ ได้แก่ loading
error
และ movies
จะพร้อมใช้งานทันทีที่เราส่งการดำเนินการ fetchAllMovies()
เมื่อเราได้รับรายชื่อภาพยนตร์แล้ว เราสามารถแสดงมันในแอปพลิเคชันของเราโดยใช้ฟังก์ชัน map
หรือตามต้องการ
นี่คือรหัสที่สมบูรณ์สำหรับไฟล์ 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
เราเริ่มต้นด้วยการส่งแอ็คชั่นภาพยนตร์ getMostPopular
(การดำเนินการนี้จะเลือกภาพยนตร์ที่ได้รับความนิยมสูงสุด) ใน useEffect
Hook ซึ่งช่วยให้เราสามารถเรียกค้นภาพยนตร์ยอดนิยมได้ทันทีที่หน้าโหลด นอกจากนี้ เราอนุญาตให้ผู้ใช้จัดเรียงภาพยนตร์ตาม releaseDate
และ popularity
สิ่งนี้ได้รับการจัดการโดยการดำเนินการ sortMoviesBy
ที่ส่งไปในโค้ดด้านบน นอกจากนี้ เราได้ส่ง fetchAllMovies
โดยขึ้นอยู่กับพารามิเตอร์การสืบค้น
นอกจากนี้ เรายังใช้ useSelector
Hook เพื่อเลือกตัวลดขนาดที่สอดคล้องกันสำหรับแต่ละการกระทำเหล่านี้ เราเลือกสถานะสำหรับการ loading
error
และ movies
สำหรับตัวลดแต่ละตัว
หลังจากได้รับ movies
จากตัวลดขนาดแล้ว เราสามารถแสดงให้ผู้ใช้ดูได้ ที่นี่ เราได้ใช้ฟังก์ชัน map
ES6 เพื่อทำสิ่งนี้ อันดับแรก เราแสดงตัวโหลดทุกครั้งที่มีการโหลดสถานะของภาพยนตร์ และหากมีข้อผิดพลาด เราจะแสดงข้อความแสดงข้อผิดพลาด สุดท้าย หากเราได้ภาพยนตร์ เราจะแสดงภาพภาพยนตร์ให้ผู้ใช้เห็นโดยใช้ฟังก์ชัน map
เรารวมองค์ประกอบทั้งหมดไว้ในองค์ประกอบ MovieListContainer
แท็ก <MovieListContainer> … </MovieListContainer>
เป็น div
ที่กำหนดโดยใช้องค์ประกอบที่มีสไตล์ เราจะพิจารณาสั้น ๆ ในเร็วๆ นี้
จัดแต่งทรงผมแอพของเราด้วยส่วนประกอบที่มีสไตล์
ส่วนประกอบที่มีสไตล์ช่วยให้เราจัดรูปแบบหน้าเว็บและส่วนประกอบของเราได้เป็นรายบุคคล นอกจากนี้ยังมีคุณสมบัติที่น่าสนใจบางอย่าง เช่น การ inheritance
Theming
การ passing of props
เป็นต้น
แม้ว่าเราต้องการจัดรูปแบบหน้าเว็บของเราเป็นรายบุคคลอยู่เสมอ แต่บางครั้งการจัดรูปแบบทั่วโลกอาจเป็นที่ต้องการ ที่น่าสนใจคือองค์ประกอบที่มีสไตล์ช่วยทำให้สำเร็จได้ด้วยฟังก์ชัน createGlobalStyle
ในการใช้องค์ประกอบที่มีสไตล์ในแอปพลิเคชันของเรา เราจำเป็นต้องติดตั้ง เปิดเทอร์มินัลของคุณในโปรเจ็กต์ react และป้อนคำสั่ง bash
ต่อไปนี้
npm install styled-components
หลังจากติดตั้ง styled-components แล้ว มาเริ่มกันที่สไตล์สากลของเรากัน
มาสร้างโฟลเดอร์แยกต่างหากในไดเร็กทอรี src
ของเราที่ชื่อ styles
นี้จะเก็บสไตล์ของเราทั้งหมด มาสร้างไฟล์ globalStyles.js
ภายในโฟลเดอร์สไตล์กันเถอะ ในการสร้างสไตล์สากลในองค์ประกอบที่มีสไตล์ เราต้องนำเข้า createGlobalStyle
import { createGlobalStyle } from "styled-components";
จากนั้นเราสามารถกำหนดรูปแบบของเราได้ดังนี้:
export const GlobalStyle = createGlobalStyle` ... `
องค์ประกอบที่มีสไตล์ใช้ประโยชน์จากเทมเพลตตามตัวอักษรเพื่อกำหนดอุปกรณ์ประกอบฉาก ภายในตัวอักษรนี้ เราสามารถเขียนโค้ด CSS
ดั้งเดิมของเราได้
นอกจากนี้เรายังนำเข้า deviceWidth
ที่กำหนดไว้ในไฟล์ชื่อ definition.js
deviceWidth
มีคำจำกัดความของเบรกพอยต์สำหรับการตั้งค่าคิวรี่สื่อของเรา
import { deviceWidth } from "./definition";
เราตั้งค่าโอเวอร์โฟลว์เป็นซ่อนเพื่อควบคุมโฟลว์ของแอปพลิเคชันของเรา
html, body{ overflow-x: hidden; }
นอกจากนี้เรายังกำหนดรูปแบบส่วนหัวโดยใช้ตัวเลือกรูปแบบ . .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%; } ... }
ที่นี่มีการกำหนดสไตล์ต่างๆ เช่น สีพื้นหลัง ดัชนี z ช่องว่างภายใน และคุณสมบัติ CSS ดั้งเดิมอื่นๆ อีกมากมาย
เราใช้ props
ที่มีสไตล์เพื่อกำหนดสีพื้นหลัง ซึ่งช่วยให้เราสามารถตั้งค่าตัวแปรไดนามิกที่สามารถส่งผ่านจากคอมโพเนนต์ของเราได้ ยิ่งกว่านั้น เรายังส่งตัวแปรของธีมเพื่อให้เราสามารถใช้การสลับธีมของเราให้เกิดประโยชน์สูงสุด
ชุดรูปแบบเป็นไปได้ที่นี่เนื่องจากเราได้รวมแอปพลิเคชันทั้งหมดของเราด้วย ThemeProvider
จากองค์ประกอบที่มีสไตล์ เราจะพูดถึงเรื่องนี้ในอีกสักครู่ นอกจากนี้ เราใช้ CSS flexbox
เพื่อจัดรูปแบบส่วนหัวของเราอย่างเหมาะสมและตั้งค่าตำแหน่งให้ fixed
เพื่อให้แน่ใจว่ายังคงได้รับการแก้ไขในส่วนที่เกี่ยวกับเบราว์เซอร์ เรายังกำหนดจุดพักเพื่อทำให้ส่วนหัวเหมาะกับอุปกรณ์พกพา
นี่คือรหัสที่สมบูรณ์สำหรับไฟล์ globalStyles.js
ของเรา
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}; } `;
ขอให้สังเกตว่าเราเขียนโค้ด CSS แท้ภายในตัวอักษร แต่มีข้อยกเว้นบางประการ ส่วนประกอบที่มีสไตล์ช่วยให้เราส่งผ่านอุปกรณ์ประกอบฉากได้ คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับสิ่งนี้ได้ในเอกสารประกอบ
นอกเหนือจากการกำหนดสไตล์สากลแล้ว เราสามารถกำหนดสไตล์สำหรับแต่ละเพจได้
ตัวอย่างเช่น นี่คือสไตล์สำหรับ PersonListPage.js
ที่กำหนดไว้ใน PersonStyle.js
ในโฟลเดอร์ 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; } } } } `;
ก่อนอื่นเรานำเข้า styled
จาก styled-components
และ deviceWidth
จากไฟล์ definition
จากนั้นเรากำหนด PersonsListContainer
เป็น div
เพื่อเก็บสไตล์ของเรา การใช้คิวรี่สื่อและจุดสั่งหยุดที่สร้างขึ้น เราทำให้หน้าเว็บเหมาะกับอุปกรณ์เคลื่อนที่โดยการตั้งค่าจุดสั่งหยุดต่างๆ
ในที่นี้ เราได้ใช้เฉพาะเบรกพอยต์ของเบราว์เซอร์มาตรฐานสำหรับหน้าจอขนาดเล็ก ขนาดใหญ่ และขนาดใหญ่มาก นอกจากนี้เรายังใช้ CSS flexbox และกริดให้เกิดประโยชน์สูงสุดเพื่อจัดรูปแบบและแสดงเนื้อหาของเราบนหน้าอย่างเหมาะสม
ในการใช้สไตล์นี้ในไฟล์ PersonListPage.js
เราเพียงแค่นำเข้าและเพิ่มลงในเพจของเราดังนี้
import React from "react"; const PersonsListPage = () => { return ( <PersonsListContainer> ... </PersonsListContainer> ); }; export default PersonsListPage;
Wrapper จะส่งออก div
เนื่องจากเรากำหนดให้เป็น div ในสไตล์ของเรา
การเพิ่มธีมและการปิดล้อม
เป็นคุณสมบัติที่ยอดเยี่ยมเสมอในการเพิ่มธีมในแอปพลิเคชันของเรา สำหรับสิ่งนี้ เราต้องการสิ่งต่อไปนี้:
- ธีมที่กำหนดเองของเรากำหนดไว้ในไฟล์แยกต่างหาก (ในกรณีของเราไฟล์
definition.js
) - ตรรกะที่กำหนดไว้ในการดำเนินการและตัวลดขนาด Redux
- เรียกธีมของเราในแอปพลิเคชันของเราและส่งผ่านโครงสร้างองค์ประกอบ
ลองตรวจสอบดู
นี่คือวัตถุ theme
ของเราในไฟล์ 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)" } };
เราได้เพิ่มคุณสมบัติสีต่างๆ สำหรับธีมสีอ่อนและสีเข้ม สีต่างๆ ได้รับการคัดสรรมาอย่างดีเพื่อให้มองเห็นได้ชัดเจนทั้งในโหมดสว่างและมืด คุณสามารถกำหนดธีมของคุณได้ตามที่คุณต้องการ นี่ไม่ใช่กฎที่ยากและรวดเร็ว
ต่อไป มาเพิ่มฟังก์ชันการทำงานให้กับ Redux
เราได้สร้าง globalActions.js
ในโฟลเดอร์การดำเนินการ Redux และเพิ่มรหัสต่อไปนี้
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)); };
ที่นี่ เราเพียงแค่นำเข้าธีมที่เรากำหนดไว้ ส่งการดำเนินการที่เกี่ยวข้องโดยส่งผ่านส่วนของธีมที่เราต้องการ ผลลัพธ์ของเพย์โหลดจะถูกเก็บไว้ในที่จัดเก็บในตัวเครื่องโดยใช้คีย์เดียวกันสำหรับธีมสีอ่อนและสีเข้ม ซึ่งช่วยให้เราคงสถานะในเบราว์เซอร์ได้
เรายังต้องกำหนดตัวลดของเราสำหรับธีม
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; } };
สิ่งนี้คล้ายกับสิ่งที่เราเคยทำมามาก เราใช้คำสั่ง switch
เพื่อตรวจสอบประเภทของการกระทำ จากนั้นจึงส่งคืนเพย์ payload
ที่เหมาะสม เรายังแสดง light
สถานะซึ่งกำหนดว่าผู้ใช้เลือกธีมสว่างหรือมืด เราจะใช้สิ่งนี้ในส่วนประกอบของเรา
เราต้องเพิ่มลงในตัวลดรูทและจัดเก็บของเราด้วย นี่คือรหัสที่สมบูรณ์สำหรับ store.js
ของเรา
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));
เนื่องจากเราจำเป็นต้องคงธีมไว้เมื่อผู้ใช้รีเฟรช เราจึงต้องดึงมาจากที่จัดเก็บในตัวเครื่องโดยใช้ localStorage.getItem()
และส่งผ่านไปยังสถานะเริ่มต้นของเรา
การเพิ่มฟังก์ชันการทำงานให้กับแอปพลิเคชัน React ของเรา
ส่วนประกอบที่มีสไตล์ทำให้เรามี ThemeProvider
ที่ช่วยให้เราส่งธีมผ่านแอปพลิเคชันของเรา เราสามารถแก้ไขไฟล์ App.js เพื่อเพิ่มฟังก์ชันนี้ได้
ลองมาดูที่มัน
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;
ด้วยการส่งธีมผ่าน ThemeProvider
เราจึงสามารถใช้อุปกรณ์ประกอบฉากของธีมในสไตล์ของเราได้อย่างง่ายดาย
ตัวอย่างเช่น เราสามารถกำหนดสีให้กับสีที่กำหนดเองของ bodyText
ได้ดังนี้
color: ${(props) => props.theme.bodyText};
เราสามารถใช้ธีมที่กำหนดเองได้ทุกที่ที่ต้องการสีในแอปพลิเคชันของเรา
ตัวอย่างเช่น ในการกำหนด border-bottom
เราทำดังต่อไปนี้
border-bottom: 2px solid ${(props) => props.theme.goldish};
บทสรุป
เราเริ่มต้นด้วยการเจาะลึกเข้าไปใน Sanity.io ตั้งค่าและเชื่อมต่อกับแอปพลิเคชัน React ของเรา จากนั้นเราตั้งค่า Redux และใช้ภาษา GROQ เพื่อสอบถาม API ของเรา เราเห็นวิธีเชื่อมต่อและใช้ Redux กับแอป React โดยใช้ react-redux
ใช้ styled-components และชุดรูปแบบ
อย่างไรก็ตาม เราแค่ขีดข่วนพื้นผิวในสิ่งที่เป็นไปได้ด้วยเทคโนโลยีเหล่านี้ ฉันแนะนำให้คุณดูตัวอย่างโค้ดใน GitHub repo ของฉัน และลองใช้โปรเจ็กต์ที่แตกต่างไปจากเดิมอย่างสิ้นเชิงโดยใช้เทคโนโลยีเหล่านี้เพื่อเรียนรู้และเชี่ยวชาญ
ทรัพยากร
- เอกสารสุขาภิบาล
- วิธีสร้างบล็อกด้วย Sanity.io โดย Kapehe
- เอกสาร Redux
- เอกสารประกอบที่มีสไตล์
- แผ่นโกง GROQ
- เอกสาร UI ของวัสดุ
- Redux Middleware และผลข้างเคียง
- เอกสาร Redux Thunk