항해 99를 하면서도 많이 부족해서 시도해보지 못하고 Toastify를 사용했었지만 이번 기회에 전역상태관리를 이용해서 모달을 만들어 보고 싶어 도전하게 되었다.

 

참고로 전역모달에 관련된 코드는 지인 분들 중 한 분에게 도움을 요청을 했었다.

(선뜻 친절하게 설명해준 승구쓰 ㅠㅠ) 

 

혼자서 며칠동안 코드를 수정하며 만들어 보게 되었고 결국 돌고 돌아 내가 원하는 기능을 넣게 되었다.

 

처음으로 내가 만들어 보고 싶었던 것은 [좋아요 모달] 이었다. 

 

상품 정보가 있는 공간에 좋아요 버튼을 누르게 되면 화면 하단에서부터 애니메이션 효과를 내며 올라오는 모달!

 

직접 어떤 효과가 어울리고 다른 쇼핑몰에서는 어떻게 구현을 하고 있는 지 어플들을 깔아 확인도 해보고 react-native에서 지원하는 Animated를 공부해보기도 하고..

 

 

대략적인 흐름은 다음과 같이 만들었다.

좋아요 버튼 클릭 > 상태 저장 > 구독하고 있던 전역 모달 컴포넌트에서 업데이트 > 여러 모달 중 상태값에 따라 해당 모달에 데이터 전달 > 모달 팝업

 

내가 실행시키고 싶은 모달의 type과 함께 필요한 상태값을 스토어에 저장을 하게 되면 전역 모달 컴포넌트에서 상태를 업데이트 받아 해당 모달에 props를 전달하는 방식이다.

 

 

우선 모달을 열고 닫기 위해 커스텀 훅으로 openModal과 closeModal을 만들어 사용했다.

 

function useModal() {
  const dispatch = useDispatch();

  const handleOpenModal = (modalType: string, props: any) => {
    dispatch(openModal(modalType, props));
  };

  const handleCloseModal = (modalType: string) => {
    dispatch(closeModal(modalType));
  };

  return { openModal: handleOpenModal, closeModal: handleCloseModal };
}

export default useModal;

 

 

좋아요 버튼을 클릭하면 openModal을 실행하고 스토어에 해당 상품의 좋아요 상태를 넘겨주었다.

스토어에서 modal reducer에 실행된 액션함수에 따라 리듀서에 상태값을 저장하거나 업데이트 처리를 하게 된다.

(좋아요 는 likes reducer에서 사가를 활용하여 좋아요 상태를 저장함과 동시에 서버에 상품의 좋아요 상태를 업데이트하는 비동기처리가 되어있다.)

 

 

export const openModal = (modalType: string, props: {}) => ({
  type: MODAL,
  payload: { modalType, isOpen: true, props },
});

export const closeModal = (modalType: string) => ({
  type: MODAL,
  payload: { isOpen: false },
});


const initialState: ModalActionType<{}> = {
  modalType: "",
  isOpen: false,
  props: {},
};


const modalState = (state = initialState, action: Action<ModalActionType>) => {
  switch (action.type) {
    case MODAL:
      return {
        ...state,
        modalType: action.payload.modalType,
        isOpen: action.payload.isOpen,
        props: action.payload.props,
      };
    default:
      return state;
  }
};

export default modalState;

 

 

 

그렇게 상태저장이 완료가 되면 스토어를 구독하고 있던 컴포넌트에서 업데이트받은 상태값으로 팝업시킬 모달을 찾아 처리한다.

 

 

 

export default function GlobalModal(): JSX.Element {
  const { modalType, isOpen, props } = useSelector(
    (state: ModalState) => state.modal
  );

  if (!isOpen) {
    return <></>;
  }

  const MODAL_COMPONENTS: { [key: string]: JSX.Element } = {
    alarm: <LikeNotification props={props} />,
  };
  return <View>{MODAL_COMPONENTS[modalType]}</View>;
}

 

 

진짜 리액트 네이티브 css도 지원안되는 거 많고 기능은 또 어떻게 구현해야하고 하는 문제때문에 허송세월을 얼마나 보냈는가..

리액트 네이티브의 css속성은 %지원이 되지않아 translate px단위로 사용하려다보니 포기하고 화면 크기를 가져와서 게산을 해야하는 등.. 까다로운 부분이 꽤나 있었다.

 

 

 



좋아요 모달을 만들면서 겪었던 트러블 슈팅 두 가지

 

트러블 슈팅1.

 

모달이 하단 y축의 60의 위치(하단탭바의 높이)에서 -5 위치까지 0.5초의 시간을 두고 올라온다. 좋아요 모달은 2초후에 사라지게 설정을 해놓았다.

 

처음에는 isLiked 상태값만 가져와서 useEffect의 의존성배열에 추가를 했었는데..(좋아요상태가 변하면 올라오니까 당연하게 생각해버림) 

 다른 상품들 중 내가 누른 좋아요 버튼의 상태와 같은 버튼이 있을 경우 모달은 팝업되지 않았다는 문제가 있었다.

 

결국 상품의 아이디를 받아와서 함께 의존성 배열에 추가하여 좋아요 상태가 변하거나 상품의 아이디가 변할때 모달이 팝업되게 수정을 하여 해결하였다.

 

 

트러블 슈팅2.

 

setTimeout을 useState에 담아 사용을 했더니 좋아요 버튼을 연속클릭 시 상태값이 저장되면서 리렌더링이 일어나게 되었다.

 

각 클릭마다 타이머가 생성되고 실행되어 결국 나중에 올라온 모달이 2초가 되기도 전에 사라지게 되었다.

 

이 문제는 useRef를 사용하여 리렌더링 되는 문제를 해결하고 이전 타이머를 취소하여 업데이트할 수 있게 되었다.

 

리렌더링 문제를 제외하고 여러 사건사고들이 많았지만 가장 어이가 없었던 상황이 생각이 난다.

버튼을 클릭할때마다 아래에서 위로 모달이 새로 올라오지 않고 위에 떠 있는 상태에서 텍스트만 바뀌고 있어서 몹시 당황스러웠다.

내가 생각했던 건 이거 아닌데..?

결국, 이 상황은 useEffect에서 컴포넌트가 종료되기 전에 모달의 y축위치를 수정하는 코드를 추가하여 애니메이션 효과를 처음부터 실행함으로써 해결할 수 있었다.

 

사실 코딩 실력이 많이 부족한 나로서 어떻게든 기능 구현을 위해서 영차영차 달린 것이긴 하지만.. 부족하게나마 구현을 했다는 사실에 기뻐하며 행복했다   

 

'TIL & WIL' 카테고리의 다른 글

react-native 트러블 슈팅  (0) 2023.06.06
230221_TIL  (0) 2023.02.21
230217_TIL  (2) 2023.02.18
230213_TIL  (0) 2023.02.14
230204_TIL  (1) 2023.02.04

 

맨땅에 헤딩하듯 공부하면서 힘들었던 만큼 시간이 지났음을 느낀다. 부족해보이는 코드가 부끄럽지만 이번에 겪은 트러블 슈팅을 적어보려 한다.

 

트러블 슈팅 1.

Navigator

    ㄴ Home - Main,Best,New,Sale

    ㄴ Category

   ...

 

현재 상황

메인(Home) 화면에서 렌더링 할 때 상품의 전체 데이터를 서버에서 가져온 다음 state에 저장 후 하위 탭바의 컴포넌트로 전달하고 있는 상황.

 

상단 탭바에 있는 다른 탭을 눌렀다가 홈을 클릭할때 마다 이미지가 번쩍이는 효과가 발생.

 

이게 뭐지 싶어서 고민을 해보게 되었다. 탭을 누를 때 마다 서버에서 다시 전체데이터를 불러오는 건가?

 

export default function Home() {
  const layout = useWindowDimensions(); //TabView 컴포넌트에서 초기 레이아웃 설정을 위해서
  const [index, setIndex] = useState(0);
  const [productInfo, setProductInfo] = useState<ProductType[]>([]);
  const [routes] = useState([
    { key: "home", title: "홈" },
    { key: "best", title: "BEST" },
    { key: "new", title: "NEW" },
    { key: "sale", title: "SALE" },
  ]);


  const fetchDataFromServer = async () => {
    try {
      const response = await fetchProductData();
      setProductInfo(response);
    } catch (error) {
      console.log(error);
    }
  };

  useEffect(() => {
    fetchDataFromServer();
  }, []);

  const HomeCategory = () => <Main productInfo={productInfo} />;
  const BestItemsCategory = () => <BestItems productInfo={productInfo} />;
  const NewItemsCategory = () => <NewItems productInfo={productInfo} />;
  const SalesCategory = () => <SaleItems productInfo={productInfo} />;

  const renderScene = SceneMap({
    home: HomeCategory,
    best: BestItemsCategory,
    new: NewItemsCategory,
    sale: SalesCategory,
  });


  return (
    <View style={{ flex: 1, backgroundColor: "white" }}>
      <TabView
        renderTabBar={(props) => (
          <Bar
            {...props}
            style={{
              backgroundColor: "unset",
            }}
            labelStyle={{ color: "black" }}
            pressColor="transparent"
            indicatorStyle={{ backgroundColor: "black" }}
            renderLabel={({ route, focused }) => (
              <TabText focused={focused}>{route.title}</TabText>
            )}
          />
        )}
        navigationState={{ index, routes }}
        renderScene={renderScene}
        onIndexChange={setIndex}
        initialLayout={{ width: layout.width }}
      />
    </View>
  );
}

 

하지만 상위 컴포넌트에서 props로 전달하는 방식이라 탭을 클릭했을 때 서버의 전체 데이터를 받아와서 적용되는 것은 아니라고 생각이 들었고,  이미지가 도대체 왜 번쩍이는지 전혀 이해를 못하고 있었다.

 

원인 발견

상단 탭바(react-native-tab-view)에서 각각의 탭들을 클릭할 때 react-native-tab-view는 현재 index를 렌더링하게 된다.

즉, 홈(Main) 탭을 클릭 할 때, 렌더링이 되기 때문에 이미지가 번쩍이는 효과가 나타는 것.

 

해결 방법

각 탭에 대한 컴포넌트들을 캐싱하는 방식으로 구현하기 위해  useMemo를 사용하였다.

 

