iT邦幫忙

第 12 屆 iThome 鐵人賽

0
Modern Web

先你一步的菜鳥 - 從 0 開始的前端網頁設計系列 第 31

Day-31 使用 hook 打造專屬 blog(15) - 在 blog 完成 redux 功能實作

嗨大家好我是 Chris,首先關於昨天的事想先說聲抱歉,另外還有一件事。

就是我以為就算在 hook 也可以直接使用原生的 redux ,但..

https://ithelp.ithome.com.tw/upload/images/20201001/20123396Tbn70NgI9F.jpg

沒錯,失敗惹。

也有可能也是因為當下很急,害我沒辦法靜下心來好好處理,所以才會用不出來。

所以這種搭配搞不好還是有可能的,有興趣的可以去試試看喔(我之後有時間再來弄看看)

好的總之,就是我們會改用 react-redux 配合 hook 釋出的 function 取代以往的 provider、connect( 和他的參數function)。

基本上總體的概念架構不變,只是換個方法取用,那麼今天就快速的介紹一下要用的 function和之前的比較,並完成整個 blog 吧。

新舊 function 對照

剛剛也說了,store、action、reducer基本上還是可以沿用以前的沒問題,所以不用大規模的改架構啦~(好險)

connect

在新的版本中,不再需要 connect 幫我們串聯 UI 和 redux,因為基本上 hook 在切割這方面也已經做得很好了,詳細的可以參考 Day-28

那沒有 connect 就沒有地方讓我們使用 mapStateToProps mapDispatchToProps 這兩個地方做 I/O(input/ouput)了。

其實這樣也好,不然還要管兩個莫名其妙的生命週期,省事 XD

最直觀的影響是我們不能 dispatch action了,取而代之的呢,是我們可以使用 useDispatch

useDispatch

使用的方法跟以前一樣,在 component 定義完後使用它:

import { useDispatch } from "react-redux";
import { GET_NOTE } from "../redux/action/Note_Action";

  const dispatch = useDispatch();

  const getnote = () =>{ dispatch( GET_NOTE() )}

  getnote()

這樣一來就可以繼續使用了,甚至我覺得比以前更好用 ~

這樣 output 端有了,我們剩下 input,

我們需要把在 state tree 的 value 抓下來,讓我們可以在頁面上取用。

useSelector

我們可以使用很簡單的方式抓取 state 的資料:

import { useSelector } from "react-redux";

const Note = useSelector((state) => state.Note.notes);

怎麼樣,簡單吧,那馬上來更改我們自己的資料流吧!

實作

首先有 3 個 package 建議下載,因為們有 call db 的行為(雖然是 json 檔),會被認定為 async call,簡單來說,就是 redux 本身不支持 async function ,詳情請看 Day-27 的介紹。

所以為了讓 redux 可以接收 async ,我們推薦裝載:

  • redux-thunk :就是靠這個套件讓我們可以接收 async 的 data(等等介紹)
  • redux-logger:讓我們能夠在 console 中追蹤 Action 造成的影響(等等介紹)
  • redux-devtools-extension:這個就很好用了,他可以讓你直觀的觀察 state tree和各種動作,這個專案沒有用到,不過還是推薦,在大專案超好用。

裝載完之後,我們到 stroe 把這些 package 實裝上去:

Store

import { applyMiddleware, createStore } from "redux";
import rootReducer from "../reducer/index";
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(
    rootReducer,
    composeWithDevTools(
        applyMiddleware(thunk, logger),
    ));
export default store;

這樣就可以讓我們更能掌控整個 redux 架構。

Action

接著我們來更新 action,按照目前的功能(getdata),我們需要在這邊 call data ,並把資料處理好之後回傳

所以我們需要創造一個 getData function ,讓我們能 call 到api,並使用 Promise,確保我們能真的接到這筆資料:

function getData() {
  return new Promise((resolve, reject) => {
    // 這邊可以 call api,意思是如果有 data 就回傳 data,沒有就回傳 null
    const state = data ? data : null;
    // 之後就是成功或失敗各自回傳的
    if (state == null) {
      reject(console.log("fetch fail"));
    }
    resolve(state);
  });
}

然後我們還需要一個 function 包裝這個 getData ,讓我們能夠在確定他成功之後執行我們需要執行的動作

export function fetchPageData() {
  return (dispatch) => {
    return getData()
      .then((json) => dispatch(GET_NOTE(json)))
      .catch((err) => {
        console.log(err);
      });
  };
}

這邊就運用到了我剛剛說的 thunk,他不是一個 api function,而是一種概念。

它會判別你所 dispatch 得是 function 或是 Action:

  • Action 就直接 dispatch
  • function 會先 執行完 裡面的動作之後,才會 dispatch,這樣就可以確保我們有足夠的時間等待promise 回傳回來。

之後來設定最基本的 Action ,我們把所有的資料一起回傳給 reducer:

function GET_NOTE(json) {
  return {
    type: Constants.GET_NOTE,
    Notes: json.Notes,
    article: json.article,
    experience: json.experience,
    skill: json.skill,
  };
}

整體會像這樣:

