React 기초

TechN0·2024년 12월 2일
post-thumbnail

리엑트로 엘리먼트를 만드는게 가능한데 아래 방법은 공부할때만 하고 실전에서는 이렇게 쓰지 않는답니다.

공부는 니꼴라와 함께…

<!DOCTYPE html>
<html>

<body>
    <div id="root"></div>
</body>

<script src="https://www.unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
<script>
    const root = document.getElementById("root");
    const span = React.createElement("span", {style: {color: "red"}, id: "lol"}, "안녕하세여"); // 리엑트로 엘리먼트를 생성함
    ReactDOM.render(span, root) // render : 사용자에게 보여준다 즉 html에 띄워준다?
</script>

</html>

React.createElement 의 인자를 알아보자

 React.createElement("엘리먼트의 타입", {id: "클래스명, 아이디, 스타일 등"}, “텍스트”)

위 코드대로 하면 웹에선 이렇게 나온다

이런 식으로 body에 html 코드를 직접 작성하지 않고 스크립트로 띄워줄 수 있는데 이를 이용해서 그때 그때 사용자에게 보여주고 싶은 콘텐츠를 HTML이 업데이트 하게 해서 동적으로 보여줄 수 있을 것 같다.

이벤트 리스너를 이렇게 한 줄로 만들 수도 있다

<!DOCTYPE html>
<html>

<body>
    <div id="root"></div>
</body>

<script src="https://www.unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
<script>
    const root = document.getElementById("root"); //HTML에서 id가 root인 블록을 가져옴
    const h3 = React.createElement("h3", {
        onMouseEnter: () => console.log("마우스 엔터") // 여기
    }, "안녕하세여"); // 리엑트로 엘리먼트를 생성함
    const btn = React.createElement(
        "button",
        {
            onClick: () => console.log("눌렀다"), // 여기
            style : {
                backgroundColor: "tomato"
            }
        },
        "나를 눌러요"
    );

    const container = React.createElement("div", null, [h3, btn]);// 두 원소를 한번에 출력하는 법

    ReactDOM.render(container, root); // render : 사용자에게 보여준다 즉 html에 띄워준다?
</script>

</html>

JSX

React.createElement 는 너무 길다 더 간단하게 만들고 싶어!

JSX를 활용하면 crateElement를 대체해서 더 편하게 개발할수 있다.

JSX : JS를 확장한 문법

근데 JSX를 쓰려면 JSX를 브라우저가 알아먹을수 있는 형태로 바꿔주는 바벨(BABEL)이라는 녀석이 필요하다.

<!-- BABEL 임포트 -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

임포트 부분 말고 아래에 엘리먼트들을 만들어준 스크립트에도 타입을 지정해 주어야 바벨이 작동한다

<script type="text/babel">
    const root = document.getElementById("root"); //HTML에서 id가 root인 블록을 가져옴
    const Title = 
        <h3 id="title" onMouseEnter={() => console.log("마우스 엔터")}>
            안녕하세여
        </h3>
    ;
    const Button = <button
        style={{backgroundColor: "tomato"}}
        onClick={() => console.log("눌렀다")}
    >
        나를 눌러요
    </button>
    const container = React.createElement("div", null, [Title, Button]);// 두 원소를 한번에 출력하는 법

    ReactDOM.render(container, root); // render : 사용자에게 보여준다 즉 html에 띄워준다?
</script>

컴포넌트에 에로우 펑션 () => 을 붙여주면 변수처럼 쓸 수도 있다

대신 컴포넌트의 시작 문자는 대문자 여야 함

소문자로 하면 리엑트와 JSX는 이 컴포넌트를 HTML 태그라고 인식한다

<script type="text/babel">
    const root = document.getElementById("root"); //HTML에서 id가 root인 블록을 가져옴
    const Title = () =>
        <h3 id="title" onMouseEnter={() => console.log("마우스 엔터")}>
            안녕하세여
        </h3>
        ;
    const Button = () => <button
        style={{backgroundColor: "tomato"}}
        onClick={() => console.log("눌렀다")}
    >
        나를 눌러요
    </button>
    const Container = <div><Title /> <Button /></div>;// 번수처럼 쓰기!

    ReactDOM.render(Container, root); // render : 사용자에게 보여준다 즉 html에 띄워준다?
</script>

F12를 눌러 브라우저에 표시된 코드를 봐보자

<script>"use strict";

var root = document.getElementById("root"); //HTML에서 id가 root인 블록을 가져옴
var Title = function Title() {
  return /*#__PURE__*/React.createElement("h3", {
    id: "title",
    onMouseEnter: function onMouseEnter() {
      return console.log("마우스 엔터");
    }
  }, "\uC548\uB155\uD558\uC138\uC5EC");
};
var Button = function Button() {
  return /*#__PURE__*/React.createElement("button", {
    style: {
      backgroundColor: "tomato"
    },
    onClick: function onClick() {
      return console.log("눌렀다");
    }
  }, "\uB098\uB97C \uB20C\uB7EC\uC694");
};
var Container = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Title, null), /*#__PURE__*/React.createElement(Button, null)); // 두 원소를 한번에 출력하는 법

