우아한 Tech React&TypeScript #4

sangha__ju·2020년 9월 17일
0

함수 컴포넌트의 상태와 컴포넌트 분리

react 어플리케이션을 생성한다.

  • index.js
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
    document.getElementById("root")
);
  • App.js
import React from "react";

const App = () => {
    return (
        <div>
            <header>
                <h1>React and TypeScript</h1>
            </header>
            <ul>
                <li>1회차: Overview</li>
                <li>2회차: Redux 만들기</li>
                <li>3회차: React 만들기</li>
                <li>4회차: 컴포넌트 디자인 및 비동기</li>
            </ul>
        </div>
    );
};

export default App;

App.js 에서 li 태그 내부에 있는 값들이 상태라고 볼 수 있다.
li 태그 내부의 데이터를 index.js 에 분리 해 속성으로 App 컴포넌트에 넘겨 줄 수 있다.

  • index.js
...
const sessionList = [
    { title: "1회차: Overview" },
    { title: "2회차: Redux 만들기" },
    { title: "3회차: React 만들기" },
    { title: "4회차: 컴포넌트 디자인 및 비동기" },
];

ReactDOM.render(
    <React.StrictMode>
        <App store={{ sessionList }} />
    </React.StrictMode>,
    document.getElementById("root")
);

index.js 수정 해 주면서 App.js 또한 같이 수정 될 수 있다.

...
const App = (props) => {
    const { sessionList } = props.store;

    return (
        <div>
            <header>
                <h1>React and TypeScript</h1>
            </header>
            <ul>
                {sessionList.map((session) => (
                    <li>{session.title}</li>
                ))}
            </ul>
        </div>
    );
};
...

하지만 map 내부에 li 태그를 생성하는 방식을 가지고 있다.
태그를 반환하는 코드가 많아질 경우 가독성이 떨어지기 때문에 컴포넌트를 한번 더 분리 하는 것이 좋다.

  • App.js
...
const SessionItem = ({ title }) => <li>{title}</li>;

const App = (props) => {
    const { sessionList } = props.store;

    return (
        <div>
            <header>
                <h1>React and TypeScript</h1>
            </header>
            <ul>
                {sessionList.map((session) => (
                    <SessionItem title={session.title} />
                ))}
            </ul>
        </div>
    );
};
...

SessionItem 과 App 컴포넌트는 상태를 가지고 있지 않다.
App 컴포넌트의 재정렬 버튼을 누르면 오름차순, 내림차순으로 바꿔 보여 주는 기능을 만들어 보자.

  • App.js
...
const App = (props) => {
    const { sessionList } = props.store;
    const orderedSessionList = sessionList.map((session, i) => ({
        ...session,
        order: i,
    }));

    return (
        <div>
            <header>
                <h1>React and TypeScript</h1>
            </header>
            <button>재정렬</button>
            <ul>
                {orderedSessionList.map((session) => (
                    <SessionItem title={session.title} />
                ))}
            </ul>
        </div>
    );
};
...

목록을 정렬하는 기준에 대한 상태를 저장하기 위해 다음과 같은 방법으로 생각 해 볼 수 있다.

const App = (props) => {
    let displayOrder = "ASC";
    const { sessionList } = props.store;
    const orderedSessionList = sessionList.map((session, i) => ({
        ...session,
        order: i,
    }));

    const toggleDisplayOrder = () => {
        displayOrder = displayOrder === "ASC" ? "DESC" : "ASC";
    };

    return (
        <div>
            <header>
                <h1>React and TypeScript</h1>
            </header>
            <button onClick={toggleDisplayOrder}>재정렬</button>
            <ul>
                {orderedSessionList.map((session) => (
                    <SessionItem title={session.title} />
                ))}
            </ul>
        </div>
    );
};

displayOrder 라는 변수를 선언하고 ASC 와 DESC 를 변환하는 toggleDisplayOrder 라는 함수를 생성했다.
버튼의 onClick 이벤트에 toggleDisplayOrder 함수를 넣어 displayOrder 를 변경하고자 한다.

하지만 지금 상태에서는 아무리 버튼을 클릭해도 displayOrder 값은 변경되지 않고 다시 렌더링 되지도 않는다.
함수형 컴포넌트에서 상태를 만들기 위해 useState 라는 Hook을 사용한다.

App 컴포넌트를 수정하고 useState 를 이용해 displayOrder 상태를 만들 수 있다.

