[React #21] State

    반응형

    01. State

    • 현재 가지고 있는 모양이나 형태를 정의하는 값으로 변화할 수 있는 동적인 값이기도 함
    • 리액트의 컴포넌트들은 모두 다 자신의 형태나 모양을 정의하는 State를 가질 수 있음
    • 현재 상태를 보관하는 변수, 상태에 따라서 각각 다른 UI를 화면에 렌더링할 수 있음
    • 하나의 컴포넌트에 여러 개의 State를 만드는 것도 가능함

    💡리렌더링?

    더보기

    컴포넌트가 다시 렌더링 되는 상황, '리렌더'라고도 함

     

     

    02. State 생성 방법

    01) import

    import { useState } from 'react';

     

    02) useState 콘솔 출력하기

    import './App.css';
    import { useState } from 'react';
    
    export default function App() {
      const state = useState();
      console.log(state);
    
      return <></>;
    }

    콘솔창

     

     

    📌 useState()

    - 새로운 state를 생성하는 함수로 두 개의 요소를 담은 배열을 반환

    - 첫 번째 요소 : state의 현재 값, 두 번째 요소 : state를 변경시키는 상태 변화 함수

    - 상태 변화 함수는 비동기로 동작

    - 배열을 반환함으로 보통 비 구조화 할당 문법을 이용함

    import './App.css';
    import { useState } from 'react';
    
    export default function App() {
      const [state, setState] = useState();
    
      return (
        <>
          <h1>{state}</h1>
        </>
      );
    }
     

     

     

    03. State 활용

    01) 버튼을 클릭하면 값 증가하기

    import './App.css';
    import { useState } from 'react';
    
    export default function App() {
      const [state, setState] = useState(0);
    
      return (
        <>
          <h1>{state}</h1>
          <button
            onClick={() => {
              setState(state + 1);
            }}
          >
            +
          </button>
        </>
      );
    }

    실행화면

     

    - 컴포넌트 내에 새로운 state를 생성하고 state의 값을 변경하면, 리액트가 내부적으로 컴포넌트의 state가 변경되었다는 것을 감지해서 이 컴포넌트를 리렌더링

    - 컴포넌트 역할을 하는 함수를 다시 호출하고 새롭게 반환함

    02) 버튼으로 토글 효과 주기

    import './App.css';
    import { useState } from 'react';
    
    export default function App() {
      const [light, setLight] = useState('OFF');
    
      return (
        <>
          <div>
            <h1>{light}</h1>
            <button
              onClick={() => {
                setLight(light === 'OFF' ? 'ON' : 'OFF');
              }}
            >
              {light === 'OFF' ? '켜기' : '끄기'}
            </button>
          </div>
        </>
      );
    }

    실행화면

     

    💡자바스크립트 변수로 변경하지 않고 State를 쓰는 이유?

    더보기

    변수의 값이 바뀐다고 컴포넌트가 리렌더링 되지 않음

    리액트는 state의 값이 변환했을 때에만 리렌더링이 일어남

    → 그래서 리액트는 가변적인 값을 관리할 때 State를 이용해서 처리함

     

    03) props 이용해서 state를 자식 컴포넌트에게 전달

    import './App.css';
    import { useState } from 'react';
    
    // 전구 역할을 하는 컴포넌트
    const Bulb = ({ light }) => {
      return (
        <div>
          {light === 'ON' ? (
            <h1 style={{ backgroundColor: 'orange' }}>ON</h1>
          ) : (
            <h1 style={{ backgroundColor: 'gray' }}>OFF</h1>
          )}
        </div>
      );
    };
    
    export default function App() {
      const [count, setCount] = useState(0);
      const [light, setLight] = useState('OFF');
    
      return (
        <>
          <div>
            <Bulb light={light} />
            <button
              onClick={() => {
                setLight(light === 'OFF' ? 'ON' : 'OFF');
              }}
            >
              {light === 'OFF' ? '켜기' : '끄기'}
            </button>
          </div>
          <hr></hr>
          <div>
            <h1>{count}</h1>
            <button
              onClick={() => {
                setCount(count + 1);
              }}
            >
              +
            </button>
          </div>
        </>
      );
    }

    실행화면

     

    - 리액트 컴포넌트들은 자신이 갖는 state가 변경되지 않아도부모로부터 받는 props의 값이 변경되면 리렌더링 됨

    - 그래서 count의 값을 변경해도 Bulb도 리렌더링 됨

    - 불필요하게 리렌더링 경우가 많아지면 성능이 떨어지기 때문에 컴포넌트를 분리하는게 좋음

     

    💡 컴포넌트가 리렌더링 할 때

    더보기

    1. 자신이 관리하는 state의 값이 변경되었을 때

    2. 자신이 제공받는 props의 값이 변경되었을 때

    3. 부모 컴포넌트가 리렌더링 될 때

     

    04) 컴포넌트 분리

    import './App.css';
    import { useState } from 'react';
    
    // 전구 역할을 하는 컴포넌트
    const Bulb = () => {
      const [light, setLight] = useState('OFF');
      return (
        <div>
          {light === 'ON' ? (
            <h1 style={{ backgroundColor: 'orange' }}>ON</h1>
          ) : (
            <h1 style={{ backgroundColor: 'gray' }}>OFF</h1>
          )}
          <button
            onClick={() => {
              setLight(light === 'OFF' ? 'ON' : 'OFF');
            }}
          >
            {light === 'OFF' ? '켜기' : '끄기'}
          </button>
        </div>
      );
    };
    
    // 카운터 컴포넌트
    const Counter = () => {
      const [count, setCount] = useState(0);
      return (
        <div>
          <h1>{count}</h1>
          <button
            onClick={() => {
              setCount(count + 1);
            }}
          >
            +
          </button>
        </div>
      );
    };
    
    export default function App() {
      return (
        <>
          <Bulb />
          <hr></hr>
          <Counter />
        </>
      );
    }

    - 전구 컴포넌트와 카운터 컴포넌트를 분리해서 불필요한 리렌더링을 막음

     

     

    04. 사용자의 입력값 받기

    01) 이름

    import React from 'react';
    import { useState } from 'react';
    
    function Register() {
      const [name, setName] = useState('');
    
      const onChangeName = (e) => {
        setName(e.target.value);
      };
    
      return (
        <>
          <div>
            <input value={name} onChange={onChangeName} placeholder='이름' />
            {name}
          </div>
        </>
      );
    }
    
    export default Register;

    - 이벤트 객체의 target에 value는 현재 인풋에 저장되어 있는 값을 보관하고 있음

    - e.target.value : 사용자가 인풋에 작성한 텍스트에 접근

    - value 값 꼭 설정하기⭐


    ✏️ setName(e.target.value);

    ① input에 값을 입력했을 때 onChangeName 함수 실행

    ② setName 함수를 호출하면서 현재 이 input에 사용자가 입력한 텍스트를 'name'에 전달

     

    02) 생일

    import React from 'react';
    import { useState } from 'react';
    
    function Register() {
      const [birth, setBirth] = useState('');
     
      const onChangeBirth = (e) => {
        setBirth(e.target.value);
      };
    
      return (
        <>   
          <div>
            <input value={birth} onChange={onChangeBirth} type='date' />
            {birth}
          </div>
        </>
      );
    }
    
    export default Register;

    03) 국적

    import React from 'react';
    import { useState } from 'react';
    
    function Register() {
      const [country, setCountry] = useState('');
    
      const onChangeCountry = (e) => {
        setCountry(e.target.value);
      };
    
      return (
        <>
          <div>
            <select value={country} onChange={onChangeCountry}>
              <option></option>
              <option value={'ko'}>한국</option>
              <option value={'uk'}>영국</option>
              <option value={'us'}>미국</option>
            </select>
            {country}
          </div>
        </>
      );
    }
    
    export default Register;

    - select 태그는 기본적으로  option 중 맨 위에 있는 값을 초기값으로 설정

    - 비어있는 값을 하고싶다면 빈 option 태그 추가하기

    - 보통은 선택지에는 친절하고 길게 텍스트를 설정하고 value에는 간결한 값을 사용함

    04) 자기소개

    import React from 'react';
    import { useState } from 'react';
    
    function Register() {
      const [bio, setBio] = useState('');
    
      const onChangeBio = (e) => {
        setBio(e.target.value);
      };
    
      return (
        <>
          <div>
            <textarea
              value={bio}
              onChange={onChangeBio}
              placeholder='자기소개를 입력해 주세요.'
            />
            {bio}
          </div>
        </>
      );
    }
    
    export default Register;

    - textarea : input 태그와 동일하지만, 여러 줄의 입력을 받을 수 있음

     

    💡 초기값을 설정해주고 싶을 때

    useState("이름"); 입력해주고 input의 'value'를 state의 값을 입력해줌

     

    실행화면

     

     

    05. 비효율적인 코드 개선하기

    01) 여러 개의 state를 객체로 만들기

    const [input, setInput] = useState({
        name: '',
        birth: '',
        country: '',
        bio: '',
    });

     

    - useState에 초기 값으로 객체를 넣고 name, birth, country, bio 각각 프로퍼티로 설정

    - 비슷한 여러 개의 state를 하나의 객체 값으로 묶어서 하나의 state로 통합해서 관리하면 편함

    02) 이벤트 핸들러 합치기

      const onChange = (e) => {
        setInput({
          ...input,
          [e.target.name]: e.target.value,
        });
      };
      
       <input
          name='name'
          value={input.name}
          onChange={onChange}
          placeholder='이름'
        />

    - 스프레드 연산자를 이용해서 기존의 input state에 들어있던 프로퍼티 값들은 그대로 유지하고, 변경하고 싶은 프로퍼티의 값만 바꿔주기

    - 여러 개의 비슷하게 생긴 이벤트 핸들러들은 통합 이벤트 핸들러로 묶어줄 수 있음


    ✏️모든 input에 onChange 이벤트 함수 실행

    ① setInput 상태 변화 함수 호출

    ② 인수로는 객체를 만들어서 전달

    ③ ...input : 스프레드 연산자로 인풋의 값을 나열해줌

    ④ [e.target.name] : '프로퍼티의 key'로 이벤트가 발생하는 name으로 변수 설정

     

    03) 최종 코드

    import React from 'react';
    import { useState } from 'react';
    
    function Register() {
      const [input, setInput] = useState({
        name: '',
        birth: '',
        country: '',
        bio: '',
      });
    
      const onChange = (e) => {
        setInput({
          ...input,
          [e.target.name]: e.target.value,
        });
      };
    
      return (
        <>
          <div>
            <input
              name='name'
              value={input.name}
              onChange={onChange}
              placeholder='이름'
            />
            {input.name}
          </div>
          <div>
            <input
              name='birth'
              value={input.birth}
              onChange={onChange}
              type='date'
            />
            {input.birth}
          </div>
          <div>
            <select name='country' value={input.country} onChange={onChange}>
              <option></option>
              <option value={'ko'}>한국</option>
              <option value={'uk'}>영국</option>
              <option value={'us'}>미국</option>
            </select>
            {input.country}
          </div>
          <div>
            <textarea
              name='bio'
              value={input.bio}
              onChange={onChange}
              placeholder='자기소개를 입력해 주세요.'
            />
            {input.bio}
          </div>
        </>
      );
    }
    
    export default Register;

     

     

    참고자료

    이정환 Winterlood, '한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지'

     

    반응형

    댓글