본 캠프

리액트 입문 1주차. (1)

Iruka913 2024. 10. 28. 20:22

신나는 알고리즘 풀이 시간. 

문제 1

문제를 열심히 풀던 상빈이는 일반화된 콜라 문제를 생각했습니다. 이 문제는 빈 병 a개를 가져다주면 콜라 b병을 주는 마트가 있을 때, 빈 병 n개를 가져다주면 몇 병을 받을 수 있는지 계산하는 문제입니다. 기존 콜라 문제와 마찬가지로, 보유 중인 빈 병이 a개 미만이면, 추가적으로 빈 병을 받을 순 없습니다. 상빈이는 열심히 고심했지만, 일반화된 콜라 문제의 답을 찾을 수 없었습니다. 상빈이를 도와, 일반화된 콜라 문제를 해결하는 프로그램을 만들어 주세요.

 

콜라를 받기 위해 마트에 주어야 하는 병 수 a, 빈 병 a개를 가져다 주면 마트가 주는 콜라 병 수 b, 상빈이가 가지고 있는 빈 병의 개수 n이 매개변수로 주어집니다. 상빈이가 받을 수 있는 콜라의 병 수를 return 하도록 solution 함수를 작성해주세요.

나의 답

function solution(a, b, n) {
    let gotBottles = 0;  
    let emptyBottles = n;  

    while (emptyBottles >= a) {
        const newBottles = Math.floor(emptyBottles / a) * b;
        gotBottles += newBottles;  
        emptyBottles = (emptyBottles % a) + newBottles;
    }
    return gotBottles
}

 

혼자서 문제 풀이 실패.

 

답에 거의 가까웠지만... 계속 생각해도 다른 답이 나와서 포기하고 gpt의 힘을 빌렸다.

 

while문을 사용하여 빈병의 갯수를 계속 업데이트해주면서 계산을 반복하는 로직이다.

다시 풀어볼 필요가 있을 듯 하다. 

문제 2

"명예의 전당"이라는 TV 프로그램에서는 매일 1명의 가수가 노래를 부르고, 시청자들의 문자 투표수로 가수에게 점수를 부여합니다. 매일 출연한 가수의 점수가 지금까지 출연 가수들의 점수 중 상위 k번째 이내이면 해당 가수의 점수를 명예의 전당이라는 목록에 올려 기념합니다. 

 

즉 프로그램 시작 이후 초기에 k일까지는 모든 출연 가수의 점수가 명예의 전당에 오르게 됩니다. k일 다음부터는 출연 가수의 점수가 기존의 명예의 전당 목록의 k번째 순위의 가수 점수보다 더 높으면, 출연 가수의 점수가 명예의 전당에 오르게 되고 기존의 k번째 순위의 점수는 명예의 전당에서 내려오게 됩니다.

 

이 프로그램에서는 매일 "명예의 전당"의 최하위 점수를 발표합니다. 예를 들어, k = 3이고, 7일 동안 진행된 가수의 점수가 [10, 100, 20, 150, 1, 100, 200]이라면, 명예의 전당에서 발표된 점수는 아래의 그림과 같이 [10, 10, 10, 20, 20, 100, 100]입니다.

 

명예의 전당 목록의 점수의 개수 k, 1일부터 마지막 날까지 출연한 가수들의 점수인 score가 주어졌을 때, 매일 발표된 명예의 전당의 최하위 점수를 return하는 solution 함수를 완성해주세요.

나의 답

function solution(k, score) {
    const answer = [];
    let hallOfFame = [];
    score.forEach((scores) => {
        hallOfFame.push(scores);
        hallOfFame.sort((a, b) => a - b);
        if (hallOfFame.length > k) {
            hallOfFame.shift();
        }
        answer.push(hallOfFame[0]);
    });
    return answer
}

 

내 생각에, 이보다 더 완벽한 코드는 없다.

 

문제는 꽤 복잡해보이는데, 10분만에 vscode의 도움 받지 않고 풀었다. 꽤 쉬운 문제였다. 

문제 3

2016년 1월 1일은 금요일입니다. 2016년 a월 b일은 무슨 요일일까요? 두 수 a ,b를 입력받아 2016년 a월 b일이 무슨 요일인지 리턴하는 함수, solution을 완성하세요. 요일의 이름은 일요일부터 토요일까지 각각 SUN,MON,TUE,WED,THU,FRI,SAT입니다.

 

예를 들어 a=5, b=24라면 5월 24일은 화요일이므로 문자열 "TUE"를 반환하세요.

나의 답

