728x90

평소에 Redux와 같은 전역상태관리 라이브러리를 사용하면서 해당 라이브러리가 어떻게 전역상태관리를 하는지에 대해서 깊게 생각해 본적이 없습니다. 이번 wanted 프리온보딩에서 관련 로직을 설명듣고, 이를 정리하기 위해서 
redux의 createStore의 코드를 다시 한번 분석해 보았습니다.

 

https://github.com/deminoth/redux/blob/master/src/createStore.ts

 

redux/src/createStore.ts at master · deminoth/redux

자바스크립트 앱을 위한 예측 가능한 상태 컨테이너. Contribute to deminoth/redux development by creating an account on GitHub.

github.com

 리덕스는 클로저를 통해서 전역 변수처럼 상태관리를 합니다.

 

자바스크립트를 공부하면 자주 접하게 되는 내용입니다.

1. 클로저란 함수와 해당 함수가 선언된 렉시컬 환경의 조합이다. 

2. 클로저는 정보 은닉을 위해서 사용된다.

 

redux에서 상태관리를 위한 store를 만들 때 createStore를 사용합니다.  관련 코드는 상위 url의 ( 42~384번째 줄까지)

 

우선 createStore의 매개변수와 타입들이 나오고

export function createStore<
  S,
  A extends Action,
  Ext extends {} = {},
  StateExt extends {} = {}
>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext {

 


135줄에  let으로 선언된 변수들이 나옵니다.

 let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: Map<number, ListenerCallback> | null = new Map()
  let nextListeners = currentListeners
  let listenerIdCounter = 0
  let isDispatching = false


이때 createStore는 함수이고, 내부에 let으로 된 변수가 있다는 사실을 기억하면 됩니다.

 

createStore내부에서는 메서드를 통해서 해당 변수들을 이용 및 수정합니다.

 function dispatch(action: A) {
    if (!isPlainObject(action)) {
      throw new Error(
        `Actions must be plain objects. Instead, the actual type was: '${kindOf(
          action
        )}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    listeners.forEach(listener => {
      listener()
    })
    return action
  }

 

 

이때 클로저에 의해서 비록 createStore의 실행이 끝났지만, 우리는 렉시컬 환경에 남아있는 변수들에 접근할 수 있습니다.

 

 

일반적인 전역변수를 사용할 경우 해당 변수가 오염될 수 있지만, 우리는  은닉화된 createStore의 렉시컬 환경속의 변수들을 관련 메서드를 통해 사용하기 때문에 좀 더 안전하게 사용할 수 있습니다.

728x90

원티드의 프리온보딩에서 관심사의 분리에  대해서 배우게 되었습니다.

항상 함수를 분리할 때  '어떻게, 왜, 얼마나' 분리해야할 지에 대해서 고민이고, 분리하고 나서도 썩 마음에 들지 않을 때가 많았었는데, 이때 사용할 수 있는 방법으로 값, 계산, 액션에 따라서 관심사를 나누어서 분리하는 것입니다.

<쏙쏙 들어오는 함수형 코딩> 에서 정의한 바에 의하면 데이터, 계산 , 액션을 아래와 같이 정의합니다.

1) 데이터 : 이벤트에 대한 사실. 문자열, 객체 등 단순한 값 그 자체
2) 계산 : 입력으로 얻은 출력. 순수 함수, 수학 함수 라고 부르기도 함.
3) 액션 : 외부 세계와 소통하므로 실행 시점과 횟수에 의존. 부수 효과를 일으킴

 

그때 보여주셨던 예시도 정규식을 활용하여 email검사하는 것이었는데, 마침 제가 짜려는 코드주엥 정규식을 활용한 방식이 있어서 해당 함수에 적용해보려고 합니다.

  const handleInputChange = (value: string) => {
    const regex = /^\d*(\.\d{0,2})?$/;
    if (regex.test(value)) {
      if (Number(value) < 0) {
        return setPercent('0.00');
      }
      if (Number(value) > 100) {
        return setPercent('100.00');
      }
      setPercent(value);
    }
  };

 위 코드는 현재 입력 값을 받았을 때, 0~100이하의 소숫점 2째짜리까지 가능한 값만 입력 가능하도록 인풋값을 조정해주는 함수입니다.

 

이때 단순히 함수 이름도 모호하게 인풋값을 바꿔주는 놈이라는 의미로 러프하게 작성했습니다.

위 코드가 불편한 이유는 현재 계산과 액션이 섞여 있습니다. 

 

useState의 setter의 경우에는  상태 값을 바꾸고 리렌더링을 하는, 즉 외부 세계와 소통하는 함수입니다.(액션)

반면에 단순히 정규식 조건에 부합하는 지 테스트하는 함수는 입력값만 동일하다면 출력 값이 동일한 순수 함수입니다. (계산)

따라서 해당 정규식을 사용하는 부분을 따로 함수로 분리하고자 합니다.

export function isValidPattern(value: string, reg: RegExp) {
  return reg.test(value);
}

 

해당 함수가 다른 곳에서도 사용될 수 있도록, 정규식도 매개변수로 받도록 하였습니다.

const FLOAT_POINT_TWO = /^\d*(\.\d{0,2})?$/;


 const setValidInput = (value: string) => {
    if (!isValidPattern(value, FLOAT_POINT_TWO)) return;

    if (Number(value) < 0) {
      return setPercent('0');
    }
    if (Number(value) > 100) {
      return setPercent('100.00');
    }
    setPercent(value);
  };

 

이제 소수점 2째 자리까의 실수가 아니면 return되고, 0 이하면 0 , 0이상이면 100이 입력되도록 변경하였습니다.

함수명은 가능한 인풋을 입력하도록 하는 녀석이니깐, setValidInput으로 명명했습니다..

또한 상수값의 경우도 소숫점 둘째자리의 실수라는 뜻으로 FLOAT_POINT_TWO라고 명명했습니다.


728x90

Next.js에서 간단한 프로젝트를 하면서 Tailwindcss를 도입하게 되었는데, 이때 styled-components에서 처럼 props를 활용해 동적 스타일링을 하려 했습니다.

하지만 분명 class의 이름은 제대로 들어가 있는데 동적 스타일링이 되지 않아서 분석하게 되었고, 이를 포스팅하게 되었습니다.

문제 상황

 

xs의 적용된 스타

function CardDescription({
  text,
  size = 'sm',
}: {
  text: string;
  size?: 'xs' | 'sm' | 'lg';
}) {
  return (
    <p
      className={`w-full m-0  ${'text-' + size} ${
        size === 'lg' ? 'opacity-' + 85 : 'opacity-' + 50
      }`}
    >
      {text}
    </p>
  );
}

 

<CardLinkWrapper link="https://map.naver.com/p/entry/place/1426094200?c=15.00,0,0,0,dh">
        <CardTitle title="오시는 길" />
        <address className="m-0 text-sm opacity-50">
          울산 남구 문수로335번길 6 <br /> 길상 빌딩 5층
        </address>
        <CardDescription text="클릭 시 네이버지도로 이동" size="xs" />
      </CardLinkWrapper>

 

현재 작성된 코드들은 위와 같습니다.

카드안에 들어갈 상세 설명들은  중요도에 따라서 크기와 투명도를 다르게 주려고 합니다.

이때 'xs', 'sm', 'lg' 세가지 타입이 있고 기본값은 sm로 등록하였습니다.

 

현재 상황은 addres 태그에는 sm의 크기로,  세부 설명은 xs크기로 주고 있는데, text-xs가 제대로 적용되고 있지 않습니다.

이를 해결하기 위해서 공식문서를 참고하였고, 몇 가지 유의사항을 확인하게 되었습니다.

 

 

https://tailwindcss.com/docs/content-configuration#dynamic-class-names

 

Content Configuration - Tailwind CSS

Configuring the content sources for your project.

tailwindcss.com

 

테일윈드 공식문서에 따르면 테일윈드가 어떻게 css 적용에 사용될 class들을 추출해 내는지를 아래와 같이 말합니다.

The way Tailwind scans your source code for classes is intentionally very simple — we don’t actually parse or execute any of your code in the language it’s written in, we just use regular expressions to extract every string that could possibly be a class name.

 

모든 코드의 언어들이 아니라, 단지 정규 표현식을 사용하여 클래스 이름으로 가능성이 있는 모든 문자열을 추출합니다.

그렇기 때문에 동적 스타일링을 할 때 유의할 점이 생깁니다.

 

1. 완벽한 잘리지 않은 단어들

The most important implication of how Tailwind extracts class names is that it will only find classes that exist as complete unbroken strings in your source files.
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS:

 

//잘못된 예시
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>

// 제대로 된 예시
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
 

2. props로 스타일을 넘기지 않기

단순하게 props가 클라스 그 자체가 아니라, 완성된 클라스를 이미 등록해놓고, props를 통해서 선택만 하도록 작성해야 합니다.

 

// 잘못된 예시
function Button({ color, children }) {
  return (
    <button className={`bg-${color}-600 hover:bg-${color}-500 ...`}>
      {children}
    </button>
  )
}
 

아래와 같이 변수에 등록을 해야지 tailwind 가 해당 class를 추출할 수 있습니다.

// 맞는 예시
function Button({ color, children }) {
  const colorVariants = {
    blue: 'bg-blue-600 hover:bg-blue-500',
    red: 'bg-red-600 hover:bg-red-500',
  }

  return (
    <button className={`${colorVariants[color]} ...`}>
      {children}
    </button>
  )
}

 

 

 

이제 위의 원칙들을 유의해서 제 코드를 수정해보겠습니다.

 

function CardDescription({
  text,
  size = 'sm',
}: {
  text: string;
  size?: 'xs' | 'sm' | 'lg';
}) {
  const sizeVariants = {
    xs: 'text-xs opacity-50',
    sm: 'text-sm opacity-50',
    lg: 'text-lg opacity-85',
  };

  return <p className={`w-full m-0 ${sizeVariants[size]}`}>{text}</p>;
}

 

 

제대로 반영된 것을 확인할 수 있습니다.

728x90

프로젝트 진행 배경

 

아버지의 학원 이름이 1.6수학과학학원인데, 상표등록시에 숫자를 사용할 수 없어서 일점육수학과학학원으로 상표등록이 되어있습니다.

이를 해결하기 위해서 간단한 웹피이지를 만들어서 검색엔진에 노출시키는 프로젝트를 진행하게 되었습니다. (네이버 지도에서도 1.6수학과학이라고 치면 나오지 않습니다.)

 

프로젝트 목표

1.6수학과학학원, 일점육수학과학원으로 검색했을 때, 검색엔진이 찾을 수 있도록 하는것이 목표입니다. 이때 metadata중에서 keyword는 최근에 악용되는 사례가 많아서 지원하지 않는 엔진이 많다고 합니다.(ex구글) 따라서 시멘틱 태그 및 다른 메타태그를 적극적으로 사용하여 검색엔진에 노출되도록 하려고 합니다.

 

메타 데이터 작성

이 중에서 간단하게 metadata를 우선적으로 적용하였습니다.

 

export const metadata: Metadata = {
  title: '1.6 수학과학전문학원',
  description:
    '울산 남구 옥동 1.6 수학과학전문학원입니다. 공식상호는 일점육수학과학전문학원이라 네이버지도에서는 해당 이름으로 검색해야 합니다.',
  openGraph: {
    title: '1.6 수학과학전문학원',
    description:
      '울산 남구 옥동 1.6수학과학전문학원(일점육수학과학전문학원)입니다.',
    images:
      'https://github.com/suhong99/1.6math/assets/120103909/205269b2-3969-4afd-8477-686a46aa76c8',
    locale: 'ko_KR',
    url: 'https://1-6math.vercel.app/',
    type: 'website',
    siteName: '1.6 수학과학전문학원',
  },
};

 

간단하게 학원 주소와 연락처만 노출시키는 사이트이기에 최적화할 부분이 적었고 lighthouse가 만점을 줘버렸습니다.

(동적인 요소가 적기 때문에,,)

 

 

하지만 문제점이 있습니다.

 

 구글과 네이버에서 검색해도 해당 사이트가 검색되지 않고 있습니다

 

 

검색엔진에 노출시키기

Robot.txt 작성

1번은 github 에 있는 asset이미지를 사용해서 그렇기에 해당 이미지를 cloudinary에 배포를 하면 해결할 수 있습니다.

2번에 대해서 알아보기 위해서, 아래 블로그 글을 참고하게 되었습니다.

 

https://velog.io/@rageboom/SEO-%EC%A0%81%EC%9A%A9

 

[SEO] 적용

앞에서 SEO는 어떻게 적용되고 엔지니어링에는 어떤 요소가 있는지 확인 했으니 간단한 테스트베드를 구축하고 실제 잘 동작하는지 확인해 보려 합니다.적용 순서를 정하고 한 단계 식 진행하고

velog.io

해당 링크를 통해서

https://search.google.com/search-console/about

 

Google Search Console

Search Console 도구와 보고서를 사용하면 사이트의 검색 트래픽 및 실적을 측정하고, 문제를 해결하며, Google 검색결과에서 사이트가 돋보이게 할 수 있습니다.

search.google.com

https://searchadvisor.naver.com/

 

네이버 서치어드바이저

네이버 서치어드바이저와 함께 당신의 웹사이트를 성장시켜보세요

searchadvisor.naver.com

 

에서 각각 검색엔진 진단을 할 수 있다는 점을 알게 되었습니다.

제 경우에는 robots.txt 파일이 존재하지 않았습니다. 이를 아래와 같이 작성하였고, 다시 진단해보니

 

import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
    },
  };
}

 

 

하지만 여전히 검색엔진에 노출되지 않았습니다

 

위 사이트를 보면 알 수 있듯이. robos.txt가 있으면 SEO 최적화에 도움되지만 검색엔진 노출에 필수적인 요소는 아님은 알 수 있습니다.

 

sitemap.xml 작성

그리고 페이지가 여러 장인 경우에는 sitemap을 작성하면, 검색엔진이 각 사이트의 우선순위를 파악하는데 도움이 된다고 합니다. 비록 한 페이지지만 sitemap도 같이 작성했습니다.

https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap

 

Metadata Files: sitemap.xml | Next.js

API Reference for the sitemap.xml file.

nextjs.org

공식문서를 참고하였습니다

import { MetadataRoute } from 'next';

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://1-6math.vercel.app/',
      lastModified: new Date(),
      priority: 1,
    },
  ];
}

 

사이트 소유 확인

각각의 검색사이트에서는 site:사이트URL 을 통해서 노출여부를 확인할 수 있습니다.

이때 구글 콘솔에서 확인해 보니 검색엔진에 등록하는 작업이 필요했습니다.

 

 

네이버의 경우에는 웹마스터 도구를 이용해서 해당 url을 등록하면 관련 Html파일을 제공해주고, 구글은 도메인 및 URL 접두어를 이용하여 소유확인이 가능합니다.

이때 vercel 무료 배포의 경우네는 custom domain이 아니기에 URL접두어 방식을 이용해야 합니다.

관련 html 파일은 public 경로에 등록하게 되면 수집 요청을 할 수 있습니다

구글

 

네이버

 

각각의 검색엔진 모두 수집요청을 해야지 노출이 되는데,, 며칠 걸린다는 내용들에 기다리다가(기다릴겸 자격증 공부)하다가 오래 걸렸습니다.

 

설명글이 좀 .. 어색해서 수정해서 다시 요청중입니다... 

하지만 학부모님이 해당 사이트를 봤을 때, 검색 순위가 높지도 않고 , 내용이 부실해서 진짜 사이트가 맞는지 오해할 수 있다고 생각했습니다.

이를 해결하기 위해서 schema.org를 사용해보려고 합니다.

관련 결과는 노출 완료시 다시 포스팅하겠습니다.  schema를 잘 적용하면 아래와 같이 사이트를 좀 더 그럴듯하게 보이게 할 수 잇습니다.

 

 

728x90

 

<input
  type="text"
  value={percent}
  onChange={(e) => handleInputChange(e.target.value)}
  placeholder="0.00~100.00"
  min="0.00"
  max="100.00"
/>

아래와 같이 0.00에서 100.00까지 값이 입력가능한 인풋이 있습니다.

 

이때 컴퍼넌트에서 우리는 해당 컴퍼넌트가 control 되야하는지 유무에 따라서 

값을 state로 다뤄야할 지 ref로 다뤄야 할 지 결정해야 합니다.

저 같은 경우에는 값이 음수는 입력이 되지 않고, 100을 초가화지 않는 2자리 소수의 값을 입력 받기 위해 100이상의 값은 100, 0이하의 값은 0으로 바꾸려고 합니다.

즉 input값이 controlled입니다.

 

따라서 state를 이용해서 초기값을 관리하고 있는데, 이때 초기값은 undefined로 지정했습니다.

  const [percent, setPercent] = useState<string>();

 

하지만 React에서 input의 초기값을 undefined로 설정할 경우에는 uncontrolled라고 해당 input을 여깁니다.

이를 제가 값이 입력될 떄는 controlled 요소처럼 다루어서 위와 같은 경고문이 발생했습니다.

 

입력전에는 placeholder 값이 보이도록 하기 위해서 초기값을 undefined로 지정했는데 placeholder는 빈문자열이 와도 보이게 됩니다.

따라서 초기값을 "" 로 설정하면 해결 할 수 있습니다.

 

  const [percent, setPercent] = useState<string>('');

 

 

 

이때 string 타입이라고 제네릭으로 지정했는데 왜 초기값이 undefined가 가능한 가에 대해서는

useState의 경우에는 2가지 타입을 받을 수 있고, undefined의 경우 따로 입력하지 않아도 두 번째 타입에 의해 할당이 가능합니다.

   /**
     * Returns a stateful value, and a function to update it.
     *
     * @version 16.8.0
     * @see {@link https://react.dev/reference/react/useState}
     */
    function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
    // convenience overload when first argument is omitted
    /**
     * Returns a stateful value, and a function to update it.
     *
     * @version 16.8.0
     * @see {@link https://react.dev/reference/react/useState}
     */
    function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
728x90

NextAuth가  다양한 프레임워크를 지원하기 위해서 확장되면서 Auth.js로 바꼈습니다.

그러면서 사용법도 살짝 바꼈고 공식문서를 보고 학습한 부분을 포스팅합니다.

 

설치방법

1. Auth.js설치

npm install next-auth@beta


beta를 생략해도 됩니다.

 

이후 .env파일을 만든후에  암호화에 사용할 AUTH_SECRET을 만듭니다.

npx auth secret


을 실행하고 env파일에 넣어주면 됩니다.

 

2. Oauth 등록

이후 google-Oauth를 사용해야하니 계정을 만들고 등록합니다.

Oauth에 대한 자세한 설명한 아래 링크 참조하시면 좋습니다.

https://goldenrabbit.co.kr/2023/08/07/oauth%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EA%B5%AC%EA%B8%80-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9D%B8%EC%A6%9D%ED%95%98%EA%B8%B0-1%ED%8E%B8/

 

OAuth를 사용한 구글 로그인 인증하기 1편 - OAuth 소개와 준비하기 - 골든래빗

'[Node.js] 자바스크립트 비동기 개념에 익숙해지기'는 총 3편에 걸쳐서 콜백 함수, 프로미스, async await 구문을 소개할 예정입니다. 1편에서는 자바스크립트 비동기 개념을 이해하고, 콜백 함수 예

goldenrabbit.co.kr

계정 생성과정까지는 동일한데, 리디랙션 URI만 아래와 같이 수정합니다

[origin]/api/auth/callback/google

http://localhost:3000/api/auth/callback/google


생성된 환경변수는 아래와 같은 환경변수로 해야하지 추가 작업이 줍니다.(아니면 일일이 사용할 떄마다 등록)
AUTH_GOOGLE_ID={CLIENT_ID}
AUTH_GOOGLE_SECRET={CLIENT_SECRET}

 
이후 루트경로에 provider를 등록합니다.

import NextAuth, { NextAuthConfig } from 'next-auth';
import Google from 'next-auth/providers/google';

export const config = {
  theme: { logo: 'https://authjs.dev/img/logo-sm.png' },
  providers: [Google],
} satisfies NextAuthConfig;

export const { handlers, signIn, signOut, auth } = NextAuth(config);

마지막으로 로그인 버튼에 아래와 같이 서버액션으로 사용하면 끝납니다.

import { signIn } from "@/auth.ts"
 
export function SignIn() {
  return (
    <form
      action={async () => {
        "use server"
        await signIn("google")
      }}
    >
      <button type="submit">Signin with Google</button>
    </form>
  )
}


이떄 서버액션을 사용하는 이유는 서버에서 작업을 하는 함수기에 보안적인 측면에서 장점이 있기 때문입니다.
https://nextjs.org/learn/dashboard-app/mutating-data

 

Learn Next.js: Mutating Data | Next.js

Mutate data using React Server Actions, and revalidate the Next.js cache.

nextjs.org


https://next-auth-example.vercel.app/

 

NextAuth.js Example

 

next-auth-example.vercel.app

을 통해서 다양한 Oauth및 로그인 방법에 대한 예시를 확인할 수 있습니다.

 

트러블 슈팅
사용할 수 없는 메서드라고 에러가 뜸

이떄 우리가 만든 auth.ts 말고도 next/auth에서 자체적으로 사용가능한 signOut함수가 있는데, 해당 함수가 import되면서 에러가 발생함. 이를 수정

참고문헌

https://authjs.dev/getting-started/installation

 

Auth.js | Installation

Authentication for the Web

authjs.dev

https://authjs.dev/getting-started/authentication/oauth

 

OAuth Providers

Authentication for the Web

authjs.dev

 

728x90

멘토링 내용..

 

패스트캠퍼스에서 주최한 3월 캠프콘 리뷰작성 이벤트에 당첨이 되어서 1부 강의를 진행하셨던 하조은 멘토님과 멘토링 할 수 있는 기회를 얻게 되었습니다.

 

크게 3가지의 질문을 준비하여서 질문했었습니다.

 

1. 캠프콘에서 말씀하셨던 내용은 '함께하고 싶은 개발자란 어떤 사람인가?' 였는데, 이력서에선 어떻게 하면 이를 어필할 수 있을까?로 이어지는 과정이 쉽지 않았습니다.  또한 세세하게 언급할 경우 이력서 양이 너무 많아지는 것 같습니다.

 

제가 들은 답변은 '프로젝트를 진행하면서 결국 어떤 고민을 하고 어떤 목표를 가지고 성취했는가에 대해서 보여주면 좋겠다' 입니다. 이때 핵심 경험에 대해서만 자세하게 언급해라고 말씀해주셨습니다.

제가 마지막으로 진행한 팀프로젝트에서 저는 처음 타입스크립트와 React-query를 도입했는데, 이러한 내용은 사실 요즘 기업들에게 당연한 부분인 거 같고 차라리 해당 부분에 대해서 자세하게 설명하기 보단 강조할만한 경험을 좀 더 추려서 자세히 얘기하는게 좋을 거 같다고 답변해주셨습니다. 

즉, 차별성이 없는 내용은 간략하게 서술하고 강조하고 싶은 부분에 대해서만 언급해야 강점이 더 매력적으로 보여줄 수 있다고 하였습니다.

 

2. HTML,CSS,JS에 대해서 깊게 공부하고 싶은데, 어떻게 깊게 공부해야 할지에 대해서 막연합니다.

 

각각에 대해서는

HTML : 시멘틱 태그에 대해서 깊게 생각해보고 이를 반영하면 좋은 학습이 될 것이다.

CSS : 라이브러리에 너무 의존하지말고 UI를 구현해보면 좋을 거 같다.

JS : 딥다이브 교재를 보고 공부하는 것도 충분하다곤 생각하지만, 더 깊게 공부하고 싶으면 라이브러리를 한 번 분석해봐라

와 같은 답변을 해주셨습니다.

 

3. 프로젝트가 끝나고 강의를 듣고 클론 코딩을 많이 진행했는데, 언급하면 좋을까요??

 

클론 코딩을 강조하는 것에 대해서는 부정적으로 말씀하셨습니다. 결국 클론 코딩이 온전한 경험으로 받아지지 않기 때문에 그렇게 말씀하셨습니다 .

 

추가적으로 디밸롭을 한 경우는 어떤가요에 대해서 물었는데(자바스크립트 프로젝트를 타입스크립트로 마이그레이션함),  작성하는 거 자체에 대해서 추천하고 싶진 않다고 하셨는데, 주 이유는 '유저에 대한 고민이 깊지 않을 확률이 높다' 였습니다. 결국 서비스란 '유저에게 어떠한 가치를 주고 싶고, 그러기 위해서 어떤 목표를 잡고 극복했냐가 중요하고 이런 사람의 깊은 고민이 매력적이다'는 답변을 해주셨습니다.

 

 

마치며..

이번 멘토링을 하면서 느꼈던 점은 이력서에 대한 조언등도 굉장히 좋았지만, 멘토님의 답변하는 모습이 굉장히 인상 깊었습니다. 제 입장에서 어떻게 하면 좀 더 좋은 해답이 될지 경청해주시고 고민해주시는 모습에서, 평소에도 함께하고 싶은 개발자에 대한 고민을 하시고 이를 실천하시는 게 느껴져서 더욱 의미 있는 시간이 되었습니다.

 

혹시 관심생기면 아래 링크로 신청가능합니다.

https://fastcampus.co.kr/b2g_campcon

 

패스트캠퍼스 IT 커리어 성장 컨퍼런스 : 캠프콘(CAMPCON) | 패스트캠퍼스

성인 교육 서비스 기업, 패스트캠퍼스는 개인과 조직의 실질적인 '업(業)'의 성장을 돕고자 모든 종류의 교육 콘텐츠 서비스를 제공하는 대한민국 No. 1 교육 서비스 회사입니다.

fastcampus.co.kr

 

'후기' 카테고리의 다른 글

정보처리기사 실기 합격 후기  (1) 2024.09.14
패스트캠퍼스 3월 CampCON  (0) 2024.03.26
728x90

풀이 

문제는 직접 보고 오신 경우가 많을 것 같아서 제 풀이 먼저 올렸습니다.

const fs = require('fs');
const input = fs
  .readFileSync(__dirname + '/input.txt')
  .toString()
  .split('\n');

// 테스트 횟수
const T = +input[0];
let index = 1;

for (let t = 1; t <= T; t++) {
// 입력값 n은 건물 개수, k는 간선의 수
  const [n, k] = input[index].split(' ').map(Number);
  // 각각의 건물마다 건설 시간
  const timeArr = input[index + 1].split(' ').map(Number);
  // 입력값에서 간선의 인덱스로 이동
  index += 2;
  
  // 간선들을 그래프에 담기 위해서 (인덱스와 건물 번호가 일치하도록 n+1)
  const graph = Array(n + 1)
    .fill()
    .map(() => []);
   
  // 그래프에 간선 정보 담기
  for (let i = 0; i < k; i++) {
    const [start, end] = input[index + i].split(' ').map(Number);
    graph[end].push(start);
  }

  // 총 건설 시간의 초기값은 각각의 건물 시간으로 줌
  const minBuild = [0, ...timeArr];
  
  // 간선 지나치기
  index += k;
  // 완성 시켜야 하는 건물
  const target = input[index++];
  //총 건설 시간 배열을 활용하여 목표 건물의 총 건설 시간 구함
  console.log(countTotal(target));

// 재귀함수를 통해서 경로를 찾음, 이때 모든 건물의 건설 시간을 구하는게 아니라
// 목표 건물의 하위 건물들만 구해줌
  function countTotal(number) {
   //반복문을 통해서 목표 건물에 선행 건물이 있다면 해당 건물의 총 건설 시간 먼저 구함
    while (true) {
    // 선행 건물이 없다면 건설시간 반환
      if (graph[number].length === 0) {
        return minBuild[number];
      }
      // 선행 대상이 있다면, graph에서 해당 노드를 추출하고 총 건설시간을 구함
      const subTarget = graph[number].pop();
      // 총 건설 시간은 기존에 있는 값과 비교하여서 더 긴 경우에만 수정
      minBuild[number] = Math.max(
        minBuild[number],
        countTotal(subTarget) + timeArr[number - 1]
      );
    }
  }
}

 

문제 설명

문제 링크 및 출처 : https://www.acmicpc.net/problem/1005

 

풀이 배경

결국 목표 건물의 건설을 알기 위해선 선행 건물의 건설시간을 파악해야 한다.

이때 선행의 선행 건물이 존재할 수 있다.

따라서 재귀함수를 이용해서 문제를 해결하기로 결정

즉 , 다이나믹 프로그래밍과 dfs를 이용해서 문제를 풀었다.

따라서 그래프에 선행 건물 정보를 담아 놓고, 선행 건물이 있다면 선행 건물의 총 건설 시간 +  목표 건물의 건설 시간을 더해서 총 건설 시간을 구하는 방식으로 풀었다.

 

다른 풀이

보통 위상 정렬을 이용해서 해당 문제를 푸는 경우를 많이 봤다. 

선행 건물의 수가 적은 순으로 큐나 스택에 정보를 담아서 구하는 방법인데 아래 블로그에서 위상정렬을 이용한 풀이 방법 

을 설명해준다.

https://velog.io/@mk0504/%EB%B0%B1%EC%A4%80-1005%EB%B2%88-ACM-Craft-JavaScript

 

[백준] 1005번, ACM Craft (JavaScript)

문제 분류 : DP, 위상 정렬, 그래프 이론문제 출처 : 백준 골드 3, ACM Craft이 문제는 이전 벽 부수고 이동하기에서 벽을 부수는 횟수가 K(1 <= K <= 10)번까지로 추가된 버전이다.벽은 부수는 횟수를 카

velog.io

 

728x90

오랜만에, deep dive 교재를 다시 읽다가 단순히 규칙처럼 사용했던 불변성에 대해서 왜 그랬을까? 라는 고민을 하게 되었고 작성하게 되었습니다.

 

자바스크립트 객체의 특징

우선 객체에 대해서 먼저 알 필요가 있습니다.

JS에서 객체는 JAVA나 C++와 같은 클래스 기반 객체지향 언어와 다르게, 동적으로 프로퍼티를 삭제 및 추가할 수 있다. 

그렇기 때문에 객체의 경우에 정확히 얼마만큼의 메모리 공간을 확보해야 할지 정할 수 없습니다. 

또한 원시값들을 처리하는 것 처럼 객체가 수정될 때마다 새로운 메모리공간에 수정된 객체를 추가하는 작업은 메모리의 효율이 떨어지고 성능을 저하시킨다.

 

따라서 자바스크립트에서는 해당 객체의 직접적인  주소를 변수에 할당하는 것이 아니라, 해당 객체가 위치하는 메모리 주소를 할당한다. 이를 원시값에서 주소를 직접적으로 할당하는 것과 구별짓기 위해서 교재에서는 ' 공유 에 의한 전달'(참조에 의한 전달)이 라고 한다.

(공유에 의한 전달이 좀 더 정확한 표현이라 느끼나 참조에 의한 전달이 보편적으로 사용되는 느낌입니다.)

deep dive 교재 151pg 참고

즉, 동적인 객체를 효율적으로 관리하기 위해서 위와 같은 공유에 의한 전달 방법을 사용하지만, 그러다 보니 여러 객체가 동일한 하나의 객체를 공유하는 문제도 발생하게 된다. 하지만 객체를 하나하나 프토퍼티 값까지 확인하는 것이 아니라 주소 값을 확인하기 때문에 효율적으로 관리할 수 있게 된다.

 

리액트에서 불변성이 중요한 이유

리액트도 결국 자바스크립트 기반의 라이브러리입니다. 리액트에서는 업데이트 전 후의 두 가지의 가상 돔을 diffing 알고리즘으로 비교하여 변경된 부분만 리렌더링합니다. 따라서 변화를 감지하기 위해서 불변 객체를  사용하여 참조(공유)하고 있는 값이 다르다면 객체가 변했다고 인지하도록 합니다. 

이때 원시값들처럼 객체를 변경할 때 객체 자체를 변경하는 것이 아니라 참조(공유)되고 있는 주소 값이 다르게 하는 것을 불변성을 지킨다고 합니다.


React will ignore your update if the next state is equal to the previous state, as determined by an Object.is comparison. This usually happens when you change an object or an array in state directly:
// 출처 : https://react.dev/reference/react/useState

https://ko.legacy.reactjs.org/tutorial/tutorial.html#why-immutability-is-important

 

 

그리고 불변성을 지키기 위해선 spread문법을 활용하여 새로운 객체를 만드는 것을 공식문서에서는 추천합니다.

setObj({
  ...obj,
  x: 10
});

 

 

참고문헌 

자바스크립트 deepdive 교재

https://react.dev/reference/react/useState

 

useState – React

The library for web and native user interfaces

react.dev

https://legacy.reactjs.org/docs/reconciliation.html?

 

Reconciliation – React

A JavaScript library for building user interfaces

legacy.reactjs.org

 

728x90

학습 계기

웹에서 유저가 좀 더 재밌는 경험을 주기 위해서 3D요소가 첨가되면 좋겠다는 생각을 종종했다. 

이러한 생각은 유튜브에 interactive Developer님이 올려주신 포트폴리오들을 보면서 더욱 많이 하게 되었다.

https://www.youtube.com/@cmiscm/videos

 

Interactive Developer

코드로 만드는 애니메이션, 영감, 실리콘밸리의 생활과 해외취업에 대해 이야기 합니다. http://links.cmiscm.com/

www.youtube.com

3D를 React에서 쉽게 구현하도록 도와주는 R3F가 있는데, 해당 라이브러리는 Three.js를 기반으로 jsx에 사용 가능하게 되었다. 따라서 Three.js의 요소에 대해서 우선적으로 학습하고 정리를 할 필요가 있다.

 

Three.js란?

WebGl 기반으로 쉽게 3D요소를 구현할 수 있게 해주는 JS 라이브러리

 

WebGL: OpenGL ES 2.0 기반 API를 이용하여 브라우저의 HTML canvas에 렌더링하여 3D 웹 콘텐츠 제작을 가능하게 하는 Web API

 

THREE.JS의 기본구조 

물체를 3D로 바라보기 위해서는 바라보는 시점과, 바라보는 대상이 존재한다.

https://designbase.co.kr/threejs-03/에서 이를 알기 쉬운 그림으로 표현해주셨다.

출처 : https://designbase.co.kr/threejs-03/

 

이를 Three.js에 요소들로 나누면 아래와 같다.

Renderer : Three.js로 구현된 요소가 렌더링된 결과로 Scene과 Camera를 기반으로 만들어진 객체

Camera : Scene을 어디서 어떻게 바라볼 지 결정하는 요소

Scene : 물체들이 존재하는 곳. M

Light : 장소에 빛이 어떻게 비추고 있는지(밝기나 그림자에 영향을 줌)

Mesh : 물체로 Geometry와 Material로 구성

Geometry : 물체의 크기, 위치등 기하학적 요소들

Material :  광택이나 투명도등 물체의 성질

 

 

THREE.JS의 사용방법

npm i three

 

three.js를 설치하고,  자바스크립트 파일에서 관련 코드를 작성한다. 그리고 해당 모듈을 불러와서 사용한다.

<body>
    <div id="app"></div>
    <script type="module" src="/main.js"></script>
  </body>

 

 

예시)

import * as THREE from 'three';

const width = window.innerWidth,
  height = window.innerHeight;

// init

const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10);
camera.position.z = 1;

const scene = new THREE.Scene();

const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
const material = new THREE.MeshNormalMaterial();

const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
renderer.setAnimationLoop(animation);
document.body.appendChild(renderer.domElement);

// animation

function animation(time) {
  mesh.rotation.x = time / 2000;
  mesh.rotation.y = time / 1000;

  renderer.render(scene, camera);
}

animation(2000);

 

https://www.npmjs.com/package/three

 

three

JavaScript 3D library. Latest version: 0.163.0, last published: 12 days ago. Start using three in your project by running `npm i three`. There are 3574 other projects in the npm registry using three.

www.npmjs.com

 

+ Recent posts