[TIL] 내일배움캠프 React 과정 2022.12.08

Today I Learend

  • React 숙련 강의 수강
  • React 입문 과제 코드 리뷰 후 피드백에 따른 수정

 

React 숙련 강의

CSS-in-Js란?

CSS-in-JS방식이란, 단어 그대로 자바스크립트로 CSS 코드를 작성하여 컴포넌트를 꾸미는 방식을 말한다.

styled-components는 우리가 리액트에서 CSS-in-JS 방식으로 컴포넌트를 꾸밀 수 있게 도와주는 패키지인데 SC의 기본적인 원리는 꾸미고자 하는 컴포넌트를 SC의 방식대로 먼저 만들고, 그 안에 스타일 코드를 작성하는 방식으로 진행된다.

 

 

styled-components의 기본적인 사용법

// styled-components에서 styled 라는 키워드를 import 합니다.
import styled from "styled-components";

// styled키워드를 사용해서 styled-components 방식대로 컴포넌트를 만듭니다. 
const StBox = styled.div`
  // 이 안에 스타일 코드를 작성합니다. 스타일 코드는 우리가 알고 있는 css와 동일합니다.
  width: 100px;
  height: 100px;
  border: 1px solid red;
  margin: 20px;
`;

const App = () => {
  // 우리가 만든 styled-components를 JSX에서 html 태그를 사용하듯이 사용합니다.
  return <StBox>박스</StBox>;
};

export default App;

styled. 뒤에는 html의 태그가 온다. 아래처럼 내가 원하는 html 태그를 사용해서 styled-components를 만들 수 있다.

div ➡️ styled.div

span ➡️ styled.span

button ➡️ styled.button

 

 

조건부 스타일링 구현해보기

CSS-in-JS 방식의 강점은 스타일 코드를 JS코드 작성하듯이 작성할 수 있다는 점! props를 통해서 부모 컴포넌트로부터 값을 전달받고, 조건문을 이용해서 조건부 스타일링을 할 수 있다.

import styled from "styled-components";

// 1. styled-components를 만들었습니다.
const StBox = styled.div`
  width: 100px;
  height: 100px;
  border: 1px solid ${(props) => props.borderColor}; // 4.부모 컴포넌트에서 보낸 props를 받아 사용합니다. 
  margin: 20px;
`;

const App = () => {
  return (
    <div>
      {/* 2. 그리고 위에서 만든 styled-components를 사용했습니다. */}
      {/* 3. 그리고 props를 통해 borderColor라는 값을 전달했습니다. */}
      <StBox borderColor="red">빨간 박스</StBox>
      <StBox borderColor="green">초록 박스</StBox>
      <StBox borderColor="blue">파랑 박스</StBox>
    </div>
  );
};

export default App;

styled-components도 말 그대로 컴포넌트이기 때문 box들에게 prosp를 통해서 border color에 대한 정보를 전달해줄 수 있다. 부모 컴포넌트인 App에서 styled-component(StBox)를 사용한다. 그리고 borderColor를 props로 자식 컴포넌트인 styled-components(StBox)에 전달한다. 

 

 

Swtich문과 map 메서드를 이용해서 조건부 스타일링 구현해보기

import styled from "styled-components";

const StContainer = styled.div`
  display: flex;
`;

const StBox = styled.div`
  width: 100px;
  height: 100px;
  border: 1px solid ${(props) => props.borderColor};
  margin: 20px;
`;

// 박스의 색을 배열에 담습니다.
const boxList = ["red", "green", "blue"];

// 색을 넣으면, 이름을 반환해주는 함수를 만듭니다.
const getBoxName = (color) => {
  switch (color) {
    case "red":
      return "빨간 박스";
    case "green":
      return "초록 박스";
    case "blue":
      return "파란 박스";
    default:
      return "검정 박스";
   }
};

const App = () => {
  return (
    <StContainer>
	{/* map을 이용해서 StBox를 반복하여 화면에 그립니다. */}
      {boxList.map((box) => (
        <StBox borderColor={box}>{getBoxName(box)}</StBox>
      ))}
    </StContainer>
  );
};