function solution(a, b) {
    let answer = "";
    let dates = {
        JAN: 31,
        FEB: 29,
        MAR: 31,
        APR: 30,
        MAY: 31,
        JUN: 30,
        JUL: 31,
        AUG: 31,
        SEP: 30,
        OCT: 31,
        NOV: 30,
        DEC: 31,
    };
    let time = 0;
    for (let i = 0; i < a - 1; i++) {
        time += Object.values(dates)[i];
    }
    time += b;
    if (time % 7 === 1) {
        answer = "FRI";
    }
    if (time % 7 === 2) {
        answer = "SAT";
    }
    if (time % 7 === 3) {
        answer = "SUN";
    }
    if (time % 7 === 4) {
        answer = "MON";
    }
    if (time % 7 === 5) {
        answer = "TUE";
    }
    if (time % 7 === 6) {
        answer = "WED";
    }
    if (time % 7 === 0) {
        answer = "THU";
    }
    return answer
}

solution(5, 24);

 

객체로 변환해줄 필요가 지금 생각해보니까 굳이 있었을까 싶다. 답은 구했으니 뭐 장땡일 지도. 

지금 생각해보니, if문을 포인터를 이용해 조금 단순화하는 것도 가능했을 거 같다. 

function solution(a, b) {
    let answer = "";
    let dates = {
        JAN: 31,
        FEB: 29,
        MAR: 31,
        APR: 30,
        MAY: 31,
        JUN: 30,
        JUL: 31,
        AUG: 31,
        SEP: 30,
        OCT: 31,
        NOV: 30,
        DEC: 31,
    };
    let dayOfTheWeek = ["THU", "FRI", "SAT", "SUN", "MON", "TUE", "WED"];
    let time = 0;
    for (let i = 0; i < a - 1; i++) {
        time += Object.values(dates)[i];
    }
    time += b;
    let pointer = 0;
    pointer += time % 7;
    answer = dayOfTheWeek[pointer];
    return answer;
}

 

그래서 단순화해왔습니다. 

리액트 입문 강의

구조 분해 할당. 

const movie = {
    title: "Inception",
    director: "Christopher Nolan",
    release: {
        year: 2010,
        month: "July",
    },
};

 

이 객체를 title과 감독으로 구조분해할당한다고 가정해보자면 

const { title, director } = movie;

 

이렇게 표현이 가능하다. 

 

근데 release 안에 있는 객체를 구조분해할당해주고 싶다면? 한 번 더 해야한다. 

const {
    title,
    release: { year },
} = movie;

 

그럼 이렇게 된다! 

function confirmReservation(user) {
    // 여기에 user 객체를 구조 분해 할당 하세요.
    const {name, roomType, date: firstDate} = user;
   
    return `${name} 고객님의 ${roomType}룸 입실날짜는 ${firstDate} 입니다.`
}

const userInfo = {
name: "James",
roomType: "Deluxe",
date: "2023-05-30"
}
const result = confirmReservation(userInfo);
console.log(result); // 출력 결과: 'James 고객님의 Deluxe룸 입실날짜는 2023-05-30 입니다.'

 

이런 식으로, 구조분해할당시의 변수 이름을 바꿔줄 수도 있다. 

rest 오퍼레이터

// rest operator
// 1. 함수의 매개변수.

function sum(...numbers) {
    console.log(numbers) // [1, 2, 3, 4, 5]
    return numbers.reduce((acc, cur) => acc + cur);
}

const result = sum(1, 2, 3, 4, 5);
console.log(result);

 

sum 안에 배열 표시 []가 없음에도 불구하고, 내부에서 numbers를 배열 취급되게 만들어주고 있다. 

스프레드와 비슷하면서도 조금 다르다. 

// (2) 객체 분해 할당 시, 여러 값을 그룹핑.

const person = {
    name: "John",
    age: 30,
    country: "USA",
    occupation: "Developer",
};

const { country, ...rest } = person;
console.log(rest); // { name: "John", age: 30, occupation: "Developer" }

논리야 놀자 연산자

논리야 놀자~

 

// 유저 정보가 제공되지 않았을 때 기본 이름 제공
function getUsername(user) {
    return user.name || '신원미상';
}

console.log(getUsername({ name: '르탄이' })); //르탄이
console.log(getUsername({})); //신원미상

 

논리 합 연산자(or을 쓸 때 사용하는 ||)를 이렇게 쓰면, 앞의 값이 falsy한 값일 때를 평가하고, 뒤의 값을 반환한다. 

// 사용자가 로그인 상태이면 환영 메시지를 출력
let loggedIn = true;
let username = '르탄이';
loggedIn && console.log('환영합니다! ' + username); //환영합니다! 르탄이

