[React]Refactoring Checklist

길현민·2022년 7월 12일
0

React

목록 보기
8/28

1. reset.scss & common.scss

reset.scss, common.scss의 위치는 index.js에서 한번만
초기화 하는 세팅은 reset.scss에!
모든 컴포넌트에 적용되어야 하는 css는 common.scss에!

// common.scss

  • {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    }

변수, mixin등의 경우에는 variables.scss, mixin.scss 별도 파일로 분리해서 필요한 .scss 파일에서 import 해서 사용하시면 됩니다.
관심사에 따라 모듈화하여 관리하면, 추후에 수정사항이 생길 시 한 가지의 파일만 수정하면 되기 때문에 유지보수가 용이해 집니다.

// style/variables.scss
$theme-background: #f6f4f1;
$theme-light-gray-background: #faf9f8;
$theme-blue: #007aff;
$theme-gray-strong: #888888;
$theme-gray-normal: #cccccc;
$theme-gray-light: #eeeeee;
$theme-white: #fff;
$theme-black: #1c1c1e;
$theme-red: #ff0000;
// 변수를 사용하는 scss 파일
@import "../../styles/variables.scss";

.main {
  .title {
    color: $theme-red;
  }
}

귀찮다고 common.scss 에 프로젝트 전체적으로 적용되는 스타일 과 변수 / 믹스인을 함께 선언하면 안됩니다. 만약 그렇게 하게 되면 변수를 사용하기 위해 각 scss 파일에서 common.scss 를 import 할텐데, 이때 전체적으로 적용되는 스타일이 중복해서 적용되게 됩니다

// Bad common.scss 🙅‍♂️ 🙅‍♂️ 🙅‍♂️
$theme-background: #f6f4f1;
$theme-light-gray-background: #faf9f8;
$theme-blue: #007aff;
$theme-gray-strong: #888888;
$theme-gray-normal: #cccccc;
$theme-gray-light: #eeeeee;
$theme-white: #fff;
$theme-black: #1c1c1e;
$theme-red: #ff0000;
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

2. Sass nesting 활용

css 그대로 두신 분들! nesting 꼭 적용해주세요. 결국 자바스크립트 모듈 시스템을 통해 모든 css 파일들이 합쳐지기 때문에, 하나의 css 내가 작성한 코드가 의도치 않게 다른 파일에 있는 요소에 영향을 줄 수 있습니다.
컴포넌트 각 html 요소들의 종속 관계와 동일하게 css 속성들도 nesting 을 합니다.

function Login() {
  return (
    <main className="login">
      <div className="loginWrapper">
        <h1 className="loginTitle">Westagram</h1>
        <form className="loginForm" action="">
          <input className="loginFormInput" placeholder="전화번호, 사용자 이름 또는 이메일" />
          <input className="loginFormInput" placeholder="비밀번호" />
          <button className="loginButton">로그인</button>
        </form>
        <a href="#">비밀번호를 잊으셨나요?</a>
      </div>
    </main>   
  );
}
// Bad 🙅‍♂️🙅‍♂️🙅‍♂️
.login {
  display: flex;
  justify-content: center;
  align-items: center;
}
.loginWrapper {
  padding: 50px;
  border: 1px solid grey;
  text-align: center;
}
.loginWrapper .loginTitle {
  font-size: 50px;
}
.loginForm {
  display: flex;
  flex-direction: column;
  margin: 80px 0 50px 0;
  font-size: 15px;
}
.loginForm .loginFormInput {
  width: 350px;
  margin-bottom: 10px;
  padding: 10px 0 10px 10px;
  border: 1px solid gray;
  border-radius: 5px;
  background-color: #fafafa;
  outline: none;
}
.loginForm .loginButton {
  padding: 15px 0;
  background-color: skyblue;
  border: none;
  border-radius: 5px;
  color: white;
  outline: none;
  opacity: 0.5;
  cursor: default;
}
// Good 🙆‍♂️🙆‍♂️🙆‍♂️
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  
  .loginWrapper {
    padding: 50px;
    border: 1px solid grey;
    text-align: center;

    .loginTitle {
      font-size: 50px;
    }

    .loginForm {
      display: flex;
      flex-direction: column;
      margin: 80px 0 50px 0;
      font-size: 15px;

      .loginFormInput {
        width: 350px;
        margin-bottom: 10px;
        padding: 10px 0 10px 10px;
        border: 1px solid gray;
        border-radius: 5px;
        background-color: #fafafa;
        outline: none;
      }

      .loginButton {
        padding: 15px 0;
        background-color: skyblue;
        border: none;
        border-radius: 5px;
        color: white;
        outline: none;
        opacity: 0.5;
        cursor: default;
      }
    }
  }
}

