[Wanted]_Week2-1_과제 피드백

hanseungjune·2023년 7월 4일
0

Wanted

목록 보기
7/21
post-thumbnail

npm ci, npm install

  • npm install은 package.json을 기준으로 의존성을 설치
  • npm ci(clean install)은 package-lock.json을 기준으로 의존성을 설치

Semantic versioning

  • NPM은 시멘틱 버저닝이란 버전 표기법을 따름
    • major.minor.patch의 형태
    • major: breaking change를 포함한 변경이 포함된 업데이트
    • minor: breaking change가 없는 변경이 포함된 업데이트
    • patch: breaking change가 없는 버그 픽스

Breaking Change란?

breaking change는 기존 코드와의 호환성을 깨뜨리는 변경을 의미합니다. 이로 인해 기존 코드가 올바르게 작동하지 않거나 컴파일되지 않을 수 있습니다. breaking change가 있는 경우, 해당 변경사항을 사용하려면 기존 코드를 수정해야 할 수도 있습니다.

간단한 예시를 통해 이해해보겠습니다. 가정해 봅시다. 당신은 패키지의 버전이 1.0.0이며, 이 패키지에는 add라는 함수가 있습니다. 이 함수는 두 개의 숫자를 더해주는 역할을 합니다.

그런데 패키지 개발자가 새로운 버전인 2.0.0을 릴리스하고, add 함수를 변경했습니다. 이제 add 함수는 세 개의 숫자를 더해주는 기능을 가지고 있습니다. 이는 breaking change입니다.

기존 버전 1.0.0을 사용하던 코드에서는 add(2, 3)과 같이 두 개의 숫자만 넘겨주면 되었지만, 새로운 버전 2.0.0에서는 add(2, 3, 4)와 같이 세 개의 숫자를 모두 넘겨주어야 합니다. 그렇지 않으면 컴파일 에러가 발생하거나 예상치 못한 결과를 가져올 수 있습니다. 이로 인해 기존 코드가 올바르게 작동하지 않으므로, breaking change가 발생한 것입니다.

breaking change는 주로 다음과 같은 경우에 발생할 수 있습니다:

  • 기존 함수의 시그니처(매개변수의 개수, 타입 등)가 변경되는 경우
  • 기존 함수나 클래스의 동작 방식이 변경되는 경우
  • 기존 함수나 클래스가 완전히 제거되는 경우

breaking change가 있는 업데이트를 수행할 때는 주의해야 하며, 업데이트 전에 변경 내용을 충분히 이해하고, 기존 코드와의 호환성을 확인하는 것이 중요합니다.

시멘틱 버전 표기법 설명

가정해 봅시다. 당신은 NPM을 사용하여 프로젝트의 의존성 관리를 하고 있습니다. 현재 사용 중인 패키지의 버전은 1.2.3입니다. 이 패키지가 어떤 변경 사항을 포함하고 있는지 파악하고자 합니다.

  1. Major 버전 업데이트: major 버전은 주로 breaking change(하위 호환성이 없는 변경)이 포함된 경우에 올려집니다. 예를 들어, 만약 패키지의 버전이 2.0.0으로 업데이트 된다면, 이는 기존 버전과 호환되지 않는 큰 변경이 있었음을 의미합니다. 예를 들어, 패키지의 API가 완전히 변경되었거나 중요한 기능이 추가되었을 수 있습니다.

  2. Minor 버전 업데이트: minor 버전은 주로 새로운 기능이 추가되었지만, breaking change는 없는 경우에 올려집니다. 예를 들어, 만약 패키지의 버전이 1.3.0으로 업데이트 된다면, 이는 기존 버전과 호환되는 새로운 기능이 추가되었음을 의미합니다. 이는 기존 API에 영향을 주지 않고 새로운 기능을 사용할 수 있게 됩니다.

  3. Patch 버전 업데이트: patch 버전은 주로 버그 수정이나 기능에 영향을 주지 않는 작은 변경 사항이 있을 때 올려집니다. 예를 들어, 만약 패키지의 버전이 1.2.4로 업데이트 된다면, 이는 기존 버전과 호환되면서 버그가 수정되었음을 의미합니다. 버그 픽스나 작은 수정 사항은 patch 버전을 통해 배포됩니다.

간단한 예시로 설명하면, 버전 1.2.3의 패키지에서는 버그가 발견되어 이를 수정하고자 합니다. 이 경우, 패치된 버전은 1.2.4가 됩니다. 이는 breaking change가 없으며, 기존 버전과 호환되기 때문에 패치 버전으로 표기됩니다.

다음으로, 패키지에 새로운 기능을 추가하고자 합니다. 이러한 변경은 breaking change가 없기 때문에 기존 버전과 호환됩니다. 따라서, minor 버전을 올려주어야 합니다. 예를 들어, 기존 버전이 1.2.4라면, 새로운 기능이 추가된 버전은 1.3.0이 됩니다.

마지막으로, 기존 API를 완전히 변경하거나 breaking change가 있는 큰 변경을 하려고 합니다. 이 경우, major 버전을 올려주어야 합니다. 예를 들어, 기존 버전이 1.3.0이라면, breaking change가 포함된 변경 사항이 있으면 버전은 2.0.0으로 업데이트됩니다.

이런 식으로 시멘틱 버전 표기법을 사용하면, 개발자는 패키지의 변경 내용을 빠르게 파악할 수 있고, 업데이트가 기존 코드와 호환되는지 여부를 확인할 수 있습니다.

pacakge.json과 package-lock.json의 예시

  • package.json
"dependencies": {
  "react": "^18.2.0",
  "react-dom": "^18.2.0",
  "react-router-dom": "^6.14.0",
  "react-scripts": "5.0.1",
},
  • lock.json
"node_modules/react-dom": {
  "version": "18.2.0",
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
  "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
  "dependencies": {
    "loose-envify": "^1.1.0",
    "scheduler": "^0.23.0"
  },
  "peerDependencies": {
    "react": "^18.2.0"
  }
},

package.json과 package.lock.json 장단점

package.json과 package-lock.json은 둘 다 NPM 프로젝트에서 사용되는 파일입니다. 각각의 파일에는 프로젝트의 의존성과 관련된 정보가 포함되어 있습니다. 이 두 파일은 서로 다른 목적과 장점을 가지고 있습니다.

package.json:

  • package.json은 프로젝트의 설정 및 의존성 관리에 사용되는 메타데이터 파일입니다.
  • 주요 장점:
    • 프로젝트의 기본 설정 정보를 포함하고 있어, 프로젝트의 이름, 버전, 저자, 라이선스 등을 명시할 수 있습니다.
    • 의존성 패키지의 버전 범위를 정의할 수 있어, 프로젝트가 사용하는 패키지의 버전을 제한하거나 업데이트할 수 있습니다.
  • 주요 단점:
    • 의존성 트리에 대한 구체적인 정보가 포함되어 있지 않기 때문에, 동일한 의존성 트리를 얻기 위해서는 package-lock.json을 사용해야 합니다.
    • package.json만으로는 정확한 의존성 버전을 보장하지 않을 수 있으며, 여러 개발자 간에 의존성 버전 충돌이 발생할 수 있습니다.

package-lock.json:

  • package-lock.json은 의존성 트리를 포함한 정확한 의존성 버전 정보를 가지고 있는 파일입니다.
  • 주요 장점:
    • 의존성 트리의 구체적인 정보를 포함하고 있어, 동일한 의존성 트리를 얻을 수 있으며, 다른 개발 환경에서도 동일한 패키지 버전을 사용할 수 있습니다.
    • 의존성 패키지의 버전 충돌을 방지하고, 의존성 관리를 더욱 정확하게 할 수 있습니다.
  • 주요 단점:
    • package-lock.json 파일은 자동으로 생성되며, 수동으로 수정하거나 관리할 필요는 없지만, 때로는 충돌이나 오작동을 유발할 수 있는 문제가 발생할 수 있습니다.
    • package-lock.json 파일의 크기가 크고 복잡할 수 있어, 저장소의 용량을 차지하거나 파일 비교 및 병합을 어렵게 할 수 있습니다.

요약하면, package.json은 프로젝트의 설정 정보와 의존성 범위를 정의하는 데 사용되며, package-lock.json은 의존성 트리의 구체적인 정보와 정확한 의존성 버전을 포함하는 데 사용됩니다. 두 파일은 함께 사용되어 프로젝트의 의존성을 관리하고, 일관된 환경을 유지할 수 있도록 도와줍니다.

npm i VS npm ci

npm i와 npm ci는 둘 다 NPM을 사용하여 프로젝트의 의존성을 설치하는 명령어입니다. 그러나 각각의 목적과 사용 시기가 다릅니다. 아래에서 각 명령어의 사용 시기를 설명하겠습니다:

npm i:

  • npm i는 주로 개발 중인 프로젝트에서 의존성 패키지를 설치하거나 업데이트할 때 사용됩니다.
  • package.json 파일에 명시된 의존성 정보를 바탕으로 의존성을 설치하며, 설치된 의존성은 package-lock.json 파일에 기록됩니다.
  • npm i 명령어는 의존성을 설치하고, 필요에 따라 package.json에 정의된 의존성 범위에 따라 업데이트를 수행합니다.
  • 개발 환경에서 의존성 패키지를 설치하거나 업데이트할 때 주로 사용되며, 개발자들이 새로운 패키지를 추가하거나 의존성을 업데이트할 때 자주 사용됩니다.

npm ci:

  • npm ci는 주로 배포나 CI/CD(Continuous Integration/Continuous Deployment) 환경에서 사용됩니다.
  • npm ci 명령어는 package-lock.json 파일을 기반으로 정확한 의존성 트리를 구성하고, 의존성 패키지를 설치합니다.
  • package-lock.json 파일에 명시된 의존성 버전을 정확히 설치하므로, 의존성 충돌을 방지하고 일관된 환경을 유지할 수 있습니다.
  • 배포 환경이나 CI/CD 파이프라인에서 자동화된 빌드나 배포 과정에서 사용되며, 정확한 의존성 버전을 필요로 하는 상황에서 유용합니다.

요약하면, npm i는 개발 중인 프로젝트에서 의존성을 설치하고 업데이트하는 데 사용되며, package.json에 정의된 의존성 범위를 고려합니다. 반면에, npm ci는 배포나 CI/CD 환경에서 정확한 의존성 트리를 구성하고, package-lock.json에 명시된 의존성 버전을 정확히 설치하는 데 사용됩니다.

packge.json의 version 표기법

package.json의 version 표기법인 Tilde(~)과 Caret(^)을 이해하는 것은 중요합니다. 아래에서 각각의 표기법에 대해 설명하겠습니다.

Tilde (~):

  • Tilde(~)는 마지막 자리수 version까지만 업데이트를 허용하는 버전 표기법입니다.
  • 버전을 X.Y.Z라고 가정해보겠습니다.
  • ~X.Y.Z 형식으로 사용할 때, 버전은 >=X.Y.Z<X.(Y+1).0 사이의 범위에 해당합니다.
  • 예를 들어, ~0.1>=0.1.0 버전부터 <0.2.0 버전까지를 허용합니다.
  • 또 다른 예로, ~0.1.1>=0.1.1 버전부터 <0.2.0 버전까지를 허용합니다.
  • 마지막으로, ~0>=0.0.0 버전부터 <1.0.0 버전까지를 허용합니다.

Caret (^):

  • Caret(^)은 Semantic Versioning(SemVer)을 따른다는 가정 하에 작동하는 버전 표기법입니다.
  • 기본적으로 minor version까지만 업데이트를 허용합니다.
  • 버전을 X.Y.Z라고 가정해보겠습니다.
  • ^X.Y.Z 형식으로 사용할 때, 버전은 >=X.Y.Z<(X+1).0.0 사이의 범위에 해당합니다.
  • 예를 들어, ^1.1.2>=1.1.2 버전부터 <2.0.0 버전까지를 허용합니다.
  • 또 다른 예로, ^1.0>=1.0.0 버전부터 <2.0.0 버전까지를 허용합니다.
  • 마지막으로, ^1>=1.0.0 버전부터 <2.0.0 버전까지를 허용합니다.
  • minor version이 1 이하인 경우에는 조금 다르게 동작합니다.
    • 예를 들어, ^0.1.2>=0.1.2 버전부터 <0.2.0 버전까지를 허용합니다.
    • 또 다른 예로, ^0.2>=0.2.0 버전부터 <0.3.0 버전까지를 허용합니다.
  • 0.1 이하의 버전인 경우에는 정확한 버전만 허용합니다.
    • 예를 들어, ^0.0.20.0.2 버전만 허용합니다.

이러한 표기법을 사용하면 개발자는 의존성 패키지의 업데이트를 통제할 수 있습니다.

dependency 버전을 고정해두지 않고, 범위로 설정하는 이유

Semantic Versioning에서는 버전 번호의 세 가지 요소를 사용합니다: major.minor.patch. 이들 요소의 의미와 업데이트의 특성을 설명하겠습니다.

  • Major 버전: 주요 변경 사항을 의미합니다. 이는 기존 버전과의 호환성이 깨질 수 있는 큰 변화를 나타냅니다. 이는 API의 변경, 기능의 제거, 데이터베이스 구조 변경 등과 같이 기존 코드와의 호환성에 영향을 줄 수 있는 업데이트입니다.
  • Minor 버전: 새로운 기능이 추가되거나 기존 기능에 작은 변화가 있는 경우에 증가합니다. 이는 새로운 기능의 도입, 기능의 개선, 새로운 옵션의 제공 등과 같은 업데이트입니다. Minor 업데이트는 대개 호환성을 유지하며 기존 코드와의 충돌이 발생하지 않습니다.
  • Patch 버전: 버그 수정, 보안 문제 해결, 성능 향상 등과 같은 작은 변화를 나타냅니다. Patch 업데이트는 주로 기존 코드와의 호환성을 유지하면서 버그를 수정하거나 기능을 개선하는 업데이트입니다.

Semantic Versioning은 이러한 업데이트 방식을 통해 의존성 패키지의 버전을 관리하고 호환성을 유지합니다. 일반적으로 Major 업데이트 이전의 버전 번호를 사용하는 경우 호환성이 유지되며, Minor와 Patch 업데이트는 보다 작은 변화를 나타내므로 호환성 이슈가 적은 업데이트입니다.

따라서 Semantic Versioning에서는 Minor 버전까지의 업데이트에서는 호환성 이슈가 발생하지 않는 것을 기대하고 있습니다. 그러나 보안 문제 해결과 같이 크리티컬한 이슈를 해결하기 위한 버그 수정은 Patch 업데이트로 처리될 수 있습니다. 따라서 bug fix 등의 업데이트는 주로 Patch 업데이트로 처리되며, 버전 표기법을 이용하여 업데이트의 허용 범위를 명시합니다.

이를 통해 의존성 패키지의 업데이트를 조절하고 필요한 보안 수정 등을 수행할 수 있습니다.

주석

const handleClickSave = async () => {
  if (modifiedTodo === '') {
    alert('입력 값이 없습니다.');
    return;
  }
  // 기존과 값이 변경된 경우에만 api 호출 => 주석은 코드를 설명하는게 아니다. (유지보수에 문제)
  if (modifiedTodo !== todo) {
    await updateTodo({ id, todo: modifiedTodo, isCompleted });
  }
  setIsOnModify(false);
};
  • 나쁜 주석
    • 코드를 설명하는 주석
    • 나중에 사용될 수도 있는 코드를 주석처리
  • 좋은 주석
    • 코드로 표현하지 못하는 부분을 주석처리
    function calculateTax(income) {
      // 소득이 특정 기준 이상인 경우 추가 세금이 부과됩니다.
      if (income > 50000) {
        const additionalTax = income * 0.1;
        return income + additionalTax;
      }
      return income;
    }

리팩토링 피드백

함수명

  • BAD
const handleCheck = async () => {
  updateTodo({ id, todo, isCompleted: !isCompleted });
};
  • GOOD
const toggleComplete = async () => {
  updateTodo({ id, todo, isCompleted: !isCompleted });
};

불필요한 state

  • BAD
const [form, setForm] = useState<IAuthType>({
  email: '',
  password: '',
});

const [isValid, setIsValid] = useState<IAuthValidType>({
  isEmail: false,
  isPassword: false,
});

const [disabled, setDisabled] = useState(true);

useEffect(() => {
  setDisabled(!(isValid.isEmail && isValid.isPassword));
}, [isValid.isEmail, isValid.isPassword]);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  event.preventDefault();
  const current = event.target.value;

  switch (event.target.type) {
    case 'email':
      const regex = /\S+@\S+\.\S+/; // eslint-disable-line

      setIsValid(prev => ({
        ...prev,
        isEmail: !regex.test(current) ? false : true,
      }));
      setForm(prev => ({ ...prev, email: current }));
      break;
    case 'password':
      setIsValid(prev => ({
        ...prev,
        isPassword: current.length < 8 ? false : true,
      }));
      setForm(prev => ({ ...prev, password: current }));
      break;
  }
};
  • GOOD
const [form, setForm] = useState<IAuthType>({
  email: '',
  password: '',
});

const isValid = {
  isEmail: /\S+@\S+\.\S+/.test(form.email),
  isPassword: form.password.length < 8,
};

const isDisabled = !(isValid.isEmail && isValid.isPassword);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  event.preventDefault();
  const current = event.target.value;

  switch (event.target.type) {
    case 'email':
      setForm(prev => ({ ...prev, email: current }));
      break;
    case 'password':
      setForm(prev => ({ ...prev, password: current }));
      break;
  }
};

함수는 한번에 한가지 일만 처리한다

  • BAD
const Login = () => {
  return <AuthContainer title="로그인" dataTestid="signin-button" />;
};
const Signup = () => {
  return <AuthContainer title="회원가입" dataTestid="signup-button" />;
};
const AuthContainer = ({ title, dataTestid }: PropsType) => {

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    try {
      setDisabled(true);
      if (location === '/signup') {
        await SIGNUP({
          email: form.email,
          password: form.password,
        });
        alert('회원가입이 성공하였습니다!\n로그인을 시도해주세요.');
        navigate('/signin');
      }
      if (location === '/signin') {
        await SIGNIN({
          email: form.email,
          password: form.password,
        });
        alert('환영합니다!');
        navigate('/todo');
      }
    } catch (error: any) {
      alert(
        location === '/signup'
          ? error.response.data.message
          : location === '/signin'
          ? '이메일이나 비밀번호를 다시 확인해주세요.'
          : ''
      );
    }
  };

  return (
    <AuthContainerPresenter
      title={title}
      data={form}
      isValid={isValid}
      dataTestid={dataTestid}
      disabled={disabled}
      onChange={handleChange}
      onSubmit={handleSubmit}
    />
  );
};
  • GOOD
const Login = () => {
  const navigate = useNavigate();

  const signin = async (email, password) => {
    try {
      await SIGNIN({
        email,
        password,
      });
      alert('환영합니다!');
      navigate('/todo');
    } catch (err) {
      alert('이메일이나 비밀번호를 다시 확인해주세요');
    }
  };

  return (
    <AuthContainer
      title="로그인"
      dataTestid="signin-button"
      onSubmit={signin}
    />
  );
};
const Signup = () => {
  const navigate = useNavigate();

  const signup = async (email, password) => {
    try {
      await SIGNUP({
        email,
        password,
      });
      alert('회원가입이 성공하였습니다!\n로그인을 시도해주세요.');
      navigate('/signin');
    } catch (err) {
      alert(err.response.data.message);
    }
  };

  return (
    <AuthContainer
      title="회원가입"
      dataTestid="signup-button"
      onSubmit={signup}
    />
  );
};
const AuthContainer = ({ title, dataTestid, onSubmit }: PropsType) => {

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setDisabled(true);
    onSubmit(form.email, form.password);
  };

  return (
    <AuthContainerPresenter
      title={title}
      data={form}
      isValid={isValid}
      dataTestid={dataTestid}
      disabled={disabled}
      onChange={handleChange}
      onSubmit={handleSubmit}
    />
  );
};

