# Full React Tutorial part1 ###### tags: `Javascript, React` # What is React ? * 是一個 JS 的框架用來創造網頁 * 可以簡易的操作SPA 為了呈現網頁 server 會傳送單一個 html page 給瀏覽器做呈現,這時候 react 就會接手管理整個網頁包含網頁的data 或是 使用者的互動元件(click event) 或是頁面的 routing 設置,所以使用者可以做到頁面的轉換藉著點擊連結,但是那些產生的新的頁面已經不會透過 server 傳到瀏覽器了,則是由 react 接手藉著 router 把內容送到瀏覽器,這樣的結果就是讓點擊後的頁面轉換非常快速便捷 ![](https://i.imgur.com/pHYoqDq.png) ## 推薦使用 Vs code 插件 ![](https://i.imgur.com/K5zAYul.png) 記得要進入 setting 後搜尋 emmet 輸入下方紅框處文字來使用 react emmet ,插件才能執行喔 ![](https://i.imgur.com/0piPscY.png) # 建立 project * 使用任何 cmd 介面確認有無下載 node * 使用 `node -v ` 確認版 * 如果沒有的可以先上[官網最新版本](https://nodejs.org/en/) ## 下一步建立要操作的資料夾內容與環境 Create React App 記得要先進入正確的資料夾 `npx create-react-app 資料夾名稱` ## 建立的資料夾內容如下: ![](https://i.imgur.com/A31NCiQ.png) ## node_modules 內容主要是各種插件以及 react 的內 ## public 要呈現到瀏覽器內的內容會放在這邊 主要 react 的內容掛載在 那 index.html 內 root 的 div 內 ![](https://i.imgur.com/NQiXxur.png) ## src 我們寫的 code 主要會在這個區塊 ![](https://i.imgur.com/yGoeOBX.png) 一般的 react component 會長這樣 ![](https://i.imgur.com/yed4W8b.png) 並且會輸出到 index.js 內呈現(此處可以看到掛載的對象就是剛index.html 內的 root div 也就是 render 畫面的地方) ![](https://i.imgur.com/6JTTo8G.png) ## package.json 可以看到所有插件的版本以及一些操作指令 以下是比較常用的指令來操作 react ```javascript= "scripts": { "start": "react-scripts start", "build": "react-scripts build" }, ``` ### node modules 這個資料夾包含了所有專案需要的插件包含 React 本身,所以它的容量非常大,通常在上傳到 github 時並不會連同這個資料夾一起上傳,所以我們該怎麼取得呢? 只要在 terminal 介面輸入 `npm install` 他就會往 pakage.json 檔案尋找所有的插件並且安裝所有的插件並且創建 node modules 資料夾給你這也就是 package.json 重要的地方 ## 開啟 React 專案 在 cmd輸入以下(必須進入專案資料夾內) `npm run start` ![](https://i.imgur.com/6SBYPAF.png) 就可以在本地端開啟網頁搂,並且具備 hot reload 功能! ![](https://i.imgur.com/ioEbs6V.png) # Components & Templates > Component 是 React 的心臟 ![](https://i.imgur.com/CeFPCDO.png) * Navbar 可以是一個 component * Content 可以是一個 component * SideBar 可以是一個 component ## Components 通常會包含 * Template 例如 呈現頁面的HTML * Logic 例如 登入登出按鈕的 click event React 讓我們執行這些動作變得非常簡單並且可以重複使用 ## Root component * 他是一個 function (可以使用箭頭函式)並且它會返回一些 html tag 的程式碼(其實是 JSX) * 它必須大寫開頭 "A"pp * 這也是目前剛開新專案唯一的 component * JSX 讓我們可以簡單地使用 html style template 並且在後臺透過 babel 編譯成 JS 引擎看得懂的語法 * 這邊 JSX 比較特別的是地方是 class 必須要寫成 className 因為那個字已經被保留了 * 在 react 比較舊的版本必須在最上方引入 react * 最後可以注意到 `export default App;` 這是為了輸出到 index.js 使用(也可以輸出到其他地方),並且輸出到DOM上 不過可以看到其實 DOM 上面 className 也一樣會被編譯回 class ![](https://i.imgur.com/SI3ygWc.png) ```javascript= import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React123 </a> </header> </div> ); } export default App; ``` # Dynamic Values in Templates 最簡單呈現動態值的方式就是透過大括號寫在 template 內 * 可以多個大括號呈現多個變數 * 變數的資料型態會在 output 之前都被轉換成字串型態 * 大括號內不能輸出的資料型態有 布林值以及物件 * 也可以在大括號內做簡單的計算 * 也可以把大括號使用在 attr (下面範例使用在 a link 的填入) ```javascript= function App() { const title = "welcome to the new blog"; const likes = 50; const link = "http://google.com" return ( <div className="App"> <div className="content"> <h1>{title}</h1> <p>Liked {likes} times</p> <p>{"hello world"}</p> <p>{[1, 2, 3, 4, 5]}</p> <p>{10}</p> <p>{Math.random() * 10000}</p> <a href={link}>Google site</a> </div> </div> ); } ``` ![](https://i.imgur.com/KHgwCfS.png) # Multiple Components 下面這張圖片可以理解,所有的 components 都會有個依附的 root 在目前的例子中是 App.js,其下可以有各種不同的 components 來達到各種不同的功能並且一起呈現在網頁上 ![](https://i.imgur.com/guc4HyY.png) 要製作新的 components 時都會需要打出其架構這時候就可以用 snippet 偷懶 使用 sfc + tab 即可產出全新的模板瞜 ![](https://i.imgur.com/0BYG5xz.png) ![](https://i.imgur.com/aixicFc.png) 首先我們在 src 新增 Navbar.js/ Home.js 檔案作為我們 Navbar/Home 的 components 並且引入 root component App.js內就可以在內部使用相關的 tag 呈現在頁面上面摟! ## Navbar 頁面 這邊放入一些超連結(有用到 router 的概念之後會再解釋) / 代表 home / create代表 New Blog ```javascript= const Navbar = () => { return ( <nav className="navbar"> <h1>The Dojo Blog</h1> <div className="links"> <a href="/">Home</a> <a href="/create">New Blog</a> </div> </nav> ); } export default Navbar; ``` ## Home 頁面 ```javascript= const Home = () => { return ( <div className="home"> <h2>Homepage</h2> </div> ); } export default Home; ``` ## App.js 頁面 因為是 root 所以會引入其他的 components 來做使用並且註冊其 tag 到 JSX 上面就可以 render 到 DOM 瞜 ```javascript= import './App.css'; import Navbar from './Navbar' import Home from './Home' function App() { return ( <div className="App"> <Navbar /> <div className="content"> <Home /> </div> </div> ); } export default App; ``` # Adding Styles 有數種方式可以在 JSX 中添加 styles ## 使用 import 引入 index.css 進 index.js 這邊使用 import index.css 引入 index.js 內就可以在這個資料夾操作整份專案的 CSS (這樣的寫法多使用在小專案中) ```javascript= import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; ReactDOM.render( <React.StrictMode > <App /> </React.StrictMode>, document.getElementById('root') ); ``` ## inline styles * 使用大括號第一層代表動態的值添加進去 html tag * 第二層大括號則是代表 JS 的物件 * 內部的 CSS 寫法要使用 camel case 的寫法不能使用 (-) 會被 JS 引擎認定為減號 ```javascript=\ <nav className="navbar"> <h1>The Dojo Blog</h1> <div className="links"> <a href="/">Home</a> <a href="/create" style={{ color: "white", backgroundColor: "#f1356d", borderRadius: "8px" }}>New Blog</a> </div> </nav> ``` # Click Event 在 Home.js 操作本篇 * 首先會設立一個變數 handleClick ,基本上各種事件都可以這樣設立例如 handle + 事件名稱(hover, click, mouseover 等等) * 接下來在 JSX html tag 中引入事件 attr ,以下方為例子就是 onClick 來表示點擊事件,其他的事件會是 on+ 事件名稱(字首大寫) **要注意的地方是直接在 tag 處直接呼叫函式的話會直接使用此函式而不會透過事件發生** ```javascript= const Home = () => { const handleClick = () => { console.log('hello,blabla'); } return ( <div className="home"> <h2>Homepage</h2> <button onClick={handleClick}>Click me</button> // 所以此處我們指示設立一個參考點在這邊而不是呼叫此函式 </div> ); } export default Home; ``` 所以當我們點畫面上的按鈕時就會觸發事件印出字串 ![](https://i.imgur.com/D0TxsmS.png) ## 但這邊就會出現問題,函式會需要引入參數來做操作,這邊該怎麼做操作呢? 操作在 Home.js 透過匿名函式的寫法寫在 JSX 內就不會被立即觸發就可以寫進去參數了 ```javascript= const Home = () => { const handleClick = (name) => { console.log('hello,blabla' + name); } return ( <div className="home"> <h2>Homepage</h2> <button onClick={() => handleClick('mario')}>Click me</button> </div > ); } export default Home; ``` ## 事件可以操作的 API 這邊的 API 跟 JS 可以操作的很像 ```javascript= const handleClick = (e) => { console.log(e); } return ( <div className="home"> <h2>Homepage</h2> <button onClick={handleClick}>Click me</button> </div > ); ``` 印出的內容如下 ![](https://i.imgur.com/Sbe24G5.png) 下方使用到的 target 也是其中的 API 可以取的現在目標的內容在接上 innerText 就可以取的內文 ![](https://i.imgur.com/u6nxMug.png) ```javascript= const handleClick = (e) => { console.log(e.target.innerText); } return ( <div className="home"> <h2>Homepage</h2> <button onClick={handleClick}>Click me</button> </div > ); ``` # Using State (useState hook) > React 中的 state 代表資料被使用在某個 component 中在每個時間點中,並且那個 state 可以是 array, booleans, strings, objects 或是任何資料我們的 component 有使用到的 ## 範例 從下方程式碼中可以看到, JSX 中的 p tag 顯示了動態的值 `{name}` ,我們設置了變數 name = 'Mario' 然後我們操作 click event 當點擊時 name 會變成 luigi 所以當我們點擊畫面,中間的 Mario 會變成 luigi 對嗎? ![](https://i.imgur.com/OzpWvgX.png) ```javascript= const Home = () => { let name = 'Mario'; const handleClick = () => { name = 'luigi'; console.log(name); } return ( <div className="home"> <h2>Homepage</h2> <p>{name}</p> <button onClick={handleClick}>Click me</button> </div > ); } export default Home; ``` 結果是沒有變化,但是裡面的 name 確實改變成 luigi 了!但卻沒有更新在 template 內 ![](https://i.imgur.com/F1VXi43.png) 這樣的原因是我們設置的變數 name 並沒有 reactive ,**意思是 React 函式庫尚未監測到這裡的改變,所以這邊 name 的改變並沒有觸發 react 去重新渲染畫面** 所以要讓 React 函式庫可以監測到這樣的改變我們這邊要使用 Hook 稱為 UseState Hook 是一種特殊的函式會做出特定的工作,可以透過呼叫他們的名字使用他們並且通常開頭是 use+ ... ## 操作 useState 首先引入 useState 進專案 `import { useState } from 'react';` 設置 useState 專屬的變數以及函式 後方的 useState('這邊初始值') ```javascript= const [name, setName] = useState('mario'); const [age, setAge] = useState(30); ``` 所以當我們想要呈現資料在網頁上面並且是會被 React 函式庫偵測到的狀態,必須這樣設置,然後在 handleClick 內部的要修改值的部分就要使用 useState 內建的函式, set+變數名稱(這可以自訂),來操作要修改的值 ```javascript= import { useState } from 'react'; const Home = () => { const [name, setName] = useState('mario'); const [age, setAge] = useState('30'); const handleClick = () => { setName('luigi') setAge('23') } return ( <div className="home"> <h2>Homepage</h2> <p>{name} is {age} old</p> <button onClick={handleClick}>Click me</button> </div > ); } export default Home; ``` 這樣畫面就會從 mario is 30 old 變成 luigi is 23 old,也就是我們使用 set function 改變後的值 ![](https://i.imgur.com/c5QhNnK.png) # Intro to React Dev Tools 推薦使用 ![](https://i.imgur.com/GwNGixy.png) React Dev Tools 可以方便我們 debug 、觀察元素、DOM、State 一般安裝好之後,可以在這邊找到他們使用 ![](https://i.imgur.com/t2t9IkK.png) 四個功能按鍵 ![](https://i.imgur.com/AjSeCd7.png) 1. 第一個時鐘先不介紹 1. 第二個眼睛的部分會直接跳轉到 component 目前的 DOM ![](https://i.imgur.com/Ys2qFv2.png) 1. 第三個會直接出現當前 component 的 console ![](https://i.imgur.com/bITeOG2.png) 4. 會直接出現原始碼的位置 ![](https://i.imgur.com/0H6OmVh.png) 以我們剛剛的主題 State 的部分,可以很方便的直接從紅色框框處觀察到 React 有沒有針對 State 做處理 ![](https://i.imgur.com/YYyBY0U.png) # Outputting Lists 當我們要把一個 list 的資料呈現到頁面上,我們可以使用 map 來印出 ```javascript= import { useState } from 'react'; const Home = () => { const [blogs, setBlogs] = useState([ { title: 'My new website', body: 'lorem ipsum...', author: 'mario', id: 1 }, { title: 'Welcome party!', body: 'lorem ipsum...', author: 'yoshi', id: 2 }, { title: 'Web dev top tips', body: 'lorem ipsum...', author: 'mario', id: 3 } ]); return ( <div className="home"> {blogs.map((blog) => ( <div className="blog-preview" key={blog.id}> <h2>{blog.title}</h2> <p>Written By{blog.author}</p> </div> ))} </div > ); } export default Home; ``` 印出結果 ![](https://i.imgur.com/AcGs0MT.png) 1. 首先使用 useState 註冊好內容以及寫好預設值 2. 函式的部分為了動態的呈現到頁面上使用大括號包裹住待會要寫入的程式碼 3. 針對註冊好的 blogs 使用 map 來印出 4. 在 blogs 擷取內部的資料貼到 JSX 上面 5. 這邊記得 key 的部分可以方便對每個 blog 做辨別一定要記得填寫上 6. 特別需要注意的地方是 因為內部是輸出是 JSX 所以在箭頭函式要接上括號`()` # Props > Props 就是把資料從 parent component 傳輸到 child component 上面輸出的 lists 如果我們想要放到比方說 home page, categories page, new page 等等,各種不同的介面,在這種情況下 ```javascript= {blogs.map((blog) => ( <div className="blog-preview" key={blog.id}> <h2>{blog.title}</h2> <p>Written By{blog.author}</p> </div> ))} ``` 這一段程式碼會一直不斷地被重複使用到,所以更好的方法是擷取出來做一個 component 來做這件事情,但是每個頁面需要的 lists 的資訊會不一樣,這時候就要透過 **props** 來傳入不一樣的內容來達成這件事情 ## BlogList 直接拉出來做 component 把剛剛需要重複使用的程式碼直接構成一個 component ,並且引入 Home.js 做使用 ### Home.js 這邊 Home.js 作為要傳出 props 的 parent component 可以在註冊使用 BlogList 的地方命名 props 這邊我命名為 blogs 並且傳送出動態的內容也就是變數 blogs(也就是 blogList 文章內容) ```javascript= import { useState } from 'react'; import BlogList from './BlogList' const Home = () => { const [blogs, setBlogs] = useState([ { title: 'My new website', body: 'lorem ipsum...', author: 'mario', id: 1 }, { title: 'Welcome party!', body: 'lorem ipsum...', author: 'yoshi', id: 2 }, { title: 'Web dev top tips', body: 'lorem ipsum...', author: 'mario', id: 3 } ]); return ( <div className="home"> <BlogList blogs={blogs} /> </div > ); } export default Home; ``` ### BlogList.js component 這邊就要接收 props ,接收方式是在箭頭函式的地方寫入 props 作為參數,並且**props.+ 剛剛命名的 props** 名稱 即可使用在此 component 的 JSX 內摟 ```javascript= const BlogList = (props) => { const blogs = props.blogs; return ( <div className="blog-list"> { blogs.map((blog) => ( <div className="blog-preview" key={blog.id}> <h2>{blog.title}</h2> <p>Written By{blog.author}</p> </div> )) } </div> ); } export default BlogList; ``` ### 多個 props 傳出以及接收 Home.js 直接在註冊 props 的地方直接命名並且使用第二個 props ```javascript= <div className="home"> <BlogList blogs={blogs} title="I am title"/> </div > ``` BlogList.js 下方程式碼的部分就是變數指派 `title = props.title ` 擷取正確的 props 名稱就可以直接使用第二個 props 摟 ```javascript= const BlogList = (props) => { const blogs = props.blogs; const title = props.title; return ( <div className="blog-list"> <h2>{title}</h2> { blogs.map((blog) => ( <div className="blog-preview" key={blog.id}> <h2>{blog.title}</h2> <p>Written By{blog.author}</p> </div> )) } </div> ); } export default BlogList; ``` ### 解構式的寫法 直接解構出 props 的內容物,使用 `{}` 就可以省略下面兩行擷取的程式碼摟 ```javascript= const BlogList = ({blogs,title}) => { return ( <div className="blog-list"> <h2>{title}</h2> { blogs.map((blog) => ( <div className="blog-preview" key={blog.id}> <h2>{blog.title}</h2> <p>Written By{blog.author}</p> </div> )) } </div> ); } export default BlogList; ``` 輸出結果 ![](https://i.imgur.com/bNzYgTZ.png) ## 總結 現在我們可以到處使用這個 component BlogList.js 只要引入之後使用 tag ,並且引入想要使用的 props 就可以快速使用這個 list 並且透過 props 修改其內容都非常容易且好重複使用摟 ```javascript= <Bloglist 後方加入props名稱 /> ``` # Reusing Components 重複使用這個 BlogList.js component ,這邊示範如何操作以及如何修改裡面的 props 從下方程式碼可以看到直接展示 使用兩次 BlogList component: 1. 可以針對`{}`內的內容做操作 2. 範例處使用的是 filter 針對 author 做篩選 3. 最後只印出了 author 是 mario 的 blog 內容 ```javascript= import { useState } from 'react'; import BlogList from './BlogList' const Home = () => { const [blogs, setBlogs] = useState([ { title: 'My new website', body: 'lorem ipsum...', author: 'mario', id: 1 }, { title: 'Welcome party!', body: 'lorem ipsum...', author: 'yoshi', id: 2 }, { title: 'Web dev top tips', body: 'lorem ipsum...', author: 'mario', id: 3 } ]); const title = "I am title" const title2 = "Only Mario" return ( <div className="home"> <BlogList blogs={blogs} title={title} /> <BlogList blogs={blogs.filter((blog) => blog.author === 'mario')} title={title2} /> </div > ); } export default Home; ``` 印出結果 也確實篩選掉了不是 mario 的 blog ![](https://i.imgur.com/1g5q6DA.png) # Pass Functions as Props 這邊我們透過刪除 blog 按鈕來展示 props 傳遞 functions BlogList.js * 先創造一個 button tag * 接下來在解構式中引入要使用 click 觸發的函式名稱(因為要從 parent component 傳遞下來) * 並且寫入 onClick 事件,注意必須使用箭頭函式才有辦法帶入參數使用 * 因為要針對指定的 blog 做刪除所以必須放入 id 作為參數好辨識是哪個 blog 被點擊了 ```javascript= const BlogList = ({ blogs, title, clickHandler }) => { return ( <div className="blog-list"> <h2>{title}</h2> { blogs.map((blog) => ( <div className="blog-preview" key={blog.id}> <h2>{blog.title}</h2> <p>Written By {blog.author}</p> <button onClick={() => clickHandler(blog.id)}>delete blog</button> </div> )) } </div> ); ``` Home.js * 定義 clickHandler 函式 * 把函式當作 props 的寫法寫進去,一樣名字可以自訂 ## clickHandler 函式 因為 props 的值不可以改變原本的值,所以這邊使用 filter 功能篩選出要的內容放入**新的陣列**因此沒有修改原本的 props 內容 ## filter 的邏輯在這邊的處理是: 當 `blog.id` 不等於 參數 id 時把這時候產生的 blog 丟進新的陣列,內容都是不包含參數 id 的 blog 也就是把參數 id 的 blog 刪掉了,這邊是刪除的邏輯所在 ```javascript= function clickHandler(id) { const newBlogs = blogs.filter(blog => blog.id !== id) setBlogs(newBlogs) } return ( <div className="home"> <BlogList blogs={blogs} title={title} clickHandler={clickHandler} /> </div > ); ``` # useEffect Hook (the basics) * useEffect 會執行一個 function 當畫面 render 的時候 * 而畫面會在頁面載入以及當 state 更新會重新 render 時候會觸發 * 通常 useEffect 可以用來 fetch 資料 或是 處理 authentication (驗證資訊) ## 使用方法 一樣先引入進去要使用的檔案,直接把這兩行加進去 Home.js 內 當頁面出現時 console.log 了一次 當我點擊刪除按鈕兩次時又再跑了 兩次 ```javascript= import { useState, useEffect } from 'react'; useEffect(() => { console.log('use effect ran'); }) ``` ![](https://i.imgur.com/4Y3TWRL.png) ## 需要注意的點 因為 useEffect 觸發的點是 state 更新的時候,所以如果 useEffect 本身的 function 改變了 state 就會變成一個無限 loop 一直彼此觸發,需要特別注意 useEffect 不要改變到 State 不然會變成無限迴圈 # useEffect Dependencies 為了避免每一次的 render 都使用 useEffect 的函式,我們要用到一個 Dependencies Array 並把它傳 useEffect hook 當作第二個參數 ```javascript= useEffect(() => { console.log('use effect ran'); },[]) ``` 這邊如果我們只放了空的 array 它的效果是讓 useEffect 只會在第一次畫面 render 的時候觸發,之後如果我按刪除按鈕就算畫面重新 render 也不會觸發 useEffect 畫面上可以看到我 blog 都刪除光了也沒有跑出其他的 useEffect 的函式跑的跡象 ![](https://i.imgur.com/AJY5Qpy.png) ## 當我們要針對某個 state 操作 useEffect 時 首先我們設置一個按鈕來專門給 useEffect 使用,所以當點擊按鈕時就會觸發 useEffect 點擊其他按鈕就不會再觸發了 操作很簡單就是在後面的陣列內部加上要操作的 state 即可 ![](https://i.imgur.com/OPLRfWk.png) ```javascript= const [name, setName] = useState('Mario') useEffect(() => { console.log('use effect ran'); console.log(name); }, [name]) return ( <div className="home"> <BlogList blogs={blogs} title={title} clickHandler={clickHandler} /> <button onClick={() => setName('luigi')}>Change Name</button> <p>{name}</p> </div > ); ``` 操作後可以發現我刪除光光其他按鈕都沒有再觸發 useEffect 了 * 只有畫面第一次 render 時觸發 * 以及點擊被監控的 state 時才有觸發 ![](https://i.imgur.com/hEkfZc6.png) # Using JSON Server [json-server](https://www.npmjs.com/package/json-server) 剛剛有提到 useEffect 很適合 fetch 資料會這邊作展示,一般真實的專案中不可能會把所有的資料死死的打在程式碼中而是會來自資料庫 首先我們會創立一個資料夾內容放入 db.json 並且在裡面輸入要當作[資料操作的內容](https://github.com/iamshaunjp/Complete-React-Tutorial/blob/lesson-16/dojo-blog/data/db.json) 下方可以看到 blogs 當作其 endpoint 操作 ![](https://i.imgur.com/nqko38j.png) 接下來輸入下方指令開啟 json-server 監聽正確的資料夾位置/ 設置 port 的位置 `npx json-server --watch data/db.json --port 8000` ## 這邊是針對 Endpoints 可以操作的一些方法 之後都會用到 ![](https://i.imgur.com/jP0Kvvj.png) # Fetching Data with useEffect 現在我們有 json-server 在運行並且觀測我們的 db.json ,並且可以操作 endpoints 來使用這些 json 資料 首先直接在 useEffect 內使用 fetch 加上剛剛使用 json-server 取得的運行中的資料庫網址,針對資料作處理後使用 setBlogs 把取得 data 放進去即可使用 db.json 的資料 ```javascript= useEffect(() => { fetch('http://localhost:8000/blogs') .then(res => { return res.json() }).then((data) => { setBlogs(data) }) }, []) ``` 但這邊會出現一個問題,因為 fetch 這個資料需要一點時間,然而我們設置 useState(null) 它的初始值為空,所以這一段取得資料的時間就會讓 React render 不出東西來,從下方圖片可以看出要使用 map 的資料為 null 也就是還沒有 fetch 到資料的部分 ![](https://i.imgur.com/GQ8Ib2R.png) 這邊可以這樣處理 ## 使用邏輯運算子 `&&` (這個技巧之後會非常常用) 當左側的值為 true 時會返回右邊的值 所以當 blogs 還沒有 fetch 到資料會呈現 null 也就是再跑判斷式的時候會變成 false 也就不會返回東西 然而當 blogs fetch 到資料了內容不為空,則為 true 就會返回右側的 BlogList component 也就可以正常執行頁面不抱錯瞜 ```javascript= return ( <div className="home"> {blogs && <BlogList blogs={blogs} title={title} />} </div > ); ``` # Conditional Loading Message 因為本篇的專案使用的檔案來自自身電腦所以載入的速度非常快,然而如果使一般正常的專案使用的資料庫來自外面的話會需要一定的載入時間,這時候就可以設置 **Conditional Loading Message** 告訴使用者目前頁面正在載入資料中 1. 首先設置新的 useState isPending 並且給予預設值為 true 1. 在 JSX 中放入想要顯示的字樣並且使用 && 判斷式來辨別如果 isPending 為 true 則顯示 div loading... 如果為 false 則甚麼都不顯示 2. 在 fetch 資料取得後的階段 setIsPending 為 false 即可正確顯示資料庫內容 ```javascript= const [isPending, setIsPending] = useState(true); useEffect(() => { fetch('http://localhost:8000/blogs') .then(res => { return res.json() }).then((data) => { setBlogs(data); setIsPending(false); }) }, []) return ( <div className="home"> {isPending && <div>Loading...</div>} {blogs && <BlogList blogs={blogs} title={title} />} </div > ); ``` # Handling Fetch Errors 這章節主要要處理的部分要把 fetch 失敗後的 state 輸出到頁面上告知使用者 首先使用 catch 去抓住錯誤資訊 ```javascript= useEffect(() => { fetch('http://localhost:8000/blogs') .then(res => return res.json() }).then((data) => { setBlogs(data); }).catch(err => { console.log(err.message) }) }, []) ``` 接下來可以測試比方說關閉 json-server 下一步針對 res 的狀態做處理 如果 res 傳輸成功 ok 的部分為 true 失敗則為 false,所這邊設立判斷式作如果傳輸失敗則丟出 error 字樣並且會被 catch 的部分接收到我們特別寫的這字串 ```javascript= if (!res.ok) { throw Error('could not fetch the data from that resource'); } ``` 所以我們這邊做測試把 fetch 網址做測試比方說多一個 s `fetch('http://localhost:8000/blogss')` 因為 res 傳輸失敗因此就會抓到這段 Error ![](https://i.imgur.com/zP5Mh7e.png) 下一步把這段 error 放到 state 上面動態呈現到頁面上 1. 設立 error 的 state 初始值為 null 2. 在 .then 裡面操作 setError 值為 null 因為此時資料是傳輸成功的 3. 在 .catch 內setError 為 自訂的錯誤訊息 setError(err.message) 4. 在 JSX 動態呈現 error ,當 error 有值時為 true 則顯示右邊的 div ,也就是 err.message 的內容會被呈現到頁面上 ```javascript= const [error, setError] = useState(null); useEffect(() => { fetch('http://localhost:8000/blogss') .then(res => { if (!res.ok) { throw Error('could not fetch the data from that resource'); } return res.json() }).then((data) => { setBlogs(data); setIsPending(false); setError(null); }).catch(err => { setError(err.message); setIsPending(false); console.log(err.message); }) }, []) return ( <div className="home"> {error && <div>{error}</div>} {isPending && <div>Loading...</div>} {blogs && <BlogList blogs={blogs} title={title} />} </div > ); ```