NomadCoder React(1)

박영빈·2023년 5월 11일

멋쟁이사자처럼

목록 보기
2/5

4주차 과제 - NomadCoder React

1. Introduction

Why React?


  • 최근 대다수의 웹 사이트가 사용 중임
    • 특히 에어비엔비, 인스타그램, 페이스북, 넷플릭스 등 메이저 회사들이 사용
    • 페이스북이라는 큰 회사가 계속해서 투자 중이며 변화 중
  • 생태계도 아주 커서 정보를 얻기 유리함
    • framework들도 아주 다양함..!



2. The Basics of React

  • 버튼을 만들고 누를 때마다 텍스트를 업데이트하고 싶다?
    • HTML로 버튼을 만들고, button을 js로 가져오고, event listener를 만들고,, 이 과정들에 대해 지름길을 만든 것이 React.js
  • 바닐라 JS로 버튼을 클릭하면 버튼 클릭 횟수를 출력하는 웹사이트
    <!DOCTYPE html>
    <html>
    <body>
        <span>Total Clicks: 0</span>
        <button id="btn">Click me</button>
    </body>
    <script>
        let counter = 0;
        const button = document.getElementById("btn");
        const span = document.querySelector("span");
        function handleClick(){
            console.log("클릭하였음");
            counter += 1;
            span.innerText = `Total Clicks: ${counter}`;
        }
        button.addEventListener("click", handleClick);
    </script>
    </html>
  • 간단한 동작임에도 꽤 긴 코드를 작성해야 한다.

  • React를 사용하여 button을 만드는 가장 어려운 방법부터 살펴보자
<!DOCTYPE html>
<html>
<body>
    <div id="root"></div>
</body>
<script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
<script>
    // 이거 암기할 필요 없음 이렇게 안 함
    const root = document.getElementById("root");
    const h3 = React.createElement("h3", {
        id:"sexy-span", style : {color:"red"}, onMouseEnter : () => console.log("mouse enter")
    }, "Hello, I'm a span"); // tag는 HTML에서 쓰는 그대로 작성해야 함
    // 우리는 html을 직접 작성하지 않을 것임, react가 작성하도록 하자 interactive하게!
    const btn = React.createElement("button", {
        onClick: () => console.log("i'm clicked") // 이렇게 eventListener를 작성 가능
    }, "Click me");
    const container = React.createElement("div", null, [h3,btn]);
    ReactDOM.render(container, root); // container를 root 안에 표시하겠다.
</script>
</html>
  • 위와 동일한 코드지만 javascript로 html을 전부 작성한다.
  • element의 property로 style, id, event까지 전부 설정 가능하다.
  • 이게 react JS의 기초

JSX

  • JavaScript를 확장한 문법, React 요소들을 만들 수 있게 해줌
  • HTML과 문법이 비슷함
    <!DOCTYPE html>
    <html>
    
    <body>
        <div id="root"></div>
    </body>
    <script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
        // 이거 암기할 필요 없음 이렇게 안 함
        const root = document.getElementById("root");
        const Title = (
            <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
                Hello, I'm a Title
            </h3>
        ); // html과 상당히 유사하게 작성 가능, JSX
        const Button = (
            <button
                style={{ backgroundColor: "tomato" }}
                onClick={() => console.log("i'm clicked")}
            >
                Click me
            </button>
        );
        const container = React.createElement("div", null, [Title, Button]);
        ReactDOM.render(container, root); // container를 root 안에 표시하겠다.
    </script>
    
    </html>
  • 브라우저는 JSX를 이해하지 못하므로 Babel을 통해 변환시킨다.
    <!DOCTYPE html>
    <html>
    
    <body>
        <div id="root"></div>
    </body>
    <script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
        // 이거 암기할 필요 없음 이렇게 안 함
        const root = document.getElementById("root");
        const Title = () => (
            <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
                Hello, I'm a Title
            </h3>2
        ); // html과 상당히 유사하게 작성 가능, JSX
        const Button = () => (
            <button
                style={{ backgroundColor: "tomato" }}
                onClick={() => console.log("i'm clicked")}
            >
                Click me
            </button>
        );
        const Container = () => (<div>
            <Title /> <Button /> 
        </div>); // Component의 첫글자는 반드시 대문자여야 함, 소문자면 그냥 html 태그가 돼버림
        ReactDOM.render(<Container />, root); // container를 root 안에 표시하겠다.
    </script>
    
    </html>



3. STATE

Understanding State


