Instagram Clone : React Native - part 3 [ FEED]
63040 ワード
Tab Navigator
Create Tabs
複数のラベルを作成します.スタック同様、Tabs.Navigator
内に必要なTabs.Screen
を入れる.Screen
全体適用オプションのためにNavigator
エscreenOptions
それぞれoptions
エScreen
の道具に入れる.複数のオプションは、次の正式なドキュメントで参照できます.
TabIcon
タブにアイコンを挿入するには、tabBarIconというオプションを使用します。戻り要素の関数を受け入れます。関数のパラメータとして、次の値が得られます。
フォーカスフォーカス:タブを選択するとtrueに戻ります。
color:アイコンの色。tabBarActiveTintColorと同じです。
size:size(number受信)
万博ベクトル-iconリスト @expo/vector-icons directory
関数は繰り返し使用されるので、繰り返し使用可能な素子にすることが望ましい.// TabIcon.tsx
...
export default function TabIcon({ focused, iconName, color }: TabIconProps) {
return (
<Ionicons
name={focused ? iconName : `${iconName}-outline`}
color={color}
size={focused ? 24 : 20}
/>
);
}
// LoggedInNav.tsx
...
function LoggedInNav() {
return (
<Tabs.Navigator ... />
<Tabs.Screen
...
options={{
tabBarIcon: ({ focused, color, size }) => (
// 컴포넌트화 전
<Ionicons name={focused ? "home" : "home-outline"}
color={color} size={focused ? 24 : 20} />
),
}}
/>
<Tabs.Screen
...
options={{
tabBarIcon: ({ focused, color, size }) => (
// 컴포넌트화 후
<TabIcon focused={focused} color={color} iconName="search" />
),
}}
/>
...
Stack & Tabs
Stacks for each Tab
INSTAGRAMアプリケーションの構成を考慮すると,各タグ内に複数のスタックがある.各ラベルをクリックすると、最初に表示されるスタックがそのラベルのページに移動し、ページ内で写真またはプロファイルをクリックすると別のスタックに移動します.
この構成をコードにするには、タブ(Tabs.Screen
)内でコールバックスタック素子(SharedStackNav
)の関数をchildrenに送信する必要がある.// LoggedInNav.tsx
...
function LoggedInNav() {
return (
<Tabs.Navigator ...>
<Tabs.Screen
name="RootFeed"
...
>
{() => <StackNavFactory screenName="Feed" />}
</Tabs.Screen>
...
SharedStackNav
エレメント内で支柱を介して伝達されるscreenName
に従って、対応するスタック(タブの最初の画面)を返します.Profile
科Photo
すべてのタブに共通のスタック.// SharedStackNav.tsx
...
const getFirstScreen = (screenName: string) => {
if (screenName === "Feed") {
return <Stack.Screen name="Feed" component={Feed} />;
} else if (screenName === "Search") {
return <Stack.Screen name="Search" component={Search} />;
} else if (screenName === "Notifications") {
return <Stack.Screen name="Notifications" component={Notifications} />;
} else if (screenName === "Me") {
return <Stack.Screen name="Me" component={Me} />;
} else {
return null;
}
};
function SharedStackNav({ screenName }: SharedStackNavProps) {
return (
<Stack.Navigator ...>
{getFirstScreen(screenName)} // ** 탭의 첫 번째 화면
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Photo" component={Photo} />
</Stack.Navigator>
);
}
Error : Found screens with the same name nested inside one another. Check:
親と子の要素の名前が同じ場合に発生するエラーは、異なるエラーに変わります.
Screen Title to Image
Feed screenのタイトルをInstagramのロゴに変更選択可能headerTitle
String
またはReact.Node
Image
とともにサイズを頭部に調整します.// SharedStackNav.tsx
...
const getFirstScreen = (screenName: string) => {
if (screenName === "Feed") {
return (
<Stack.Screen
...
options={{
headerMode: "screen",
headerTitle: () => (
<Image
style={{ maxHeight: 40, maxWidth: 120 }}
resizeMode="contain"
source={require("../assets/logo.png")}
/>
),
}}
/>
);
...
};
...
Apollo Auth
setContext in Apollo Client
Webに示すように、すべてのリクエストヘッダにタグを含めることができます.
FEED(ApolloクライアントのHeader-setContextを参照)
ただし、違いがある場合は、Webはタグをローカルストレージから抽出してヘッダーに配置し、この機会はタグをReactive Variables変数に配置します.// Apollo.ts
...
const AuthLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
token: tokenVar(), // 현재 tokenVar에 저장된 토큰을 불러옴
},
};
});
FlatList
ScrollView vs FlatList
Scroll View
Nativeでは、自分だけにスクロールすることはできませんView
この素子内に、スクリーンより大きいものがあればScrollView
スクロール可能です.// 상하 스크롤
...
return (
<View ...>
<ScrollView>
<View style={{ height: 20000, flex: 1, backgroundColor: "blue" }}>
<Text>I'm super big</Text>
</View>
</ScrollView>
</View>
);
// 좌우 스크롤
...
return (
<View ...>
<ScrollView horizontal>
<View style={{ width: 20000, flex: 1, backgroundColor: "blue" }}>
<Text>I'm super big</Text>
</View>
</ScrollView>
</View>
);
}
FlatList
ただし、アプリケーションの性能を考慮すると、大量のデータを同時にレンダリングすることはできないView
この場合、不活性なロードが必要です(コンポーネントがスクリーンにある場合はレンダリング)、ない場合はレンダリングされません.ScrollView
コンポーネントが簡単に実現できる.FlatList
3種類の支柱を要求する.
// TabIcon.tsx
...
export default function TabIcon({ focused, iconName, color }: TabIconProps) {
return (
<Ionicons
name={focused ? iconName : `${iconName}-outline`}
color={color}
size={focused ? 24 : 20}
/>
);
}
// LoggedInNav.tsx
...
function LoggedInNav() {
return (
<Tabs.Navigator ... />
<Tabs.Screen
...
options={{
tabBarIcon: ({ focused, color, size }) => (
// 컴포넌트화 전
<Ionicons name={focused ? "home" : "home-outline"}
color={color} size={focused ? 24 : 20} />
),
}}
/>
<Tabs.Screen
...
options={{
tabBarIcon: ({ focused, color, size }) => (
// 컴포넌트화 후
<TabIcon focused={focused} color={color} iconName="search" />
),
}}
/>
...
Stacks for each Tab
INSTAGRAMアプリケーションの構成を考慮すると,各タグ内に複数のスタックがある.各ラベルをクリックすると、最初に表示されるスタックがそのラベルのページに移動し、ページ内で写真またはプロファイルをクリックすると別のスタックに移動します.
この構成をコードにするには、タブ(
Tabs.Screen
)内でコールバックスタック素子(SharedStackNav
)の関数をchildrenに送信する必要がある.// LoggedInNav.tsx
...
function LoggedInNav() {
return (
<Tabs.Navigator ...>
<Tabs.Screen
name="RootFeed"
...
>
{() => <StackNavFactory screenName="Feed" />}
</Tabs.Screen>
...
SharedStackNav
エレメント内で支柱を介して伝達されるscreenName
に従って、対応するスタック(タブの最初の画面)を返します.Profile
科Photo
すべてのタブに共通のスタック.// SharedStackNav.tsx
...
const getFirstScreen = (screenName: string) => {
if (screenName === "Feed") {
return <Stack.Screen name="Feed" component={Feed} />;
} else if (screenName === "Search") {
return <Stack.Screen name="Search" component={Search} />;
} else if (screenName === "Notifications") {
return <Stack.Screen name="Notifications" component={Notifications} />;
} else if (screenName === "Me") {
return <Stack.Screen name="Me" component={Me} />;
} else {
return null;
}
};
function SharedStackNav({ screenName }: SharedStackNavProps) {
return (
<Stack.Navigator ...>
{getFirstScreen(screenName)} // ** 탭의 첫 번째 화면
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Photo" component={Photo} />
</Stack.Navigator>
);
}
Error : Found screens with the same name nested inside one another. Check:親と子の要素の名前が同じ場合に発生するエラーは、異なるエラーに変わります.
Screen Title to Image
Feed screenのタイトルをInstagramのロゴに変更選択可能
headerTitle
String
またはReact.Node
Image
とともにサイズを頭部に調整します.// SharedStackNav.tsx
...
const getFirstScreen = (screenName: string) => {
if (screenName === "Feed") {
return (
<Stack.Screen
...
options={{
headerMode: "screen",
headerTitle: () => (
<Image
style={{ maxHeight: 40, maxWidth: 120 }}
resizeMode="contain"
source={require("../assets/logo.png")}
/>
),
}}
/>
);
...
};
...
Apollo Auth
setContext in Apollo Client
Webに示すように、すべてのリクエストヘッダにタグを含めることができます.
FEED(ApolloクライアントのHeader-setContextを参照)
ただし、違いがある場合は、Webはタグをローカルストレージから抽出してヘッダーに配置し、この機会はタグをReactive Variables変数に配置します.// Apollo.ts
...
const AuthLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
token: tokenVar(), // 현재 tokenVar에 저장된 토큰을 불러옴
},
};
});
FlatList
ScrollView vs FlatList
Scroll View
Nativeでは、自分だけにスクロールすることはできませんView
この素子内に、スクリーンより大きいものがあればScrollView
スクロール可能です.// 상하 스크롤
...
return (
<View ...>
<ScrollView>
<View style={{ height: 20000, flex: 1, backgroundColor: "blue" }}>
<Text>I'm super big</Text>
</View>
</ScrollView>
</View>
);
// 좌우 스크롤
...
return (
<View ...>
<ScrollView horizontal>
<View style={{ width: 20000, flex: 1, backgroundColor: "blue" }}>
<Text>I'm super big</Text>
</View>
</ScrollView>
</View>
);
}
FlatList
ただし、アプリケーションの性能を考慮すると、大量のデータを同時にレンダリングすることはできないView
この場合、不活性なロードが必要です(コンポーネントがスクリーンにある場合はレンダリング)、ない場合はレンダリングされません.ScrollView
コンポーネントが簡単に実現できる.FlatList
3種類の支柱を要求する.
// Apollo.ts
...
const AuthLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
token: tokenVar(), // 현재 tokenVar에 저장된 토큰을 불러옴
},
};
});
ScrollView vs FlatList
Scroll View
Nativeでは、自分だけにスクロールすることはできません
View
この素子内に、スクリーンより大きいものがあればScrollView
スクロール可能です.// 상하 스크롤
...
return (
<View ...>
<ScrollView>
<View style={{ height: 20000, flex: 1, backgroundColor: "blue" }}>
<Text>I'm super big</Text>
</View>
</ScrollView>
</View>
);
// 좌우 스크롤
...
return (
<View ...>
<ScrollView horizontal>
<View style={{ width: 20000, flex: 1, backgroundColor: "blue" }}>
<Text>I'm super big</Text>
</View>
</ScrollView>
</View>
);
}
FlatList
ただし、アプリケーションの性能を考慮すると、大量のデータを同時にレンダリングすることはできない
View
この場合、不活性なロードが必要です(コンポーネントがスクリーンにある場合はレンダリング)、ない場合はレンダリングされません.ScrollView
コンポーネントが簡単に実現できる.FlatList
3種類の支柱を要求する.function Feed({ navigation }: FeedProps) {
const { data, loading } = useQuery(FEED_QUERY);
const renderPhoto: ListRenderItem<seeFeed_seeFeed || null> =
({ item: photo }) => (
<View style={{ flex: 1 }}>
<Text style={{ color: "white" }}>{photo.caption}</Text>
</View>
);
return (
<ScreenLayout loading={loading}>
<FlatList
data={data?.seeFeed}
// string
keyExtractor={(photo) => "" + photo.id}
renderItem={renderPhoto}
/>
</ScreenLayout>
);
}
Photo
Rendering Photos
UseWindowDimensions
ネイティブで画像をレンダリングするにはwidthとheightの値を指定する必要があります.幅と高さを任意に指定すると、画像のスケールが異常になる可能性があります.Instagramでは画面の幅と同じ幅にします.FlatList
方法は画面の幅と高さを求めることができる.
Image.getSize
画像ファイルの実際のサイズを取得できます.3つの因子を受ける.
Render
上記の2つの方法で、写真をより大きなサイズにレンダリングします.幅はスクリーンの幅に等しく、高さは実際の画像の高さに等しく、大きすぎると3に分けられます.
// Photo.tsx
...
function Photo(...) {
const { width, height } = useWindowDimensions();
const [imageHeight, setImageHeight] = useState(height - 450);
useEffect(() => {
Image.getSize(file, (width, height) => {
setImageHeight(height / 3);
});
}, [file]);
return (
<Container>
...
<File
resizeMode="cover"
style={{ width, height: imageHeight }}
source={{ uri: file }}
/>
...
</Container>
);
}
Navigation
Photoコンポーネントでは、プロファイル、コメント、賛などをクリックして該当する画面に移動できます.しかし、Photoは画面上のコンポーネントなのでnavigation propはありません.2つの方法があります.
UseWindowDimensions
使用
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
Pull to Refresh
Pull to Refresh
使用useNavigation
素子のFlatList
・refreshing
道具とonRefresh
医useQuery
refetch
queryを再呼び出しする関数です.refetch
を状態にするrefreshing
後にリフレッシュを終了する関数を作成し、それを伸ばしてリフレッシュを実現します.// Feed.tsx
...
function Feed(...) {
const { ..., refetch } = useQuery(FEED_QUERY);
const refresh = async () => {
setRefreshing(true);
await refetch();
setRefreshing(false);
};
const [refreshing, setRefreshing] = useState(false);
return (
<ScreenLayout loading={loading}>
<FlatList
refreshing={refreshing}
onRefresh={refresh}
showsVerticalScrollIndicator={false} // 스크롤바 숨기기
/>
...
</ScreenLayout>
);
}
Infinite Scrolling
Infinite Scrolling
クライアントが最後までスクロールすると、seeFeedリクエストがサーバに送信され、データの受信が続行されます.表示されたデータのインデックスはrefetch
であり、2つのインデックスを受信したデータのみを格納する.
では、2枚の写真のうち最後の写真の末尾に到達したときに、offset
をサーバに転送し、次の2枚の写真を再び受け取る.無限反復の機能は,この過程を示す写真がなくなるまで無限スクロールである.
seeFeed queryの変更
offset
の価格で、2枚の写真を作成する順番でコードを送信します.// SeeFeed.resolvers.ts
...
const resolvers: Resolvers = {
Query: {
seeFeed: protectedResolver((_, { offset }, { loggedInUser }) =>
// photo를 찾을 때, 팔로워 목록에 내 이름이 있는 유저들의 photo를 찾음
client.photo.findMany({
take: 2, // 찾을 photo의 개수
skip: offset, // 나머지 하나는 offset으로 표시 안함
...
// SeeFeed.typeDefs.ts
...
type Query {
seeFeed(offset: Int!): [Photo] // offset variables 추가
}
...
fetchMore
クライアントは、特定のスクロール位置でseeFeedリクエストを送信し続けます.offset
医FlatList
・onEndReached
和onEndReacedThreshold
医useQuery
を用いて体現する.次の機能があります.
// SeeFeed.resolvers.ts
...
const resolvers: Resolvers = {
Query: {
seeFeed: protectedResolver((_, { offset }, { loggedInUser }) =>
// photo를 찾을 때, 팔로워 목록에 내 이름이 있는 유저들의 photo를 찾음
client.photo.findMany({
take: 2, // 찾을 photo의 개수
skip: offset, // 나머지 하나는 offset으로 표시 안함
...
// SeeFeed.typeDefs.ts
...
type Query {
seeFeed(offset: Int!): [Photo] // offset variables 추가
}
...
// Feed.tsx
...
export const FEED_QUERY = gql`
query seeFeed($offset: Int!) {
seeFeed(offset: $offset) {
...
}
...
`;
function Feed(...) {
const { ..., fetchMore } = useQuery<
seeFeed,
seeFeedVariables
>(FEED_QUERY, {
variables: { offset : 0 }, // 초기 값 : 0번째 사진부터
});
...
return (
<ScreenLayout loading={loading}>
<FlatList
onEndReached={() =>
fetchMore({
variables: {
// 지금까지 받아온 피드의 수 만큼 스킵
offset: data?.seeFeed?.length,
},
})
}
onEndReachedThreshold={0.02}
...
/>
</ScreenLayout>
);
}
これで、サーバは新しいフィードバックを受信し続けますが、画面には何の変化もありません.コンポーネントやステータスの変化がないため、何もレンダリングされません.タイプポリシーの設定
Apollo cacheのタイプを設定します.Apolloは転送パラメータ(offset)に従ってクエリーを独立した空間に格納するため、リストはレンダリングされません.
FlatList : [seeFeed.offset : 0] // 실제 렌더링 되는 리스트
[seeFeed.offset : 2] // 계속해서 피드를 새로 받고있지만 업데이트 되지 않음
[seeFeed.offset : 4]
[seeFeed.offset : 6]
伝達パラメータに基づいてqueryが区別されないようにseeFeedに限定してtypePollicesを設定します.新しい種が加われば既存種と結合して形成できるfetchMore
.// apollo.ts
...
const client = new ApolloClient({
...
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// seeFeed에 한하여,
seeFeed: {
// 전달인자에 따라 cache를 구별하지 않는다.
keyArgs: false,
// 새로운 데이터를 어떻게 처리해야하는지 알려줌
// 기존 데이터나 유입 데이터 둘 중 하나만 존재할 수 있으니 빈 배열이 기본 값
merge: (existing = [], incoming = []) => [...existing, ...incoming],
},
...
offsetLimitPagination
上記手順は、自機内蔵関数
fetchMore
にも簡単に設定できます.// apollo.ts
...
typePolicies: {
Query: {
fields: {
seeFeed: offsetLimitPagination(),
...
Cache Persist
Cache Persist
サーバとの接続が切断されていても、キャッシュに格納されているデータの限られた領域でアプリケーションを使用できます.Cache使用offsetLimitPagination
同期メモリに保存し、AppLoadingにロードすればよい.
Export Cache
キャッシュをグローバルにエクスポートして、プリロード時にロードします.// apollo.ts
export const cache = new InMemoryCache({ ... });
const client = new ApolloClient({
link: AuthLink.concat(httpLink),
cache,
});
Persist Cache
プリロードすると、保存されたキャッシュがロードされます.また、新しいcacheはpersistCache
に格納するように設定することができる.AsyncStorage
初期化する前に、cacheを読み込まなければならない.// App.tsx
const preload = async () => {
...
await persistCache({
cache,
storage: new AsyncStorageWrapper(AsyncStorage),
});
return ...
};
if (loading) {
return (
<AppLoading
startAsync={preload}
...
/>
);
}
return (
<ApolloProvider client={client}>
...
</ApolloProvider>
);
}
Reference
この問題について(Instagram Clone : React Native - part 3 [ FEED]), 我々は、より多くの情報をここで見つけました
https://velog.io/@gwanuuoo/instaclone-uoavwpq1
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
// apollo.ts
export const cache = new InMemoryCache({ ... });
const client = new ApolloClient({
link: AuthLink.concat(httpLink),
cache,
});
// App.tsx
const preload = async () => {
...
await persistCache({
cache,
storage: new AsyncStorageWrapper(AsyncStorage),
});
return ...
};
if (loading) {
return (
<AppLoading
startAsync={preload}
...
/>
);
}
return (
<ApolloProvider client={client}>
...
</ApolloProvider>
);
}
Reference
この問題について(Instagram Clone : React Native - part 3 [ FEED]), 我々は、より多くの情報をここで見つけました https://velog.io/@gwanuuoo/instaclone-uoavwpq1テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol