Front-end/React

[노마드코더] #2 State & Props

성중 2021. 11. 16. 22:03

State

Counter

버튼을 누르면 counter가 하나씩 증가하는 페이지를 구성하는 경우,

첫 렌더링 이후 계속해서 값을 띄우기 위해 render() 함수를 만들어 반복 실행했다

<!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");
        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>
        ReactDOM.render(<Container/>, root); 
    </script>
</html>

이 때, React는 HTML코드에서 오직 '바뀐 값만 리렌더링'하는 효율성을 보여준다

여기저기서 render() 함수를 호출하는 것은 매우 비효율적인데,,

useState를 활용하면 데이터와, 데이터의 업데이트 함수를 바로 생성할 수 있다

<!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">
        function App () {
            const [counter, setCounter] = React.useState(0);
            const onClick = () => {
                setCounter((current) => current + 1);
            }
            return(
                <div>
                    <h3>Total clicks: {counter}</h3>
                    <button onClick={onClick}>Click me</button>
                </div> 
            );
        } 
        ReactDOM.render(<App/>, root); 
    </script>
</html>

이전 값을 활용해 State를 변경하려면 setState()함수에 직접 넣어줘도 되지만,,

setCounter(counter+1);

State가 다른 곳에서 변경될 수도 있기 때문에 current 함수로 처리하는 것이 안전하다!

setCounter((current) => current + 1);

Super Converter

JSX 상에서는 일부 HTML 코드를 변환해 사용해야 한다

ex) class -> className / for -> htmlFor

 

Input과 State를 활용해 단위 변환기를 만들어보자!

<!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">
        function App () {
            const [minutes, setMinutes] = React.useState();
            const onChange = (event) => {
                setMinutes(event.target.value);
            };
            return(
                <div>
                    <h1>Super Converter</h1>
                    <label htmlFor="minutes">Minutes</label>
                    <input
                        value={minutes}
                        id="minutes" 
                        placeholder="Minutes" 
                        type="number"
                        onChange={onChange} 
                    />
                    <h4>You want to convert {minutes}</h4>
                    <label htmlFor="hours">Hours</label>
                    <input id="hours" placeholder="Hours" type="number" />
                </div> 
            );
        } 
        ReactDOM.render(<App/>, root); 
    </script>
</html>
  1. input에서 받는 value를 state와 연결
  2. 함수를 생성해 onChange에 연결
  3. 매개변수 event로 target.value(현재 입력값) 연결
  4. 해당 값을 setState()에 넣어 실시간 입력값을 state로 업데이트
function App () {
            const [minutes, setMinutes] = React.useState();
            const onChange = (event) => {
                setMinutes(event.target.value);
            };
            const reset = () => {
                setMinutes(0);
            }
            return(
                <div>
                    <h1>Super Converter</h1>
                    <div>
                        <label htmlFor="minutes">Minutes</label>
                        <input
                            value={minutes}
                            id="minutes" 
                            placeholder="Minutes" 
                            type="number"
                            onChange={onChange} 
                        />
                        <h4>You want to convert {minutes}</h4>
                    </div>
                    <div>
                        <label htmlFor="hours">Hours</label>
                        <input 
                            value={Math.round(minutes / 60)}
                            id="hours" 
                            placeholder="Hours" 
                            type="number"
                            disabled
                        />
                    </div>
                    <button onClick={reset}>Reset</button>
                </div> 
            );
        } 
        ReactDOM.render(<App/>, root);

Hours의 input에서 state를 value로 받아와 60으로 나눠 Converter 완성!

 

Flip 버튼으로 시간/분 변환을 반대로 바꿔볼 것이다

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); /*현재값이 true면 flase, false면 true*/
            }
            return(
                <div>
                    <h1>Super Converter</h1>
                    <div>
                        <label htmlFor="minutes">Minutes</label>
                        <input
                            value={flipped ? amount * 60 : amount} /*fliped 여부에 따라 다른 값*/
                            id="minutes" 
                            placeholder="Minutes" 
                            type="number"
                            onChange={onChange}
                            disabled={flipped} /*input 비활성화 여부*/
                        />
                    </div>
                    <div>
                        <label htmlFor="hours">Hours</label>
                        <input 
                            value={flipped ? amount : Math.round(amount / 60)} /*fliped 여부에 따라 다른 값*/
                            id="hours" 
                            placeholder="Hours" 
                            type="number"
                            onChange={onChange}
                            disabled={!flipped} /*input 비활성화 여부*/
                        />
                    </div>
                    <button onClick={reset}>Reset</button>
                    <button onClick={onFlip}>{!flipped ? 'Flip': 'Turn back'}</button>
                </div> 
            );
        } 
        ReactDOM.render(<App/>, root);