<script type="text/babel">
    const root = document.getElementById("root");
    let counter = 0;
    function countUp() {
        counter = counter + 1;
        Render();
    }
    function Render(){
        ReactDOM.render(<Container />, root); // 여기에다가 리렌더링 하는 코드를 추가하면 된다!
    }
    const Container = () => ( // 얘는 그저 함수일 뿐
        <div>
            <h3>Total clicks: {counter}</h3>
            <button onClick={countUp}>Click me</button>
        </div>
    );
    // 별도의 이벤트리스너 없이 함수를 element에 넣어서 동작 가능
    // 하지만 바로 반영되지 않음 -> UI 업데이트는 밑에서 한 번만 실행되기 때문 -> line 19
    // vanillaJS에서는 div->h3->count가 전부 업데이트 되지만 React에서는 count만 업데이트 된다.
    Render(); // render하는 함수 호출
</script>

</html>
  • JSX와 변수를 사용하여 업데이트하는 방식 학습
  • React가 VanillaJS보다 강력한 점 → 전부를 새로 업데이트 하는 것이 아니고 변경되는 요소만 업데이트 된다!
  • but 매 함수 호출마다 Render()를 호출해야 하는 불편함이 존재

<script type="text/babel">
    const root = document.getElementById("root");
    function App(){ // 얘는 그저 함수일 뿐
        // const data = React.useState(101);
        // console.log(data); // [undefined, f] 리스트 출력 -> 순서대로 counter와 countUp의 역할을 한다.
        // const counter = data[0];
        // const modifier = data[1];
        // 이렇게 해도 되지만 좀 더 나은 방식이 존재함
        const [counter, modifier] = React.useState(0);
        return (
            <div>
                <h3>Total clicks: {counter}</h3>
                <button>Click me</button>
            </div>
        );
    };
    ReactDOM.render(<App />, root);
</script>
  • React.useState()의 활용법과 해당 리스트의 변수 사용법
  • const [counter, modifier] = React.useState(0); → 이렇게 하면 data[0] 이렇게 안 해도 됨


<script type="text/babel">
    const root = document.getElementById("root");
    function App(){ // 얘는 그저 함수일 뿐
        const [counter, setCounter] = React.useState(0);
        const onClick = () => {
            //counter += 1;
            //console.log(counter);
            // state를 사용하긴 했지만 여전히 리렌더링은 일어나지 않음 -> 변수를 직접 사용하지 말고 modifier를 써보자
            setCounter(counter+1); // 여기서 counter = 15121; ReactDOM.render()가 전부 일어남
        };
        
        return (
            <div>
                <h3>Total clicks: {counter}</h3>
                <button onClick = {onClick}>Click me</button>
            </div>
        );
        
    };
    ReactDOM.render(<App />, root);
</script>
  • useState에서 사용하는 함수를 통해 값을 변경하면
    • counter += 1
    • ReactDOM.render()가 전부 일어난다.

⇒ 결국 render 함수를 일일이 체크해서 작성할 필요 없이 매번 해줌

  • state가 변경될 때마다 매번 rendering이 진행된다.

<script type="text/babel">
    function App() { // 얘는 그저 함수일 뿐
        const [minutes, setMinutes] = React.useState();
        const onChange = (event) => {
            setMinutes(event.target.value);
            console.log(event);
        };
        const reset = () =>{
            setMinutes(0);
        }
        return (
            <div>
                <div>
                    <h3>Super Converter</h3>
                    <label htmlFor="minutes" >Minutes</label>
                    <input
                        value={minutes}
                        id="minutes"
                        placeholder="Minutes"
                        type="Number"
                        onChange={onChange}
                    />
                </div>
                <div>
                    <label htmlFor="hours" >Hours</label>
                    <input value={minutes/60} id="hours" placeholder="Hours" type="Number" disabled />
                </div>
                <button className="reset" onClick={reset}>Reset</button>
            </div>
        );
    };
    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
</script>
  • React에서 form을 다루는 방법
  • event.target.value를 통해 input 값 접근 가능하다.
  • 추가적으로 JSX는 JavaScript이므로 html에서 쓰던 for나 class를 그대로 사용 불가
    • htmlfor or className 사용
  • reset 함수는 minutes 변수 이용하여 0으로 초기

<script type="text/babel">
    function App() { // 얘는 그저 함수일 뿐
        const [amount, setAmount] = React.useState();
        const [flipped, setFlipped] = React.useState(false);
        const onChange = (event) => {
            setAmount(event.target.value);
            console.log(event);
        };
        const reset = () => {
            setAmount(0);
        }
        const onFlip = () => {
            reset();
            setFlipped((current) => !current);
        }
        return (
            <div>
                <div>
                    <h3>Super Converter</h3>
                    <label htmlFor="minutes" >Minutes</label>
                    <input
                        value={flipped ? amount*60 : amount}
                        id="minutes"
                        placeholder="Minutes"
                        type="Number"
                        onChange={onChange}
                        disabled={flipped}
                    />
                </div>
                <div>
                    <label htmlFor="hours" >Hours</label>
                    <input
                        value={flipped ? amount : Math.round(amount / 60)}
                        id="hours"
                        placeholder="Hours"
                        type="Number"
                        disabled={!flipped}
                        onChange={onChange}
                    />
                </div>
                <button className="reset" onClick={reset}>Reset</button>
                <button onClick={onFlip}>Flip</button>
            </div>
        );
    };
    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
</script>
  • Flipped state를 사용하여 각각 disabled 특성을 설정한다.
  • Flipped에 따라 두 input form에 출력하는 값을 따로 설정해준다.
    • amount 변수 하나로 두 가지 모두 해결한 것이 so cool 한 듯

script type="text/babel">
    function MinutesToHours() { // 얘는 그저 함수일 뿐
        const [amount, setAmount] = React.useState();
        const [flipped, setFlipped] = React.useState(false);
        const onChange = (event) => {
            setAmount(event.target.value);
            console.log(event);
        };
        const reset = () => {
            setAmount(0);
        }
        const onFlip = () => {
            reset();
            setFlipped((current) => !current);
        }
        return (
            <div>
                <div>
                    <h3>Minutes To Hours</h3>
                    <label htmlFor="minutes" >Minutes</label>
                    <input
                        value={flipped ? amount * 60 : amount}
                        id="minutes"
                        placeholder="Minutes"
                        type="Number"
                        onChange={onChange}
                        disabled={flipped}
                    />
                </div>
                <div>
                    <label htmlFor="hours" >Hours</label>
                    <input
                        value={flipped ? amount : Math.round(amount / 60)}
                        id="hours"
                        placeholder="Hours"
                        type="Number"
                        disabled={!flipped}
                        onChange={onChange}
                    />
                </div>
                <button className="reset" onClick={reset}>Reset</button>
                <button onClick={onFlip}>Flip</button>
            </div>
        );
    };
    function KmToMiles() {
        const [amount, setAmount] = React.useState();
        const [flipped, setFlipped] = React.useState(false);
        const onChange = (event) => {
            setAmount(event.target.value);
            console.log(event);
        };
        const reset = () => {
            setAmount(0);
        }
        const onFlip = () => {
            reset();
            setFlipped((current) => !current);
        }
        return (
            <div>
                <div>
                    <h3>Kilometers To Miles</h3>
                    <label htmlFor="kilometers" >Kilometers</label>
                    <input
                        value={flipped ? amount * 1.61 : amount}
                        id="kilometers"
                        placeholder="kilometers"
                        type="Number"
                        onChange={onChange}
                        disabled={flipped}
                    />
                </div>
                <div>
                    <label htmlFor="miles" >Miles</label>
                    <input
                        value={flipped ? amount : Math.round(amount * 0.621)}
                        id="miles"
                        placeholder="miles"
                        type="Number"
                        disabled={!flipped}
                        onChange={onChange}
                    />
                </div>
                <button className="reset" onClick={reset}>Reset</button>
                <button onClick={onFlip}>Flip</button>
            </div>
        );
    }
    function App() {
        const [index, setIndex] = React.useState("xx");
        const onSelect = (event) => {
            setIndex(event.target.value);
        }
        return (
            <div>
                <h1>Super Converter</h1>
                <select value={index} onChange={onSelect}>
                    <option value="xx">Select your units</option>
                    <option value="0">Minutes & Hours</option>
                    <option value="1">KM & Miles</option>
                </select>
                <hr />
                {index === "xx" ? "Please select your unit" : null}
                {index === "0" ? <MinutesToHours /> : null}
                {index === "1" ? <KmToMiles /> : null}
            </div>
        ); // 기능들을 component화, 분할 정복, 유저 선택에 따라 다른 기능을 보여주고 싶음 -> App에서 state
    };
    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
</script>
  • 최종적으로 Mitutes to Hours, Kilometers To Miles 두 가지 기능을 구현한 계산기


4. Props

