iT邦幫忙

2023 iThome 鐵人賽

DAY 19
0

從製作語言切換功能,認識 Redux

前面我們曾介紹過 Props 能將父元件的資料傳到子元件。不過 Props 只能層層傳遞,無法在同層之間傳遞。例如當我要從 CreateButton 將資料傳到 MenuList ,必須將資料放在共同的父元件 App ,才能讓他們共用資料。
https://ithelp.ithome.com.tw/upload/images/20230923/20129635uIBssuCSne.png
https://ithelp.ithome.com.tw/upload/images/20230923/20129635r0DmtGgcoq.png

問題來了,如果有很多元件,關聯又過於複雜時,那要幫他們包多少父元件?假設通通丟到 App.js ,就像上圖那樣,那又得面對 props 傳遞好幾層的問題。而且元件的功能性獨立,資料如今卻被放到毫不相干的 App.js 中,邏輯好像也怪怪的。
https://ithelp.ithome.com.tw/upload/images/20230923/20129635rAYt1puo2q.png

在本節中我們將一起來認識 Redux ,並創建語言切換功能。 Redux 是一個可用於 JavaScript 的狀態管理工具,能套用在任何程式語言。雖然常和 React 搭配使用,但 React 並不包含 Redux 。講簡單點, Redux 在做的就是把 Data 單獨出來,要取用的元件再去取。
https://ithelp.ithome.com.tw/upload/images/20230923/201296353WGzoN1Yz0.png

而 Redux 主要包含這四個部分:

  • Store :儲存全域 State 的物件。
  • Action :用一個函式, return 一個物件,去改變 State 的行為。裡面的 type 代表這個 action 的名字。
  • Reducer :確認現在要用哪個 action ,進而執行並改變 State 。第一個參數是初始值,第二個是 action 。
  • Dispatch :負責傳送 action 給 reducer 。

要使用需要先安裝 React Redux :

npm install redux react-redux

Tips : Redux 的架構說複雜好像也蠻複雜的,但照著步驟做不至於很困難。我會先把整個流程講過,建議即使不能完全理解也先跟著做做看。

打開 App.jsx 開始撰寫程式碼。第一步是引入 createStore 。後面會透過它來建立 Store ,以保存全域資料。

import {createStore} from 'redux';

第二步:建立行動 Action 。 Action 並不是撰寫後就立刻執行,而是先將要執行的寫下來,等呼叫時才執行。 其中, type 是 Action 的名稱, payload 則是若需傳入參數,可將參數放在裡頭。由於之後會需要在其他元件引入,因此要記得 export 他。

export const changeLanguage = str => {
  return {
    type: 'CHANGE_LANGUAGE',
    payload: str,
  };
};

第三步:建立 Reducer 。他會判斷傳進來的值是哪個行動名稱,並執行對應的事情。第一個參數是初始值,第二個參數是傳進來的 Action 。以下面的例子來說,當傳入 CHANGE_LANGUAGE ,會將初始值裡的 language 改變為傳入的參數。

const initialState = {
  language: 'zh',
};
const languageReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'CHANGE_LANGUAGE':
      return {
        ...state,
        language: action.payload,
      };
    default:
      return state;
  }
};

最後將上面的 Reducer 放進 Store 中:

const store = createStore(languageReducer);

Redux 主要透過 Dispatch 來執行,而 Store 可以用 getState() 來取得。透過下面的程式碼,能用 console.log() 印出 State 的值,檢查並確保 Redux 每個步驟都有接好:

store.subscribe(() => console.log(store.getState()));
store.dispatch(changeLanguage('eng'));

確定有串接好後,就能繼續把全域資料實際提供給大家使用。要使用 Store 有一個前提,是他必須被 Provider 元件包住,所以我們通常會將它放在最外層的 App 中。最後只要提供 store={store} ,內部的元件就都能取到全域資料了。

