---
# System prepended metadata

title: Side Project learn 03. React Search Anime 筆記
tags: [React Project]

---

### 開發紀錄雜項：

- 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, 即將到來的

```