/*
  const HomeCategory = () => <Main productInfo={productInfo} />;
  const BestItemsCategory = () => <BestItems productInfo={productInfo} />;
  const NewItemsCategory = () => <NewItems productInfo={productInfo} />;
  const SalesCategory = () => <SaleItems productInfo={productInfo} />;

  const renderScene = SceneMap({
    home: HomeCategory,
    best: BestItemsCategory,
    new: NewItemsCategory,
    sale: SalesCategory,
  });
*/


const HomeCategory = useMemo(
    () => <Main productInfo={productInfo} />,
    [productInfo]
  );
  const BestItemsCategory = useMemo(
    () => <BestItems productInfo={productInfo} />,
    [productInfo]
  );
  const NewItemsCategory = useMemo(
    () => <NewItems productInfo={productInfo} />,
    [productInfo]
  );
  const SalesCategory = useMemo(
    () => <SaleItems productInfo={productInfo} />,
    [productInfo]
  );

  const renderScene = ({ route }: { route: { key: string } }) => {
    switch (route.key) {
      case "home":
        return HomeCategory;
      case "best":
        return BestItemsCategory;
      case "new":
        return NewItemsCategory;
      case "sale":
        return SalesCategory;
      default:
        return null;
    }
  };

 

route객체는 왜 받고 route.key는 또 뭐야?

rednerScene의 route.key는 TabView에서 현재 활성화된 탭에 해당하는 컴포넌트를 렌더링하기 위해 routes라는 useState 값을 사용 중이다.

routes = [{ key: ... , title: ...},{...}]

 

 

 

 

트러블 슈팅 2.

Navigator

    ㄴ Home 

          ㄴ Main

                 ㄴ ProductList

 

 

현재 상황

상품의 리스트를 나타내는 컴포넌트인 ProductList에서 좋아요 버튼을 눌렀을 때 서버에 좋아요 상태를 변경함.

상태를 변경할때마다 서버에서 데이터를 다시 불러와서 적용하는 것은 지양하고 싶었기 때문에, 로컬에서 setState를 사용하여 좋아요 상태를 변경하여 다시 적용하려 의도 함.

 

export default function ProductList({ products }: ProductListProps) {
  const [productsData, setProductsData] = useState(products);

  const toggleProductLikedStatus = ({ productId, currentIsLiked }: LikesProductType) => {
      updateProductLikedStatus({
        productId,
        isLiked: currentIsLiked,
      });
      setProductsData((prevProducts) => {
        return prevProducts?.map((product) => {
          if (product.id === productId) {
            return {
              ...product,
              isLiked: !product.isLiked,
            };
          }
          return product;
        });
      });
    };
 
  return (
    <Wrapper>
      {productsData?.map((product) => {
        return (
          <ProductCard key={product.product_name}>
            <ImageContainer>
              <Img source={{ uri: product.image }} />
              <LikesBtn
                name={product.isLiked ? "heart-fill" : "heart"}
                isLiked={product.isLiked}
                size={24}
                onPress={() =>
                  toggleProductLikedStatus({
                    productId: product.id,
                    currentIsLiked: product.isLiked,
                  })
                }
              />
            </ImageContainer>
            <ProductContainer>
              <FlexContainer>
                {product.product_color?.map((color) => {
                  return <ProductColor key={color} color={color} />;
                })}
              </FlexContainer>

              <ProductName numberOfLines={1} ellipsizeMode="tail">
                {product.product_name}
              </ProductName>
              <ProductPrice>
                {product.product_price.toLocaleString()}
              </ProductPrice>
            </ProductContainer>
          </ProductCard>
        );
      })}
    </Wrapper>
  );
}

하지만 좋아요 버튼을 눌렀을 때 좋아요 버튼이 변경되는 것은 확인 했으나 다른 탭을 클릭한 후, 다시  홈 탭에 돌아왔을 때 좋아요 버튼이 원래대로 돌아가있는 상황을 발견하게 되었다.

 

원인 발견

트러블 슈팅1과 같은 현상으로 탭을 이동할 때 현재 react-natvie-tab-view가 현재 Index의 컴포넌트를 렌더링하게 되면서 처음 렌더링할 때의 productData를 적용하게 된 것.

상위 컴포넌트의 데이터를 전달받아 사용중이 었기때문에 현재 버튼을 누른 상품의 상태만을 바꾸는 것은 의미없는 행동이 되어버림.

 

이 문제 때문에 좋아요 버튼을 누를 때 서버에서 상품의 데이터를 가져와서 다시 적용할까 엄청 고민을 했었지만, 조금만 더 고민해보고 노력해보기로 했다.

 

해결 방법

상위 컴포넌트인 Home에서 함수를 만들어 ProductList컴포넌트에 props로 할당하면 되는 간단한 방법도 있었지만 리덕스를 활용하여 업데이트하는 방법을 사용해보고 싶었다. 

 

호기심에 해본 작업이었지만 하면서도 되게 후회했다. 좋아요 상태업데이트 하나에 너무 복잡하게 돌아간다는 느낌..?

 

ProductList 컴포넌트에서 리덕스를 활용하여 좋아요 버튼을 클릭했을 때 아이디와 좋아요 상태를 관리하고, Main 컴포넌트에서는 useSelector 훅을 사용하여 likes 상태를 구독하였다.

좋아요 버튼을 눌렀을 때 상태가 저장되게 되고, 메인 컴포넌트에서는 productInfo를 업데이트한다. 
메인 컴포넌트가 리렌더링 되면서 업데이트된 데이터 역시 하위컴포넌트에게 전달이 된다.

또한, setProductsData 함수를 호출함으로써 ProductList 컴포넌트가 새로운 데이터를 받아 렌더링하게 한다.

 

// Home.tsx

const likes = useSelector((state: LikeState) => state.likes.likes);
  
useEffect(() => {
    setProductInfo((prevProductInfo) => {
      return prevProductInfo.map((product) => {
        if (product.id === likes.productId) {
          return {
            ...product,
            isLiked: !likes.isLiked,
          };
        }
        return product;
      });
    });
}, []);
// ProductList.tsx

const toggleProductLikedStatus = ({
    productId,
    isLiked,
  }: LikesProductType) => {
    dispatch(toggleLike({ productId, isLiked }));

    setProductsData((prevProducts) => {
      return prevProducts?.map((product) => {
        if (product.id === productId) {
          const isLiked = !product.isLiked;
          return {
            ...product,
            isLiked: isLiked,
          };
        }
        return product;
      });
    });
  };
  useEffect(() => {
    setProductsData(products);
  }, [products]);

 

 

// likes reducer
  
export interface AuthState {
  likes: LikesProductType;
  loading: boolean;
  error: any;
}

interface LikesSagaAction {
  type: string;
  payload: LikesProductType;
}

const prefix = "http://로컬 IP";

// 액션 타입 정의
const PENDING = `${prefix}/PENDING`;
const SUCCESS = `${prefix}/SUCCESS`;
const FAIL = `${prefix}/FAIL`;
const TOGGLE_LIKE = `${prefix}/TOGGLE_LIKE`;

// 액션 생성 함수
const pending = () => ({ type: PENDING });
const success = (likes: LikesProductType) => ({
  type: SUCCESS,
  payload: likes,
});
const fail = (error: Error | null) => ({ type: FAIL, payload: error });

export const toggleLike = (product: LikesProductType) => ({
  type: TOGGLE_LIKE,
  payload: product,
});

// 초기 상태 정의
const initialState: AuthState = {
  likes: { productId: null, isLiked: false },
  loading: false,
  error: null,
};

// 리듀서 함수 정의
const reducer = (
  state: AuthState = initialState,
  action: Action<LikesProductType>
): AuthState => {
  switch (action.type) {
    case PENDING:
      return { ...state, loading: true };
    case SUCCESS:
      return {
        ...state,
        likes: action.payload,
        loading: false,
        error: null,
      };
    case FAIL:
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
};

// 사가 함수 정의
function* likesSaga(action: LikesSagaAction) {
  try {
    yield put(pending());
    yield call(updateProductLikedStatus, {
      productId: action.payload.productId,
      isLiked: !action.payload.isLiked,
    });

    // 성공 액션 디스패치
    yield put(
      success({
        productId: action.payload.productId,
        isLiked: !action.payload.isLiked,
      })
    );
  } catch (error: any) {
    yield put(fail(error));
  }
}

// 루트 사가 함수 정의
export function* callSaga() {
  yield takeEvery(TOGGLE_LIKE, likesSaga);
}

export default reducer;

 

 

느낀점

typescript와 react-native에 대한 기초개념을 공부한 이후에 처음 만들어보는 프로젝트이기도하고, redux-saga 또한 처음 공부해서 시도해보고 있다.

역시 맨땅에 헤딩을 해서 그런지 간단한 작업조차 이틀이 걸렸다(물론 쉬면서 해서 그렇지만..)

리액트 네이티브에서 map함수를 사용하면 성능이 좋지않아 권장하지 않는다는 이야기를 들었고,

대규모 데이터를 활용할때 좋은 FlatList를 활용하고자 하였지만 FlatList 또한 Scroll속성이 있어 ScrollView와 함께 사용하면 충돌이 일어난다는 사실도 알게 되었다.

이것 때문에 시간이 더 잡힌 것도 없지않아 있었지만 결국 적용을 하지 못 했다는 것에 아쉬움이 크다.

메인 홈페이지에서는 전체 ui가 스크롤 되어야하지만 FlatList는 적용하는 대상에만 스크롤이 걸리기때문에 상품의 정보를 렌더링하는 ProductList 컴포넌트에 사용이 가능한 거라 결국 메인 컴포넌트에는 사용을 하지 못했다. 

물론 잘 알지못해서 적용을 못 했을 수도 있다고 생각하지만 이것 또한 과정이라 생각하기로 했다.

 

결론: 혼자서 헤딩하는 것은 너무 힘들다

'TIL & WIL' 카테고리의 다른 글

230607 TIL - 전역 모달을 만들어보다  (0) 2023.06.07
230221_TIL  (0) 2023.02.21
230217_TIL  (2) 2023.02.18
230213_TIL  (0) 2023.02.14
230204_TIL  (1) 2023.02.04

1. 같거나 서브 타입인 경우, 할당이 가능하다. => 공변

// primitive type
let sub7: string = '';
let sup7: string | number = sub7;

// object - 각각의 프로퍼티가 대응하는 프로퍼티와 같거나 서브타입이어야 한다.
let sub8: { a: string; b: number } = { a: '', b: 1 };
let sup8: { a: string | number; b: number } = sub8;

// array - object 와 마찬가지
let sub9: Array<{ a: string; b: number }> = [{ a: '', b: 1 }];
let sup9: Array<{ a: string | number; b: number }> = sub8;

 

  • A(좁은 타입)가 B(넓은 타입)의 서브타입이면, T<A>는 T<B>의 서브타입이다.
  • A ⊂ B  T<A> ⊂ T<B>
  • 대다수의 경우가 포함 됨

 

2. 함수의 매개변수 타입만 같거나 슈퍼타입인 경우, 할당이 가능하다. => 반공변

class Person {}
class Developer extends Person {
  coding() {}
}
class StartupDeveloper extends Developer {
  burning() {}
}

function tellme(f: (d: Developer) => Developer) {}

// Developer => Developer 에다가 Developer => Developer 를 할당하는 경우
tellme(function dToD(d: Developer): Developer {
  return new Developer();
});

// Developer => Developer 에다가 Person => Developer 를 할당하는 경우
tellme(function pToD(d: Person): Developer {
  return new Developer();
});

// Developer => Developer 에다가 StartipDeveloper => Developer 를 할당하는 경우
tellme(function sToD(d: StartupDeveloper): Developer {
  return new Developer();
});

 

  • A(좁은 타입)가 B(넓은 타입)의 서브타입이면, T<B>는 T<A>의 서브타입이다.
  • A ⊂ B  T<B> ⊂ T<A>
  • 그런데 위의 공변성 규칙이 함수의 매개변수로 전달된 경우, 이것이 반대로 동작한다.

 

위 코드에서 마지막 d: StartupDeveloper라는 매개변수는 Developer에 속할 수 없다.

하지만 타입스크립트의 strict옵션을 제거한 형태로는 에러가 뜨지 않는다.

tsconfig.json 파일에서 strictFunctionTypes 옵션을 켜면 함수를 할당할 시에 함수의 매개변수 타입이 같거나 슈퍼타입인 경우가 아닌 경우, 에러를 통해 경고한다.

이는 어느 정도 타입스크립트가 융통성을 부여한다고 볼 수 있다.

 

 

 

출처 - 

 

📘 타입스크립트 공변성 & 반공변성 완벽 이해

타입의 공변성과 반공변성 타입스크립트는 자바스크립트에 타입을 추가해준 라이브러리 이지만, 타입을 다루는 언어이기도 하다. 그래서 어느새 타입 자체를 코딩하고 있는 자신을 발견하기도

inpa.tistory.com

'Typescript 내용 정리' 카테고리의 다른 글

Basic Type(2)  (0) 2023.04.07
Basic Type(1)  (0) 2023.04.01

Array

  • array 는 객체.
  • 사용방법
    • Array<타입>
    • 타입[]
let list: number[] = [1, 2, 3];

let list: Array<number> = [1, 2, 3];

 

 

Tuple

  • 배열인데 타입이 한가지가 아닌 경우
  • tuple은 객체.
  • 꺼내 사용할때 주의가 필요하다.
    • 배열을 Destructuting 하면 타입이 제대로 얻어짐
let x: [string, number]; // Declare a tuple type
x = ["hello", 10]; // OK
x = [10, "hello"]; // Error

x[3] = "world"; // Type '"world"' is not assignable to type 'undefined'.

const person: [string, number] = ["mark", 35];

const [first, second, third] = person; // Error

 

 

Any

  • 어떤 타입이어도 상관없는 타입이다.
  • 핵심은 any를 최대한 쓰지 않는 것. 왜냐면 컴파일 타임에 타입 체크가 정상적으로 이뤄지지 않기 때문.
  • 그래서 컴파일 옵션 중에는 any를 써야하는데 쓰지 않으면 오류를 뱉도록 하는 옵션도 있다고 한다.
    • noImplicitAny
function returnAny(message): any {
    console.log(message);
}

returnVoid('리턴은 아무거나');
  • any 는 계속해서 개체를 통해 전파된다.
  • 결국, 모든 편의는 타입 안전성을 잃는 대가로 온다는 것을 기억해야 한다.
  • 타입 안전성은 TypeScript 를 사용하는 주요 동기 중 하나이며 필요하지 않은 경우에는 any 를 사용하지 않도록 해야 한다.
let looselyTyped: any = {};

let d = looselyTyped.a.b.c.d;
//  ^ = let d: any

 

 

 

Unknown

  • 응용 프로그램을 작성할 때 모르는 변수의 타입을 묘사해야 할 수도 있다.
  • 이 경우, 컴파일러와 미래의 코드를 읽는 사람에게 이 변수가 무엇이든 될 수 있음을 알려주는 타입을 제공하기를 원하므로 unknown 타입을 제공.
  • typeof 검사, 비교 검사 또는 고급 타입 가드를 수행하여 보다 구체적인 변수로 좁힐 수 있음.
declare const maybe: unknown;
// 'maybe' could be a string, object, boolean, undefined, or other types
const aNumber: number = maybe; // Type 'unknown' is not assignable to type 'number'.

if (maybe === true) {
  // TypeScript knows that maybe is a boolean now
  const aBoolean: boolean = maybe;
  // So, it cannot be a string
  const aString: string = maybe; // Type 'boolean' is not assignable to type 'string'.
}

if (typeof maybe === "string") {
  // TypeScript knows that maybe is a string
  const aString: string = maybe;
  // So, it cannot be a boolean
  const aBoolean: boolean = maybe; // Type 'string' is not assignable to type 'boolean'.
}

 

 

Never

  • 리턴에 사용된다.
  • 리턴에 사용되는 경우, 아래 3가지 정도의 경우가 대부분
// Function returning never must have unreachable end point
function error(message: string): never {
    throw new Error(message);
}

// Inferred return type is never
function fail() {
    return error("Something failed");
}

// Function returning never must have unreachable end point
function infiniteLoop(): never {
    while (true) {
    }

 

  • never 타입은 모든 타입의 subtype 이며, 모든 타입에 할당 할 수 있다.
  • 하지만, never 에는 그 어떤 것도 할당할 수 없다.
  • any 조차도 never 에게 할당 할 수 없다.
  • 잘못된 타입을 넣는 실수를 막고자 할 때 사용하기도 한다.
let a: string = 'hello';

if (typeof a !== 'string') {
    let b: never = a;
}

 

 

 

Void

  • 어떤 타입도 가지지 않는 빈 상태를 의미.
  • 값은 없고 타입만 있다.
  • 일반적으로 값을 반환하지 않는 함수의 리턴 타입으로 사용한다.
    그 외에는 사용할 일이 거의 없다.
  • 할당이 가능한 값은 undefined.
function returnVoid(message): void {
    console.log(message);
}

returnVoid('리턴이 없다');

let unusable: void = undefined;

'Typescript 내용 정리' 카테고리의 다른 글

타입 호환성(type compatibility)  (0) 2023.04.10
Basic Type(1)  (0) 2023.04.01

Primitive Types

  • 오브젝트와 레퍼런스 형태가 아닌 실제 값을 저장하는 자료형이다.
  • 프리미티브 형의 내장 함수를 사용 가능한것은 자바스크립트 처리 방식 덕분
  • (ES2015 기준) 6가지
    • boolean
    • number
    • string
    • symbol (ES2015)
    • null
    • undefined
let name = "minseung";

name.toString();
  • 래퍼 겍체로 만들 수 있다.
new Boolean(false); // typeof new Boolean(false) : 'object'

new String('world'); // typeof new String('world') : 'object'

new Number(42); // typeof new Number(42) : 'object'

 

 

Boolean / boolean

  • 가장 기본적인 데이터 타입
  • 단순한 true 혹은 false 값이다.
  • JavaScript / TypeScript 에서 'boolean' 이라고 부른다. 
let isDone: boolean = false;

isDone = true;

console.log(typeof isDone); // boolean;

let isOk: Boolean = true;

// Type 'Boolean' is not assignable to type 'boolean'.
// 'boolean' is a primitive, but 'Boolean' is a wrapper object.
// Prefer using 'boolean' when possible.
let isNotOk: boolean = new Boolean(true);

 

 

Number / number

  • JavaScript 와 같이, TypeScript 의 모든 숫자는 부동 소수점 값이다.
  • TypeScript는 16진수 및 10진수 리터럴 외에도, ECMAScript 2015에 도입된 2진수 및 8진수를 지원한다.
  • NaN
  • 1_000_000 과 같은 표기 가능
let decimal: number = 6;

let hex: number = 0xf00d; //16진수

let octal: number = 0o744; //8진수

let binary: number = 0b1010; //2진수

let NotANumber: number = NaN;

let underscoreNum: number = 1_000_000;

 

 

template string

  • 행에 걸쳐 있거나, 표현식을 넣을 수 있는 문자열
  • 이 문자열은 backtick (= backquote) 기호에 둘러쌓여 있다.
  • 포함된 표현식은 `${ expr }` 와 같은 형태로 사용한다.
let fullName: string = "Minseung Gang";

let age: number = 32;

let sentence: string = `Hello, my name is ${fullName}.
I'll be ${age + 1} years old next month.`;

console.log(sentence); // I'll be 33 years old next month.

 

 

Symbol

  • ECMAScript 2015 의 Symbol
  • new Symbol 로 사용할 수 없다.
  • Symbol 을 함수로 사용해서 symbol 타입을 만들어낼 수 있다.
console.log(Symbol("foo") === Symbol("foo")); //false
  • 프리미티브 타입의 값을 담아서 사용한다.
  • 고유하고 수정불가능한 값으로 만들어준다.
  • 그래서 주로 접근을 막거나 접근을 필요한 경우에만 허락할 때 사용
const sym = Symbol();

const obj = {
  [sym]: "value",
};

obj["sym"]; //접근 불가능
obj[sym]; // 접근 가능

 

Undefined & Null

  • number 에 null 또는 undefined 를 할당할 수 있다는 의미.
  • 하지만, 컴파일 옵션에서 `--strictNullChecks`사용하면, null 과 undefined 는 void 나 자기 자신들에게만 할당할 수 있다.
    • 이 경우, null 과 undefined 를 할당할 수 있게 하려면, union type 을 이용해야 한다.
let name: string = null;
let age: number = undefined;

// strictNullChecks => true
// Type 'null' is not assignable to type 'string'.
let name: string = null; (X)

// null => null || void, undefined => undefined || void
// Type 'null' is not assignable to type 'undefined'.
let u: undefined = null; // (X)

let v: void = undefined; // (O)

let union: string | null | undefined = 'str';

 

 

Object

  • javascript의 객체와 조금 다르다.
  • "primitive type 이 아닌 것" 을 나타내고 싶을 때 사용하는 타입
// create by object literal
const person1 = { name: "Minseung", age: 32 };

// person1 is not "object" type.
// person1 is "{name: string, age: number}" type.

// create by Object.create
const person2 = Object.create({ name: "Minseung", age: 32 });

 

let obj: object = {};

obj = { name: "Mark" };

obj = [{ name: "Mark" }];

obj = 39; // Error

obj = "Mark"; // Error

obj = true; // Error

obj = 100n; // Error

obj = Symbol(); // Error

obj = null; // Error

obj = undefined; // Error


// 실제 사용의 예
declare function create(o: object | null): void;

create({ prop: 0 });

create(null);

create(42); // Error

create("string"); // Error

create(false); // Error

create(undefined); // Error


// Object.create
Object.create(0); // Error

 

 

'Typescript 내용 정리' 카테고리의 다른 글

타입 호환성(type compatibility)  (0) 2023.04.10
Basic Type(2)  (0) 2023.04.07

프로세스는 메모리에 적재되고 CPU 자원을 할당 받아 프로그램이 실행되고 있는 상태

스레드는 프로세스 내에서 실행되고 있는 여러가지 실행의 단위

스레드는 스택메모리를 제외한 다른 메모리 영역들을 프로세스 내 다른 스레드들과 공유합니다.

 

 차이점

프로세스는 자원을 공유하지 않지만, 스레드는 자원을 공유한다는 차이가 있습니다.

 

- 멀티 프로세스 & 멀티 스레드란?

멀티 프로세스는 하나의 프로그램을 여러개의 프로세스로 구성하여 각 프로세스가 하나의 작업(task)을 처리하는 것입니다.

하나의 프로세스가 잘못 되어도 프로그램은 동작한다는 장점이 있지만 Context Switching 비용이 발생합니다.

    

멀티 스레드는 프로그램을 여러개의 스레드로 구성하고 각 스레드가 작업(task)을 처리하는 것입니다. 캐싱적중률이 높아 시스템 자원 소모 감소, 처리비용 감소(실행 속도 향상), 스레드간 자원 공유(stack 제외)라는 장점이 있지만 디버깅이 어렵고, 동기화 이슈가 발생할 수 있다는 단점이 있습니다.

 

멀티코어는 하드웨어 측면에서 실행단위를 병렬적으로 처리할 수 있도록 여러 프로세서가 있는 것

 

Redux 상태관리의 주요 개념들과 연결 관계를 설명해주세요. 다른 상태관리 도구와 비교 설명 해주세요

Redux는 flux구조를 따르고 있는 전역상태관리 라이브러리이다. 

액션 - 디스패쳐 - 스토어 - 뷰 그리고 다시 액션을 통해 디스패쳐로 이어지는 구조로 되어 있다.

디스패처의 특정 메소드를 실행하면 스토어에 변화를 일으킬 수 있는데, 이 메소드를 호출할 때는 데이터 묶음을 인수로 전달한다. 이 때 이 데이터 묶음을 액션(Action)이라 한다.

Flux 애플리케이션의 모든 데이터 흐름을 관리하는 허브 역할을 한다.

액션이 발생하면 디스패처로 메시지가 전달되고 디스패처는 디스패처에 등록된 콜백 함수를 통해 이 메시지를 스토어에 전달한다.

스토어는 상태를 저장한다. 모든 상태 변경은 스토어에 의해 결정되며 상태 변경을 위한 요청을 스토어에 직접 할 수는 없다. 상태 변경을 위해서는 꼭 액션 생성자를 통해 디스패쳐 단계를 거친 후 액션을 보내야만 상태값 변경이 가능하다.

뷰는 상태를 가져와서 보여주고 사용자로 부터 입력 받을 화면을 보여준다.

Redux는 단방향으로 한번만 이루어지는 구조이기 때문에 변화를 감지하고 화면을 업데이트하는 코드를 매번 작성해야 하는 단점이 있지만, 데이터 변화에 따른 성능 저하 없이 DOM 객체 갱신이 가능하다는 장점이 있다.

액션이 이루어지면 디스패쳐를 통해 스토어로 전달이되고 그리고 스토어에서 useSelector 함수를 통해 뷰로 다시 가져와서 적용 할 수 있다.

 

 

 

React의 state와 props에 대해서 설명해주세요

 

리액트에서 state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다.

 

state를 사용하는 컴포넌트는 클래스형 컴포넌트와 함수형 컴포넌트로 나뉘어져 있다.

클래스 컴포넌트에서는 constructor메서드로 state를 초기화 한 후 state들을 지니면서 사용할 수 있다. 

더 자세하게는 super(props)과정에서 전달받은 props를 초기화 한후 this.state를 설정해두고 사용한다.

state는 컴포넌트 내부에서 선언되며, 객체 형태로 관리된다.

함수형 컴포넌트에서는 React에서는 state 값을 직접 수정할 수 없다. 그리하여 useState라는 Hook을 통해 state를 관리하는데,  state를 변경하면 컴포넌트가 다시 렌더링된다. 이러한 상태 데이터를 변경하여 컴포넌트의 동작을 제어할 수 있다.

(useState 훅은 배열을 반환하며, 첫 번째 요소는 현재 상태 값이고, 두 번째 요소는 상태를 변경하는 함수이다. 이러한 상태 값과 상태 변경 함수를 사용하여 컴포넌트의 상태를 관리할 수 있다.)

 

props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며, 컴포넌트(하위) 자신은 해당 props를 읽기 전용으로만 사용할 수 있다.

props는 불리언을 포함한 숫자, 배열과 같은 다양한 형태의 데이터를 담을 수 있다. 단, props에 있는 데이터는 문자열인 경우를 제외하면 모두 중괄호({})로 값을 감싸야 한다.

props를 받아 사용할때는 object 형태가 인자로 전달이 된다. 사용할때엔 props. 의 방식으로 꺼내 사용 할 수 있고 중괄호를 감싸는 형태로 사용한다.

function Component(props){
	return{
   	     <div>props.name</div>
             <div>props.age</div>
    }
}

 

 

 

<li>요소는 왜 <ul>요소의 자식 요소여야만 하나요?

 

li 요소는 목록 아이템을 보여주기 위한 요소이다.

그래서 목록을 담는 ul 요소의 자식 요소여야 한다.

화면 상으로는 아무런 문제가 없더라도 이렇게 li 요소와 ul 요소의 의미에 맞게, 시멘틱하게 HTML을 작성하는 것이 어떤 개발자가 와서 보더라도 쉽게 이해할 수 있기 때문에 중요하다.

semantic한 태그의 사용의 중요성과도 관련이 깊다고 할 수 있다.

HTML spec에서도 li요소는 ol,ul,menu의 아래에서 사용할 수 있고, 다른 요소들과의 관계가 없다고 하면서 같이 사용하도록 권장하고 있다.

(https://html.spec.whatwg.org/#the-li-element)

 

 

Semantic HTML의 필요성을 예시를 들어 설명해주세요.

 

Semantic HTML은 적절한 의미의 태그를 사용하여 정보의 구조에 맞게 마크업하는 HTML 작성방법이라고 할 수 있다.

시멘틱 태그를 사용하게되면 프로젝트를 유지보수할때나 개발자들이 코드를 파악하기 쉬워진다는 장점이 있다.

또한 검색 엔진 최적화(SEO)와 웹 접근성을 향상시키는 데 큰 도움이 된다.

예를 들어, 웹 사이트에 블로그 게시물이 있다고 가정해 보자.

블로그 게시물의 제목, 날짜, 본문 등은 모두 다른 의미를 가지고 있다. 이러한 정보를 의미 있는 HTML 요소로 표시하면 검색 엔진이 이해하기 쉽고, 스크린 리더 사용자도 콘텐츠를 더 잘 이해할 수 있다.

h1태그를 예로 들 수 있다.  h1는 페이지의 가장 중요한 제목을 나타내는 의미를 가진다.

블로그 게시물의 제목을 h1 요소로 표시하여 , 검색 엔진이 페이지 내에서 가장 중요한 내용을 파악하는 데 도움을 준다.

또한 본문은 article 요소로 감싸서 표시할 수 있다. 이렇게 하면 검색 엔진이 해당 영역을 본문으로 인식하며, 스크린 리더 사용자도 페이지의 본문을 쉽게 구분할 수 있다.

이와 같은 방식으로 의미 있는 HTML 요소를 사용하면 검색 엔진 최적화와 웹 접근성을 개선하며, 브라우저나 디바이스의 종류에 관계없이 일관된 사용자 경험을 제공할 수 있다.

 

검색엔진최적화(SEO)와 웹 접근성에 대해 더 자세히 알고싶어요.

 

검색 엔진 최적화 측면에서는, 시맨틱 태그를 사용하면 검색 엔진이 웹 페이지의 내용을 이해하기 쉽다.

예를 들어, header, nav, main, article, aside, footer 등의 시맨틱 태그를 사용하면 해당 영역이 어떤 내용을 담고 있는지 검색 엔진이 쉽게 파악할 수 있다.

이렇게 검색 엔진이 웹 페이지의 내용을 정확하게 파악하면, 검색 결과에서 해당 웹 페이지가 더 잘 노출될 가능성이 높다.

 

또한, 웹 접근성 측면에서는 시맨틱 태그를 사용하면 스크린 리더기와 같은 보조 기술을 사용하는 사용자들이 웹 페이지의 내용을 이해하기 쉬워진다. 시맨틱 태그를 사용하면 해당 영역이 어떤 내용을 담고 있는지 명확하게 구분할 수 있으므로, 사용자들이 원하는 정보를 빠르고 쉽게 찾을 수 있다.

또한, 시맨틱 태그를 사용하면 웹 페이지의 구조를 논리적으로 나타내므로, 보조 기술을 사용하는 사용자들이 웹 페이지를 보다 쉽게 이해할 수 있다.

+ Recent posts