嗨大家好我是 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 明年不一定再見!