#10 App: Setup
#10.0 Creating the Project
1. npm install --global expo-cli 설치 진행
2. expo init prismagram-app 로 프로젝트 초기화 (blank 선택)
3. npm i styled-components react-navigation apollo-boost graphql react-apollo-hooks apollo-cache-inmemory apollo-cache-persist 설치 진행
4. expo install expo-font expo-asset 설치진행
5. 안드로이드스튜디오를 다운 받자(안되면, 모바일 전용 expo 어플 설치)
#10.1 Preloading Assets
Preload는 실제 app을 실행 시킨 후, data를 받아오기 전까지 loading 화면 시 이루어지는 작업들을 뜻한다.
기본 파일 트리
>app.js
import React, { useState, useEffect } from "react";
import { Ionicons } from "@expo/vector-icons";
import { AppLoading } from "expo";
import * as Font from "expo-font";
import { Asset } from "expo-asset";
import { Text, View } from "react-native";
export default function App() {
const [loaded, setLoaded] = useState(false);
const preLoad = async () => {
try {
await Font.loadAsync({
...Ionicons.font,
});
await Asset.loadAsync([require("./assets/logo.png")]);
setLoaded(true);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
preLoad();
}, []);
return loaded ? (
<View>
<Text>Open up App.js to start working on your app!</Text>
</View>
) : (
<AppLoading />
);
}
#10.2 Preloading Cache
>apollo.js
const options = {
uri: "http://localhost:4000/graphql"
};
export default options;
#10.4 isLoggedIn part One
>app.js
import React, { useState, useEffect } from "react";
import { Ionicons } from "@expo/vector-icons";
import { AppLoading } from "expo";
import { Asset } from "expo-asset";
import * as Font from "expo-font";
import { Text, View, AsyncStorage } from "react-native";
import { InMemoryCache } from "apollo-cache-inmemory";
import { persistCache } from "apollo-cache-persist";
import ApolloClient from "apollo-boost";
import { ThemeProvider } from "styled-components";
import { ApolloProvider } from "react-apollo-hooks";
import apolloClientOptions from "./apollo";
import styles from "./styles";
export default function App() {
const [loaded, setLoaded] = useState(false);
const [client, setClient] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(null);
const preLoad = async () => {
try {
await Font.loadAsync({
...Ionicons.font,
});
await Asset.loadAsync([require("./assets/logo.png")]);
const cache = new InMemoryCache();
await persistCache({
cache,
storage: AsyncStorage,
});
const client = new ApolloClient({
cache,
...apolloClientOptions,
});
const isLoggedIn = await AsyncStorage.getItem("isLoggedIn");
if (isLoggedIn === null || isLoggedIn === false) {
setIsLoggedIn(false);
} else {
setIsLoggedIn(true);
}
setLoaded(true);
setClient(client);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
preLoad();
}, []);
return loaded && client && isLoggedIn !== null ? (
<ApolloProvider client={client}>
<ThemeProvider theme={styles}>
<View>
{isLoggedIn === true ? <Text>I'm in</Text> : <Text>I'm out</Text>}
</View>
</ThemeProvider>
</ApolloProvider>
) : (
<AppLoading />
);
}
여기서 자세히 봐야할 부분은, isloggedIn의 useState 초기값이 null이라는 점이다. 이는 false도 아니고 true도 아니고 undefine도 아니다. 이는 아예 로그인 관련 체크를 안했다는 뜻이다. 로그인 로그아웃을 추적하기 위해 null로 초기값을 셋팅해놓았다. 그리고 리액트와 마찬가지로 테마칼라 설정이 가능하다.
>styles.js
export default {
blackColor: "#262626",
greyColor: "#F9F9F9",
darkGreyColor: "#999",
lightGreyColor: "#c7c7c7",
redColor: "#ED4956",
blueColor: "#3897f0",
darkBlueColor: "#003569",
};
~~#10.7 AuthContext part Two
최종 정리를 해보자.
>app.js
import React, { useState, useEffect } from "react";
import { Ionicons } from "@expo/vector-icons";
import { AppLoading } from "expo";
import { Asset } from "expo-asset";
import * as Font from "expo-font";
import { AsyncStorage } from "react-native";
import { InMemoryCache } from "apollo-cache-inmemory";
import { persistCache } from "apollo-cache-persist";
import ApolloClient from "apollo-boost";
import { ThemeProvider } from "styled-components";
import { ApolloProvider } from "react-apollo-hooks";
import apolloClientOptions from "./apollo";
import styles from "./styles";
import NavController from "./components/NavController";
import { AuthProvider } from "./AuthContext";
export default function App() {
const [loaded, setLoaded] = useState(false);
const [client, setClient] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(null);
const preLoad = async () => {
try {
await Font.loadAsync({
...Ionicons.font,
});
await Asset.loadAsync([require("./assets/logo.png")]);
const cache = new InMemoryCache();
await persistCache({
cache,
storage: AsyncStorage,
});
const client = new ApolloClient({
cache,
...apolloClientOptions,
});
const isLoggedIn = await AsyncStorage.getItem("isLoggedIn");
if (!isLoggedIn || isLoggedIn === "false") {
setIsLoggedIn(false);
} else {
setIsLoggedIn(true);
}
setLoaded(true);
setClient(client);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
preLoad();
}, []);
return loaded && client && isLoggedIn !== null ? (
<ApolloProvider client={client}>
<ThemeProvider theme={styles}>
<AuthProvider isLoggedIn={isLoggedIn}>
<NavController />
</AuthProvider>
</ThemeProvider>
</ApolloProvider>
) : (
<AppLoading />
);
}
useEffect를 통해, 리턴 렌더값이 초기에 발동 후, preLoad함수가 실행된다.
preLoad함수는 아래 순서대로 진행 된다.
1. Ionicons의 font값을 가지고 온다.
2. assets에 있는 기본 이미지를 가지고 온다.
3. persistCache를 통해 storage값을 cache에 넣는다.
4. client(gql서버)에 연결하는 함수를 만들어 준다.
5. AsyncStorage(웹 에서 로컬 스토리지 개념)에서 key=isLoggedIn의 value값을 가지고 오는 함수를 만들어 준다.
5.1 isLoggedIn값이 false거나 true가 아니면(null) setIsLoggedIn(false)를 해줌으로써 IsLoggedIn의 값을 false로 바꿔준다.
5.2 isLoggedIn값이 true면 setIsLoggedIn(true)해줌으로써 IsLoggedIn의 값을 true로 바꿔준다.
6. loaded state값을 true로 바꿔준다.
7. client state값에 위 4번 값을 넣어준다.
위 preLoad함수가 실행 되면, state값은 loaded는 true가 되고, client값에는 함수가 들어가 있고, isLoggedIn값에는 false로 셋팅 된다.
해당 3개의 값이 null이 아니기 때문에 그 아래 컴포넌트 값들이 실행된다.
컴포넌트에 AuthProvider부분을 보면. props값으로 isLoggedIn값이 전달 된다.(현재 값: false)
그리고 그 하위 컴포넌트는 NavController이다. 이 두개를 봐보자.
AuthProvider는 AuthContext.js에서 가지고온 함수이다. 이 구조를 분석해보자.
context를 사용하는 이유는 각 단계마다 일일이 props를 전달할 필요없이 컴포넌트 전체에 데이터를 제공 할 수 있게 된다.
**app.js에서 Apploading후, 로그인 여부를 바로 알 수 있다. AsyncStorage에 값이 담겨져 있기 때문에.!
>AuthContext.js
import React, { createContext, useContext, useState } from "react";
import { AsyncStorage } from "react-native";
export const AuthContext = createContext();
export const AuthProvider = ({ isLoggedIn: isLoggedInProp, children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(isLoggedInProp);
const logUserIn = async () => {
try {
await AsyncStorage.setItem("isLoggedIn", "true");
setIsLoggedIn(true);
} catch (e) {
console.log(e);
}
};
const logUserOut = async () => {
try {
await AsyncStorage.setItem("isLoggedIn", "false");
setIsLoggedIn(false);
} catch (e) {
console.log(e);
}
};
return (
<AuthContext.Provider value={{ isLoggedIn, logUserIn, logUserOut }}>
{children}
</AuthContext.Provider>
);
};
export const useIsLoggedIn = () => {
const { isLoggedIn } = useContext(AuthContext);
return isLoggedIn;
};
export const useLogIn = () => {
const { logUserIn } = useContext(AuthContext);
return logUserIn;
};
export const useLogOut = () => {
const { logUserOut } = useContext(AuthContext);
return logUserOut;
};
createContext를 선언한 후 변수명에 AuthContext라고 칭하였다., return값에 AuthContext.Provider로 감싸야 한다.
해당 Context를 다른 컴포넌트에 사용하기 위해서는, 위 app.js에 나온것과 같이. AuthProvider로 감싸게 되면, AuthProvider의 value값들을 사용할 수 있게 된다. (여기서 value값은 isLoggedIn값(boolean), logUserIn(onClick함수),logUserOut(onClick함수)이다.
그리고 그 아래, useContext(context명칭)를 하면 context를 사용 할 수 있게 되는데,
첫번쨰 함수는 value의 isLoggedIn 값을 사용 할 수 있는 것이고,
두번째 함수는 value의 logUserIn 값을 사용 할 수 있는 것이고,
세번째 함수는 value의 logUserOut 값을 사용 할 수 있게 된다.
>NavController.js
import React from "react";
import { Text, View, TouchableOpacity } from "react-native";
import { useIsLoggedIn, useLogIn, useLogOut } from "../AuthContext";
export default () => {
const isLoggedIn = useIsLoggedIn();
const logIn = useLogIn();
const logOut = useLogOut();
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
{isLoggedIn ? (
<TouchableOpacity onPress={logOut}>
<Text>Log Out</Text>
</TouchableOpacity>
) : (
<TouchableOpacity onPress={logIn}>
<Text>Log in</Text>
</TouchableOpacity>
)}
</View>
);
};
앞서 AuthContext.js에서 useContext함수 3개를 만든것을 가지고 온다. 이를 통해 context에 접근 할 수 있게 된다.
해당 값을 이용해 하위 컴포넌트(nav)를 구현 하였다.
끝