export default App;

map 메서드를 이용해서 boxList 배열에서 컬러를 가져오고, 이 컬러에 따라서 switch문으로 StBox의 innerText를 바꾼다.

 

 

Swith문 공부하기

기본 문법

switch (condition) {
  case value1:
    statement1;
    break;
  case value2:
    statement2;
    break;
  default:
    statement3;
}

switch 문은 switch 문의 condition을 평가하여 그 값과 일치하는 표현식을 갖는 case 문으로 실행 순서를 이동시킨다. condition의 값이 value1이면 statement1을, value2면 statement2를, 그 어느 것도 아니라면 default가 적용되어 statement3을 실행한다. 조건이 맞아 break를 만나면 그 이후의 비교는 하지 않으며, switch문을 종료시킨다. default문은 switch문의 가장 마지막에 위치하므로 default 문의 실행이 종료하면 switch 문을 빠져나간다. 따라서 break를 생략하는 것이 일반적이다.

 

switch문 예시

function switchOfStuff(val) {
  var answer = "";
  switch (val){
    case "a":
      answer = "apple";
      break;
    case "b":
      answer ="bird";
      break;
    case "c":
      answer ="cat";
      break;
    default:
      answer ="stuff"; 
      break;
  }
  return answer;
}

switchOfStuff(1)       //"stuff"  
switchOfStuff("c")     //"cat"

출처 https://velog.io/@grinding_hannah/Switch-조건문-사용하기

 

 

화살표 함수는 생략이 가능하다

border: 1px solid ${(props) => props.borderColor};

// 생략 전에는 이렇게 생겼다.
${(props)⇒{ return props.borderColor }}

// 이렇게 수정해도 정상 작동한다.
border: 1px solid ${props => props.borderColor};

여기서 borderColor를 화살표 함수를 이용해서 넘겨받고 있는데 왜 중괄호를 쓰지 않는지, {props.borderColor}이 아닌지가 궁금했다. 그런데 알고 보니 함수가 한 줄일 때는 return과 괄호를 생략할 수 있는 것이었다.

// 화살표 함수의 유일한 문장이 'return'일 때 'return'과
// 중괄호({})를 생략할 수 있다.
elements.map(element => element.length); // [8, 6, 7, 9]

출처 https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/Arrow_functions

 

 

Styled Components 전역 스타일링 (Global Style)

// style/global.js
import { createGlobalStyle } from 'styled-components';

우선 createGlobalStyle을 import한다.

 

// style/global.js
const GlobalStyle = createGlobalStyle`
* {
    font-family: 'Roboto', sans-serif;
    margin: 0;
    padding: 0;
}

body {
    box-sizing: border-box;
}
`;

export default GlobalStyle;

그리고 GlobalStyle을 생성한 후에 적용할 CSS를 입력한다.

 

// App.js
import GlobalStyle from './style/global';

function App() {
  return (
    <>
      <GlobalStyle/>
        <div className="App">
          <div>Test !!</div>
        </div>
    </>
  );
}

최상위 컴포넌트에 import하면 적용이 된다.

 

출처 https://velog.io/@tlatjdgh3778/Styled-Components-에서-createGlobalStyle-사용하기

 

 

CSS reset이란?

웹 브라우저마다 margin, paddind 값 등 default로 설정되어 있는 CSS 스타일을 초기화하는 것.

 

React 적용 방법

// yarn
$ yarn add styled-reset

// npm 
$ npm i styled-reset

터미널에서 설치한 후에 다음과 같이 코드를 작성한다.

import * as React from 'react'
import { Reset } from 'styled-reset'

const App = () => (
  <React.Fragment>
    <Reset />
    <div>Hi, I'm an app!</div>
  </React.Fragment>
)

출처 https://im-designloper.tistory.com/77


 

React 입문 과제 수정하기

리스트 추가할 때 id 값 조건 추가

id: todo.length === 0 ? 0 : todo.length,