export default AuthContainer;

불필요한 useMemo

const [auth, setAuth] = useState({
  email: '',
  pw: '',
});

const emailRegex = /@+/;
const pwRegex = /.{8,}/;

//const isValidEmail = useMemo(() => emailRegex.test(auth.email), [auth.email]);
//const isValidPw = useMemo(() => pwRegex.test(auth.pw), [auth.pw]);
//const isValid = isValidEmail && isValidPw;

const isValidEmail = emailRegex.test(auth.email);
const isValidPw = pwRegex.test(auth.pw);
const isValid = isValidEmail && isValidPw;

파일안에 내용 작성 순서

  • BAD
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { validate } from '../../utils';
import { useAuth } from '../../hooks';
import { Input, Button, InvisibleLabel } from '..';
import { PATH, DATATESTID } from '../../constants';

const formConfig = {
  signin: {
    formTypeText: '로그인',

    dataTestId: DATATESTID.SIGN_IN_BTN,
    link: PATH.SIGN_UP,
    linkText: '처음 오셨나요?',
  },
  signup: {
    formTypeText: '회원가입',
    dataTestId: DATATESTID.SIGN_UP_BTN,
    link: PATH.SIGN_IN,
    linkText: '이미 회원이신가요?',
  },
};

const Form = styled.form`
  display: flex;
  justify-content: space-between;
  flex-direction: column;
`;

const Warning = styled.span`
  font-size: 14px;
  color: red;
  padding-left: 3px;
`;

const SubmitButton = styled(Button)`
  margin: 6px 0 0 0;
`;

const ChangeFormButton = styled(Button)`
  margin: 5px 0px;
  background-color: white;
  color: Dodgerblue;
`;

const SignInput = styled(Input)`
  margin: 12px 0 6px 0;
`;

const SignForm = ({ formType }) => {
  const { formValues, error, handleValueChange, handleSignInSubmit, handleSignUpSubmit } = useAuth();
  const [emailValid, passwordValid] = validate(formValues);

  const allValid = emailValid && passwordValid;
  const signIn = formType === 'signin';

  return (
    <>
      <h1>{formConfig[formType].formTypeText}</h1>
      <Form onSubmit={signIn ? handleSignInSubmit : handleSignUpSubmit}>
        <InvisibleLabel htmlFor="email">email input</InvisibleLabel>
        <SignInput
          id="email"
          name="email"
          value={formValues.email}
          onChange={handleValueChange}
          data-testid={DATATESTID.EMAIL_INPUT}
          placeholder="이메일을 입력해주세요."
          inValid={formValues.email && !emailValid}
        />
        {formValues.email && !emailValid && <Warning>아이디는 @를 포함해야합니다.</Warning>}
        <InvisibleLabel htmlFor="password">password input</InvisibleLabel>
        <SignInput
          id="password"
          name="password"
          type="password"
          value={formValues.password}
          onChange={handleValueChange}
          data-testid={DATATESTID.PASSWORD_INPUT}
          placeholder="비밀번호를 입력해주세요."
          inValid={formValues.password && !passwordValid}
        />
        {formValues.password && !passwordValid && <Warning>비밀번호는 8자 이상이여야 합니다.</Warning>}
        {error && <Warning>{error.response.data.message}</Warning>}
        <SubmitButton type="submit" data-testid={formConfig[formType].dataTestId} disabled={!allValid}>
          {formConfig[formType].formTypeText}
        </SubmitButton>
      </Form>
      <Link to={formConfig[formType].link}>
        <ChangeFormButton>{formConfig[formType].linkText}</ChangeFormButton>
      </Link>
    </>
  );
};

export default SignForm;
  • GOOD
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { validate } from '../../utils';
import { useAuth } from '../../hooks';
import { Input, Button, InvisibleLabel } from '..';
import { PATH, DATATESTID } from '../../constants';