<script type="text/babel">
    //props 하나로 모든 인자 핸들링, 객체임
    function Btn({banana, big}) {
        return <button style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            borderRadius: 10,
            fontSize: big ? 18 : 14,
        }}
        >
            {banana}
        </button>
    }
    // props.banana로 사용해도 되지만 shortcut으로 {banana}로 사용 가능
    function ConfirmBtn() {
        return <button>Confirm</button>
    }
    // 1.component는 JSX를 반환하는 단지 함수일 뿐임
    // 2.두 버튼은 텍스트만 다른데 스타일을 계속 지정하는건 귀찮음 -> 텍스트만 바꾸자
    // 3. ConfirmBtn 삭제, SaveBtn -> Btn
    function App() {
        return (
            <div>
                <Btn banana ="SaveChange" big={true} />
                <Btn banana ="Confirm" />
                
            </div>
        ); // 인자를 넘기는 것과 동일하게 동작, 인자명은 마음대로
    };
    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
</script>
  • 함수에 인자를 넘기는 방법에 대해 학습하였다.
  • 같은 button인데 텍스트만 바꾸고 싶다? 함수를 여러 개 선언하지 않고 하나만 선언해서 내부 텍스트를 인자로 받자
  • props라는 객체를 쓰면 props.banana 이렇게 사용 가능하지만 보통 {banana, big} 이런 식으로 shortcut을 많이 쓴다.

<script type="text/babel">
    function Btn({ banana, changeValue }) {
        console.log(banana, "was created");
        // 앞의 버튼만 리렌더링되지 않고 두 버튼 다 리렌더링된다.
        // 이걸 방지하려면? React memo, memorize
        return (
            <button
                onClick={changeValue}
                // function을 인자로 받아와서 이벤트 리스너에 직접 넣어줘야 함
                style={{
                    backgroundColor: "tomato",
                    color: "white",
                    padding: "10px 20px",
                    borderRadius: 10,
                }}
            >
                {banana}
            </button>
        )
    }
    function ConfirmBtn() {
        return <button>Confirm</button>
    }
    const MemorizedBtn = React.memo(Btn); // 이걸 사용하자
    function App() {
        const [value, setValue] = React.useState("Save Changes");
        const changeValue = () => {
            setValue("Revert Changes");
        }
        return (
            <div>
                <MemorizedBtn banana={value} changeValue={changeValue} />
                <MemorizedBtn banana="Confirm" />

            </div>
        ); // <Btn banana = {value} onClick={changeValue}/> 같이 작성하는건 그저 인자지 절대 이벤트리스너가 아님
        // 해당 컴포넌트 내부에 이벤트를 직접 작성해야 함
    };
    // 핵심은 부모 컴포넌트의 state가 변경되면 자식 컴포넌트는 모두 re-render됨, 일단 알아만두자
    // 해당 컴포넌트의 props가 변경되어야만 변경된다.
    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
</script>
  • 인자로 함수를 넘기는 것 가능, 그렇지만 인자에 적어넣는다고 자동으로 이벤트리스너에 등록되는 것이 아님을 명심할 것
  • 직접 받아온 함수를 이벤트리스너에 작성해야 한다.
  • 부모 컴포넌트가 변경되면 자식들은 매번 변경 되는데 이걸 막기 위해 → React memo, memorize 활용
  • React.memo를 사용하여 원하지 않는 부분은 매번 re-render 하지 않도록 설정 가능

<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/prop-types@15.7.2/prop-types.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
    //props 하나로 모든 인자 핸들링, 객체임
    function Btn({ banana, fontSize }) {
        console.log(banana, "was created");
        return (
            <button
                style={{
                    backgroundColor: "tomato",
                    color: "white",
                    padding: "10px 20px",
                    borderRadius: 10,
                    fontSize: fontSize,
                }}
            >
                {banana}
            </button>
        )
    }
    const MemorizedBtn = React.memo(Btn); // 이걸 사용하자
    Btn.propTypes = {
        banana: PropTypes.string.isRequired,
        fontSize: PropTypes.number,
    };
    // 여기서 타입 검사 가능, type을 지정하면 해당 타입이 아닐 경우 오류 발생, isRequired를 지정하면 입력 안할시 오류 발생
    function App() {
        const [value, setValue] = React.useState("Save Changes");
        const changeValue = () => {
            setValue("Revert Changes");
        }
        return (
            <div>
                <Btn banana={value} fontSize={20} />
                <Btn banana="Confirm" />
            </div>
        ); // 이렇게 fontSize에 문자열을 넣어도 코드상 에러는 없음, React는 무슨 타입을 받을지 모른다
        // 그치만 propTypes를 지정하여 타입 검사 가능
    };
    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
</script>
  • prop의 타입을 검사할 수 있음
  • 검사 없이는 React가 해당 prop에 뭐를 받아야 할지 모름
    • fontSize인데 텍스트를 받아서 저장할 수도 있음
  • PropTypes을 사용하는데 이걸 쓰려면 위에 script 추가하는 것 잊지 말기
  • 타입을 지정하면 해당 타입이 아니면 오류 발생
  • 마찬가지로 isRequired를 지정하면 입력 안 하면 오류 발생



