嗨大家好我是 Chris,首先關於昨天的事想先說聲抱歉,另外還有一件事。
就是我以為就算在 hook 也可以直接使用原生的 redux ,但..

沒錯,失敗惹。
也有可能也是因為當下很急,害我沒辦法靜下心來好好處理,所以才會用不出來。
所以這種搭配搞不好還是有可能的,有興趣的可以去試試看喔(我之後有時間再來弄看看)
好的總之,就是我們會改用 react-redux 配合 hook 釋出的 function 取代以往的 provider、connect( 和他的參數function)。
基本上總體的概念架構不變,只是換個方法取用,那麼今天就快速的介紹一下要用的 function和之前的比較,並完成整個 blog 吧。
剛剛也說了,store、action、reducer基本上還是可以沿用以前的沒問題,所以不用大規模的改架構啦~(好險)
在新的版本中,不再需要 connect 幫我們串聯 UI 和 redux,因為基本上 hook 在切割這方面也已經做得很好了,詳細的可以參考 Day-28。
那沒有 connect 就沒有地方讓我們使用 mapStateToProps、 mapDispatchToProps  這兩個地方做 I/O(input/ouput)了。
其實這樣也好,不然還要管兩個莫名其妙的生命週期,省事 XD
最直觀的影響是我們不能 dispatch action了,取而代之的呢,是我們可以使用 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 抓下來,讓我們可以在頁面上取用。
我們可以使用很簡單的方式抓取 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 ,我們推薦裝載:
裝載完之後,我們到 stroe 把這些 package 實裝上去:
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,按照目前的功能(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 ,我們把所有的資料一起回傳給 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 ,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 拿到的資料就好,因為陣列包陣列會出錯滴。
如果擔心會出錯的話,可以搭配我的範例文檔一起看。
接下來跑一次試試看吧!

整個畫面基本上是 ok的,剩下的你們自己慢慢看~
接下來我們看看 console 吧:

可以看到我們傳遞的 action 被記錄在這裡,這就是redux-logger 的作用。
再來推薦大家一個好用的,這個 chrome 擴充

這個擴充可以搭配我們剛剛下載的第三個套件 redux-devtools-extension。
如果你裝好的話她會在 chrome 的右上角

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

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