2023年1月4日 星期三

[React]Redux說明&導入

 

此篇主要說明Redux,範例程式碼可參考這裡


基本上Redux的功用就是管理狀態以及畫面與資料分開管理,與之前加上登入機制所使用的Context功能大同小異,優於Context的地方是在有多個state要管理時會優於React原生的Context。

p.s. Redux和Context API可混用,需頻繁操作的state可以用Redux,反之則用Context。

主要核心元件為redux和react-redux,redux是函式庫本體,react-redux則是React綁定使用套件。

Redux組成主要有三個元素

1.action

定義操作state方法,會傳入reducer。

2.reducer

保管State並針對傳入的action去對state做動作。

3.store

管理並整合reducer。

以下為Redux 的流程示意圖


接下來我們就直接用計數器範例來說明各元素的功能以及作用


開始前先用npm安裝套件語法如下

//安裝redux和react-redux

npm install --save redux react-redux


//安裝react-redux的Typescript套件

npm install --save --dev @types/react-redux


程式碼部分我們先建立reducer部分,新增reducers資料夾和counter檔案(src/reducers/counter.ts)

counter.ts裡面有Action類別定義、Actions和reducer本體,程式碼如下


counter.ts

-------------------------------------------------------------------------------------------------------------------

//初始state
const initState = {
    count: 0
};

// #region 定義Action類別
//增加
const INCREMENT = 'INCREMENT';
//減少
const DECREMENT = 'DECREMENT';
// #endregion 

// #region 匯出Action
//增加
export const countPlus = { type: INCREMENT }
//減少
export const countMinus = { type: DECREMENT }
// #endregion

// #region reducer本體
const counterReducer = (state = initState, action: any) => {
    //依照action的類型作判斷
    switch (action.type) {
        case INCREMENT:
            return { count: state.count + 1 };
        case DECREMENT:
            return { count: state.count - 1 };
        default:
            return state;
    }
};
// #endergion 

export default counterReducer;

-------------------------------------------------------------------------------------------------------------------

簡單說明,定義以及匯出Action主要是集中管理狀態的處理,對後續維護以及測試有許多幫助。

而reducer本體則是依照傳入的action去操作state並回傳最新的State,使之更新view。


再來建立集中管理reducer的store,新增stores資料夾和configureStore檔案(src/stores/configureStore.ts)


configureStore.ts

-------------------------------------------------------------------------------------------------------------------

import { createStore, combineReducers } from 'redux';
import counterReducer from '../reducers/counter';
const rootReducer = combineReducers({
    counterReducer,
});
const store = createStore(rootReducer);
export default store;

-------------------------------------------------------------------------------------------------------------------

如果只有一個reducer的可以直接createStore,但預想未來專案可能會有多個reducer,所以中間多一個combineReducers,有新增時在填入。


再來要修改index.tsx讓所有Component可以使用,需要使用react-redux的Provider來包覆住APP。


//index.tsx(紅字部分為新增)

-------------------------------------------------------------------------------------------------------------------

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import './i18n';
import { Provider } from 'react-redux';
import store from './stores/configureStore';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
    <React.StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
  </React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(console.log);

-------------------------------------------------------------------------------------------------------------------

透過Provider的store讓全系統都可使用Redux。


前面這樣定義就完成了,開始撰寫計數器,新增counter.tsx頁面

//counter.tsx

-------------------------------------------------------------------------------------------------------------------

import * as React from 'react';
import { useSelector,useDispatch } from 'react-redux';
import { countMinus, countPlus } from '../reducers/counter';
export function Counter() {
    
    //透過useDispatch來使用action
    const dispatch = useDispatch();
    //透過useSelector去取得State
    const count: number = useSelector(state => (state as any).counterReducer.count);
    return (
        <React.Fragment>
            <h1>Counter</h1>
            <h3> Redux</h3>
            <p aria-live="polite">Current count: <strong>{count}</strong></p>
            <button style={{ margin: '5px' }}
                type="button"
                className="btn btn-primary btn-lg"
                onClick={() => { dispatch(countPlus); }}>
                Increment
            </button>
            <button style={{ margin: '5px' }}
                type="button"
                className="btn btn-primary btn-lg"
                onClick={() => { dispatch(countMinus); }}>
                Decrement
            </button>
        </React.Fragment>
    );
}

-------------------------------------------------------------------------------------------------------------------

說明一下程式碼,

操作部分使用useDispatch來取得dispatch,在按鈕時在使用dispatch傳入定義好的action操作state。

而畫面更新部分則是使用useSelector取代useState,如果state更新則有使用到useSelector的Component也會更新。


實際就可以來Run看看畫面,看起來是可以正常加減。



p.s.因為是全域的state,所以要注意如果切頁再回來值不會變回0。


這樣就完成了資料與畫面分離的部分,對於未來的維護上應該也就簡單許多,建議大家還是要使用Redux。


一樣在結尾也說明一下其實還有更多的應用沒有一起說明,例如操作資料庫或是用redux-logger來記錄每次操作state...等等,後續如果有用到時再分享給大家,謝謝!










沒有留言:

張貼留言

【.Net Core】 EF Core + Web API 實作

 EF Core是Entity Framework在.Net Core使用的版本,功能幾乎相同,但具有輕巧、高擴充性以及高效能等優點,建議各位學習。 通常在.Net Core如果要用ORM的方式會有兩種選擇分別是EF Core以及Dapper。 從其他網路文章可以看到這兩種在最新...