โœ๐Ÿป [Code Camp_TIL] 31์ผ์ฐจ: RefreshToken, observable & flatMap

code_Jยท2023๋…„ 5์›” 1์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
38/41
post-thumbnail

RefreshToken



AccessToken์€ ๋งŒ๋ฃŒ๊ธฐํ•œ์ด ์ •ํ•ด์ ธ ์žˆ์–ด์„œ ๋งŒ๋ฃŒ๋˜๊ณ  ๋‚˜๋ฉด ์ƒˆ๋กœ์šด AccessToken์„ ๋ฐ›์•„์™€์•ผ ํ•œ๋‹ค. ์ด๋•Œ ์‚ฌ์šฉ๋˜๋Š” ํ† ํฐ์ด RefreshToken์ด๋‹ค.

์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธ์„ ํ•˜๋ฉด ๋ฐฑ์—”๋“œ๋กœ๋ถ€ํ„ฐ AccessToken๊ณผ RefreshToken์„ ๋ฐ›์•„์˜ค๊ฒŒ ๋œ๋‹ค. AccessToken์€ 1-2์‹œ๊ฐ„ ์ •๋„์˜ ์งง์€ ๋งŒ๋ฃŒ ๊ธฐํ•œ์„ ๊ฐ–๊ณ  ์žˆ๊ณ , RefreshToken์€ 2์ฃผ~1๊ฐœ์›” ์ •๋„์˜ ๊ธด ๋งŒ๋ฃŒ ๊ธฐํ•œ์„ ๊ฐ–๊ณ  ์žˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋ฐœ๊ธ‰๋œ AccessToken์˜ ์œ ํšจ ๊ธฐ๊ฐ„์ด ์ง€๋‚˜ ๋งŒ๋ฃŒ ๋˜๋Š” ์‹œ์ ์—์„œ RefreshToken์„ ํ†ตํ•ด์„œ ๋กœ๊ทธ์ธ ์—†์ด ์ƒˆ๋กœ์šด AccessToken์„ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

๋‹ค๋งŒ, RefreshToken ์—ญ์‹œ ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— RefreshToken์˜ ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์ด ์ง€๋‚˜๊ฒŒ ๋œ๋‹ค๋ฉด ์ด๋•Œ๋Š” ๋‹ค์‹œ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ํ•ด ์ƒˆ๋กœ์šด RefreshToken์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๋‹ค.


๋ณ€์ˆ˜์—๋Š” accesstoken์„ ๋„ฃ๊ณ , ์ฟ ํ‚ค์— refreshtoken์„ ๋„ฃ์–ด์„œ accesstoken์„ ํ†ตํ•ด ๊ถŒํ•œ ๋ถ„๊ธฐ ๋“ฑ์„ ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

์ฟ ํ‚ค๋Š” secure / httpOnly์™€ ๊ฐ™์€ ์˜ต์…˜์ด ์žˆ์–ด์„œ ๋ณด์•ˆ์ด ๋” ์ž˜ ๋˜์–ด ์žˆ๋‹ค. ๋ฐฑ์—”๋“œ์—์„œ ์˜ต์…˜์„ ๊ฑด ์ฑ„๋กœ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋ณด๋‚ด๋ฉด, ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” recoil์— ํ† ํฐ์„ ๋‹ด๊ฑฐ๋‚˜ ํ•  ์ˆ˜ ์—†๋‹ค.

httpOnly: ๋ธŒ๋ผ์šฐ์ €์—์„œ Javascript๋ฅผ ์ด์šฉํ•ด ์ฟ ํ‚ค์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ณ , ํ†ต์‹ ์œผ๋กœ๋งŒ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
secure: https ํ†ต์‹  ์‹œ์—๋งŒ ํ•ด๋‹น ์ฟ ํ‚ค๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.


fetchUser api ์š”์ฒญ์„ ํ•ด์„œ (unauthenticated error)ํ† ํฐ๋งŒ๋ฃŒ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, restoreAccessToken api๋ฅผ ํ†ตํ•ด ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ณ , ํ† ํฐ(accesstoken) ์žฌ๋ฐœ๊ธ‰ ํ›„์— ์‹คํŒจํ•œ ์ฟผ๋ฆฌ์— accesstoken๋งŒ ๋ฐ”๊ฟ”์„œ api ์š”์ฒญ์„ ์žฌ์‹œ๋„ํ•˜๋ฉด, ์ธ๊ฐ€์— ์„ฑ๊ณตํ•˜๊ฒŒ ๋œ๋‹ค.

์œ„ ๊ณผ์ •์„ silent authentication ์ด๋ผ๊ณ  ํ•œ๋‹ค. ์œ ์ €๋Š” ํ† ํฐ์ด ์žฌ๋ฐœ๊ธ‰๋˜๋Š” ๊ฒƒ์„ ์•Œ์ง€ ๋ชปํ•˜๊ณ , ๋’ท๋‹จ์—์„œ ์กฐ์šฉํžˆ ์ด ๊ณผ์ •์ด ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰๋˜๋Š” ๊ฒƒ์ด๋‹ค.


RefreshToken์„ ์ด์šฉํ•ด AccessToken์„ ์ƒˆ๋กœ ๋ฐœ๊ธ‰๋ฐ›๋Š” ๊ณผ์ •

  1. AccessToken ๋งŒ๋ฃŒ ํ›„ ์ธ๊ฐ€ ์š”์ฒญ
  2. ํ•ด๋‹น ์˜ค๋ฅ˜๋ฅผ ํฌ์ฐฉํ•ด์„œ ์ธ๊ฐ€ ์—๋Ÿฌ์ธ์ง€ ์ฒดํฌ
  3. RefreshToken์œผ๋กœ AccessToken ์žฌ๋ฐœ๊ธ‰ ์š”์ฒญ
  4. ๋ฐœ๊ธ‰ ๋ฐ›์€ AccessToken์„ state์— ์žฌ์ €์žฅ
  5. ๋ฐฉ๊ธˆ ์‹คํŒจํ–ˆ๋˜(error) API๋ฅผ ์žฌ์š”์ฒญ

์ฐธ๊ณ !

์š”์ฆ˜ ๋ฐฑ์—”๋“œ์—์„œ๋Š” ๋กœ๊ทธ์ธ ๊ด€๋ จ API๋“ค์€ AuthService๋กœ, ๋กœ๊ทธ์ธ์„ ์ œ์™ธํ•œ API๋“ค์€ ResourceService๋กœ ๋‚˜๋ˆ„๋Š” ์ถ”์„ธ๋‹ค. ResourceService๋„ UserService, BoardService ๋“ฑ์œผ๋กœ ์ž์ž˜ํ•˜๊ฒŒ ์ชผ๊ฐœ๊ธฐ๋„ ํ•œ๋‹ค(MicroServiceArchitecture).

๋˜ํ•œ, ์†Œ์…œ ๋กœ๊ทธ์ธ(๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ, ์นด์นด์˜ค ๋กœ๊ทธ์ธ ๋“ฑ)์„ ํ•  ๋•Œ์—๋Š” Open Authentication(OAuth) ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.


RefreshToken ์‹ค์Šต

์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ฟผ๋ฆฌ ๋ฐฉ์‹
1. useQuery: ํŽ˜์ด์ง€ ์ ‘์†ํ•˜๋ฉด ์ž๋™์œผ๋กœ data์— ๋ฐ›์•„์ง€๊ณ (data๋Š” ๊ธ€๋กœ๋ฒŒ์Šคํ…Œ์ดํŠธ ์ €์žฅ) ๋ฆฌ๋ Œ๋”๋ง๋จ
2. useLazyQuery: ๋ฒ„ํŠผ ํด๋ฆญ์‹œ data์— ๋ฐ›์•„์ง€๊ณ (data๋Š” ๊ธ€๋กœ๋ฒŒ์Šคํ…Œ์ดํŠธ ์ €์žฅ) ๋ฆฌ๋ Œ๋”๋ง๋จ
3. useApolloClient: axios์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•(data๋Š” ๊ธ€๋กœ๋ฒŒ์Šคํ…Œ์ดํŠธ ์ €์žฅ). ์›ํ•˜๋Š” ์‹œ์ ์— ์‹คํ–‰ ํ›„ ์›ํ•˜๋Š” ๋ณ€์ˆ˜์— ๋‹ด๋Š” ๋ฐฉ์‹.


  1. useApolloClient๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ fetchUserLoggedIn์„ ๋ฐ›์•„์™”๋‹ค.
const FETCH_USER_LOGGED_IN = gql`
  query fetchUserLoggedIn {
    fetchUserLoggedIn {
      email
      name
    }
  }
`;

export default function LoginSuccessPage() {
  const client = useApolloClient();

  const onClickButton = async () => {
    const result = await client.query({
      query: FETCH_USER_LOGGED_IN,
    });
    console.log(result);
  };

  return (
    <button onClick={onClickButton}>ํด๋ฆญํ•˜์„ธ์š”</button>
  );
}

  1. apollo setting ํŒŒ์ผ ์ˆ˜์ •
export default function ApolloSetting(props: IApolloSettingProps) {
  const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
  const [userInfo, setUserInfo] = useRecoilState(userInfoState);
	if (!accessToken || !userInfo) return;
	    setUserInfo(JSON.parse(userInfo));
	  }, []);
	
	const errorLink = onError(({ graphQLErrors, operation, forward })=>{
		// 1-1. ์—๋Ÿฌ๋ฅผ ์บ์น˜

		// 1-2. ํ•ด๋‹น์—๋Ÿฌ๊ฐ€ ํ† ํฐ๋งŒ๋ฃŒ ์—๋Ÿฌ์ธ์ง€ ์ฒดํฌ(UNAUTHENTICATED)		

    // 2-1. refreshToken์œผ๋กœ accessToken์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ
				
		// 2-2. ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ accessToken ์ €์žฅํ•˜๊ธฐ

		// 3-1. ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ accessToken์œผ๋กœ ๋ฐฉ๊ธˆ ์‹คํŒจํ•œ ์ฟผ๋ฆฌ์ •๋ณด ์ˆ˜์ •ํ•˜๊ธฐ

		// 3-2. ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ accessToken์œผ๋กœ ๋ฐฉ๊ธˆ ์ˆ˜์ •ํ•œ ์ฟผ๋ฆฌ ์žฌ์š”์ฒญํ•˜๊ธฐ

	})

	  const uploadLink = createUploadLink({
	    uri: "http://backend08.codebootcamp.co.kr/graphql",
	    headers: { Authorization: `Bearer ${accessToken}` },
		  credentials: "include",
	  });
	
	  const client = new ApolloClient({
	    link: ApolloLink.from([uploadLink]),
	    cache: APOLLO_CACHE,
	    connectToDevTools: true,
	  });
	

	  return (
	    <ApolloProvider client={client}>
	        {props.children}
	    </ApolloProvider>
	  )
	}

graphQLErrors : ์—๋Ÿฌ๋“ค์„ ์บ์น˜ํ•ด์คŒ
operation : ๋ฐฉ๊ธˆ์ „์— ์‹คํŒจํ–ˆ๋˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ญ์˜€๋Š”์ง€ ์•Œ์•„๋‘ 
forward : ์‹คํŒจํ–ˆ๋˜ ์ฟผ๋ฆฌ๋“ค์„ ์žฌ์ „์†ก


  1. errorLink ์ƒ์„ฑ ๋ฐ ๊ตฌ์กฐ ๊ธฐ๋ฐ˜ ์„ค์ •