State의 true/false 여부로 상태 관리가 가능하다!

<!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">
    function MinutesToHours() {
      const [amount, setAmount] = React.useState(0);
      const [inverted, setInverted] = React.useState(false);
      const onChange = (event) => {
        setAmount(() => event.target.value);
      };
      const onInverted = () => {
        setInverted((current) => !current);
        return setAmount(() => 0);
      };
      return (
        <div>
          <h3>Minutes & Hours</h3>
          <div>
            <label htmlFor="minutes">Minutes</label>
            <input
              value={inverted ? amount * 60 : amount}
              id="minutes"
              placeholder="Minutes"
              type="number"
              onChange={onChange}
              disabled={inverted}
            />
          </div>
          <div>
            <label htmlFor="Hours">Hours</label>
            <input
              value={inverted ? amount : Math.round(amount / 60)}
              id="Hours"
              placeholder="Hours"
              type="number"
              disabled={!inverted}
              onChange={onChange}
            />
          </div>
          <button onClick={() => setAmount(() => 0)}>Reset</button>
          <button onClick={onInverted}>
            {inverted ? "Turn back" : "Invert"}
          </button>
        </div>
      );
    }
    function KmToMiles() {
      const [amount, setAmount] = React.useState(0);
      const [invert, setInvert] = React.useState(false);
      const kmChange = (event) => {
        setAmount(() => event.target.value);
      };
      return (
        <div>
          <h3>KM & Miles</h3>
          <div>
            <label htmlFor="km">Kilometers</label>
            <input
              onChange={kmChange}
              disabled={invert}
              value={invert ? parseFloat(amount * 1.60934).toFixed(4) : amount}
              id="km"
              placeholder="Kilometers"
              type="number"
            />
          </div>
          <div>
            <label htmlFor="miles">Miles</label>
            <input
              onChange={kmChange}
              value={invert ? amount : parseFloat(amount * 0.621371).toFixed(4)}
              disabled={!invert}
              id="miles"
              placeholder="Miles"
              type="number"
            />
          </div>
          <button onClick={() => setAmount(() => 0)}>Reset</button>
          <button
            onClick={() => {
              setInvert((current) => !current);
              return setAmount(() => 0);
            }}
          >
            {!invert ? "Invert" : "Turn back"}
          </button>
        </div>
      );
    }
    function App() {
      const [index, setIndex] = React.useState("-1");
      const onSelect = (event) => {
        return setIndex(() => event.target.value);
      };
      return (
        <div>
          <h1>Super Converter</h1>
          <select onChange={onSelect}>
            <option value="-1">Select</option>
            <option value="0">Minutes & Hours</option>
            <option value="1">Km & Miles</option>
          </select>
          <hr />
          {index === "0" ? (
            <MinutesToHours />
          ) : index === "1" ? (
            <KmToMiles />
          ) : <h3>Select Option!</h3>}
        </div>
      );
    }
    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>
</html>

같은 원리로 state를 활용해 option에 따라 다른 컴포넌트를 렌더링 할 수 있다! (Flip -> Invert)

 

Props

Props를 통해 부모 컴포넌트에서 자식 컴포넌트로 데이터를 보낼 수 있다

<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
  </body>
  <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">
    function Btn({ buttonName, fontSize = 10 } /*props*/ ) {
      return (
        <button
          style={{
            backgroundColor: "teal",
            color: "white",
            padding: "10px 20px",
            border: 0,
            borderRadius: "10px",
            marginRight: "5px",
            fontSize, /*fontSize: props.fontSize*/
          }}
        >
          {buttonName /*props.buttonName*/}
        </button>
      );
    }
    Btn.propTypes = {
      buttonName: PropTypes.string.isRequired,
      fontSize: PropTypes.number,
    };
    const App = () => {
      return (
        <div>
          <Btn buttonName={"Save Changes"} fontSize={18} />
          <Btn buttonName={"Confirm"} />
        </div>
      );
    };

    const root = document.getElementById("root");
    ReactDOM.render(<App />, root);
  </script>
</html>

컴포넌트 커스텀을 통해 효율적인 재사용이 가능하며 PropTypes로 Props의 Type을 제한하기도 한다

 

본 내용은 노마드코더의 'ReactJS로 영화 웹 서비스 만들기'를 바탕으로 작성되었습니다