ReactDOM.render(Container, root); // render : 사용자에게 보여준다 즉 html에 띄워준다?
</script>

함수형식으로 콘솔 로그를 리턴하는 기존의 방식으로 번역된다.

const Button = () => <button
        style={{backgroundColor: "tomato"}}
        onClick={() => console.log("눌렀다")}
    >
        나를 눌러요
    </button>
  • Button함수는 () =>뒤에 있는 값을 리턴한다는 뜻

클릭 카운팅

버튼을 클릭한 횟수를 출력하게 할건데 빨간약과 파란약이 있다.

안좋은 방법부터 해보자

<script type="text/babel">
    const root = document.getElementById("root"); //HTML에서 id가 root인 블록을 가져옴
    let counter = 0;
    function countUp() {
        counter += 1;
        ReactDOM.render(<Container />, root);
        // 랜더링을 또 해주지 않으면 변수값만 바뀌고 UI는 그대로임
    }

    const Container =() =>(
        <div>
            <h3>누른 횟수: {counter}</h3> // {}안에 변수를 넣어서 사용
            <button onClick = {countUp}>나를 눌러요</button>
        </div>);// 두 원소를 한번에 출력하는 법

    ReactDOM.render(<Container />, root); // render : 사용자에게 보여준다 즉 html에 띄워준다?
</script>
<script type="text/babel">
    const root = document.getElementById("root"); //HTML에서 id가 root인 블록을 가져옴
    let counter = 0;
    function countUp() {
        counter += 1;
        Render();
        // 랜더링을 또 해주지 않으면 변수값만 바뀌고 UI는 그대로임
    }

    function Render() {
        ReactDOM.render(<APP />, root);
    }

    function APP() {
        return(
            <div>
            <h3>누른 횟수: {counter}</h3>
            <button onClick={countUp}>나를 눌러요</button>
        </div>// 두 원소를 한번에 출력하는 법
        );
    }
    Render();
    ReactDOM.render(<APP />, root);

</script>

둘다 같은 코드인데 이렇게 표현법이 다르다고 보여주고 싶어서 두개로 해봤다 난 북쪽이 더 좋다.

버튼을 누를때 마다 counter 를 1씩 더해주고 리렌더링을 해주는 방식인데

버튼을 누를 때 마다 렌더링을 계속하면 당연히 좋지 못할 것이다.

쌩 바닐라 JS로 하면 리렌더링을 하면 바뀌지 않은 값들도 다시 렌더링 하지만

리엑트에서는 변경된 놈들만 렌더링하기 때문에 매우 인터랙티브 한 웹을 개발할 수 있다.

(이걸 눈으로 보고싶으면 바닐라 버전과 리엑트 버전을 띄우고 크롬에서 F12를 누른후 버튼을 눌러보라)

이제 좋은 약을 먹어보자

<script type="text/babel">
    const root = document.getElementById("root"); //HTML에서 id가 root인 블록을 가져옴

    function APP() {
        const [counter, setCounter] = React.useState(0); // useStates는 리턴값이 리스트인데 첫번째 원소는 변수의 값이고 두번째는 그 값을 변화시키는 함수이다
        const onClick = () =>{
            setCounter(counter + 1);
            /*
            counter += counter;
            ReactDOM.render(<APP />, root);
            //setCounter는 위 두줄을 한줄로 쓸수 있게 해준다
            */
        }
        return(
            <div>
            <h3>누른 횟수: {counter}</h3>
            <button onClick = {onClick}>나를 눌러요</button>
        </div>
        );
    }
    ReactDOM.render(<APP />, root);

</script>

따로 함수 선언도 안하고 더 짧고 잘 보이고 쉽다!

일단 function APP 안에 counter랑 setCounter을 만들어 줄 거다.

React.useState 를 쓸건데 이게 뭐냐면

React.useState 의 반환값은 변수와 그 변수를 변화시키는 함수 가 리스트로 리턴된다.

반환값

[0, f]

그럼 위에선 React.useState가 어떻게 쓰였나

const [counter, setCounter] = React.useState(0);

            [변수명, 함수명]                                     (변수 초기화 값);

리스트가 할당연산자 앞에 오는 기괴한 문법이지만 매우 편리해 보이는 문법이다.

좋지아니한가

원곡인 크라잉넛도 좋은데 YdBB밴드도 맛있습니다. 서로 다른 맛이니 누가 더 좋니 마니 싸우지 마십쇼 -_-

