基本功練習: Hacker News! — Part 3: 單獨頁面
基本功練習: 使用純 JS + Redux 把 Hacker News 做出來。
前情提要
我們的列表頁面已經寫好了,現在是個別頁面以及留言顯示了!
個別頁面
注意到之前的頁面了嗎?每當我們點進去一則新聞時,route 會變成這個: #/item?id=25757398
依照 React 的 Component 邏輯,我們要再創建一個叫做 Item 的 Component 來當個別頁面。
我們先來到 router 這邊把 item 丟進去:
router.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Stories from './pages/stories.js';
import Item from './pages/item.js'; // item 頁面
// 這邊省略
createRoutes() {
const routes = [
{ path: '/', page: Stories },
{ path: '/new', page: Stories },
{ path: '/ask', page: Stories },
{ path: '/show', page: Stories },
{ path: '/item', page: Item }
];
// 下面省略
還是那句話,先確保東西有吐在畫面上再慢慢 format。
item.js:
1
2
3
4
5
import view from '../utils/view.js';
export default function Item() {
view.innerHTML = `<div>item</div>`
}
好,東西有了,那我們開始把畫面刻出來。
再開始之前,我們知道每條新聞都有自己專屬的 id,我們需要抓到這個 id,才可以呼叫 API 顯示它對應的留言。
那要怎麼拿呢,我們可以用 .split 的方式來寫:
1
const storyId = window.location.hash.split('?id=')[1];
split 會創建一個 array,把 ?id= 前後的資訊分別塞進 array 裡。
我們只要這樣即可拿到 id。
我們接著寫呼叫 API 的 code。
1
2
3
4
5
6
async function getStory() {
const storyId = window.location.hash.split('?id=')[1];
const response = await fetch(`https://node-hnapi.herokuapp.com/item/${storyId}`);
const story = await response.json();
return story;
}
因為是 async 所以我們要等資料回傳,app 才不會壞掉。
可是既然我們要這麼抓 story ,那我們顯示 Item 的那塊就也要變成 async function (要等 story 進來)。
所以…
item.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Story from '../components/Story.js';
import view from '../utils/view.js';
export default async function Item() {
const story = await getStory();
view.innerHTML = `
<div>
${Story(story)}
</div>`
}
async function getStory() {
const storyId = window.location.hash.split('?id=')[1];
const response = await fetch(`https://node-hnapi.herokuapp.com/item/${storyId}`);
const story = await response.json();
return story;
}
登登~
這邊我們重複利用了之前寫的 Story Component,把資料顯示出來。
不過,因為這個頁面不是列表,所以我們要修改 story.js 把 index 拿掉。
story.js:
1
<span class="gray">${story.index || ""}</span>
這樣 Item 就不會出現 undefined 了(因為沒有排列數字)。
我們接著我們要讓 comments 出來,就像我們之前寫 Story 一樣,以此類推:
item.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 以上省略
export default async function Item() {
const story = await getStory();
const hasComments = story.comments.length > 0;
view.innerHTML = `
<div>
${Story(story)}
</div>
<hr/>
${hasComments ? story.comments.map(comment => JSON.stringify(comment)).join('') : '沒留言耶'}
`
}
// 以下省略
然後,我們又可以看到那個醜到不行的頁面了 XD
可是,如果 Item 的 id 不對呢? 沒有這個頁面,怎麼辦?
我們可以在呼叫 API 時,寫一個判斷確保它沒出錯,如果出錯了(id 錯誤,抓不到之類的),
我們會直接跟用戶講。
這邊用到配合 async + await function 的 try catch 功能。
1
2
3
4
5
6
7
8
9
10
11
12
let story = null;
let hasComments = false;
let hasError = false;
try {
story = await getStory();
hasComments = story.comments.length > 0;
} catch(error) {
hasError = true;
console.error(error);
}
另外,既然我們要跟用戶說畫面有錯誤,我就需要一個 local state boolean 去判斷,然後顯示畫面。
1
2
3
4
5
let hasError = false;
if (hasError) {
view.innerHTML = `<div class="error">抓不到資料耶。</div>`;
}
再把它裝進 try catch 裡面。
組裝起來就是這樣:
item.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export default async function Item() {
let story = null;
let hasComments = false;
let hasError = false;
try {
story = await getStory();
hasComments = story.comments.length > 0;
} catch(error) {
hasError = true;
console.error(error);
}
if (hasError) {
view.innerHTML = `<div class="error">抓不到資料耶。</div>`;
}
view.innerHTML = `
<div>
${Story(story)}
</div>
<hr/>
${hasComments ? story.comments.map(comment => JSON.stringify(comment)).join('') : 'No comments'}
`
}
我們接著來亂輸入 Item id,就會出現這個畫面了:
接下來就是把 Comments 全部都吐出來了!