iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
Modern Web

React 走出新手村 系列 第 23

React 走出新手村 — 認識Next

  • 分享至 

  • xImage
  •  

動機

主要還是想順著開頭篇章繼續補完 React server component,所以接下來就是會碰到 Next 這個框架,因為他與 React18 合作開發出能方便使用 React server component 的 app router,那基礎的 React 如果很熟悉,進到 Next 你通常只會覺得很容易,因為 Next 主要就是改變 React 的渲染機制而產生的框架,當然他也能做後端 api server 的工作,還有很直覺的路由機制...,但我們還是先著重在前端的部分,如果要用一張圖告訴你他能處理哪些好處,那應該會是下面這張。
https://ithelp.ithome.com.tw/upload/images/20230920/20129020AlCPZJv5MT.png
圖片來源

那還記得前面介紹架構和渲染模式的示意圖嗎?
https://ithelp.ithome.com.tw/upload/images/20230920/20129020w7NncYWFrk.png
那麼我們會按照上面所述的三個模式先概略講解一下

渲染機制發展歷程

渲染模式已經走過了漫長的路程,從伺服器端渲染(SSR)和客戶端渲染(CSR),到今天在不同論壇上討論和評估的高度細緻的模式,這些可能會讓人搞得頭昏眼花,但重要的是要記住每個模式都是為了應對特定的使用案例而設計的,在某一個使用案例有益的模式特性,放到另一個使用案例中可能會有害,在同一個網站上的不同類型的頁面,可能也需要不同的渲染模式。

主要也是受限 google,Chrome 團隊鼓勵開發人員優先考慮靜態或伺服器端渲染,那也就帶表參與 SEO 的標準必須合乎他們的規範。隨著時間的推移,逐步加載和渲染技術可能在使用現代框架時,默認情況下幫助在性能和功能交付之間取得良好的平衡。

Next實作

那麼Next對於渲染機制的看法又是什麼呢?我們後面的章節會一一講述,首先讓我們先安裝next的專案。

我的宗旨是你聽或念了千百遍,不如自己實際做一遍。

我這裡還是先以 page router為例,主要介紹渲染機制,後面介紹到 React server component 的時候會再做一個範例,這裡請大家跟著官網練習基本上是一樣的,記得選到page router的選項,懶的話你也可以照著我的步驟做