const App = (props) => {
    const [displayOrder, setDisplayOrder] = useState("ASC");
    const { sessionList } = props.store;
    const orderedSessionList = sessionList.map((session, i) => ({
        ...session,
        order: i,
    }));

    const toggleDisplayOrder = () => {
        setDisplayOrder(displayOrder === "ASC" ? "DESC" : "ASC");
    };

    console.log(displayOrder);

    return (
        <div>
            <header>
                <h1>React and TypeScript</h1>
            </header>
            <button onClick={toggleDisplayOrder}>재정렬</button>
            <ul>
                {orderedSessionList.map((session) => (
                    <SessionItem key={session.id} title={session.title} />
                ))}
            </ul>
        </div>
    );
};

useState의 인자는 상태의 초기값이며 [상태, 상태를 변경하는 함수] 형태의 배열을 반환한다.

useState 의 반환값 중 상태를 변경하는 함수를 이용 해 setState 와 같게 상태를 변경 할 수 있다.
displayOrder 변수가 ASC 와 DESC 로 번갈아가며 바뀌는 것을 확인 할 수 있다.

제너레이터 / 비동기

제너레이터는 function* 키워드로 선언한다.

function* foo() {}

비동기 함수는 function에 async 키워드를 붙여 사용한다.

async function bar() {}

제너레이터와 비동기 함수는 Promise 와 밀접한 연관이 있다.
아래는 동기적으로 실행되는 js 코드의 예시이다.

const x = 10;
const y = x * 10;

y 변수에 x 라는 의존성이 있으므로 x 가 없이 y 가 생길 수 없다.

const x = () => 10;
const y = x() * 10;

하지만 이 코드는 x 의 값이 확정되는 순간은 y 의 호출 시점이 된다.
js 는 함수를 반환 할 수 있다는 특성 때문에 x 와 같이 지연 호출이 가능하다.

const p = new Promise(function (resolve, reject) {
    setTimeout(() => {
        resolve("1");
    }, 1000);
});

p.then(function (r) {
    console.log(r);
});

Promise 객체 또한 resolve 가 지연 호출 될 수 있다.

제너레이터

제너레이터는 코루틴이라는 함수의 구현체이다.
제너레이터라는 이름이 생긴 이유는 값을 계속 생산하기 때문이다.

function* make() {
    return 1;
}

console.log(make());

일반적인 함수의 반환값은 1이 될 것이다.

하지만 function* 으로 선언 된 제너레이터는 이미지와 같이 객체가 반환된다.
제너레이터는 function* / yield 키워드를 사용한다.

function* makeNumber() {
    let num = 1;

    while (true) {
        yield num++;
    }
}

const g = makeNumber();

makeNumber() 와 같이 제너레이터를 생성해도 값이 반환되지 않는다.

실제 제너레이터의 값을 가져오기 위해서는 next() 메서드를 이용해야 한다.

console.log(g.next());

next 메서드를 호출하면 다음과 같이 객체를 반환한다.

객체의 value는 yield 로 반환 된 값이며, done 은 true 가 될 경우 제너레이터가 종료된다.
제너레이터의 next 함수는 또 다른 값을 받을 수 있다.

function* makeNumber() {
    let num = 1;

    while (true) {
        const x = yield num++;
        console.log(x);
    }
}

const g = makeNumber();

console.log(g.next());
console.log(g.next("a"));

next 에 매개변수로 넘겨준 a 가 yield 의 반환값으로 담겨 출력되는 것을 볼 수 있다.

제너레이터를 이용해 다음과 같은 코드도 작성 할 수 있다.

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

function* main() {
    console.log("시작");
    yield delay(3000);
    console.log("3초 뒤");
}

const it = main();

const { value } = it.next();

value.then(() => {
    it.next();
});

실행 시키면 "시작" 이 출력되고 3초가 흐른 뒤 "3초 뒤" 가 출력된다.

async-await 를 사용한 것과 비슷하게 delay 함수가 종료 될 때까지 기다리게 된다.
async-await 는 반환값이 promise 객체인 경우에만 사용 할 수 있는 키워드이다.

하지만 예제처럼 제너레이터를 이용하는 경우 promise 형태의 값이 반환되지 않아도 사용 가능하다.
적용 가능한 범위가 넓기 때문에 redux-saga 에서는 제너레이터를 통해 비동기를 처리한다.

profile
NexCloud(Nexclipper) FrontEnd Developer / Personal Learning Storage

0개의 댓글