loggedIn = false;
loggedIn && console.log('환영합니다! ' + username); //아무것도 출력되지 않음

 

논리 곱 연산자(and를 쓸 때 사용하는 &&)를 이렇게 쓰면 앞의 값이 truthy한 값일 때를 평가하고, 뒤의 값을 반환한다. 

// 사용자의 위치 설정이 없으면 기본 위치를 제공
let userLocation = null;
console.log(userLocation ?? 'Unknown location');  // 출력: Unknown location

userLocation = 'Seoul';
console.log(userLocation ?? 'Unknown location');  // 출력: Seoul

// 사용자 입력이 0인 경우에도 0을 유효한 값으로 취급
const temperature = 0;
console.log(temperature ?? 25);  // 출력: 0

 

null 병합 연산자는 논리 합 연산자와 비슷한데, 앞의 값이 null 아니면 undefined일 때를 평가하고 뒤의 값을 반환한다. 

 

truthy falsy와 관계없이, null만 undefined만을 평가한다. 헷갈리지 않게 조심하다. 

 

optional chaining

const user = {
    profile: {
        name: "르탄이",
        details: {
            age: 30,
            location: "서울시 강남구"
        }
    }
};

 

이런 객체가 있다고 가정해보자. 

const user = {};
console.log(user.profile?.details?.hobby);  // 출력: undefined

 

지금 나는 객체에는 존재하지 않는 값인 hobby를 출력하려 했는데, 이는 details 안에는 없는 값이다.

원래는 이 때, 에러가 뜨게 되는데, 이 때 출력을 undefined로 고쳐준다. 

const result = user.profile?.getDetails?.();  // user.profile.getDetails가 존재하지 않으면 undefined 반환

 

이런 식으로 메서드에서도 적용할 수 있다. 

 

본격적인 리액트 강의.

리액트를 시작하려면 yarn을 통해서 미리 만들어진 꾸러미 비스무레한 걸 받아와서 세팅하는 게 편하다.

 

이 꾸러미에는 크게 CRA, 그리고 VITE가 있는데, 우리는 VITE를 쓸 예정. 새로운 프로젝트를 만들 때마다, VITE를 통해서 개발 환경을 세팅해준 다음에 진입해야할 듯 하다. 

 

리액트는 SPA를 추구한다. 복수의 페이지를 두지 않고, 하나의 페이지에서 모든 걸 해결한다고 생각하면 될 듯 하다. 

 

근데 우리 눈에는 복수의 페이지가 있는 것처럼 보이는데, 이건 일종의 트릭. 

 

브라우저 내에서만 취급하는 데이터인 해시를 통해서 사용자로부터 정보를 받고, 그 정보를 통대로 하나밖에 없는 HTML에 자바스크립트를 통해 이런저런 정보를 없앴다가 표시했다가를 하는 듯 하다. 

 

컴포넌트란?

 

UI를 재사용이 가능한 개별적인 여러 조각으로 나누고, 그 조각을 개별적으로 살펴볼 수 있다. 

-> 자바 스크립트 함수와 매우 유사하다. props라는 임의의 입력을 받고, react엘리먼츠를 반환한다. 

 

클래스 형 컴포넌트도 있는데, 잘 쓰지 않는다. 쓰는 걸 지양하는 듯. 

 

컴포넌트를 볼 때는 여러 영역으로 나누어서 보는 게 편하다. 

1. import하는 영역. 맨 위에 있음. 컴포넌트 밖. 내가 필요한 파일을 가져오는 영역.

2. 자바 스크립트만을 쓸 수 있는 영역, return 위.

3. return 아래. JSX를 쓸 수 있는 영역. (HTML)과 유사한 코드를 입력하는 곳. 

JSX 문법

import React from "react";

const App = () => {
    const number = 1000;
    const arr = [1, 2, 3, 4, 5];
    return <div id="abc" className="abc"><p style={{
      color : "orange",
      fontSize: "20px"
    }}>첫 번째 줄</p></div>;
};

export default App;

 

return 위로는 자바스크립트 문법.

아래로는 html과 굉장~히 유사한 jsx 문법을 사용한다. 

 

jsx에서 {} 중괄호 안에 들어있는 건 '자바스크립트 요소'를 불러들일 때 사용한다. 이 안에 함수를 넣을 수도 있고, 변수도 넣을 수도 있다. 

 

단, html과 몇 가지 차이가 존재한다. class는 className을 통해서 선언하며, css에서 사용했던 각종 언어들에 들어가 있던 - 대신, 카멜 케이스를 사용해서 표기한다.

 

그리고 이런 식으로 스타일을 적용할 때는 '객체 방식'으로 불러들이는 형태를 취한다고 한다. 

 