// 基本上都和Next官方的步驟相同
npx create-next-app@latest
// 按下enter後會跑以下訊息
What is your project named? nxt_page // 可以改自己喜歡的命名喔
Would you like to use TypeScript? No / Yes // 我是推薦Y
Would you like to use ESLint? No / Yes // 我是推薦Y
Would you like to use Tailwind CSS? No / Yes // 我是推薦Y
Would you like to use `src/` directory? No / Yes // 選Y的話會都放在src的裡面
Would you like to use App Router? (recommended) No / Yes // 這裡幫我選N
Would you like to customize the default import alias? No / Yes // 這是看你要不要簡化../../為自定義的節點
What import alias would you like configured? @/* // 上面選Y才會問

做完之後到該路徑下可以看到路徑會長這樣
https://ithelp.ithome.com.tw/upload/images/20230920/20129020Mvn1Z6NDrh.jpg

src/pages 路徑下面會有 _app.tsx, _document.tsx, index.tsx 和 api 的資料夾,除了index.tsx 以外我們先不會用到,我們先進 index.tsx來試試我們熟悉的CSR模式,可以將原本的檔案改成以下:

import { useState } from "react"

export default function Home() {
  // 基本上都和你寫react沒有區別
  const [count, setCount] = useState<number>(0);
  return (
    <main className="container mx-auto">
      <h2>這裡會是首頁主要內容</h2>
      <div className="flex justify-center items-center">
        <button 
          className="p-2 mx-2 rounded-lg shadow-md bg-blue-500 hover:bg-blue-300 disabled:bg-gray-100 min-w-40"
          onClick={() => setCount(pre=> pre-=1)}
          disabled={count===0} >-</button>
        <div>{count}</div>
        <button 
          className="p-2 mx-2 rounded-lg shadow-md bg-blue-500 hover:bg-blue-300 min-w-40"
          onClick={() => setCount(pre=> pre+=1)}>+</button>
      </div> 
    </main>
  )
}

那你會發現基本使用上都和之前在 React 裡面做的都一樣,這裡不要忘記把src/styles/globals.css的預設css清空喔!不然直接貼上會跑版,那麼我們嘗試接一些api的操作看看吧!

無痛移轉

這裡我就使用 Rick and Morty 的 API 來練習,如果大家想要練習新的框架怎麼運作,除了寶可夢以外,我非常推薦 Rick and Morty API,因為他的網站寫得很清楚,另外還提供 GraphQL 可以練習;

那我們先以角色清單和基本的換頁功能為主。

import { useCallback, useEffect, useMemo, useState } from "react"

interface RickandmortyCharacter {
  id: number,
  name: string,
  status: string,
  species: string,
  type: string,
  gender: string,
  origin: {
    name: string,
    url: string
  },
  location: {
    name: string,
    url: string
  },
  image: string,
  episode: string[],
  url: string,
  created: string,
}
interface RickandmortyCharacterRes {
  info: {
    count: number,
    pages: number,
    next: string | null,
    prev: string | null,
  },
  results: RickandmortyCharacter[]
}

interface PageInfo {
  pageUrl: string;
  next: string | null;
  prev: string | null;
  curr: number;
  loading: boolean;
}

export default function Home() {
  const [pageInfo, setPageInfo] = useState<PageInfo>({
    pageUrl:'https://rickandmortyapi.com/api/character',
    next: null,
    prev: null,
    loading: true,
    curr: 0
  });
  const [resData, setResData] = useState<RickandmortyCharacterRes|null>(null)
  const chkResData = useMemo(() => resData ,[resData])
  const pageChange = useCallback((status: string) => {
    setPageInfo(pre => ({...pre, loading: true}));
    if (status === 'next') {
      fetch(pageInfo.next!)
      .then((response) => response.json())
      .then((response: RickandmortyCharacterRes) => {
        const info = response.info
        setResData(response)
        setPageInfo(pre => ({
          ...pre, 
          next: info.next, 
          pageUrl: pageInfo.next!,
          prev: info.prev, 
          curr: pre.curr + 1, 
          loading: false
        }))
      });
    }
    if (status === 'prev') {
      fetch(pageInfo.prev!)
      .then((response) => response.json())
      .then((response: RickandmortyCharacterRes) => {
        const info = response.info
        setResData(response)
        setPageInfo(pre => ({
          ...pre, 
          next: info.next, 
          pageUrl: pageInfo.prev!,
          prev: info.prev, 
          curr: pre.curr - 1, 
          loading: false
        }))
      });
    }
  }, [pageInfo])
  
  useEffect(() => {
    if(!chkResData) {
      const controller = new AbortController();
      const signal = controller.signal;
      setPageInfo(pre => ({...pre, loading: true}));
      fetch(pageInfo.pageUrl, {
        signal: signal
      })
      .then((response) => response.json())
      .then((response: RickandmortyCharacterRes) => {
        const info = response.info
        setResData(response)
        setPageInfo(pre => ({...pre, next: info.next, prev: info.prev, loading: false}))
      });
      return () => controller.abort();
    }
  }, [pageInfo.pageUrl, chkResData])
  return (
    <main className="container mx-auto">
      <h2>這裡會是首頁主要內容</h2>
      <div>資料頁面</div>
      <div className="flex justify-center items-center">
        <button 
          className="p-2 mx-2 rounded-lg shadow-md bg-blue-500 hover:bg-blue-300 disabled:bg-gray-100 min-w-40"
          onClick={() => pageChange('prev')}
          disabled={pageInfo.prev===null || pageInfo.loading} >prev</button>
        <div>{pageInfo.curr}</div>
        <button 
          className="p-2 mx-2 rounded-lg shadow-md bg-blue-500 hover:bg-blue-300 min-w-40"
          onClick={() => pageChange('next')}
          disabled={pageInfo.next===null || pageInfo.loading}>next</button>
      </div>
      {resData?.results?.map((character) => (
        <div key={character.id}>
          {character.name}
        </div>
      ))}
    </main>
  )
}

總結

以上就是 Next CSR 的基本操作,也可以當作是無痛移轉的範例,可是我們使用Next的動機,就是想使用其他渲染機制,那麼下一篇我們接著範例延續,接著做其他渲染方式的實踐,以及理解各個模式的概念。

給全新手的大禮包

React基本Hook教學

參考資料

patterns
Next官方


上一篇
React 走出新手村 — CSR處方簽
下一篇
React 走出新手村 — Next SSG
系列文
React 走出新手村 31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言