// apollo setting ํŒŒ์ผ์˜ errorLink๋ถ€๋ถ„

import { onError } from "@apollo/client/link/error";

const errorLink = onError(({ graphQLErrors, operation, forward }) => {
    // 1. ์—๋Ÿฌ๋ฅผ ์บ์น˜
    if (graphQLErrors) {
      for (const err of graphQLErrors) {
        // 2. ํ•ด๋‹น ์—๋Ÿฌ๊ฐ€ ํ† ํฐ ๋งŒ๋ฃŒ ์—๋Ÿฌ์ธ์ง€ ์ฒดํฌ(UNAUTHENTICATED)
        if (err.extensions.code === "UNAUTHENTICATED") {
          // 3. refreshToken์œผ๋กœ accessToken์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ
        }
      }
    }
  });

์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค. refreshToken์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” graphQL ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ ํ•˜๋Š”๋ฐ, errorLink๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๋Š” ApolloProvider ๋ฐ”๊นฅ์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์— useQuery๋‚˜ useApolloClient๋“ฑ์„ ์ด์šฉํ•ด graphQL ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์—†๋‹ค.

๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ graphql-request๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ๋ณผ ๊ฒƒ์ด๋‹ค.

graphql-request ์„ค์น˜: yarn add graphql-request


graphql-request ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด error๋ฅผ ์บ์น˜ํ•œ ๋’ค accessToken์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๋Š” ์ฝ”๋“œ๋ฅผ ์ ์–ด์ค€๋‹ค.

import { GraphQLClient } from "graphql-request";

export default function ApolloSetting(props: IApolloSettingProps) {
  const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
  const [userInfo, setUserInfo] = useRecoilState(userInfoState);
	const RESTORE_ACCESS_TOKEN = gql`
	  mutation restoreAccessToken {
	    restoreAccessToken {
	      accessToken
	    }
	  }
	`;
	
	if (!accessToken || !userInfo) return;
	    setUserInfo(JSON.parse(userInfo));
	  }, []);

// ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋งŒ๋ฃŒ ์—๋Ÿฌ ์บ์น˜ & ๋ฐœ๊ธ‰
		const errorLink = onError(({ graphQLErrors, operation, forward })=>{
		// 1-1. ์—๋Ÿฌ๋ฅผ ์บ์น˜
		if(graphQLErrors){
			for(const err of graphQLErrors){
				// 1-2. ํ•ด๋‹น ์—๋Ÿฌ๊ฐ€ ํ† ํฐ๋งŒ๋ฃŒ ์—๋Ÿฌ์ธ์ง€ ์ฒดํฌ(UNAUTHENTICATED)
				 if (err.extensions.code === "UNAUTHENTICATED") {

         // 2-1. refreshToken์œผ๋กœ accessToken์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ
					const graphqlClient = new GraphQLClient(
          "https://backend-practice.codebootcamp.co.kr/graphql",
          { credentials: "include" }
	        );
					const result = await graphqlClient.request(RESTORE_ACCESS_TOKEN);
		      // RESTORE_ACCESS_TOKEM์ด๋ผ๋Š” gql์„ ์š”์ฒญํ•œ ๋’ค ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฐ๊ณผ๊ฐ’์„ result์— ๋‹ด๋Š”๋‹ค.
	        const newAccessToken = result.restoreAccessToken.accessToken;
	        // 2-2. ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ accessToken ์ €์žฅํ•˜๊ธฐ
	        setAccessToken(newAccessToken);

					//3-1. ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ accessToken์œผ๋กœ ๋ฐฉ๊ธˆ ์‹คํŒจํ•œ ์ฟผ๋ฆฌ์ •๋ณด ์ˆ˜์ •ํ•˜๊ธฐ
					if(typeof newAcessToken !== "string") return
					operation.setContext({
	                headers: {
	                  ...operation.getContext().headers,
	                  Authorization: `Bearer ${newAccessToken}`, // accessToken๋งŒ ์ƒˆ๊ฑธ๋กœ ๋ฐ”๊ฟ”์น˜๊ธฐ
	                },
	              });
					//3-2. ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ accessToken์œผ๋กœ ๋ฐฉ๊ธˆ ์ˆ˜์ •ํ•œ ์ฟผ๋ฆฌ ์žฌ์š”์ฒญํ•˜๊ธฐ
					forward(operation)
        }
			}
		}
	})
	
	 const uploadLink = createUploadLink({
	    uri: "http://backend08.codebootcamp.co.kr/graphql",
	    headers: { Authorization: `Bearer ${accessToken}` },
		  credentials: "include",
	  });
	
	  const client = new ApolloClient({
	    link: ApolloLink.from([uploadLink]),
	    cache: APOLLO_CACHE,
	    connectToDevTools: true,
	  });
	
	  return (
	    <ApolloProvider client={client}>
	        {props.children}
	    </ApolloProvider>
	  )
	}

  1. getAccessTokenํŒŒ์ผ ๋ถ„๋ฆฌ: graphql-request๋ฅผ ์ด์šฉํ•˜์—ฌ accessToken์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๋Š” ์ฝ”๋“œ๋ฅผ ๋ณ„๋„ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ์ž…๋ ฅํ•ด ์ค€๋‹ค.
import { gql } from "@apollo/client";
import { GraphQLClient } from "graphql-request";

const RESTORE_ACCESS_TOKEN = gql`
  mutation restoreAccessToken {
    restoreAccessToken {
      accessToken
    }
  }
`;

export async function getAccessToken() {
  try {
    const graphqlClient = new GraphQLClient(
      "https://backend-practice.codebootcamp.co.kr/graphql",
      {
        credentials: "include",
      }
    );
    const result = await graphqlClient.request(RESTORE_ACCESS_TOKEN);
    const newAccessToken = result.restoreAccessToken.accessToken;

    return newAccessToken;
  } catch (error) {
    console.log(error.message);
  }
}

์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•œ ํ›„์— errorLink ๋‚ด๋ถ€ ๋กœ์ง์„ ์ˆ˜์ •ํ•ด์ค€๋‹ค. ํ•จ์ˆ˜์˜ return ๊ฐ’์ด promise ์ด๋ฏ€๋กœ .then()์„ ์ด์šฉํ•ด์„œ ์ดํ›„์˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

// 2-1. refreshToken์œผ๋กœ accessToken์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ
	          return fromPromise(
	            getAccessToken().then((newAccessToken) => {

์ƒˆ๋กœ๊ณ ์นจ ์‹œ ํ† ํฐ ์œ ์ง€ํ•˜๊ธฐ

// ํ† ํฐ์„ ๋„ฃ์–ด๋‘๋Š” global state - recoilState
const [accessToken, setAccessToken] = useRecoilState(accessTokenState);

  useEffect(() => {
    // 1. ๊ธฐ์กด๋ฐฉ์‹(refreshToken ์ด์ „)
    // console.log("์ง€๊ธˆ์€ ๋ธŒ๋ผ์šฐ์ €๋‹ค!!!!!");
    // const result = localStorage.getItem("accessToken");
    // console.log(result);
    // if (result) setAccessToken(result);

    // 2. ์ƒˆ๋กœ์šด๋ฐฉ์‹(refreshToken ์ดํ›„) - ์ƒˆ๋กœ๊ณ ์นจ ์ดํ›„์—๋„ ํ† ํฐ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก
    void getAccessToken().then((newAccessToken) => {
      setAccessToken(newAccessToken);
    });
  }, []);



observable & flatMap


observable

์—ฐ์†์ ์ธ ๋น„๋™๊ธฐ ์ž‘์—…(์—ฐ์†์ ์ธ ํŽ˜์ด์ง€ ํด๋ฆญ, ์—ฐ์†์ ์ธ ๊ฒ€์ƒ‰์–ด ๋ณ€๊ฒฝ ๋“ฑ)์„ ์œ„ํ•ด observable ์‚ฌ์šฉํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, 3๋ฒˆ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ–ˆ๋‹ค๊ฐ€ ๋น ๋ฅด๊ฒŒ 5๋ฒˆ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ–ˆ์„ ๊ฒฝ์šฐ 3๋ฒˆ ํŽ˜์ด์ง€ ์š”์ฒญ์„ ์ทจ์†Œ ํ›„ 5๋ฒˆ ํŽ˜์ด์ง€๋ฅผ ๋ณด๋‚ด์ค˜์•ผ ํ•˜๋Š”๋ฐ, ๋ฐฑ์—”๋“œ์—์„œ๋Š” 3๋ฒˆ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” 3๋ฒˆ ํŽ˜์ด์ง€ ์š”์ฒญ์„ ์ทจ์†Œํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์‚ฌ์šฉ์ž์˜ ๋ถˆํŽธํ•œ ๊ฒฝํ—˜์„ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

ํ•˜์ง€๋งŒ, ์ด๋Ÿฐ๊ฒฝ์šฐ๋Š” promise๋กœ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฒŒ ์‰ฝ์ง€ ์•Š๋‹ค. ์ด๋Ÿด ๋•Œ observable์„ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹๋‹ค!


apollo-client์˜ flatmap

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋งค์†Œ๋“œ์™€๋Š” ๋‹ค๋ฅธ flatMap์ด๋‹ค. ์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•˜๋Š” flatMap์€ apollo-client์—์„œ ์ง€์›ํ•˜๋Š” flatMap์ด๋‹ค!

์‹ค์Šต์€ apollo-client์—์„œ ์ง€์›ํ•˜๋Š” observable์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

์„ค์น˜๋ชฉ๋ก
yarn add zen-observable
yarn add @types/zen-observable --dev
yarn add graphql-request


์‹ค์Šต ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

import {from} from 'zen-observable'

export default function ObservableFlatmapPage(): JSX.Element{

	const onClickButton = (): void => {
		// new Promise((resolve, reject) => {})
		// new Observable((observer) => {})

		// from์„ hoverํ•˜๋ฉด observable์ด ๋‚˜์˜ด
		from(["1๋ฒˆ useQuery", "2๋ฒˆ useQuery", "3๋ฒˆ useQuery"]) // fromPromise
			.flatMap((el: string)=> from([`${el} ๊ฒฐ๊ณผ์— qqq ์ ์šฉ`,`${el} ๊ฒฐ๊ณผ์— zzz ์ ์šฉ`]))
			.subscribe((el)=>(console.log(el)))
	} // subscribe -> ๋น„๋™๊ธฐ์ ์ธ ์‹œ์ ์— ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ง€์†์ ์œผ๋กœ ํ˜ธ์ถœ ๊ฐ€๋Šฅ

	return <button onClick={onClickButton}> ํด๋ฆญ</button>
}

fromPromise?
promise๋ฅผ observableํƒ€์ž…์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” ๋„๊ตฌ.
onError ํ•จ์ˆ˜๋Š” return ํƒ€์ž…์œผ๋กœ observable์„ ๋ฐ›๊ณ  ์žˆ์ง€๋งŒ, ์šฐ๋ฆฌ๊ฐ€ return ํ•ด์ฃผ๋Š” ๊ฐ’์€ promise์ด๊ธฐ ๋•Œ๋ฌธ์— fromPromise๋ฅผ ์‚ฌ์šฉํ•ด์„œ observable ํƒ€์ž…์œผ๋กœ ๋ฐ”๊ฟ”์ค˜์•ผ ํ•จ.



profile
Web FE ๊ฐœ๋ฐœ์ž ์ทจ์ค€์ƒ

0๊ฐœ์˜ ๋Œ“๊ธ€