투두 리스트가 한 개도 없을 때 추가하기 버튼을 눌렀을 때 id값을 어떻게 줘야 하는지 삼항 연산자를 통해 조건을 추가했다.

 

 

Button 컴포넌트 추가하기

if (Todo.isDone === false) {
    document.querySelector('#btn').innerText = '완료'
} else {
    document.querySelector('#btn').innerText = '취소'
}

기존 컴포넌트 안에 if문 추가해봤는데 이렇게는 안되나 보다 ㅎㅎ

 

function Button (props) {

  if (Todo.isDone === false) {
    return (
      <button onClick={() => props.doneHandler(props.list.id)} 
        style={{backgroundColor: '#acaaed'}}>완료</button>
    );

  } else {
    return (
      <button onClick={() => props.cancelHandler(props.list.id)} 
        style={{background: '#FF9F9F'}}>취소</button>
    );
  }
}

버튼 컴포넌트를 따로 만들어 보려 했는데 이 상태에서는 todo의 배열 정보를 가져올 수 없는 거 같다. 아주 제대로 꼬여버렸다😓 

 

결국.. 성공했다!! 거의 두 시간을 매달림 ㅋㅋㅋ

function Button (props) { 

    const { list, doneHandler, cancelHandler } = props;

    if (list.isDone === false) {
      return (
        <button onClick={() => doneHandler(list.id)} 
          style={{backgroundColor: '#acaaed'}}
          className="btn">완료</button>
      );
  
    } else if (list.isDone === true) {
      return (
        <button onClick={() => cancelHandler(list.id)} 
          style={{backgroundColor: '#FF9F9F'}}
          className="btn">취소</button>
      );
    }
}

버튼 컴포넌트 추가해서 isDone 값에 따라 취소 버튼, 완료 버튼으로 나뉘고 버튼에 따라 동작하는 함수도 당연히 달라진다. 그리고 전체적으로 props 구조 분해 할당도 추가했다.

 

 

엔터 눌렀을 때 [추가하기] 버튼 동작하기

제목과 내용을 입력하고 엔터만 누르면 버튼을 직접 클릭할 필요 없이 바로 버튼이 동작하도록 하고 싶었다. 구글링 해보니 엔터의 keycode가 13인데 이걸 이용해서 함수를 이용해라...라는 글이 많이 나왔는데 시도했다가 실패했고 결국 간단하게 input들을 form 태그로 감싸줬다.

 

그런데 form 태그를 이용하니 엔터를 누르든 버튼을 클릭하든 제출하는 동시에 새로고침이 되면서 입력한 투두 리스트가 바로 삭제되는 문제가 있었다.(데이터가 따로 저장되는 것이 아니기 때문에 새로고침 하면 작성한 투두 리스트는 사라진다.) 그래서 추가하기 버튼이 작동하기 위한 addHandler 함수에 event.preventDefault();를 추가했다.

 

 

객체의 id 값을 uuid로 수정하기

id: uuidv4(),

to do list 카드를 5개 만든 후에 세 번째 카드를 삭제하면 id 값이 1-2-4-5로 남아서 나중에 카드를 추가할 때 key 값이 중복되어서 문제가 발생하기 때문에 id 값을 uuid로 주기로 했다.

 

// 원래 코드
todo[id].isDone = false;

근데 문제는 카드를 완료하거나 취소할 때 index = id 값이 동일했기 때문에 인덱스로 객체를 불러와서 isDone 값을 수정해 주었는데 이제는 이 방법을 쓸 수 없어서 완료, 취소 함수까지 수정해야 한다.

 

const changeDoneHandler = (id) => {
    let copy = [...todo]
    const isDoneChange = copy.map((list) =>
      // 삼항연산자
      list.id === id ? {...list, isDone: !list.isDone} : list
 		
      // if문  
      // if (list.id === id) {
      //   return {...list, isDone: !list.isDone}
      // } else {
      //   return list
      // } 
    );
    
    setTodo(isDoneChange);
};

