react專案使用redux入門-6-redux-thunk中介軟體處理非同步請求
阿新 • • 發佈:2022-01-14
場景:切換語言、請求產品列表
-
src/index.tsx
import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' // react-redux 利用上下文context,提供的資料元件Provider import 'antd/dist/antd.css' import './index.css' import App from './App' import 'i18n' import store from 'redux/store' ReactDOM.render( <React.StrictMode> {/* 使用Provider, 並載入 資料倉庫 store, 就可以在全域性範圍內使用store */} <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root') )
-
ListPage
元件中請求資料import { Component } from 'react' import { connect } from 'react-redux' import { ThunkDispatch } from 'redux-thunk' // ThunkDispatch<S,E,A> 為了支援函式型別的action,提供dispatch的型別 import { RootState } from 'redux/store' import { ActionsProductProps, fetchProductsActionCreator } from 'redux/product/actions' const mapStateToProps = ({ product }) => product // const mapStateToProps = ({ product }: RootState) => { // return { // loading: product.loading, // productList: product.productList // } // } const mapDispatchToProps = (dispatch: ThunkDispatch<RootState, unknown, ActionsProductProps> ) => { fetchProduct : () => dispatch(fetchProductsActionCreator()) } class ListPageComponent extends Component<ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>, any> { componentDidMount() { const { fetchProduct } = this.props fetchProduct() } render(){ const { productList, loading } = this.props return loading? <div>正在載入……</div>:( <div> { productList.map(item => <p key={item.id}>{item.name}</p>) } </div> ) } } export const ListPage = connect(mapStateToProps, mapDispatchToProps)(ListPageComponent)
-
Header
元件中使用import { Component } from 'react' import { withRouter, RouteComponentProps } from 'react-router-dom' import { MenuInfo } from 'rc-menu/lib/interface' import { nanoid } from 'nanoid' // 使用react-redux import { Dispatch } from 'redux' import { connect } from 'react-redux' import store, { RootState } from 'redux/store' import { addLanguageActionCreator, changeLanguageActionCreator } from 'redux/language/actionCreators' const mapStateToProps = (state: RootState) => { return { lng: state.language.lng, languageList: state.language.languageList } } const mapDispatchToProps = (dispatch: Dispatch) => { return { addLanguageDispatch: (language: { code: string, language: string }) => { dispatch(addLanguageActionCreator(language)) } changeLanguageDispatch: (lng: 'en'|'zh') => { dispatch(changeLanguageActionCreator(lng)) } } } class HeaderComponent extends Component<RouteComponentProps & ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>>{ render(){ const { history, lng, languageList, addLanguageDispatch, changeLanguageDispatch } = this.props /* meun 的點選事件 */ const oprateLanguage = ( e: MenuInfo ) => { if(e.key !== 'new'){ changeLanguageDispatch(e.key) }else{ addLanguageDispatch({code: `lng${nanoid()}`, language: '新語種'}) } } const menu = ( <Menu> <Menu.Item key='new' onClick={oprateLanguage}> 新增新語言 </Menu.Item> {languageList.map(language => ( <Menu.Item key={language.code} onClick={oprateLanguage}> {language.language} </Menu.Item> ))} </Menu> ) return ( <div> <Typography.Text className='mr40'>讓旅遊更幸福</Typography.Text> <Dropdown overlay={menu}> <Button> {languageList.find(language => language.code === lng)?.language} <GlobalOutlined /> </Button> </Dropdown> </div> ) } } export const Header = connect(mapStateToProps, mapDispatchToProps)(withRouter(HeaderComponent))
-
store
資料封裝- 新建目錄:
src/redux
、src/redux/language
- 新建檔案:
src/redux/store.ts
、src/redux/language/actionCreators.ts
,src/redux/language/reducers.ts
mkdir src/redux src/redux/language touch src/redux/language/actionCreators.ts src/redux/language/reducer.ts touch src/redux/product/actions.ts src/redux/product/reducer.ts
store.ts
import { createStore, combineReducers, applyMiddleware } from 'redux' import languageReducer form './language/reducer.ts' import productReducer form './product/reducer.ts' import thunk form 'redux-thunk' // 使用 combineReducers方法,連線多個reducer const rootReducer = combineReducers({ language: languageReducer, product: productReducer }) // redux-thunk 使用thunk中介軟體,使得redux的action除了物件形式,還是支援函式型別 const store = createStore(rootReducer, applyMiddleware(thunk)) // 使用ts的條件型別 ReturnType<T>,T:函式型別。 獲取函式返回值的型別 export type RootState = ReturnType<typeof store.getState> export default store
- product:-
product/actions.ts
、product/reducers.ts
actions.ts
// 使用redux-thunk,支援函式型別的action,這個action的型別:ThunkAction<R,S,E,A> import { ThunkAction } from 'react-thunk' import axios from 'axios' import { RootState } from 'redux/store' // fetch_start 主要是用來控制loading, 開始發介面,loading值為true export const FETCH_START = 'product/fetch_start' // fetch_success 主要用來 介面資料成功,將資料data存入store export const FETCH_SUCCESS = 'product/fetch_success' export const FETCH_FAIL = 'product/fetch_fail' interface ActionFetchStartProps { type: typeof FETCH_START} interface ActionFetchSuccessProps { type: typeof FETCH_SUCCESS, payload: any} interface ActionFetchFailProps { type: typeof FETCH_FAIL } export type ActionsProductProps = ActionFetchStartProps | ActionFetchSuccessProps | ActionFetchFailProps // actionCreator export const fetchStartActionCreator = (): ActionFetchStartProps => ({ type: FETCH_START}) export const fetchSuccessActionCreator = (data:any):ActionFetchSuccessProps => ({ type: FETCH_SUCCESS, payload: data }) export const fetchFailActionCreator = ():ActionFetchFailProps => ({ type: FETCH_FAIL }) // 函式型別的action export const fetchProductsActionCreator = (): ThunkAction<void, RootState, unknown, ActionsProductProps>=> async (dispatch, getState) => { dispactch(fetchStartActionCreator()) try{ const { data } = await axios.get('/api/products') dispatch(fetchSuccessActionCreator(data)) }catch{ dispatch(fetchFailActionCreator()) } }
reducer.ts
import { ActionsProductProps, FETCH_START, FETCH_SUCCESS, FETCH_FAIL } from './actions.ts' interface ProductState { loading: boolean, productList: any[] } const defaultState: ProductState = { loading: false, productList: [] } export default (state = defaultState, action:ActionsProductProps ) => { switch (action.type) { case FETCH_START: return { ...state, loading: true } case FETCH_SUCCESS: return { ...state, loading: false, tourists: action.payload } case FETCH_FAIL: return { ...state, loading: false } default: return state } }
- language:-
language/actions.ts
、language/reducers.ts
actions.ts
/* 用常量定義action.type,減少程式碼敲錯 */ export const ADD_LANGUAGE = 'language/add' export const CHANGE_LANGUAGE = 'language/change' /* action的型別申明 */ const AddActionProps = { type: typeof ADD_LANGUAGE, payload: { code: string, language: string } } const ChangeActionProps = { type: typeof CHANGE_LANGUAGE, payload: 'zh' | 'en' } export type LanguageActionProps = AddActionProps | ChangeActionProps /* 用工廠模式建立action */ export const addLanguageActionCreator = (language: {code: string, language: string}):ADD_LANGUAGE => { return { type: ADD_LANGUAGE, payload: language } } export const changeLanguageActionCreator = (lng: 'zh' | 'en'):CHANGE_LANGUAGE => { return { type: CHANGE_LANGUAGE, payload: lng } }
2. `reducer.ts`
import { ADD_LANGUAGE, CHANGE_LANGUAGE, LanguageActionProps } from './actions' export interface LanguageState { lng: 'zh' | 'en', languageList: {code: string, language: string}[] } const defaultStoreState: LanguageState = { lng: 'zh', languageList: [{ code: 'zh', language: '中文'}, { code: 'en', language: 'English'}] } export default (state = defaultStoreState, action:LanguageActionProps) => { switch (action.type) { case CHANGE_LANGUAGE: return { ...state, lng: action.payload } case ADD_LANGUAGE: return { ...state, languageList: [...state.languageList, action.payload] } default: return state } }
- 新建目錄: