전체 글 (165)

07
12

대단한 기술을 하찮은 곳에 사용합니다. 하지만 가장 중요하죠.

1. 문제의 발단

  • 스타일 가이드를 고치면서 확인하니, z-index가 모든 alert, modal등에서 꼬여있다.
  • 가장 최상의 단위를 잡고, 위계를 생각하며 기획이 되어야 하지만 추가되는 사항이 많고, 리팩토링 하지 못 해서 꼬여있었다.
  • 그래서 임시로 추가할 alert에 z-index를 9999로 설정하고 만들었다.
  • 하지만 하위 노드에서 이미 modal을 만들고, 그것을 기준으로 다음 컴포넌트들을 만들어 z-index와 상관없이 최상위로 올라오지 않는 이슈가 있었다.
  • 그래서 아예 처음부터 가장 최상위에 올려서 렌더링 하기로 하였다.

2. Potal?

  • 지정한 DOM의 외부에 렌더링을 하게 만들어주는 함수
  • 첫 번째 인자에는 컴포넌트, 두 번째 인자로 DOM을 넣어준다.

3. 예시

import { Dispatch, SetStateAction, useEffect } from 'react';
import { createPortal } from 'react-dom';

export function Alert({
  children,
  setState,
}: {
  children: React.ReactNode;
  setState: Dispatch<SetStateAction<boolean>>;
}) {
  // useEffect로 timeout을 걸어 3초 뒤 자동으로 꺼지게 만든다.
  useEffect(() => {
    const timeout = setTimeout(() => {
      setState(false);
    }, 3000);

    // 다시 alert가 열리더라도, 처음 timeout을 초기화 하여 가장 나중에 열린 alert를 3초 유지시킨다.
    return () => clearTimeout(timeout);
  }, []);

  // 가장 최상위 DOM을 선택한다.
  const modalRoot = document.getElementById('modal-root');
  
  // createPortal로 return한다. 
  return createPortal(
    <Backdrop onClick={() => setState(false)}>
      <div onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </Backdrop>,
    modalRoot!
  ) as any;
}

4. 소감

  • 처음 스타일 가이드를 잡을때, z-index도 따로 설정에 잡아 놓는 것이 가장 좋겠다.
  • 예를 들어, 항상 최상위에 오는 Backdrop을 최상위, 그 아래로 header, footer 등으로 말이다.
  • 그리고 상수로 z-index를 관리하면 중간에 다른 단계를 넣거나 수정하더라도 편하게 사용할 수 있다.
COMMENT
 
06
29

그만 둬요 제발. 사실 제가 했습니다. 죄송합니다. 실수에요. 근데 정말 어 할만한 상황이었어요. 들어보세요.

1. 문제의 발단

  • axios response type을 'Blob'으로 받아 서버에서 엑셀 다운로드를 가능하게 하는 API를 연결하는 작업을 하고 있었다.
  • 받는 타입을 Blob으로 해야 브라우저의 다운로드 기능까지 연결되어 처리가 가능하다.
  • 기능은 원하는 방향으로 잘 작동하나 문제는 에러가 났을 경우이다.
  • response도 blob 형식으로 온다. 그래서 서버와 합의한 error code를 뽑아 상황에 맞는 에러 처리가 되지 않았다.
  • 액세스 토큰이 만료되었을 때, axios 인스턴스에 설정해 놓은 대로 reissueToken이 안 되는 현상을 발견하여 확인해 보니, 토큰 만료 시 나오는 에러 code를 잡지 못하고 있었다.

2. 처음 상황

export async function getData() {
  return await axiosInstance.get(`${baseURLV3}/data`, {
    responseType: 'blob',
  });
}
  • 위와 같이 api를 호출하는 코드를 만들어 놓은 상황
  • 에러가 날 경우 Blob 객체 형태로 error를 받음

3. 해결법

export async function getData() {
  return await axiosInstance.get(`${baseURLV3}/data`, {
    responseType: 'blob',
  });
}

...
catch(err) {
	const errorResponse = JSON.parse(await err.response.data.text());

	if(errorResponse.code === '토큰이 만료 되었을 때 나는 에러 코드') {
		return reissueToken()
	} else {
		return console.log('is not token issue')	
	}
}
  • JSON.parse(await error.response.data.text()); 으로 다시 처리하면 우리가 알던 방식 그대로 error의 형태를 바꿀 수 있다.
  • 대신, 비동기로 작동한다는 것! 
