昨天我們使用 chatgpt 幫我們在 react native 裡面串接 api,不過老實說有點太粗糙。我今天試了一天,如果使用AI要求他修改成想要的形式,會花滿多時間再和他溝通的。我花了一些時間來改寫一下程式碼。今天來說明一下比較偏架構的部分。
前面有說過,我們使用 jwt 認證來實作。當登入的時候,會回傳只屬於 user 的 token。在後面請求所有資源或操作的時候都會需要驗證這個 token。所以登入後會需要把這個 token存起來。
那 token 要放哪裡呢?如果單純放在 redux的 state 裡面的話。這樣你下次打開就沒有 token了。不然就是每次都要不然就是每次使用都要在登入一次,這樣太不合邏輯了吧。
APP 也有個像是網頁 localStorge的東西。 AsyncStorage ,要存東西的時候可以使用 setItem 取出使用 getItem
import AsyncStorage from '@react-native-community/async-storage';
.....
await AsyncStorage.setItem('userToken', result.accessToken);
await AsyncStorage.getItem('userToken', result.accessToken);
那什麼時後存放呢,可以在登入後成功取得回傳資料後就直接在 Action 存放在 Stroage裡面。這樣,下次打開 App 依然可以使用該 Token 來進行身份驗證。
在 userAction中執行存放 token的動作。
userAction.ts
export const loginUser = createAsyncThunk(
'user/login',
async (data: {username: string; password: string}, thunkAPI) => {
try {
const response = await apiService.loginUser(data);
await AsyncStorage.setItem('userToken', result.accessToken);
return response.data;
} catch (error: unknown) {
const errorMessage = (error as ErrorResponse)?.message || '未知錯誤';
return thunkAPI.rejectWithValue(errorMessage);
}
},
);
取得 token的方法可以用 getItem。
const getToken = async () => {
try {
const userToken = await AsyncStorage.getItem('userToken');
return userToken ? userToken : null;
} catch (error) {
console.error('Failed to retrieve token:', error);
return null;
}
};
認證的部分使用放在 Header 裡面請求的 Bearer token ,但要在每個 api call 的時候都寫取得 Token 也不太合理吧。一樣的程式碼重複寫,不太好。因此我們可以利用 Axios 攔截器 自動將 Token 附加到請求 Header 中。
instance.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
const token = await getToken();
if (token) {
config.headers = new AxiosHeaders({
...config.headers,
Authorization: `Bearer ${token}`,
});
}
return config;
},
error => {
return Promise.reject(error);
},
);
[!NOTE]
axios 和 fetch 都是用來發送 HTTP 請求的工具。大部分時間都差不多,不過因為我實際專案使用的是 Axios。
- axios 是一個外部庫,內建有許多便捷功能,如自動解析 JSON、錯誤處理(HTTP 4xx、5xx 會自動觸發錯誤處理)、攔截器、請求取消等。
- fetch 是瀏覽器原生 API,需要手動處理 JSON 回應與錯誤,對於網路錯誤會觸發錯誤處理,但對於 HTTP 4xx、5xx 狀態碼則需要自行處理。
[!Hint]
我們明天會來講一下 Api redux 串接設計的部分,會詳細講一下。 我們會寫一個axios config這個檔案來處理所有 api call的行為。
Token搭配Storage也可以用來確認登入狀態,透過檢查 AsyncStorage 中是否存有 Token 來確認使用者的登入狀態,並根據結果決定是否讓使用繼續瀏覽頁面或是強制導向登入頁。同時檢查用戶是否已經登入,若未登入,則導向登入頁面。
routers.tsx
export const Router = ({initialRoute}: {initialRoute: string}) => {
const navigation = useNavigation();
const checkToken = async () => {
try {
const userToken = await AsyncStorage.getItem('userToken');
if (!userToken) {
// 顯示警告
Alert.alert(
'未登入', // 標題
'請先登入以繼續', // 訊息
[
{
text: '確認',
onPress: () => {
navigation.reset({
index: 0,
routes: [{name: RouteNames.Login} as never],
});
},
},
],
{cancelable: false},
);
}
} catch (e) {
console.error(e);
}
};
return (
<Stack.Navigator initialRouteName={initialRoute}>
{screens.map(screen => (
<Stack.Screen
key={screen.name}
name={screen.name}
component={screen.component}
options={screen.options}
listeners={
screen.name !== RouteNames.Login &&
screen.name !== RouteNames.Register
? {focus: checkToken}
: undefined
}
/>
))}
</Stack.Navigator>
);
};
JWT Token 會設置有效期限,因此我們需要在每次發送 API 請求前檢查 Token 是否過期。如果過期,應該移除 Token 並強制用戶重新登入。
先寫一個可以確認 token是否過期的函數
import jwtDecode from 'jwt-decode';
const isTokenExpired = (token: string): boolean => {
if (!token) return true; // 如果沒有 token,視為過期
const decoded: any = jwtDecode(token);
const currentTime = Date.now() / 1000; // 當前時間(秒)
return decoded.exp < currentTime; // 如果過期,返回 true
};
再來寫一個 token 過期的話直接移除 Storage 裡面的 token 然後直接回到登入頁面的函數
const checkAndRemoveToken = async () => {
const token = await AsyncStorage.getItem('userToken');
if (isTokenExpired(token)) {
await AsyncStorage.removeItem('userToken'); // 移除過期的 token
// 這裡可以重定向用戶到登入頁面
navigation.reset({
index: 0,
routes: [{ name: RouteNames.Login }],
});
}
};
再來在 Axios 攔截器中使用,檢查 Token 是否過期並決定是否允許 API 請求
apiClient.interceptors.request.use(async (config) => {
const token = await AsyncStorage.getItem('userToken');
if (isTokenExpired(token)) {
await AsyncStorage.removeItem('userToken');
// 這裡可以重定向用戶到登入頁面
navigation.reset({
index: 0,
routes: [{ name: RouteNames.Login }],
});
} else {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
AsyncStorage
中,以便後續操作使用。Bearer Token
。今天我們介紹了 token 在 app 的用途和概念的實作的部分,雖然要用 AI幫你做出來也是可以,但前提是要先知道要怎麼引導他,還有要要驗證。
不過我會覺得自己修改比較快😅
明天會和大家說一下 串接 api的部分的設計 slice , action , api , axios 設定 還有錯誤處理的方式。我覺得架構的基本功如果寫得好,後面開發體驗會比較舒服。
相對來說,如果你專案的架構寫得好的話,AI幫你生成code的時候也會照著你的架構實作。是不是有點像在帶新人的感覺🙂