인스타그램 클론코딩 15일차 - App

박병준·2021년 8월 25일
0
post-thumbnail

#9.7 Tab Navigator

Tab Navigator는 Stack Navigator와 마찬가지로 페이지를 이동할 수 있다. 대신 Tab은 하단의 탭들로 이동한다. 사용법은 Stack Navigator와 마찬가지로 Tabs를 셍성한후 option을 설정한다.

option들은 공식 문서에서 확인할 수 있다.

//LoggedInNav.js
const Tabs = createBottomTabNavigator();

export default function LoggedInNav() {
    return (
        <Tabs.Navigator
            screenOptions={{
                tabBarActiveTintColor: "white",
                tabBarShowLabel: false,
                title: false,
                headerBackTitleVisible: false,
                headerTransparent: true,
                tabBarStyle: {
                    borderTopColor: "rgba(255, 255, 255, 0.3)",
                    backgroundColor: "black",
                },
            }}
        >
            <Tabs.Screen
                name="Feed"
                component={Feed}
                options={{
                    tabBarIcon: ({ focused, color, size }) => (
                        <Ionicons name="home" color={color} size={focused ? 24 : 20} />
                    ),
                }}
            />

#9.8 Stack in Tabs

Tab Navigator안에 Stack Navigator가 필요한 경우가 있다. 인스타그램을 예로 들면 Search에서 user의 사진과 프로필 페이지를 들어갈 수 있고 Feed에서도 사진과 프로필에 들어갈 수 있다. 이 때 Tabs Navigator에서 Stack Navigator를 통해 사진과 프로필 페이지를 공유하게 한다.

아래와 같이 스크린 이름을 받아와서 스크린 이름에따라 네비게이션을 설정한다.

//StackNavFactory.js
const Stack = createStackNavigator();

export default function StackNavFactory({ screenName }) {
    return (
        <Stack.Navigator
            screenOptions={{
                headerBackTitleVisible: false,
                headerTintColor: "white",
                headerStyle: {
                    shadowColor: "rgba(255, 255, 255, 0.3)",
                    backgroundColor: "black",
                },
            }}
        >
            {screenName === "Feed" ? (
                <Stack.Screen name={"Feed"} component={Feed} />
            ) : null}
            {screenName === "Search" ? (
                <Stack.Screen name={"Search"} component={Search} />
            ) : null}
            {screenName === "Notifications" ? (
                <Stack.Screen name={"Notifications"} component={Notifications} />
            ) : null}
            {screenName === "Me" ? <Stack.Screen name={"Me"} component={Me} /> : null}
            <Stack.Screen name="Profile" component={Profile} />
            <Stack.Screen name="Photo" component={Photo} />
        </Stack.Navigator>
    );
};

Tab Navigator에서 child로 stack Navigator로 설정해준다. 이 때 Stack Navigator를 사용하면 Tab.Screen의 prop으로 component를 지정해주면 안된다.

//LoggedInNav.js
<Tabs.Screen
                name="Feed"
                options={{
                    tabBarIcon: ({ focused, color, size }) => (
                        <TabIcon iconName={"home"} color={color} focused={focused} />
                    ),
                }}
            >
                {() => <StackNavFactory screenName="Feed" />}
            </Tabs.Screen>

#9.9 FlatList vs Scroll View

Feed를 볼 때 스크롤을 내리는 방법이 2가지 있다.

Scroll View
data를 한번에 랜더링 한다. 스크롤 안에 너무 많은 것을 로딩하면 Navigator의 움직임이 이상해진다.

height대신 width를 쓰면 가로로 스크롤이 가능하다.

//Feed.js
<ScrollView>
    <View style={{ height: 20000, flex: 1, backgroundColor: "blue" }}>
        <Text>SCrollllll</Text>
    </View>
</ScrollView>

FlatList
FlatList는 항목들을 게으르게 랜더링 한다. 스크롤하는 중 item이 저 밑에 있거나 맨 위에 있으면 랜더링하지 않는다. 오직 화면에 있는 것만 랜더링 한다.

//Feed.js
export default function Feed() {
    const { data, loading } = useQuery(FEED_QUERY);
    const renderPhoto = ({ item: photo }) => {
        return (
            <View style={{ flex: 1 }}>
                <Text style={{ color: "white" }}>{photo.caption}</Text>
            </View>
        );
    };
    return (
        <ScreenLayout loading={loading}>
            <FlatList
                data={data?.seeFeed}
                keyExtractor={(photo) => "" + photo.id}
                renderItem={renderPhoto}
            />
        </ScreenLayout>
    );
}

#9.10 Photo

React Native에서는 웹에서 이미지를 불러오기 위해서는 width와 height 값이 필수이다.

useWindowDimensions을 통해 app의 width 와 height를 받아올 수 있다.

//Photo.js
const { width, height } = useWindowDimensions();
    const [imageHeight, setImageHeight] = useState(height - 450);
    useEffect(() => {
        Image.getSize(file, (width, height) => {
            setImageHeight(height / 3);
        });
    }, [file]);
    return (
        <Container>
            <Header onPress={() => navigation.navigate("Profile")}>
                <UserAvatar resizeMode="cover" source={{ uri: user.avatar }} />
                <Username>{user.username}</Username>
            </Header>
            <File
                resizeMode="contain"
                style={{
                    width,
                    height: imageHeight,
                }}
                source={{ uri: file }}
            />

#9.11 Pull to Refresh

아래로 당겨 새로고침하는 기능을 FlatList의 refreshing과 onRefresh로 만들 수 있다.

useQuery의 refetch로 다시 백엔드에서 불러온다.

//Feed.js
export default function Feed() {
    const { data, loading, refetch } = useQuery(FEED_QUERY);
    const renderPhoto = ({ item: photo }) => {
        return <Photo {...photo} />;
    };
    const [refreshing, setRefreshing] = useState(false);
    const refresh = async () => {
        setRefreshing(true);
        await refetch();
        setRefreshing(false);
    };
    return (
        <ScreenLayout loading={loading}>
            <FlatList
                refreshing={refreshing}
                onRefresh={refresh}
                style={{ width: "100%" }}
                showsVerticalScrollIndicator={false}
                data={data?.seeFeed}
                keyExtractor={(photo) => "" + photo.id}
                renderItem={renderPhoto}
            />
        </ScreenLayout>
    );
}

#9.12 Infinite Scrolling

Flatlist의 props 중 onEndReached와 onEndReachedThreshold가 있다.

onEndReached : 스크롤의 끝에 도달하면 실행한다.
onEndReachedThreshold : 스크롤의 끝을 지정할 수 있다.

useQuery의 fetchMore을 이용하여 더 불러올 수 있다.

// Feed.js
const { data, loading, refetch, fetchMore } = useQuery(FEED_QUERY, {
    variables: {
      offset: 0,
    },
  });
return (
    <ScreenLayout loading={loading}>
      <FlatList
        onEndReachedThreshold={0.05}
        onEndReached={() =>
          fetchMore({
            variables: {
              offset: data?.seeFeed?.length,
            },
          })
        }

DB에서 불러오고 apollo 캐시에도 저장을 해줘야한다.
keyArgs는 argument인 offset을 무시해준다.
merge에서 existing(기존 value), incoming(새로운 value)를 합쳐준다.

// apollo.js
const client = new ApolloClient({
    link: authLink.concat(httpLink),
    cache: new InMemoryCache({
        typePolicies: {
            Query: {
                fields: {
                    seeFeed: {
                        keyArgs: false,
                        merge(existing = [], incoming = []) {
                            return [...existing, ...incoming];
                        }
                    }
                },
            },
        },
    }),
});

위 처럼 직접해줘도 되지만 이미 만들어진 함수도 있다.
offsetLimitPagination 함수는 위와 동일한 역할을 한다.

// apollo.js
const client = new ApolloClient({
    link: authLink.concat(httpLink),
    cache: new InMemoryCache({
        typePolicies: {
            Query: {
                fields: {
                    seeFeed: offsetLimitPagination(),
                },
            },
        },
    }),
});

#9.13 Cache Persist

인스타그램은 로딩을 할 때 로딩화면만 보여주지 않는다. 내가 전에 봤던 피드들을 보여주고 로딩이되면 불러온다.
오프라인일 때도 나의 프로필을 볼 수 있고 이전에 봤던 피드 몇개를 볼 수 있다.

이러한 이유는 cache persist로 핸드폰의 하드웨어에 미리 저장해놓고 불러오기 때문이다.

apollo3-cache-persist를 설치해준다.
npm install apollo3-cache-persist

앱을 실행할 때 preload히면서 같이 불러온다.

cache는 apollo.js에서 export해준다.

// App.js
const preload = async () => {
    const token = await AsyncStorage.getItem("token");
    if (token) {
      isLoggedInVar(true);
      tokenVar(token);
    }
    await persistCache({
      cache,
      storage: new AsyncStorageWrapper(AsyncStorage),
    });
    return preloadAssets();
  };

9.14 useLazyQuery

useQuery는 component가 mount될 때 자동으로 바로 실행되므로 우리가 검색을 원할 때만 실행될 수 있는 함수가 필요하다. 그것이 바로 useLazyQuery이다.

startQueryFn을 부르기 전까지 request하지 않는다.
called는 요청되었는지를 boolean타입으로 반환한다.

const [startQueryFn, { loading, data, called }] = useLazyQuery(SEARCH_PHOTOS);

profile
뿌셔뿌셔

0개의 댓글

관련 채용 정보