基本功練習: Hacker News! — Part 6: 使用 Store

基本功練習: Hacker News! — Part 6: 使用 Store

基本功練習: 使用純 JS + Redux 把 Hacker News 做出來。

前情提要

view

Store 處理好了,兩個主要功能:加入我的最愛 + 移除我的最愛也創建了,現在我們來運用它吧!

我的最愛: Store

我們的 store 創建好了,那現在我們要把它擺在哪邊咧?所有 data 在的地方:stories.js

在顯現 stories 的地方,我們需要再加一個 data,用來判斷這條新聞,有沒有被加進「我的最愛」裡面。

1
${hasStories ? stories.map((story, i) => Story({ ...story, index: i + 1 })).join('') : '沒新聞耶'}

該怎麼處理咧?

首先,我們要先把 store 導進來,並拿到我們的「我的最愛」清單。

1
2
3
import store from '../store.js';

const { favorites } = store.getState();

他們兩個都進來後,現在我們要判斷每條新聞,是否已經被加進去 favorites 的 state 裡面了。

這時候我們需要寫一個 function 檢查它,再回傳個 true or false boolean。

checkFavorite.js:

1
2
3
4
5
import checkFavorite from '../utils/checkFavorite.js';
 
export default function checkFavorite(favorites, story) {
  return favorites.some(favorite => favorite.id === story.id);
}

這個檢查 function 寫好後,我們就可以把它塞進我們的 data 裡了!

1
${hasStories ? stories.map((story, i) => Story({ ...story, index: i + 1, isFavorite: checkFavorite(favorites, story) })).join('') : '沒新聞耶'}

這邊 Story function 會接受 object parameter,裡面有個 property 叫做 isFavorite。

isFavorite 裡面會直接用 checkFavorite function 檢查,這條新聞是否有在 favorites state 裡面。

寫好後,我們就可以去 Story.js 按照判斷把 toggle 寫出來。

story.js:

1
2
3
4
5
6
7
8
9
10
export default function Story(story) {
  return `
    // 中間省略
      <span class="favorite">
        &hearts;
        ${story.isFavorite ? "從我的最愛移除" : "新增至我的最愛"}
      </span>
   // 這邊也省略
  `;
}

撰寫點擊 event

現在就是當我們按下,「新增至我的最愛」或「從我的最愛移除」這筆資料會進我們的 state,或從我們的 state 裡面消失。

我們需要一個 click event!就放在 這邊吧!

我們再次回到 stories.js:

1
2
3
4
5
document.querySelectorAll('.favorite').forEach(favoriteButton => {
  favoriteButton.addEventListener('click', function() {
    //... 我們要在這裡寫判斷
  }); 
});

但是等等,如果這個 span 上面已經有我要的 story data,那我判斷起來會更方便,我們來修改一下

1
2
3
4
<span class="favorite" data-story='${JSON.stringify(story)}'>
  $hearts;
  ${story.isFavorite ? "從我的最愛移除" : "新增至我的最愛"}
</span>

這樣的話,我們直接用 this.dataset.story 就可以抓到 story 了!

首先為了檢查這筆資料是不是在 state 裡面,我們需要把它變回 object,然後檢查他,所以…

1
2
const story = JSON.parse(this.dataset.story); // 讓它變回 object
const isFavorited = checkFavorite(favorites, story); // 會回傳 boolean

接下來,我們就可以用 dispatch 更新 store 了

1
store.dispatch({ type: isFavorited ? "REMOVE_FAVORITE" : "ADD_FAVORITE", payload: { favorite: story } })

如果他已經被加進「我的最愛」了,那就發 REMOVE_FAVORITE 的 Action,沒有的話那發 ADD_FAVORITE 的 Action。

payload 按照我們的 state object 來寫: { favorite: story }。

接著我們更新 store:

1
await Stories(path);

然後,就出錯了 XD

因為我們的 callback function 不是 async,但是沒關係,加上去就 ok 了:

stories.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
26
// 省略 import

export default async function Stories(path) {
  const { favorites } = store.getState();  
  const stories = await getStories(path);
  const hasStories = stories.length > 0;
                    
  view.innerHTML = `<div>
    ${hasStories ? stories.map((story, i) => Story({ ...story, index: i + 1, isFavorite: checkFavorite(favorites, story) })).join('') : '沒新聞耶'}
  </div>`; 
  
  document.querySelectorAll('.favorite').forEach(favoriteButton => {
     favoriteButton.addEventListener('click', async function() {
       const story = JSON.parse(this.dataset.story);  
       const isFavorited = checkFavorite(favorites, story);
       if (isFavorited) {
         store.dispatch({ type: "REMOVE_FAVORITE", payload: { favorite: story } })  
       } else {
         store.dispatch({ type: "ADD_FAVORITE", payload: { favorite: story } })    
       }
       await Stories(path);
     }); 
  });
}

// 省略 getStories

我們終於把加入我的最愛 + 移除我的最愛,除了讓 code 更精簡以外,還有最後一件事,我們的 Hacker News 就大功告成了,那就是…

創建「我的最愛頁面」!(講了超多次,到底要不要寫啦!