5. Create React APP

  • 이전까지는 스크립트를 일일이 추가해서 전부 직접 작성해주었다.
  • 하지만 create-react-app을 사용하여 한 번에 설정이 가능하다!
  • node.js를 설치하고 npx가 동작하는지 확인해보자
  • npx create-react-app (project name)
  • npm start
  • 야호 잘 수행된다. 강의와 버전이 달라서 생기는 코드들은 깔끔하게 정리하자

  • recat로 작성함에 따라 각 요소를 하나의 js파일로 작성 가능하고, css파일을 개별적으로 작성이 가능해졌다.
  • style을 하나의 모듈로 적용이 가능하다.
  • CRA가 style을 모든 class 이름을 랜덤하게 설정하므로 나중에 classname이 겹쳐도 상관없음



6. Effects

useEffect

import { useState, useEffect } from "react";
import styles from "./App.module.css";
// useEffect가 개발되어 있음 -> 첫 인자로는 한 번만 실행하고 싶은 코드, 두 번째 인자는 좀 나중에 설명
function App() {
  const [counter, setCounter] = useState(0);
  const [keyword, setKeyword] = useState("");
  // CRA를 써서 일일이 React.~ 쓸 필요 없음
  const onClick = () => setCounter((prev) => prev + 1);
  const onChange = (event) => {
    setKeyword(event.target.value);
  };
  // 특히나 input에서 변화할때마다 render하면 문자 하나 입력할 때마다 출력된다.
  console.log("i run all the time");
  useEffect(() => {
    console.log("i run only once.");
  }, []);
  // useEffect안에 넣어 놓은 코드는 첫 실행 시 한번만 출력된다.
  // console.log("SEARCH FOR", keyword);
  // 이것도 다른 state 변화 시 계속 출력 됨 -> 우리가 원하는건 키워드를 입력할 때만 검색하는 것
  useEffect(() => {
    if (keyword != "" && keyword.length > 5) {
      console.log("SEARCH FOR", keyword);
    }
  }, [keyword]); // 이렇게 하면 keyword가 변할 때만 실행 가능하다.
  useEffect(() => {
    console.log("i run when counter changes");
  }, [counter]); // 이렇게 하면 counter가 변할 때만 실행 가능하다.
  useEffect(() => {
    console.log("i run when keyword and counter changes");
  }, [keyword]); // 이렇게 하면 둘 다 변할 때 모두 실행된다..
  return (
    <div>
      <input
        value={keyword}
        onChange={onChange}
        type="text"
        placeholder="Search here..."
      />
      <h1>{counter}</h1>
      <button onClick={onClick}>click me</button>
    </div>
  );
}
// 첫 render시에만 코드가 실행되도록 하길 원할 수 있음 ex) API 호출
export default App;
  • 우리가 앞서 배운 state를 활용하여 매번 rendering을 할 수 있도록 했다.
  • 이는 아주 유용하지만 때로는 원할 때만 실행했으면 하는 코드가 있을 수 있다.
  • 이를 위해 useEffect를 학습하였다.
  • useEffect(function, []); ⇒ function에는 실행할 코드를 넣고, 뒤의 괄호에는 계속해서 관찰할 state를 넣어준다
  • 그러면 state가 변할 때만 실행된다!

Cleanup

import { useState, useEffect } from "react";
import styles from "./App.module.css";

function Hello() {
  function byFn(){
    console.log("Bye :(");
  }
  function hiFn(){
    console.log("Created :)")
    return byFn;
  }
  useEffect(hiFn,[]);
  return <h1>Hello</h1>;
}

function App() {
  const [showing, setShowing] = useState(false);
  const onClick = () => {
    setShowing((prev) => !prev);
  };
  return (
    <div>
      {showing ? <Hello /> : null}
      <button onClick={onClick}>{showing ? "Hide" : "Show"}</button>
    </div>
  );
  // 버튼을 누르면 Hello가 생기고 다시 누르면 destroy된다.
  // 따라서 Created는 새로 생성 될 때마다 실행된다.
  // 파괴할 때도 실행하고 싶다면?
  // useEffect에서 return으로 넣어두자
}

export default App;
  • 자주 쓰이지는 않지만 component가 사라질 때 함수를 쓰고 싶을 수 있음
  • 그럴 때는 useEffect에서 호출하는 함수에 return 값으로 사라질 때 호출할 함수를 return하자
profile
안녕하세요<br>반가워요<br>안녕히가세요

0개의 댓글