COMMENT
 
06
26

비즈니스 모델 vs 사용자 경험

  • 비즈니스 모델 : 어떠한 상품을 소비자에게 제공할때, 어떻게 이윤을 추구 할 것인가?
  • 사용자 경험 : 사용자가 생각하는 제품을 이용할때 축적하게 되는 경험

개발자가 생각하는 관점 vs 유저가 생각하는 관점

  • 유저에게 어디까지 친절할 것인가? → 사용하지도 않는 버튼으로 가득찬 TV 리모컨
  • ‘개발자 지식의 저주’ → 개발자와 똑같이 유저가 모든 환경과 기능을 이해하고 사용할 것이라는 착각. Qa 때도 자주 발생한다.
  • 통제감 → 단순하게 만들어야 유저는 제품에 대해 통제감을 느끼고 사용할 의지를 느끼게 된다.
    • 디자인도 중요하지만, 만들어지는 방식, 사용법등 제품에 대한 모든 것을 단순화 하는 것이 더욱 도움이 된다.
    • 조니 아이브 : 본질적이지 않은 부분을 제거하여 그 제품의 본질만을 남기는 것 = 단순화
  • 좋은 사용자 경험이란? → 비즈니스 모델을 생각한 (중점이 되는) 유저 사용감의 상승
    • 즉, 개발자도 비즈니스 모델을 이해하고, 비즈니스 모델을 극대화 하는 사용자 경험을 만들어야 한다. 이것이 단순화가 될 수 도 있고, 오히려 복잡해 질 수 도 있다.
    • 사용자 경험에 절대적인 명제는 없으며, 비즈니스 모델, 로컬의 문화 등의 모든 부분을 종합하여 고려해야 한다.

내가 사용자 경험을 말할 때

네이버 활용백서

네이버 PC 메인

  • 처음 애니메이션 및 스크롤 애니메이션으로 시선을 끔
  • 텍스트는 양을 줄이고 기능을 강조
  • 기능확인 탭 → 네비게이트 버튼 있음 (최상, 하단에서는 사라짐, 컨텐츠를 보고 있을 경우에만 존제)
  • 개인설정 탭 설명은 동영상으로 대체 → 너무 많은 정보를 담고 있고, 자신이 관련없는 개인설정일 경우에 확인 할 필요가 없는 컨텐츠이므로
  • 견해
    • 장점 : 화려함. 설명이 부족한 부분이 없고 필요한 정보만을 제공중. 어떤 연령의 이용자가 보아도 어느정도 납득이 가는 컨텐츠
    • 단점 : 유저가 보지 않을 경우 말짱 도루묵 → 그만큼 사용하기 어려운 검색 포털 → 검색 포털이 이렇게 어려울 필요가 있을까? → 비즈니스 모델과 관련이 있을 것으로 예상, 주된 비즈니스 모델이 ‘광고’인데 네이버 광고는 블로그, 카페 등으로 이루어진 컨텐츠형 광고가 많은 것으로 생각됨. 그래서 그런 광고의 노출도를 올리고 광고 컨텐츠의 생산을 위해 어쩔 수 없는 경우가 아니였을까 생각됨.
    • 그런거 치고도 꽤 깔끔하고 직관적이라고 생각됨. 아마 기업 이미지나 이때까지 사용하던 사용자 경험이 큰 영향을 주고 있다고 생각됨.
COMMENT
 
06
25

제 코드가 어디선가 유출되고 있나 봅니다. 똑같군요.

1. 문제의 발단

  • 디자이너님과 함께 정형화되지 않았던 스타일 가이드를 다시 고치고, 공용 스타일로 사용되지 않던 컴포넌트들을 다시 통일시키고 있다.
  • 스타일도 통일 시키고, 그에 따라 필요한 인자들을 넘겨주어 정형화되고 완벽한 어디에도 사용할 수 있는 컴포넌트를 만드는 과정에서 깨달은 것들을 적어본다.

2. Button Size

  • 가장 기본적으로 사이즈로 나눌수 있다.

width는 fit, height를 3가지 종류로 나눴다.

