前面我們曾介紹過 Props 能將父元件的資料傳到子元件。不過 Props 只能層層傳遞,無法在同層之間傳遞。例如當我要從 CreateButton 將資料傳到 MenuList ,必須將資料放在共同的父元件 App ,才能讓他們共用資料。
問題來了,如果有很多元件,關聯又過於複雜時,那要幫他們包多少父元件?假設通通丟到 App.js ,就像上圖那樣,那又得面對 props 傳遞好幾層的問題。而且元件的功能性獨立,資料如今卻被放到毫不相干的 App.js 中,邏輯好像也怪怪的。
在本節中我們將一起來認識 Redux ,並創建語言切換功能。 Redux 是一個可用於 JavaScript 的狀態管理工具,能套用在任何程式語言。雖然常和 React 搭配使用,但 React 並不包含 Redux 。講簡單點, Redux 在做的就是把 Data 單獨出來,要取用的元件再去取。
而 Redux 主要包含這四個部分:
要使用需要先安裝 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_LANGUAG
E ,會將初始值裡的 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>
);
}
接著需要製作一個切換語言的頁面,並呼叫 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>
Tips :實際在做不同語系的功能,這不是最好的方法。透過 react-native-localization 這個套件其實就能設定了。不過若在不同語系中要顯示不一樣的元件、文案等,那也許可以參考這個方案。若有高手能分享在這種情況下更好的實作方式,也十分感激!
如果有不只一個 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);
回到 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);