const SignForm = ({ formType }) => {
  const { formValues, error, handleValueChange, handleSignInSubmit, handleSignUpSubmit } = useAuth();
  const [emailValid, passwordValid] = validate(formValues);

  const allValid = emailValid && passwordValid;
  const signIn = formType === 'signin';

  return (
    <>
      <h1>{formConfig[formType].formTypeText}</h1>
      <Form onSubmit={signIn ? handleSignInSubmit : handleSignUpSubmit}>
        <InvisibleLabel htmlFor="email">email input</InvisibleLabel>
        <SignInput
          id="email"
          name="email"
          value={formValues.email}
          onChange={handleValueChange}
          data-testid={DATATESTID.EMAIL_INPUT}
          placeholder="이메일을 입력해주세요."
          inValid={formValues.email && !emailValid}
        />
        {formValues.email && !emailValid && <Warning>아이디는 @를 포함해야합니다.</Warning>}
        <InvisibleLabel htmlFor="password">password input</InvisibleLabel>
        <SignInput
          id="password"
          name="password"
          type="password"
          value={formValues.password}
          onChange={handleValueChange}
          data-testid={DATATESTID.PASSWORD_INPUT}
          placeholder="비밀번호를 입력해주세요."
          inValid={formValues.password && !passwordValid}
        />
        {formValues.password && !passwordValid && <Warning>비밀번호는 8자 이상이여야 합니다.</Warning>}
        {error && <Warning>{error.response.data.message}</Warning>}
        <SubmitButton type="submit" data-testid={formConfig[formType].dataTestId} disabled={!allValid}>
          {formConfig[formType].formTypeText}
        </SubmitButton>
      </Form>
      <Link to={formConfig[formType].link}>
        <ChangeFormButton>{formConfig[formType].linkText}</ChangeFormButton>
      </Link>
    </>
  );
};

const formConfig = {
  signin: {
    formTypeText: '로그인',

    dataTestId: DATATESTID.SIGN_IN_BTN,
    link: PATH.SIGN_UP,
    linkText: '처음 오셨나요?',
  },
  signup: {
    formTypeText: '회원가입',
    dataTestId: DATATESTID.SIGN_UP_BTN,
    link: PATH.SIGN_IN,
    linkText: '이미 회원이신가요?',
  },
};

const Form = styled.form`
  display: flex;
  justify-content: space-between;
  flex-direction: column;
`;

const Warning = styled.span`
  font-size: 14px;
  color: red;
  padding-left: 3px;
`;

const SubmitButton = styled(Button)`
  margin: 6px 0 0 0;
`;

const ChangeFormButton = styled(Button)`
  margin: 5px 0px;
  background-color: white;
  color: Dodgerblue;
`;

const SignInput = styled(Input)`
  margin: 12px 0 6px 0;
`;

export default SignForm;

설명

BAD 코드와 GOOD 코드의 차이점은 다음과 같습니다:

BAD 코드:

  • formConfig 객체가 컴포넌트 위에 위치하고 있습니다.
  • Form, Warning, SubmitButton, ChangeFormButton, SignInput 스타일드 컴포넌트가 컴포넌트 위에 위치하고 있습니다.
  • SignForm 컴포넌트와 스타일 관련 코드가 섞여 있습니다.

GOOD 코드:

  • formConfig 객체가 SignForm 컴포넌트 아래에 위치하고 있습니다.
  • Form, Warning, SubmitButton, ChangeFormButton, SignInput 스타일드 컴포넌트가 컴포넌트 아래에 위치하고 있습니다.
  • SignForm 컴포넌트와 스타일 관련 코드가 분리되어 있습니다.

GOOD 코드는 코드의 가독성과 유지 보수성을 개선하기 위해 관련 코드들을 논리적인 블록으로 그룹화하고 구조화했습니다. 스타일 관련 코드를 컴포넌트 아래에 위치시킴으로써 컴포넌트의 구조를 더욱 명확하게 표현하고, 코드의 재사용성을 높일 수 있습니다. 또한 formConfig 객체도 관련된 코드 아래에 위치시킴으로써 읽기 쉽고 이해하기 쉬운 구조를 갖추게 되었습니다.

profile
필요하다면 공부하는 개발자, 한승준

0개의 댓글