### 開發紀錄雜項:
- 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, 即將到來的
```