북쪽 코드는 쌩 바닐라로 만든 클릭 횟수 출력이고 아래는 리엑트 버전이다.

<!DOCTYPE html>
<html>
<body>
    <span>누른 횟수: 0</span>
    <br>
    <button id = "btn">Click me</button>
</body>
<script>
    let counter = 0;
    const button = document.getElementById("btn");
    const span = document.querySelector("span");
    function handleClick() {
        counter += 1;
        span.innerText = `누른 횟수: ${counter}`;
    }
    button.addEventListener("click", handleClick);
</script>
</html>
<!DOCTYPE html>
<html>
<body>
    <div id="root"></div>
</body>
<script src="https://www.unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.3.1/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");
    function APP() {
        const [counter, setCounter] = React.useState(0);
        const onClick = () =>{
            setCounter(counter + 1);
        }
        return(
            <div>
            <h3>누른 횟수: {counter}</h3>
            <button onClick = {onClick}>나를 눌러요</button>
        </div>
        );
    }
    ReactDOM.render(<APP />, root);
</script>
</html>

임포트 부분을 빼면 길이 자체는 비슷해 보이지만 리엑트를 쓰는 편이 역시 함수나 변수를 덜 선언하고 만들 수 있어서 효율이 좋고 차후 인터랙티브한 웹을 구현할 때 변경된 부분만 렌더링을 할 수 있어 더 적합하다.

근데 또 더 안전한 방법이 있다

<script type="text/babel">
    const root = document.getElementById("root");

    function APP() {
        const [counter, setCounter] = React.useState(0);
        const onClick = () =>{
            // setCounter(counter + 1);
            setCounter((current)=> current+1)// 여기를 잘 보시라
        }
        return(
            <div>
            <h3>누른 횟수: {counter}</h3>
            <button onClick = {onClick}>나를 눌러요</button>
        </div>
        );
    }
    ReactDOM.render(<APP />, root);

</script>

setCounter(counter + 1); 이거는 counter 값이 현재 counter 의 값이라는 보장이없다.

거의 없겠지만 다른데서 이 변수를 건들거나 뭔가 업데이트가 되어서(비동기 등)업데이트 전의 값이나 잘못된 값이 전달되어 버그가 날 수도 있다.

setCounter((current)=> current+1)

current 는 React가 자동 현재 상태 값을 넘겨주는 기능이라 현재 counter 의 값을 확실하게 넘겨줄 수 있다.


JSX에서 주의해야 할 점이 있다

<script type="text/babel">
    const root = document.getElementById("root");

    function APP() {
        return (
            <div>
                <h1 className="hi">짱좋은 변환기</h1>
                <label htmlFor="minutes">Minutes</label>
                <input id = "minutes" placeholder="Minutes" type="number" />
                <label htmlFor="hours">Hours</label>
                <input id ="hours" placeholder="Hours" type="number" />
            </div>
        );
    }
    ReactDOM.render(<APP />, root);

</script>

html속성 과 JSX의 예약어가 겹친다는 것인데

class 는 className

for 는 htmlFor

로 적어야한다.


잠시 쉬어가는 타임

JS에는 == 연산자와 ===연산자가 있습니다.

둘이 뭐가 다를까요~?

== 연산자는 두 값을 비교할 때, 타입을 자동으로 변환하여 비교합니다! 자료형이 달라도 값만 같으면 true를 반환하지요

=== 연산자는 타입 변환을 하지 않고 두 값을 비교합니다! 즉 자료형이 다르면 false를 반환합니다!


시간변환기

<script type="text/babel">
    const root = document.getElementById("root");

    function APP() {
        const [amount, setAmount] = React.useState()
        const [flipped, setFlipped] = React.useState(false);
        const onChange = (event) => {
            setAmount(event.target.value);
        };
        const reset = () => setAmount(0);
        const onFlip = () => {
            reset();
            setFlipped((current) => !current);
        }

        return (
            <div>
                <div>
                    <h1 className="hi">-시간 변환기</h1>
                    <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={minutes / 60}
                        value={flipped ? amount : Math.round(amount / 60)}
                        id="hours"
                        placeholder="Hours"
                        type="number"
                        disabled={!flipped}
                        onChange={onChange}
                    />
                </div>
                <button onClick={reset}>Reset</button>
                <button onClick={onFlip}>Flipped</button>
            </div>
        );
    }
    ReactDOM.render(<APP />, root);

</script>

컴포넌트 재활용

props

<script type="text/babel">

    function Btn(props) {
        return <button style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: "10px",
            fontSize: props.big? 18: 16,
        }}>
        {props.text}
    </button>
    }

    function APP() {
        return (
            <div>
                <Btn text="저장" big={true} />
                <Btn text="확인" big={false} />
            </div>
        );
    }
    const root = document.getElementById("root");
    ReactDOM.render(<APP />, root);