또한 부모 자식 관계 역시 설정해줄 수 있다. 

 

// src/App.js
import React from "react";

function Child() {
  return <div>연결 성공</div>;
}

function Mother() {
  return <Child />;
}

function GrandFather() {
  return <Mother />;
}

function App() {
  return <GrandFather />;
}

export default App;

 

이렇게. 이런 걸 보면 컴포넌트의 맨 앞 글자는 무조건 대문자로 만들어야하는 듯 하다. 왜냐하면 소문자로 입력하면 그냥 html요소로 읽어들이기 때문... 이라고 한다. 

 

Props

 

1. 컴포넌트끼리의 정보교환 방식. 

부모 컴포넌트가 자식 컴포넌트에게 물려준 데이터. 컴포넌트 간의 정보 교류 방법. 

props는 반드시 위에서 아래 방향으로 흐른다. 즉, 부모에서 자식 방향으로만 흐른다. 

 

2. props는 읽기 전용이며, 변경하지 않는다. 

import React from "react";

function User(props) {
  return <div>{props.children}</div>;
}

function App() {
  return <User>안녕하세요</User>;
}
export default App;

 

이런 식으로 props를 이용하면 사전에 만들어준 컴포넌트를 일종의 태그처럼도 쓸 수 있다.

 

레이아웃을 만들 때 주로 사용하는 방법.

 

지금 보면 Users 안에 props가 들어가 있는데, 이걸 콘솔 로그를 찍어보면

 

 

이런 식으로 객체를 반환한다. 이 경우에는 칠드런이라는 키와 안녕하세요라는 밸류를 가진 객체가 반환됨을 알 수 있다. 

이걸 응용하면 위처럼 배경을 고정하고 싶을 때, 유저라는 컴포넌트를 일종의 html 태그처럼 쓸 수도 있는 것이다. 

 

state

 

리액트에서는 모종의 값을 변경할 때, 단순히 변경할 수가 없어서 state라는 걸 써야 한다. 

 

import React, { useState } from "react";

const App = () => {
    const [name, setName] = useState("썬더 드래곤");
    return (
        <>
            <h1>React state</h1>
            <button
                onClick={() => {
                    setName("초뇌룡-썬더 드래곤");
                }}
            >
                초뇌룡으로!
            </button>
            <p>{name}</p>
        </>
    );
};

export default App;

 

 

useState 안에 들어가 있는 썬더 드래곤은 기본값이다. 

 

버튼을 누르면 초뇌룡으로 바뀌게 하고 싶은데...

 

바꾸게 하려면 setName이라는 함수를 실행시키고, 그 안에 바꾸고 싶은 값을 넣으면 된다. 

 

초뇌룡이 좋아

 

import React, { useState } from "react";

const App = () => {
    const [test, setText] = useState("");
    const handleInputChange = (event) => {
        setText(event.target.value);
    };
    console.log(test);
    return (
        <div>
            <input type="text" onChange={handleInputChange} value={test} />
            <br />
            {test}
        </div>
    );
};

export default App;

 

state를 이용하면 이런 로직도 작성이 가능하다. 

 

내가 input안에 뭔가 입력할 떄마다, 아래 test가 실시간으로 반영되며 바뀐다! 

 

import React, { useState } from "react";

function App() {
    const [items, setItems] = useState([1, 2, 3]);

    const addItem = () => {
        setItems([...items, items.length + 1]); // 상태 업데이트
    };

    return (
        <div>
            <button onClick={addItem}>Add Item</button>
            <ul>
                {items.map((item, index) => (
                    <li key={index}>{item}</li>
                ))}
            </ul>
        </div>
    );
}

export default App;

 

 

우리가 state를 쓰는 이유는 react에서 상태 변화를 감지시키기 위해서이다. 아무리 변수를 바꾸든 뭘 하든 state가 바뀌지 않으면 리액트에선 이를 잡아내지 못 한다. 그래서 뭔가 변화를 줄 때에는 무조건 state를 써야한다.

 

그리고 변화를 줄 때, 배열에 push같은... 알고리즘에서 자주 썼던 메서드를 사용하면 원본이 와장창난다.

 

이러면 코드가 겉잡을 수 없을 정도로 망가지기 쉽기 때문에, 원본을 유지하면서 원하는 결과를 출력할 수 있게끔 만들어야 한다. 

 

그래서 스프레드 오퍼레이터를 통한 얕-복을 이용해 원본을 손상시키지 않으면서 state를 통해 변하는 무언가를 만들 수 있다. 위에는 버튼을 클릭하면 계속 새로운 리스트가 추가된다.