import {Provider} from 'react-redux';
function App() {
  return (
    <Provider store={store}>
      <NavigationContainer>
        <BottomeNavigation />
      </NavigationContainer>
    </Provider>
  );
}

https://ithelp.ithome.com.tw/upload/images/20230923/20129635uxwGnftbbi.png

接著需要製作一個切換語言的頁面,並呼叫 dispatch ,來進行後面一連串的動作。首先建立頁面元件,設定兩個按鈕,點選後會傳不同參數給 handlePress 。

function LanguageScreen() {
  const handlePress = language => {
  };

  return (
    <View>
      <TouchableOpacity onPress={() => handlePress('zh')}>
        <Text>中文</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => handlePress('en')}>
        <Text>English</Text>
      </TouchableOpacity>
    </View>
  );
}

要執行 dispatch 須引入 useDispatch 這個 hooks ,並引入要呼叫的 action 。宣告一個叫 dispatch 的變數綁定 useDispatch() 後,就可以用以呼叫 action 並傳入參數了。

import {useDispatch} from 'react-redux';
import {changeLanguage} from '../App';

function LanguageScreen() {
  const dispatch = useDispatch();

  const handlePress = language => {
    dispatch(changeLanguage(language));
  };

如果要在另一個元件取得存在全域的資料,則要透過 useSelector 。只要取得了 nowLanguage ,就能根據全域資料中的語言來渲染不同元件。

import {useSelector} from 'react-redux';

function HomeScreen() {
  const nowLanguage = useSelector(state => state.language);
  return (
    <View>
      <Text>目前語言</Text>
      <Text>{nowLanguage === 'zh' ? '中文' : '英文'}</Text>

https://ithelp.ithome.com.tw/upload/images/20230923/20129635FO6lBdQ1xX.png

Tips :實際在做不同語系的功能,這不是最好的方法。透過 react-native-localization 這個套件其實就能設定了。不過若在不同語系中要顯示不一樣的元件、文案等,那也許可以參考這個方案。若有高手能分享在這種情況下更好的實作方式,也十分感激!


不只一種的 Reducer

如果有不只一個 Reducer ,則需要從 redux 載入 combineReducers ,將 Reducers 放進物件。

import {createStore, combineReducers} from 'redux';
const rootReducer = combineReducers({
  language: languageReducer,
});
const store = createStore(rootReducer);

不過要這樣做的話,在取資料時也要加上 combineReducers 裡的屬性名稱。

function HomeScreen() {
  const nowLanguage = useSelector(state => state.language.language);

用資料夾來管理 Redux

回到 App.js 中,現在 App 裡的程式碼又長又複雜。因此,可以透過拆分資料夾來加以管理。

在根目錄開 actions 資料夾,底下開 index.js 並把相關程式碼丟進去。

export const changeLanguage = str => {
  return {
    type: 'CHANGE_LANGUAGE',
    payload: str,
  };
};

別忘了到引入 changeLanguage 的元件改位置:

import {changeLanguage} from '../actions';
function LanguageScreen() {

同樣在根目錄開 reducers 資料夾,底下開 index.js 和 languageReducer.js 。當然如果只有一隻 reducer ,也可以直接把東西貼在 index.js 就好。

// reducers/languageReducer.js 
const initialState = {
  language: 'zh',
};

const languageReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'CHANGE_LANGUAGE':
      return {
        ...state,
        language: action.payload,
      };
    default:
      return state;
  }
};

export default languageReducer;

// reducers/index.js 
import {combineReducers} from 'redux';
import languageReducer from './languageReducer';
const rootReducer = combineReducers({
  language: languageReducer,
});
export default rootReducer;

最後在 App.js 引入 rootReducer 創建 store :

import rootReducer from './reducers';
const store = createStore(rootReducer);

上一篇
Day 21. 從實作抽屜,認識 onContentSizeChange
下一篇
Day 23. 從實作收藏功能,認識 AsyncStorage
系列文
即使明天老闆突然叫你用 React Native 也可以跟他說好沒問題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言