iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Mobile Development

單人開發者之路:React Native 與 Expo 帶你從開發到上架系列 第 13

Day 13 - 在你的APP實現永久性登入登出機制

  • 分享至 

  • xImage
  •  

本篇要實現
「你滑掉我的APP,再點一次還是能進入首頁」
那麼如何做到的呢
讓我們開始一步步操作吧

※本篇教學文稍長,稍微整理一下大綱

  • 使用AsyncStorage存取使用者資訊
  • 監控使用者狀態Component
  • APP入口實裝監控使用者狀態
  • 登入、登出功能結合useContext實作

請先在專案新增以下資料夾及空白js檔
https://ithelp.ithome.com.tw/upload/images/20230917/20130821kIu65ga0CP.png

使用AsyncStorage存取使用者資訊

在UserFunc.js引入AsyncStorage
import AsyncStorage from "@react-native-async-storage/async-storage";
接著製作以下四種Function

保存使用者資訊

使用名為userData的鍵值名稱做為使用者資訊存取

async function StoreUserInfo(user) {
  try {
    await AsyncStorage.setItem("userData", JSON.stringify(user));
  } catch (error) {
    console.log("Something went wrong", error);
  }
}

try...catch為JavaScript對於程式例外時需捕捉的語法
可參考try...catch介紹

載入使用者資訊

取出名為userData的鍵值名稱做為使用者資訊帶出
回傳使用者資訊userResult

async function GetUserInfo() {
  try {
    let userData = await AsyncStorage.getItem("userData");
    const userResult = JSON.parse(userData);
    return userResult;
  } catch (error) {
    console.log("GetUserDataError", error);
  }
}

確認是否登入

async function CheckAlReadyLogin() {
  let checkResult = false;
  await AsyncStorage.getItem("userData").then((res) => {
    if (res !== undefined && res !== null) checkResult = true;
  });
  return checkResult;
}

then語法使用

非同步函式會回傳一種叫做Promise的承諾型物件

Promise 物件代表一個即將完成、或失敗的非同步操作,以及它所產生的值。

而then就是向Promise取得它承諾要回傳的資訊(使用者資訊)
只要成功拿到使用者資訊
代表使用者已登入,設定checkResult=true
反之,取不到使用者資訊就必須重新登入

清除使用者資訊

function ClearUserInfo() {
  AsyncStorage.clear();
}

User Function全部匯出

export { StoreUserInfo, CheckAlReadyLogin, GetUserInfo, ClearUserInfo };

監控使用者狀態Component

在AuthState.js引入React Hook、UserFunc登入登出相關Function

import React, { useEffect, useState, createContext } from "react";
import {
  StoreUserInfo,
  ClearUserInfo,
  CheckAlReadyLogin,
  GetUserInfo,
} from "../../Common/UserFunc";

建立空的Component、授權上下文

除了AuthContext,其餘功能皆要放入AuthState裡

const AuthContext = createContext();
const AuthState = (props) => {

}

新增登入狀態、登入後資訊暫存狀態

這邊同時存放使用者資訊
讓其他Component能直接使用

const [isLogin, setIslogin] = useState(false);
const [userInfo, setuserInfo] = useState({});

監控使用者是否登入Effect

這段Effect是整個APP判斷登入的核心效果
只要你APP一打開,就會執行此函式
可透過setIslogin調整登入判斷

useEffect(() => {
  CheckAlReadyLogin().then((res) => {
    if (res) {
      GetUserInfo().then((loginInfo) => {
        setuserInfo(loginInfo); //重開APP時從AsyncStorage將使用者資訊帶回去
        //如果你一開始記住登入資訊是取消的,下次登入進來要回登入畫面重打
        setIslogin(loginInfo.IsRemberMe);
      });
    } else {
      setIslogin(false);
    }
  });
}, []);

※ 這邊有個判斷要注意
「記住登入資訊」如果不勾選就登入
可以將使用者踢回登入畫面

有些使用者為確保帳號安全性
不會希望一直長久登入

確認登入

const onAuthentication = async (loginInfo) => {
  StoreUserInfo(loginInfo);
  setuserInfo(loginInfo); //存一份登入資訊放在userInfo上下文裡
  setIslogin(true);
};

loginInfo為登入時所要帶入的資訊(帳號)

確認登出

const onLogOut = () => {
    ClearUserInfo();
    setIslogin(false);
    setuserInfo({});
};

組織上方所有Function並匯出

return (
  <AuthContext.Provider
    value={{
      onAuthentication,
      isLogin,
      onLogOut,
      userInfo,
    }}
  >
    {props.children}
  </AuthContext.Provider>
);

