### 開發紀錄雜項: - 11/20 功課,待修正事項: - [ ] global getAnime 函數封裝 - [ ] isSearch 應該要用 dispatch - [x] 網址URL 結構 - [x] HomePage 用 Map - [ ] AnimaItem map - [ ] Iframe 意義 - [ ] Upcoming render() + map問題 - [ ] 瀏覽器引擎 發展史 - [ ] build / bundler - [ ] 額外 濃縮成一個 component Airing/Popular/UpComing - [ ] 把 className 換成 styled ### 專案使用套件: - 載入 react-router-dom , styled-components ```cmd npm i react-router-dom styled-components ``` - 使用:Jikan API [連結](https://jikan.moe/) ### 定義 Context 上下文: ```context.jsx import { createContext, useContext } from "react"; // 創建了一個全域上下文對象,並將其賦值給 GlobalContext 變數。 const GlobalContext = createContext(); // 上下文提供者,其目的是將定義好的上下文 GlobalContext 中的值提供給其子元件。 export const GlobalContextProvider = ({ children }) => { return ( <GlobalContext.Provider value={"hello"}>{children}</GlobalContext.Provider> ); }; // 後續元件要使用 Context import 此變數,可以使後續組件都透過這個變數來傳遞資料 export const useGlobalContext = () => { return useContext(GlobalContext); }; ``` ### 如何使用上下文? 1. 先到進入點頁面,包覆整個 App: ```main.jsx // context import { GlobalContextProvider } from "./context/global.jsx"; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> // 請包在 <React.StrictMode> 裡面 <GlobalContextProvider> <GlobalStyle /> <App /> </GlobalContextProvider> </React.StrictMode> ); ``` 2. 測試資料是否正確,我們剛剛已經在 GlobalContext.Provider 設置好一個 value={'hello'},預期在下面會取得這個資料。 ```jsx // imports import Popular from "./components/Popular"; import { useGlobalContext } from "./context/global"; function App() { const Global = useGlobalContext(); console.log(Global); // hello , 這表示 ok return ( <div className="App"> <Popular /> </div> ); } export default App; ``` 3. 我們已經在上下文宣告了一個變數:`useGlobalContext` 他的內容是返回整個上下文對象: ```jsx export const useGlobalContext = () => { return useContext(GlobalContext); }; // 後續要導入資料就可以透過這個對象來取得上下文的值。 ``` ### 透過 styled-component 來撰寫全域 styles: - styled-components 裡提供用來創建全域樣式的工具。 ```jsx // 這樣可以使樣式不侷限在元件,而是類似 index.css 一樣的做法。 const GlobalStyle = createGlobalStyle` body { font-family: 'Arial', sans-serif; margin: 0; padding: 0; } // 其他全域樣式... `; ``` ### 再一次認識 useReducer Hooks: - 與 **useState** 相似,都是用於**管理狀態**的勾子。 - 特別**適用於處理複雜的狀態邏輯。** - useReducer 提供了更多的**控制和組織代碼的能力,特別適用於處理包含多個相關值的複雜狀態。** - useReducer **接受兩個參數**:一個是 `reducer 函數`,另一個是`init State`。它返回一對值,第一個值是當前的狀態,第二個值是一個 dispatch 函數,用於分發 action 來觸發狀態的改變。 --- 1. 基本語法 ```jsx // useReducer const [state, dispatch] = useReducer(reducer, initialState) // state:當前的狀態,初始值為 initialState。 // dispatch:一個函數,它用來向 reducer 發送動作(action)。 dispatch: 是一個函數,用於發送動作(action)。當你調用 dispatch 時, 只是觸發 reducer 函數的執行,而不是整個 reducer 函數。 ``` - 了解上述基本語法後,來看看 `reduce` 函數是怎麼回事: 2. reducer 函數: ```jsx // 這裡的 state , 實際上是從 useReducer 傳入的 initial State const reducer = (state, action) => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } }; // useReducer 裡面的 dispatch // 可以根據 reducer 的 action 類別去觸發相對應的事件, // 最後再將執行結果返回 state 上。 ``` 3. dispatch 函數的使用: - 在 reducer 函數中,`action` **參數用來判斷要執行哪個 case**,從而決定應用的狀態應該如何更新。 ```jsx dispatch({ type: 'INCREMENT' }); ``` - useReducer 概念總結: ```jsx 總的來說,我要使用 useReducer 這個 Hooks 如下 const [state, dispatch] = useReducer(reducer, initialState) state 是初始狀態 = initialState dispatch 則是用來呼叫 reducer 裡面的 action type 而 reducer 函數則是這個 useReducer 裡的第一個參數 在這個參數 state , 用來返回 action 類別執行以後的結果 第二個參數 action 則是用來判讀 switch 裡的 action.type 要執行哪一個 case , 再去執行裡面的程序 返回結果在 state 上,改變其狀態。 // 補充,action.payload - 總的來說, dispatch 函數可以去控制 action.type , action.payload ``` - 亦可瞭解一下 [Flux](https://note.pcwu.net/2017/02/18/flux-note/)。 --- ### 設置 actions ```jsx // actions const LOADING = "LOADING"; const SEARCH = "SEARCH"; const GET_POPULAR_ANIME = "GET_POPULAR_ANIME"; const GET_UPCOMING_ANIME = "GET_UPCOMING_ANIME"; const GET_AIRING_ANIME = "GET_AIRING_ANIME"; const GET_PICTURES = "GET_PICTURES"; ``` ### App 使用 React-router-dom 配置: - 這邊會發現 Route 的設置滿特別的: ```jsx function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Homepage />} /> <Route path="/anime/:id" element={<AnimeItem />} /> <Route path="/character/:id" element={<Gallery />} /> </Routes> </BrowserRouter> ); } ``` ```jsx // 這是一個動態設置路由的寫法 :id ``` - 當你使用 `/anime/:id` 這樣的路徑模式時,它告訴 React Router 在 `/anime/` 後面的**任何東西都會被視為 id 參數**,並將其提供給相應的組件。 - 舉例: ```jsx 如果你的應用程式 URL 是 /anime/123 ,React Router 就會將 123 視為 id 參數,並將其傳遞給相應的 AnimeItem 組件。 ``` ### const { id } = useParams(); - React Router 中的 useParams 鉤子,它是一個 React Hook,用於從路由中獲取 URL 參數。在這裡,它用來獲取路由中的 id 參數。 ```jsx const { id } = useParams(); ``` ### 事先解構回傳回來的屬性: ```jsx // 1. 透過這邊取得資料: //get anime based on id const getAnime = async (anime) => { const response = await fetch(`https://api.jikan.moe/v4/anime/${anime}`); const data = await response.json(); setAnime(data.data); // 透過這行,我把我的 anime state , 設置 data.data }; ``` ```jsx // 2. 此時,anime state 獲得數據,透過這邊來解構。 const { title, // 動畫標題 synopsis, // 動畫簡介 trailer, // 動畫預告片 duration, // 播放時長 aired, // 播放日期 season, // 播放季節 images, // 相關圖片 rank, // 排名 score, // 評分 scored_by, // 評分人數 popularity, // 受歡迎程度 status, // 動畫狀態(例如正在播放、已完結等) rating, // 年齡分級 source, // 原作來源(例如漫畫、小說等) } = anime; ``` --- ### 展開動畫簡介邏輯區塊,AnimeItem.jsx: - 使用者點擊 'Read More' & 'Show less' 展開跟收合簡介。 - 首先 showMore 是一個 state `(false)` 而 synopsis 則是被解構後的對象。是一**個動畫簡介**。 - 根據第一段來說: 簡單來說,當 `showMore` **true** , 就會完整顯示解構後的動畫簡介 若為 **false** , 就會按照字串符的規則來顯示字數並附上`‘...’` ```jsx {/* 展開動畫描述的功能區塊 */} <p className="description"> {showMore ? synopsis : synopsis?.substring(0, 450) + "..."} <button onClick={() => { setShowMore(!showMore); }} > {showMore ? "Show Less" : "Read More"} </button> </p> {/* 展開動畫描述的功能區塊 */} ``` - 這段判斷是一個條件渲染,根據 showMore 的值來決定渲染的內容。如果 showMore 為真,則顯示完整的動畫簡介 synopsis;如果 showMore 為假,則顯示動畫簡介的前 450 個字符,並在末尾加上省略號 "..."。這樣的效果是,當用戶點擊 "Read More" 按鈕時,可以展開顯示完整的簡介,再次點擊則收起。 ### 關於 substring() - 一個 JavaScript 字符串方法之一,它用於從字符串中提取部分字符。這個方法接受兩個參數,起始索引和結束索引,並返回由這兩個索引之間的字符組成的新字符串。 1. 起始索引 (參數1) 2. 結束索引 (參數2) 3. 關於字串符方法後面的 '...' ```js + "..." // 這邊是用於在實際畫面上顯示 '...' ``` - 關於 button 設置條件就沒什麼好說了,就是反向切換操作。 --- ### 取得動畫介紹影片(you tube) / AnimeItem.jsx - 這段用於顯示預告片段,若有則顯示,沒有則顯示 h3 title。 ```jsx <h3 className="title">Trailer</h3> <div className="trailer-con"> {trailer?.embed_url ? ( <iframe src={trailer?.embed_url} title="Inline Frame Example" width="800" height="450" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen ></iframe> ) : ( <h3>Trailer not available</h3> )} </div> ``` --- ### 負責渲染動畫的角色信息。 - characters 原先的狀態: ```jsx const [characters, setCharacters] = useState([]); ``` - 關於 method 前下 ? - 安全存取運算符`(?)` ```jsx 在React中,使用map方法對陣列進行迭代時, 若陣列的值可能為null或undefined,建議使用安全存取運算符(?)進行防禦性編程。 這可確保在處理異步數據或未確定的數據情況下, 不會因為嘗試在未定義的值上調用map而導致應用崩潰。 ``` ```jsx <h3 className="title">Characters</h3> <div className="characters"> {characters?.map((character, index) => { const { role } = character; const { images, name, mal_id } = character.character; return ( <Link to={`/character/${mal_id}`} key={index}> <div className="character"> <img src={images?.jpg.image_url} alt="" /> <h4>{name}</h4> <p>{role}</p> </div> </Link> ); })} </div> ``` ### Homepage - 設置好 state, 以及 swtich components ```jsx const [rendered, setRendered] = useState("popular"); const switchComponent = () => { // 根據 rendered 條件,去設置相對應的元件內容 switch (rendered) { case "popular": return <Popular rendered={rendered} />; case "airing": return <Airing rendered={rendered} />; case "upcoming": return <Upcoming rendered={rendered} />; default: return <Popular rendered={rendered} />; } }; ``` ### 首頁標題的條件判斷 & 切換: - 這裡主要是根據前面說的` State` , 根據 `rendered` case 的切換,同步去更新這邊的標題。 ```jsx <h1> {rendered === "popular" ? "Popular Anime" : rendered === "airing" ? "Airing Anime" : "Upcoming Anime"} </h1> ``` ### from 區塊,同時設置了兩個 event - 回顧一下,`handleSubmit` 以及 `searchAnime` ```jsx // handle submit const handleSubmit = (e) => { e.preventDefault(); if (search) { // 參數表示 user 輸入的字串 searchAnime(search); state.isSearch = true; } else { state.isSearch = false; alert("Please enter a search term"); } }; ``` ```jsx // search anime const searchAnime = async (anime) => { dispatch({ type: LOADING }); const response = await fetch( `https://api.jikan.moe/v4/anime?q=${anime}&order_by=popularity&sort=asc&sfw` ); const data = await response.json(); // payload , 載入 data 資訊 dispatch({ type: SEARCH, payload: data.data }); }; ``` - 這區塊主要是方便操作,所以同時設置了兩個處理方法: - 總之,這兩個操作事件,都可以觸發 `handleSubmit` ```jsx <form action="" className="search-form" onSubmit={handleSubmit}> <div className="input-control"> <input type="text" placeholder="Search Anime" value={search} onChange={handleChanges} /> <button type="submit">Search</button> </div> </form> ``` ### 一些英文: ```jsx // 文盲紀錄專區 XD: - Popular, 受歡迎的 - Airing, 播出 - Upcoming, 即將到來的 ```