가장 상위 요소의 className 은 컴포넌트 이름과 동일하게 작성해 줍니다. 해당 className 아래로 css를 네스팅 하는 것을 통해, 각 컴포넌트의 스타일링을 다른 컴포넌트 파일과 확실하게 구분지어 줄 수 있습니다.(위 컨벤션은 회사마다 / 개발팀 마다 다를 수 있습니다.)

3. className의 동적 사용

style에 변화를 줄 때 inline으로 바로 줄 수도 있지만 우리는 css 파일을 사용하고 있기 때문에 className을 적극 활용합시다!
인라인 스타일링은 꼭 필요한 경우가 아니라면 지양해 주세요. 스타일링이 HTML 파일에서 부여되고 가장 높은 우선 순위를 가지기 때문에, 후에 CSS 유지 보수를 어렵게 할 수 있습니다.

// inline style 사용 🙅‍♂️
<button
  onClick={handleCommentInputButton}
  style={{ color: isCommentButtonActive ? "#0095f6" : "#b5e1ff" }}
>
  로그인
</button>

// className 사용 🙆‍♂️
<button
  onClick={handleCommentInputButton}
  className={isCommentButtonActive ? "activated" : "deactivated"}
>
  로그인
</button>
.activated {
  color: #0095f6;
}

.deactivated {
  color: #b5e1ff;
}

4. 비구조화 할당, 구조분해 할당 (destructuring)

비구조화 할당(구조분해 할당)은 객체, 배열에 적용할 수 있는 ES6 문법입니다. 이 기능을 통해 긴 코드를 간결하게 쓸 수 있습니다.

// 비구조화 할당 미적용
const handlePasswordInput = event => {
  setPassword(event.target.value);
  ...
}
// 비구조화 할당 적용
const handlePasswordInput = event => {
  const { value } = event.target;
  setPassword(value);
  ...
}

핸들러 함수에서 뿐만 아니라 함수 컴포넌트 바디 안에서도 props 객체에 비구조화 할당을 적용해 코드를 간결하게 할 수 있습니다.

// 비구조화 할당 미적용
function PasswordInput (props) {
  return (
    <input value={props.passwordValue} onClick={props.handlePasswordInput}/>
  )
}

// 비구조화 할당 적용
function PasswordInput (props) {
  const { passwordValue, handlePasswordInput } = props;
  return (
    <input value={passwordValue} onClick={handlePasswordInput}/>
  )
}

// props 자리에서 바로 비구조화 할당을 할 수도 있습니다.
function PasswordInput ({ passwordValue, handlePasswordInput }) {
  return (
    <input value={passwordValue} onClick={handlePasswordInput}/>
  )
}

5. Boolean 데이터 타입의 활용

// Before 🙅‍♂️
if (event.target.value.includes("@") && event.target.value.length >= 5) {
  setIsIdValid(true);
} else {
  setIsIdValid(false);
}
// After 🙆‍♂️
const { value } = event.target;
const isIdInputValid = value.includes("@") && value.length >= 5;

setIsIdValid(isIdInputValid);

6. 반복되는 코드는 Array.map() 활용

// footerData.js
// named export (vs. default export)
export const INFO_LIST = [
  {id: 1, content: "도움말"},
  {id: 2, content: "홍보 센터"},
  {id: 3, content: "API"},
  {id: 4, content: "채용정보"},
  {id: 5, content: "개인정보처리방침"},
  {id: 6, content: "약관"},
  {id: 7, content: "위치"},
  {id: 8, content: "인기 계정"},
]

// Footer.js (Footer 컴포넌트)
import { INFO_LIST } from './footerData.js'

return (
  ...
  {INFO_LIST.map(info => {
     return (
       <li key={info.id}>
         <a href="">{info.content}</a>
       </li>
     )
 });

map을 사용할 때는 return 되는 JSX 요소마다 유니크한 key 값이 존재해야 합니다.
key 속성은 제일 바깥에 있는 태그에 부여합니다.

Array.map(el => {
  return (
    <li key={el.id}>
      <span>{el.content}</span>
    </li>
  )
})

위 상황에서 INFO_LIST 변수를 함수 컴포넌트 바디에서 선언할 경우 컴포넌트가 render 될 때마다 새로운 변수가 계속 선언되기 때문에, 값이 변하지 않는 변수는 컴포넌트 밖에서 선언합니다.

7. '계산된 속성명' 을 이용한 inputHandler 함수 합치기

input 태그에는 name 이라는 속성이 있습니다. 말 그대로 input 태그에 이름을 지어 주는 건데요. 이 name 속성 덕분에 여러 개의 input handler를 하나로 합칠 수 있습니다.

// 비슷하게 생긴 코드가 보이면 줄이는 방법에 대해 고민해보시기 바랍니다!

// Before
const [inputValues, setInputValues] = useState({
  email: "",
  password: "",
});

const handleEmail = event => {
  const { value } = event.target;

  setInputValues({
    ...inputValues,
    email: value,
  })
}

const handlePassword = event => {
  const { value } = event.target;

  setInputValues({
    ...inputValues,
    password: value,
  })
}

return (
    ...
    <input
      className="emailInput"
      type="text"
      onChange={handleEmail}
    />
    <input
      className="passwordInput"
      type="password"
      onChange={handlePw}
    />
    ...
  );
}
// input에 name 추가, 함수 하나로

// After
const [inputValues, setInputValues] = useState({
  email: "",
  password: "",
});

const handleInput = event => {
  const { name, value } = event.target;
  setInputValues({
    ...inputValues,
    [name]: value,
  })
}

return (
    ...
    <input
      className="emailInput"
      name="email"
      type="text"
      onChange={handleInput}
    />
    <input
      className="passwordInput"
      name="password"
      type="password"
      onChange={handleInput}
    />
    ...
  );
}

name 속성의 값은 value처럼 event.target으로 가져올 수 있습니다. (event.target.name)
name 속성은 오직 input 태그에서만 사용할 수 있습니다. 다른 태그에서 사용하려고 시도하지 마세요!

a tag는 사용하면 새로고침 하는 것처럼 html을 새로 다 받아 오는 반면, Link 를 사용하면 컴포넌트만 바꿔줍니다. 렌더링 최적화를 위해서 Link 사용해주세요!

9. import 순서

1.라이브러리
-React 관련 패키지
-외부 라이브러리
2.컴포넌트
-공통 컴포넌트 → 먼 컴포넌트 → 가까운 컴포넌트
3.함수, 변수 및 설정 파일
4.사진 등 미디어 파일(.png)
5.css 파일 (.scss)

10. 불필요한 state 값 제거

state의 업데이트는 렌더링을 발생시킵니다. 만약 state로 관리하지 않아도 되는 값을 state로 관리할 경우, 일어나지 않아도 되는 렌더링이 발생하게 됩니다. 따라서 불필요한 state 값들은 제거해 주시기 바랍니다 👐
그렇다면 state로 관리하기에 부적합한 값은 무엇이 있을까요? 그 기준은 다음과 같습니다.
1.부모로부터 전달받는 props
2.시간이 지나도 변하지 않는 값 (UI 변화에 관여하지 않는 값)
3.컴포넌트 안의 다른 state 나 props 로 계산 가능한 값
하나씩 예제로 살펴보도록 하겠습니다.
1. 부모로부터 전달받는 props

function Parent() {
    const [age, setAge] = useState(0);
    const [secondAge, setSecondAge] = useState(0);
    // 🙅‍♂️ totalAge는 age 와 secondAge를 통해 계산할 수 있으므로 state로 관리할 필요가 없습니다.
    const [totalAge, setTotalAge] = useState(0);
  
    useEffect(() => {
      setTotalAge(age + secondAge);
    }, [age, secondAge]);
  
    const handleAgeInput = (event) => {
      const { value } = event.target;
      setAge(Number(value));
    };
  
    const handleSecondAgeInput = (event) => {
      const { value } = event.target;
      setSecondAge(Number(value));
    };
  
    return (
      <div>
        <input onChange={handleAgeInput} placeholder="나이를 입력해 주세요!" />
        <Child age={age} />
      </div>
    );
  }
  
  function Child({ age }) {
    // 🙅‍♂️ props로 전달받은 값을 추가적으로 state에 저장할 필요가 없습니다.
    const [childAge, setChildAge] = useState(props.age);
  
    return (
      <div>
        입력된 나이는: <span>{childAge}</span>
      </div>
    );
  }
function Child({ age }) {
    return (
      <div>
        **입력된 나이는: <span>{age}</span>**
      </div>
    );
  }

2. 시간이 지나도 변하지 않는 값 (UI 변화에 관여하지 않는 값)

function Footer() {
    // 🙅‍♂️ 변하지 않는 값은 state로 관리하지 않습니다.
    const [footerInfo, setFooterInfo] = useState([
      { id: 1, content: "도움말" },
      { id: 2, content: "홍보 센터" },
      { id: 3, content: "API" },
      { id: 4, content: "채용정보" },
      { id: 5, content: "개인정보처리방침" },
      { id: 6, content: "약관" },
      { id: 7, content: "위치" },
      { id: 8, content: "인기 계정" },
    ]);
  
    return (
      <div>
        {footerInfo.map((info) => {
          return (
            <li key={info.id}>
              <a href="">{info.content}</a>
            </li>
          );
        })}
      </div>
    );
  }
const FOOTER_INFO = [
    { id: 1, content: "도움말" },
    { id: 2, content: "홍보 센터" },
    { id: 3, content: "API" },
    { id: 4, content: "채용정보" },
    { id: 5, content: "개인정보처리방침" },
    { id: 6, content: "약관" },
    { id: 7, content: "위치" },
    { id: 8, content: "인기 계정" },
  ];
  
  function Footer() {
    return (
      <div>
        {FOOTER_INFO.map((info) => {
          return (
            <li key={info.id}>
              <a href="">{info.content}</a>
            </li>
          );
        })}
      </div>
    );
  }

3. 컴포넌트 안의 다른 state 나 props 로 계산 가능한 값

 export default function TotalAge() {
    const [age, setAge] = useState(0);
    const [secondAge, setSecondAge] = useState(0);
    // 🙅‍♂️ totalAge는 age 와 secondAge를 통해 계산할 수 있으므로 state로 관리할 필요가 없습니다.
    const [totalAge, setTotalAge] = useState(0);
  
    useEffect(() => {
      setTotalAge(age + secondAge);
    }, [age, secondAge]);
  
    const handleAgeInput = (event) => {
      const { value } = event.target;
      setAge(Number(value));
    };
  
    const handleSecondAgeInput = (event) => {
      const { value } = event.target;
      setSecondAge(Number(value));
    };
  
    return (
      <div>
        <input onChange={handleAgeInput} placeholder="나이를 입력해 주세요!" />
        <input onChange={handleSecondAgeInput} placeholder="두번째 나이를 입력해 주세요!" />
        <div>
          **나이의 합: <span>{totalAge}</span>**
        </div>
      </div>
    );
  }
 export default function TotalAge() {
    const [age, setAge] = useState(0);
    const [secondAge, setSecondAge] = useState(0);
  
    const handleAgeInput = (event) => {
      const { value } = event.target;
      setAge(Number(value));
    };
  
    const handleSecondAgeInput = (event) => {
      const { value } = event.target;
      setSecondAge(Number(value));
    };
  
    const totalAge = age + secondAge;
  
    return (
      <div>
        <input onChange={handleAgeInput} placeholder="나이를 입력해 주세요!" />
        <input onChange={handleSecondAgeInput} placeholder="두번째 나이를 입력해 주세요!" />
        <div>
          나이의 합: <span>{totalAge}</span>
        </div>
      </div>
    );
  }

🐔참고문헌

·리액트로 사고하기

https://ko.reactjs.org/docs/thinking-in-react.html#step-3-identify-the-minimal-but-complete-representation-of-ui-state

https://stackoverflow.com/questions/43087007/react-link-vs-a-tag-and-arrow-function

·mdn 객체 초기자

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Object_initializer#%EA%B3%84%EC%82%B0%EB%90%9C_%EC%86%8D%EC%84%B1%EB%AA%85

profile
맛집탐방러

0개의 댓글