匯出Context & Component

export { AuthContext };
export default AuthState;

有關naming export、default export寫法可參考這篇英文文章

APP入口實裝監控使用者狀態

可參考前篇的程式碼作為對照

強制進入Auth Provider

修改App.js
將AuditState包裝在AppNavigator父層

import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, SafeAreaView } from "react-native";
import AuditState from "./Context/AuthState";
import AppNavigator from "./Navigation/AppNavigator";

export default function App() {
  return (
    <SafeAreaView className="flex-1">
      <StatusBar style="auto" />
      <AuditState>
        <AppNavigator />
      </AuditState>
    </SafeAreaView>
  );
}

※這樣包裝的好處是
在進入APP前,一定得先穿越AuditState
就會先執行上方「監控使用者是否登入Effect」
在還沒進入Navigator前,就能知道是否有登入過

結合useContext上下文判斷畫面顯示

修改AppNavigator.js
isLoginAuthContext引用進來
登入、首頁只選一種畫面來顯示

import React, { useContext } from "react";
import { AuthContext } from "../Context/AuthState";
import LoginScreen from "../src/Home/LoginScreen";
import IndexScreen from "../src/Home/IndexScreen";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";

export default function AppNavigator() {
  const RootStack = createStackNavigator();
  const { isLogin } = useContext(AuthContext);
  return (
    <NavigationContainer>
      <RootStack.Navigator initialRouteName="Login">
        {isLogin ? (
          <RootStack.Screen
            name="Index"
            component={IndexScreen}
            options={{ title: "首頁" }}
          />
        ) : (
          <RootStack.Screen
            name="Login"
            component={LoginScreen}
            options={{ title: "登入" }}
          />
        )}
      </RootStack.Navigator>
    </NavigationContainer>
  );
}

※useContext的好處
就是能將外部元件的狀態isLogin放進其他元件做判斷
然而JSX是可以直接使用三元運算子(?:)做為登入登出Screen判定

登入、登出功能結合Context實作

可參考前篇的程式碼作為對照

修改登入畫面登入Function

LoginScreen.js

import { AuthContext } from "../Context/AuthState";
const { onAuthentication } = useContext(AuthContext);
function onLogin() {
    if (loginInfo.Account === "" || loginInfo.Password === "") {
      Alert.alert("失敗","請輸入使用者資訊");
      return false;
    } else{
      loginInfo.Password = "";
      onAuthentication(loginInfo).then(() =>
      {
        Alert.alert("訊息", "登入成功!");
        navigation.navigate("Index");
      })
    }
}

※在儲存登入資訊前,先將密碼清除
密碼為機敏資訊,故不存取

增加首頁畫面「登出」觸控按鈕

IndexScreen.js 畫面

import { AuthContext } from "../../Context/AuthState";
const { onLogOut, userInfo } = useContext(AuthContext);
  return (
    <View className="flex-1 items-center justify-center">
      <Text>{userInfo.Account}你好</Text>
      <TouchableOpacity
        className="bg-red-600 rounded-lg w-10/12 m-3"
        onPress={CheckLogout}
      >
        <Text className="text-white text-xl m-3 text-center">登出</Text>
      </TouchableOpacity>
    </View>
  );

登出 Function

function CheckLogout() {
    Alert.alert("訊息", "確定要登出嗎?", [
      {
        text: "取消",
        onPress: () => {
          return false;
        },
      },
      { text: "確認", onPress: () => onLogOut() },
    ]);
  }

※這裡先使用Alert警示使用者確定要登出
使用者確認後,在執行登出


功能實作結束
本篇Snack完整程式碼附上:https://snack.expo.dev/@peter_lu/loginandlogout
測試時請使用實體手機試玩
NativeWind無法在Snack使用,樣式部分敬請見諒


結語:
整個登入登出系列文到這邊就告一段落了
現在,你的APP登入能儲存資訊
登出時能清除資訊

以往開發ASP.NET時
都是直接使用Session、Cookie去存取
首次接觸前端AsyncStorage結合Context製作登入登出機制
真的花了不少功夫去鑽研技術

開發時,還是要以實際需求為例
範例只能輔助你開發
因為客戶提出的功能永遠都天馬行空🤑

下一篇
會開始以「登入後」功能為主
介紹開發過程中所安裝、接觸到的套件
會先從「切換式分頁」- TabNavigator 開始講起。


上一篇
Day 12 - React Native 進階知識 - useEffect、useContext、AsyncStorage
下一篇
Day 14 - BottomTabNavigator實作底部切換式分頁
系列文
單人開發者之路:React Native 與 Expo 帶你從開發到上架30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言