const Button = styled('button', {
  base: css`
    ${apply`border-1 rounded-[4px] px-4 bg-[#2ecc71] border-2 text-white bg-cyan-500 shadow-lg shadow-cyan-500/50`};
  `,
  variants: {
    size: {
      small: `h-[33px]`,
      medium: `h-[41px]`,
      large: `h-[49px]`,
    },
  },
  defaults: {
    size: 'large',
  },
});

export default function Projects() {
  return (
    <div className={tw`flex justify-center items-center gap-2 mt-[50vh]`}>
      <Button size='small'>Small</Button>
      <Button size='medium'>Medium</Button>
      <Button>Default Large</Button>
    </div>
  );
}
  • 가장 기본적인 스타일만을 넣은 버튼이다.
  • 대부분 2 ~ 3개 정도의 버튼 스타일을 쓴다.

3. outlined

import { styled } from '@twind/react';
import { apply, tw } from 'twind';
import { css } from 'twind/css';

const Button = styled('button', {
  base: css`
    ${apply`border-1 rounded-[4px] px-4 bg-[#2ecc71] border-2 text-white bg-cyan-500 shadow-lg shadow-cyan-500/50`};
  `,
  variants: {
    size: {
      small: `h-[33px]`,
      medium: `h-[41px]`,
      large: `h-[49px]`,
    },
    variant: {
      normal: ``,
      outlined: `border-[#2ecc71] text-[#2ecc71] bg-white`,
    },
  },
  defaults: {
    variant: 'normal',
    size: 'large',
  },
});

export default function Projects() {
  return (
    <div className={tw`flex justify-center items-center gap-2 mt-[50vh]`}>
      <Button>Normal</Button>
      <Button variant='outlined'>Outlined</Button>
    </div>
  );
}
  • 색상 반전의 느낌으로 스타일링을 바꾼다.
  • 기본값을 잘 넘겨줘야 한다.

4. hover

import { styled } from '@twind/react';
import { apply, tw } from 'twind';
import { css } from 'twind/css';

const Button = styled('button', {
  base: css`
    ${apply`border-1 rounded-[4px] px-4 bg-[#2ecc71] border-2 text-white bg-cyan-500 shadow-lg shadow-cyan-500/50`};
  `,
  variants: {
    size: {
      small: `h-[33px]`,
      medium: `h-[41px]`,
      large: `h-[49px]`,
    },
    variant: {
      normal: ``,
      outlined: `border-[#2ecc71] text-[#2ecc71] bg-white`,
      hover: `transition duration-150 ease-out hover:(ease-in bg-red-500)`,
    },
  },
  defaults: {
    variant: 'normal',
    size: 'large',
  },
});

export default function Projects() {
  return (
    <div className={tw`flex justify-center items-center gap-2 mt-[50vh]`}>
      <Button>Normal</Button>
      <Button variant='hover'>Hover</Button>
    </div>
  );
}
  • hover를 넣어주고 똑같이 만들어 준다.
  • outline에 hover도 가능하다.

import { styled } from '@twind/react';
import { apply, tw } from 'twind';
import { css } from 'twind/css';

const Button = styled('button', {
  base: css`
    ${apply`border-1 rounded-[4px] px-4 bg-[#2ecc71] border-2 text-white bg-cyan-500 shadow-lg shadow-cyan-500/50`};
  `,
  variants: {
    size: {
      small: `h-[33px]`,
      medium: `h-[41px]`,
      large: `h-[49px]`,
    },
    variant: {
      normal: ``,
      outlined: `border-[#2ecc71] text-[#2ecc71] bg-white`,
    },
    isHover: {
      normal: ``,
      true: `transition duration-150 ease-out hover:(ease-in bg-red-500)`,
    },
  },
  defaults: {
    variant: 'normal',
    size: 'large',
  },
});

export default function Projects() {
  return (
    <div className={tw`flex justify-center items-center gap-2 mt-[50vh]`}>
      <Button>Normal</Button>
      <Button variant='outlined' isHover>
        Hover
      </Button>
    </div>
  );
}
  • props 종류를 2개로 찢은 다음에 2개 다 넣어준다.

5. 소감

  • 생각해 보니 button에 스타일 제외, 다른 속성과 이벤트를 넣을 것이 없다..! (onClick이 전부 아닌가...?)
  • 이후에 모달, 팝업, warring 등에는 다양한 인자를 props로 넘겨 사용할 수 있을 것이다.
COMMENT