はじめに
Reduxを実装したりtoolkitを使っているとAPIと通信しなければならない時があります。
なので簡単なAPIを使ってなるべく簡潔に動作させてみたいと思います。
React Nativeで実装していきます
実現すること
- 犬と猫の画像をランダムで表示するだけのAPIと通信する
- 必要なAPIの数が多くなっても管理しやすい形にする
画面実装
この2記事で実装した画面を使いまわします。
上から順にコピペしていくだけでOKです
React NativeでNavigationを使っていい感じに画面遷移する
Redux toolkitでReduxをなるべく簡単に実装する
ライブラリをインストールする
公式ドキュメントに書いてあるredux-sagaに加えてaxiosというライブラリもインストールします
yarn add redux-saga
yarn add axios
入れたライブラリはこんな感じです
ライブラリ | 用途 |
---|---|
redux-saga | redux-saga本体 |
axios | サーバーからデータを取得する |
ライブラリ入れたらpod installもとりあえずやりましょう
cd ios && pod install && cd ..
react-native run-ios
これで下準備が完了!!
sliceのファイルにactionを追加する
まず最初にuserSliceの中に犬の画像をgetするための記述を追加します
payloadのところにはアクションの実行に必要な任意のデータを入れます
userSlice.js
import {createSlice} from '@reduxjs/toolkit';
const userInitialState = {
userName: null,
userEmail: null,
// stateを追加
img: null,
};
export const userSlice = createSlice({
name: 'user',
initialState: userInitialState,
reducers: {
getInfo: state => {
state.userName = 'Tanaka Taro';
state.userEmail = 'practice@example.com';
},
resetInfo: state => {
state.userName = userInitialState.userName;
state.userEmail = userInitialState.userEmail;
},
// payloadしてimgに画像を入れる
getImg: (state, {payload}) => {
state.img = payload;
},
},
});
export const {getInfo, resetInfo, getImg} = userSlice.actions;
ファイルを追加する
userディレクトリ配下にAPIと繋ぐためのuserSaga.jsを用意します(userSliceと同じディレクトリに配置)
applyAxiosの中には呼び出したいAPI(url)があって、
そこから返ってきた値をcallApiでレスポンスとして受け取っているイメージです。
また、sagaはeffectという「非同期処理を同期的に行うための関数」を使用します。
put , call , takeLatestがその関数にあたります
参考記事
userSaga.js
import axios from 'axios';
import {put, call, takeLatest} from 'redux-saga/effects';
import {getImg} from './userSlice';
function applyAxios() {
const url = 'https://dog.ceo/api/breeds/image/random';
return axios.get(url);
}
function* callApi() {
try {
const res = yield call(applyAxios);
yield put(getImg(res));
} catch (error) {
console.log(error);
}
}
export const getDog = () => ({type: 'GET_DOG'});
export function* userSaga() {
yield takeLatest('GET_DOG', callApi);
}
todoの方にも反映させる
同様にtodoSliceも修正し、同じディレクトリ内にtodoSagaも作成しましょう
todoSlice.js
import {createSlice} from '@reduxjs/toolkit';
const todoInitialState = {
userTodo: null,
img: null,
};
export const todoSlice = createSlice({
name: 'todo',
initialState: todoInitialState,
reducers: {
setTodo: state => {
state.userTodo = 'jogging';
},
resetTodo: state => {
state.userTodo = todoInitialState.userTodo;
},
getImg: (state, {payload}) => {
state.img = payload;
},
},
});
export const {setTodo, resetTodo, getImg} = todoSlice.actions;
- stateを追加
- getImgでimgをpayloadする
これらの記述を追加しました
todoSaga.js
import axios from 'axios';
import {put, call, takeLatest} from 'redux-saga/effects';
import {getImg} from './todoSlice';
function applyAxios() {
const url = 'https://aws.random.cat/meow';
return axios.get(url);
}
function* callApi() {
try {
const res = yield call(applyAxios);
yield put(getImg(res));
} catch (error) {
console.log(error);
}
}
export const getCat = () => ({type: 'GET_CAT'});
export function* todoSaga() {
yield takeLatest('GET_CAT', callApi);
}
各sagaをまとめるファイルを作る
redux配下にsagas.jsを作成し、その中に作成したsagaをまとめたいと思います
(これによりAPIが増えてもファイル1つで管理できる)
sagas.jsの中ではyield allを使って各sagaをまとめます
sagas.js
import {all} from 'redux-saga/effects';
import {userSaga} from './user/userSaga';
import {todoSaga} from './todo/todoSaga';
export default function* rootSaga() {
yield all([userSaga(), todoSaga()]);
}
storeを生成する
個人的にこれはもう暗記しちゃった方が手っ取り早いと思ってます。
さっき作ったsagas.jsの中のrootSagaを、storeに結合して使えるようにする処理です
store.js
import {configureStore} from '@reduxjs/toolkit';
import {rootReducer} from './reducers';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: rootReducer,
middleware: [sagaMiddleware],
});
sagaMiddleware.run(rootSaga);
export default store;
これでRedux-sagaの大まかな実装は終わりです
画面に反映させる
実際に画面上でAPIを受け取って画像を受け取りましょう
dispatchしてAPIを叩き、返ってきた画像を受け取るための処理を追加します
HomeScreen.js
import React from 'react';
import {StyleSheet, View, Text, Button, Image} from 'react-native';
import {getInfo, resetInfo} from '../redux/user/userSlice';
// userSagaから該当するAPIをimport
import {getDog} from '../redux/user/userSaga';
import {useSelector, useDispatch} from 'react-redux';
const HomeScreen = ({navigation}) => {
const dispatch = useDispatch();
const user = useSelector(state => state.user);
// dogというstateがpayloadされた時に入る画像URL
const dogImage = user.img?.data.message;
return (
<View style={styles.container}>
<Text>私の名前は{user.userName}</Text>
<Text>メールアドレスは{user.userEmail}</Text>
<Image
style={styles.img}
source={{
uri: dogImage,
}}
/>
<Button
title="セットする"
onPress={() => {
dispatch(getInfo());
}}
/>
<Button
title="リセットする"
onPress={() => {
dispatch(resetInfo());
}}
/>
<Button
title="犬を取得"
onPress={() => {
dispatch(getDog());
}}
/>
<Button
title="画面遷移する"
onPress={() => {
navigation.navigate('Detail');
}}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
input: {
width: '50%',
borderWidth: 1,
},
// 画像表示用のstyle
img: {
width: '75%',
height: '50%',
},
});
export default HomeScreen;
犬を取得ボタンを押下した時、犬が表示されたかと思います。
とても可愛らしいですね。
DetailScreenでも同じことをしましょう
getCatをdispatchし、帰ってきた値をImageコンポーネントのuriに入れます
DetailScreen.js
import React from 'react';
import {StyleSheet, View, Text, Button, Image} from 'react-native';
import {setTodo, resetTodo} from '../redux/todo/todoSlice';
import {getCat} from '../redux/todo/todoSaga';
import {useSelector, useDispatch} from 'react-redux';
const DetailScreen = ({navigation}) => {
const dispatch = useDispatch();
const todo = useSelector(state => state.todo);
const catImage = todo.img?.data.file;
return (
<View style={styles.container}>
<Text>明日のTodoは{todo.userTodo}</Text>
<Image
style={styles.img}
source={{
uri: catImage,
}}
/>
<Button
title="セットする"
onPress={() => {
dispatch(setTodo());
}}
/>
<Button
title="リセットする"
onPress={() => {
dispatch(resetTodo());
}}
/>
<Button
title="猫を取得"
onPress={() => {
dispatch(getCat());
}}
/>
<Button
title="Home画面に遷移する"
onPress={() => {
navigation.navigate('Home');
}}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
img: {
width: '75%',
height: '50%',
},
});
export default DetailScreen;
猫の画像が表示されたかと思います
僕の実家には茶色い猫と白い猫の2匹がいます。
駆け足で説明しましたがこれでsagaの大まかな実装が完了しました
最後に
簡単なAPIを繋いで画面に反映させるところまでを実装しました。
命名規則に一部統一感のない部分がありますが、sagas.jsでそれぞれのsagaをまとめておくことで通信するAPIが多くなったとしても一元的に管理ができるかと思います。
参考
https://qiita.com/macotok/items/ec5460ac17f5a20c4735
https://dev.classmethod.jp/articles/redux-saga-cheatsheet/