기존에는 완료 버튼을 처리하는 doneHandler와 취소 버튼을 처리하는 cancelHandler 함수가 각자 존재했는데 이번에 코드를 수정하면서 하나로 합쳐버렸다. 어차피 두 함수 모두 기능은 객체의 isDone 값을 true<->false로 왔다 갔다 하는 것이었기 때문에 합칠 수 있었다.

 

원래는 if문을 이용해서 코드를 작성했었는데 튜터님의 피드백으로 삼항 연산자를 이용하니 코드가 훨씬 깔끔해진 거 같다. 기존에 두 개의 함수가 있었을 때는 isDone = false 혹은 isDone = true 로 직접 값을 지정해줬는데 이번에는 not 연산자로 작성해서 반대의 값이 주어지도록 했다. 

 

원래 주석 처리된 if문 부분을 작성하다가 에러가 발생해서 튜터님께 질문하러 갔었는데 문제는 map 함수에 return을 적어주지 않았다는 거였다. 검색을 해보니 map 메서드를 사용할 때 return을 적어주는 경우도 있고 없는 경우도 있기 때문에 필수로 적어주는 것은 아니고 어떤 조건일 때는 return을 꼭 작성해줘야 하는 거 같다. 근데 그 조건을 아직은 완벽하게 이해하지 못했다.

 

그리고 함수명 정할 때는 보통 동사-> 명사 순으로 짓는다! 튜터님의 꿀팁🍯

 


 

Programmers 문제 풀기

캐릭터의 좌표

function solution(keyinput, board) {
    let start = [0, 0]    
        for (const key of keyinput) {
            if ( Math.abs(start[0]) < (board[0] - 1) / 2 && key === "left") {
                start[0] = start[0] - 1
            }
            if ( Math.abs(start[0]) < (board[0] - 1) / 2 && key === "right") {
                start[0] = start[0] + 1
            }
            if ( Math.abs(start[1]) < (board[1] - 1) / 2 && key === "down") {
                start[1] = start[1] - 1
            }
            if ( Math.abs(start[1]) < (board[1] - 1) / 2 && key === "up") {
                start[1] = start[1] + 1
            }
        }
    return start
}

코드가 지저분하긴 해도 풀긴 풀었는데 역시나 생각했던 부분에서 문제가 생겼다. 테스트 3의 경우처럼 캐릭터가 왼쪽 끝에 도착한 경우에는 Math.abs(start[0]) < (board[0] - 1) 조건 때문에 오른쪽으로 다시 돌아가지 못한다는 것이다. 이 문제를 어떻게 해결해야 할지 실마리조차 생각나지 않는다.🙁

 

function solution(keyinput, board) {
    let start = [0, 0]
        for (const key of keyinput) {
            if ( Math.abs(start[0] - 1) <= (board[0] - 1) / 2 && key === "left") {
                start[0] = start[0] - 1
            } 
            if ( Math.abs(start[0] + 1) <= (board[0] - 1) / 2 && key === "right") {
                start[0] = start[0] + 1
            }
            if ( Math.abs(start[1] - 1) <= (board[1] - 1) / 2 && key === "down") {
                start[1] = start[1] - 1
            }
            if ( Math.abs(start[1]+ 1) <= (board[1] - 1) / 2 && key === "up") {
                start[1] = start[1] + 1
            }
        }
    return start
}

아침부터 팀원 분과 긴 시간 토론(?) 끝에 결국 문제점을 해결했다. 내가 작성했던 코드에서 수정한 부분은 if문 조건에 start[0] => start[0] - 1로 수정 그리고 부등호에 =을 추가해준 것이었다. 애초에 if문을 작성할 때 기존 start 배열을 기준으로 할 것이 아니라 상하좌우 한 칸을 이동했을 때를 조건으로 걸어서 작동해야 하는 거였다!

 

 

영어가 싫어요

replace( )

String 타입은 replace()함수를 제공하며 이것을 이용하여 문자열의 특정 문자열을 다른 문자열로 변환할 수 있습니다. 

let str = 'Hello world, Java';