</script>

Btn 컴포넌트를 이와 같이 재활용 할수있다

Btn 컴포넌트에 text와 big이라는 변수를 추가해서 버튼 컴포넌트를 커스텀 할 수도 있다.

그리고 여기에 다는 추가 사항들은 모두 props이지 이벤트 리스너를 만드는 것은 아니다

컴포넌트를 쓸때 넣어준 변수에 이렇게 접근할 수도 있다

function Btn({text, big}) {
        return <button style={{
            backgroundColor: "tomato",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: "10px",
            fontSize: props.big? 18: 16,
        }}>
        {text}
    </button>
    }
<script type="text/babel">

    function Btn({text, changeVal}) {
        return <button
        onClick={changeVal}
            style={{
                backgroundColor: "tomato",
                color: "white",
                padding: "10px 20px",
                border: 0,
                borderRadius: "10px",
            }}>
            {text}
        </button>
    }

    function APP() {
        const [val, setVal] = React.useState("Save Changes")
        const changeVal = () => setVal("Revert Changes")
        return (
            <div>
                <Btn text={val} changeVal={changeVal} />
                <Btn text="확인" />
            </div>
        );
    }
    const root = document.getElementById("root");
    ReactDOM.render(<APP />, root);
</script>

Prop Types 패키지를 쓰면 prop의 자료형이 잘못 쓰이고 있는지 알수있다

<script src="https://unpkg.com/prop-types@15.7.2/prop-types.js"></script>

이렇게 잘못된 값들을 넣어주면

Btn.propTypes ={
        text: PropTypes.string,
        fontSize: PropTypes.number,
    }

    function APP() {
        return (
            <div>
                <Btn text="Save Changes" fontSize={18} />
                <Btn text={14} fontSize={"Continue"} />
            </div>
        );
    }

짜잔


이제 리엑트 를 진짜 설치해서 연습해 보자

콘솔에 아래 명령어를 입력하자

npx create-react-app Hwiflix

아,,, 이름에 대문자 금지란다

npx create-react-app hwiflix

이걸로 다시 하자…

다 깔리고 npm start 를 콘솔에 입력하면

시공의 폭풍같은 웹서버가 실행된다.


import { useState, useEffect } from "react";
function App() {
  const [counter, setValue] = useState(0);
  const onClick = () => setValue((prev) => prev + 1);
  console.log("i run all the time"); // 렌더링 할때마다 실행
  useEffect(() => {
    console.log("Call the API");
  }, []); //처음 한번만 렌더할 함수는 이곳에 넣어준다

  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={onClick}>나를 눌러요</button>
    </div>
  );
}

export default App;

useEffect 를 활용하면 코드가 한번만 실행되게 할 수 있다. 더링이 일어나도 useEffect 에 있는 코드는 처음 렌더링 할 때 한번만 실행된다.

import { useState, useEffect } from "react";
function App() {
  const [counter, setValue] = useState(0);
  const [keyword, setKeyword] = useState("");
  const onClick = () => setValue((prev) => prev + 1);
  const onChange = (event) => setKeyword(event.target.value);

  useEffect(() => {
    console.log("Call the API");
  }, []); //처음 한번만 렌더할 함수는 이곳에 넣어준다

  useEffect(() => {
    if (keyword !== "" && keyword.length > 6) {
      console.log("SERACH", keyword);
    }
  }, [keyword]); // keyword가 바뀔때만 실행

  return (
    <div>
      <input 
      value={keyword}
      onChange={onChange} 
      type="text" 
      placeholder="Serch"/>
      <h1>{counter}</h1>
      <button onClick={onClick}>나를 눌러요</button>
    </div>
  );
}

export default App;

[ ]안에 데이터를 넣어주면 해당 데이터가 변화할 때 만 실행한다

import { useState, useEffect } from "react";

function Hello() {
  useEffect(() => {
    console.log("Created"); // Hello가 렌더링 될때만
    return () => console.log("distroyed");// Hello가 제거될때만
  }, []);
  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>
  );
}

export default App;

useEffect로 불러와서 렌더링 됳 때와 다시 제거될 때 코드가 각각 실행되게도 할 수 있다.

function Hello() {
  function DestroyedFn() {
    console.log("Destroyed");
  }
  function CreatedFn() {
    console.log("Created");
    return DestroyedFn;
  }
  useEffect(CreatedFn, []);
  return <h1>Hello</h1>;
}

이렇게 나눌 수 도 있는데 기능은 똑같다

아무튼 강의보고 프론트엔드만 만들었다.

https://techn-0.github.io/hwiflix_react/

이제 백엔드를 쓰는 게시판 형태의 웹서버도 만들어야 한다

1개의 댓글

comment-user-thumbnail
2024년 12월 3일

개추~

답글 달기