import data from "../../data/data";
import { Constants } from "../Constants";
export function fetchPageData() {
  return (dispatch) => {
    return getData()
      .then((json) => dispatch(GET_NOTE(json)))
      .catch((err) => {
        console.log(err);
      });
  };
}
function getData() {
  return new Promise((resolve, reject) => {
    const state = data ? data : null;
    if (state == null) {
      reject(console.log("fetch fail"));
    }
    resolve(state);
  });
}
function GET_NOTE(json) {
  return {
    type: Constants.GET_NOTE,
    Notes: json.Notes,
    article: json.article,
    experience: json.experience,
    skill: json.skill,
  };
}

這樣 action 就設定好了。

Reducer

接下來看到 reducer ,Action 執行後的 payload 會被我們從這裡新增到 state tree,會像這樣:

import data from "../../data/data"
import {Constants} from '../Constants'
const initialState = {
  Notes: [],
  article:[],
  experience: [],
  skill: [],
};

export default function Note(state = initialState, action) {
  switch (action.type) {
    case Constants.GET_NOTE:
      return {
       // 請記得一定要先拆分之前的 state 之後再新增,不然會蓋掉之前的檔案,就違反 redux 的原則了。
        ...state,
        article:action.article,
        skill: action.skill,
        experience:action.experience,
        Notes: action.Notes,
      };
    case Constants.PUT_NOTE:
      return {
        // update Notes
      };
    default:
      return state;
  }
}

好的資料端被處理完之後,我們來把資料接上畫面吧

因為我們剛剛很懶,一個 action 一次拿完全部的資料,所以我們只要在最外層的 component 一次做完之後就可以在下面那些頁面用 useSelector 取代我們放在 page 的資料就好了

最外層的 component 就是 App.js 所以我們在裡面運用 useEffect 拿到全部資料吧:

//以上 import 省略 
import {useDispatch } from "react-redux";
import { fetchPageData } from "./redux/action/Note_Action";

export default function App() {
  const dispatch = useDispatch();
//getall 就是我們剛剛在 action 包裝的那個,雖然他不是 action 物件,但記得還是要使用 dispatch 喔,不然下面的 function 會沒有定義到 dispatch :P
  const getall = () => dispatch(fetchPageData());
 
  useEffect(() => {
    getall();
  });

  return (
    <div>
      <Navbar />
      <Switch>
        <Route path="/Notes/:param" component={NotesDetail} />
        <Route exact path="/Notes" component={Notes} />
        <Route path="/About" component={About} />
        <Route exact path="/" component={Home} />
      </Switch>
      <Footer />
    </div>
  );
}

好的,這樣一來,就可以把下面的那些資料流取代掉了,我們以 home page 作範例:

//home.js
//以上 import 省略 
import { useSelector  } from "react-redux";

export default function Home() {

// 像這樣就可以把在 state tree 裡的資料拿下來了
  const article = useSelector((state) => state.Note.article);
  const skill = useSelector((state) => state.Note.skill);
// 
  return (
    <div>
      <FeaturedBoard title="FeaturedBoard" className="title">
        <Banner article={article} />
      </FeaturedBoard>
      <InfoBoard pic={pic} className="board">
        <Article article={article} title="About Me" className="subtitle" />
      </InfoBoard>
      <SkillBoard pic={pic2}>
        <Article article={skill} title="Skill" className="subtitle" />
      </SkillBoard>
    </div>
  );
}

接下來就讓你們自己去尋吧,找 bug 和了解架構是成長的第一步!

這邊補充一點,因為格式的關係,在 article.js 就把 useState 拿掉吧,用從 redux 拿到的資料就好,因為陣列包陣列會出錯滴。

如果擔心會出錯的話,可以搭配我的範例文檔一起看。

接下來跑一次試試看吧!

https://ithelp.ithome.com.tw/upload/images/20201001/20123396AUt73Up1xU.png

整個畫面基本上是 ok的,剩下的你們自己慢慢看~

接下來我們看看 console 吧:

https://ithelp.ithome.com.tw/upload/images/20201001/20123396j3TefS1k9m.png

可以看到我們傳遞的 action 被記錄在這裡,這就是redux-logger 的作用。

再來推薦大家一個好用的,這個 chrome 擴充

https://ithelp.ithome.com.tw/upload/images/20201001/201233968OMZVfqwAk.png

這個擴充可以搭配我們剛剛下載的第三個套件 redux-devtools-extension

如果你裝好的話她會在 chrome 的右上角

https://ithelp.ithome.com.tw/upload/images/20201001/20123396FcTdOTiHP1.png

接著我們點開他,會看到這樣一個介面,裡面功能真的很讚,可是我光說沒用,你們自己摸摸看吧,基本就是 logger 的強化版。

https://ithelp.ithome.com.tw/upload/images/20201001/201233964kPkMnHMuE.png

好的,整個系列到這邊結束,謝謝大家一路以來的觀看,其實我有留很大的建構空間,如果你想繼續擴充的話是一定可以的,大家一起加油,中秋節快樂,我是 Chris 明年不一定再見!


上一篇
Day-30 鐵人提前完賽和無法如期完成專案通知(今天可以補完我就馬上補)
系列文
先你一步的菜鳥 - 從 0 開始的前端網頁設計31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言