str = str.replace('Java', 'JavaScript');
console.log(str); // Hello world, JavaScript

출처 https://codechacha.com/ko/javascript-replace-in-string/

 

일단 replace()를 써야 된다는 건 알겠는데 이걸 문자열을 어떻게 돌아야 하는지, 그리고 만약에 숫자가 중복될 경우에는 어떻게 처리해야 되는지 모르겠다. 일단 대충 작성해 본 엉망인 코드를 남겨놓고 내일 해결하기로 한다..

function solution(numbers) {
    let answer = ''
    answer = numbers.replace('one', '1')
    // two = numbers.replace('two', '2')
    // three = numbers.replace('three', '3')
    // four = numbers.replace('four', '4')
    // five = numbers.replace('five', '5')
    // six = numbers.replace('six', '6')
    // seven = numbers.replace('seven', '7')
    // eight = numbers.replace('eight', '8')
    // nine = numbers.replace('nine', '9')
    // zero = numbers.replace('zero', '0')
    return answer
}

 

회고

원래 오늘은 리액트 숙련 강의를 들으려고 했지만 다른 일들을 처리하느라 1강 밖에 들지 못했다. 일단 styled-components 처음 배우고 느낀 점은 편하게 css 쓰면 되는데 왜 굳이 자바스크립트 코드 복잡하게 작성하며 저걸 써야 되는데?;;이다. 뭐 아직 초반밖에 안 들었으니까 점점 이것의 장점을 알아가게 되겠지 흠..

 

오전에는 팀원 분과 30분 정도 알고리즘 문제를 해결하느라 시간을 보냈다. 이렇게 다른 사람의 코드와 비교해가며 의견을 나누는 시간은 새로운 시각에서 코드 구조를 볼 수 있기 때문에 언제나 재밌다. 그리고 서로 몰랐던 부분을 알려주며 많이 배우기도 한다!

 

오후에는 원장 튜터님께 부탁드렸던 todolist 과제의 코드 리뷰를 받을 수 있었다. 내가 구현하고 싶었지만 하지 못했던 헤더와 폼 부분 밑으로 전체적으로 컴포넌트화 시키는 과정을 지켜볼 수 있었고 이제 나 혼자서 다시 수정해보라는 과제(?)를 내주셨다. 이것 외에도 많은 피드백을 주셨는데 적어보자면 이렇다.

  1. input에 입력 값 없을 때 focus 가도록 구현 - 만약에 내용만 입력하고 제목이 입력되지 않았으면 제목에 focus가 가도록
  2. enter 키 눌러서 추가하기 버튼이 동작하도록 구현하기
  3. id 값에 중복이 생기지 않도록 uuid 값을 주기
  4. 컴포넌트 세분화하기 Todolist -> Todocard -> Button
  5. 주석 꼼꼼하게 쓰기 (console.log는 다 지우기)
  6. app.jsx 파일에 jsx 부분 말고 javascript 코드가 너무 많아서 구조를 한눈에 파악하기 힘드니 javasciprt 파일을 분리할 것
  7. 반응형 웹페이지로 만들기

여기서 오늘 한 게 1, 2, 3이고 내일 오전에 4, 5, 7을 하려고 한다. (6번은 일단 포기 ㅎ)

 

오늘 저녁에는 튜터님께 질문을 하면서 계속 피드백을 받으며 거의 라이브 코딩쇼(?)를 했는데 너무 튜터님들의 시간을 빼앗았나 싶기도 하지만 고민됐던 부분들도 해결하고 이것저것 많이 배울 수 있어서 즐거웠다. 그리고 코딩하는 걸 재밌어하는 거 같다고 말씀해주셨는데 나도 하면서 재미있다고 생각하긴 한다. 부트캠프 지원하기 전에 고민했던 게 내가 코딩에 재능도 없고 재미도 못 느끼면 어떡하지 하는 부분이었는데 다행히 계속해서 재미는 느끼고 있다. 앞으로 직업으로 삼을 건데 이왕이면 즐겁게 공부하고 싶다 ㅋㅋ