빙응의 공부 블로그

[React]리액트 - Form, Lifting State Up, Inheritance, Context 본문

React

[React]리액트 - Form, Lifting State Up, Inheritance, Context

빙응이 2024. 5. 12. 17:15

📝 Forms

폼 = 양식
사용자로부터 입력을 받는 것의 총칭이다.

HTML Form
<form>
    <label>
        이름
        <input type="text" name="name"/>
    </label>
    <button type="submit">제출</button>
</form>

🚩 리액트의 Controlled Component

import React, { useState } from "react";


function NameForm(props){
    const [value, setValue] = useState('');

    const handleChange = (event) => {
        setValue(event.target.value);
    }
    const handleSubmit = (event) => {
        alert('입력한 이름: ' + value);
        event.preventDefault();
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>
                이름:
                <input type="text" value={value} onChange={handleChange}/>
            </label>
            <button type="submit">제출</button>
        </form>
    )
}

해당처럼 useState를 사용하여 폼을 제어한다. 

 

🚩 다양한 폼

Textarea 태그 
import React, { useState } from "react";


function NameForm(props){
    const [value, setValue] = useState('');

    const handleChange = (event) => {
        setValue(event.target.value);
    }
    const handleSubmit = (event) => {
        alert('입력한 이름: ' + value);
        event.preventDefault();
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>
                이름:
                <textarea value={value} onChange={handleChange}/>
            </label>
            <button type="submit">제출</button>
        </form>
    )
}
SELECT
import React, { useState } from "react";


function NameSelect(props){
    const [value, setValue] = useState('grape');

    const handleChange = (event) => {
        setValue(event.target.value);
    }
    const handleSubmit = (event) => {
        alert('입력한 이름: ' + value);
        event.preventDefault();
    }

    return (
        <form onSubmit={handleSubmit}>
            <label>
                이름:
                <select value={value} onChange={handleChange}>
                      <option value="apple">사과</option>
                      <option value="banana">바나나</option>
                      <option value="grape">포도</option>
                      <option value="watermelon">수박</option>
                </select>
            </label>
            <button type="submit">제출</button>
        </form>
    )
}
File input 태그

하나 도는 여러 개의 파일을 선택할 수 있게 해주는 HTML 태그이다.

<input type="file"/>

 

📝Lifting State Up

컴포넌트 사이에서 State를 공유하는 법을 알아보자

🚩 Shared State

자식 컴포넌트들이 공통된 부모 컴포넌트의 State를 공유하는 것을 말한다. 

 

function BoilingVerdict(props) {
    if (props.celsius >= 100) {
        return <p>물이 끓습니다.</p>;
    }
    return <p>물이 끓지 않습니다.</p>;
}

function toCelsius(fahrenheit) {
    return ((fahrenheit - 32) * 5) / 9;
}

function toFahrenheit(celsius) {
    return (celsius * 9) / 5 + 32;
}

function tryConvert(temperature, convert) {
    const input = parseFloat(temperature);
    if (Number.isNaN(input)) {
        return "";
    }
    const output = convert(input);
    const rounded = Math.round(output * 1000) / 1000;
    return rounded.toString();
}

function Calculator(props) {
    const [temperature, setTemperature] = useState("");
    const [scale, setScale] = useState("c");

    const handleCelsiusChange = (temperature) => {
        setTemperature(temperature);
        setScale("c");
    };

    const handleFahrenheitChange = (temperature) => {
        setTemperature(temperature);
        setScale("f");
    };

    const celsius =
        scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit =
        scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
        <div>
            <TemperatureInput
                scale="c"
                temperature={celsius}
                onTemperatureChange={handleCelsiusChange}
            />
            <TemperatureInput
                scale="f"
                temperature={fahrenheit}
                onTemperatureChange={handleFahrenheitChange}
            />
            <BoilingVerdict celsius={parseFloat(celsius)} />
        </div>
    );
}

export default Calculator;
const scaleNames = {
    c: "섭씨",
    f: "화씨",
};

function TemperatureInput(props) {
    const handleChange = (event) => {
        props.onTemperatureChange(event.target.value);
    };

    return (
        <fieldset>
            <legend>
                온도를 입력해주세요(단위:{scaleNames[props.scale]}):
            </legend>
            <input value={props.temperature} onChange={handleChange} />
        </fieldset>
    );
}

export default TemperatureInput;
  • 위 코드처럼 하위 모듈을 상위에서 정의하여 하위 컴포넌트끼리 서로 부모 컴포넌트의 State를 공유한다. 

 

📝 Composition VS Inheritance

🚩 Composition

Composition : 여러 개의 컴포넌트를 합쳐서 새로운 컴포넌트를 만드는 것 
Containment
  • 하위 컴포넌트를 포함하는 형태의 합성 방법 
  • props의 children을 사용하는 것
function FancyBorder(props){
    return(
        <div className={'FancyBorder FancyBorder-' + props.color}>
            {props.children}
        </div>
    )
}

FancyBorder 컴포넌트 안에 있는 모든 JSX 태그는 children으로 전달된다.

 

그렇기에 Containment는 child를 받아 부모 컴포넌트에서 합성하듯 렌더링하는 방식이다.

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

function App() {
  return (
    <Card>
      <h2>제목</h2>
      <p>내용 내용 내용</p>
    </Card>
  );
}
전문화, 특수화
범용적인 상황이 아닌 구체적인 상황에 대한 대응을 하는 구현이다.
리액트에서는 합성을 사용하여 특수화를 구현한다.
function Dialog(props){
    return (
        <FancyBorder coler = "blue">
            <h1 className="Dialog-title">
                {props.title}
            </h1>
            <p className="Dialog-message">
                {props.message}
            </p>
        </FancyBorder>
    );
}

function WelcomeDialog(props){
    return (
        <Dialog
            title="어서오세요"
            message="우리 사이트에 방문하신 것을 환영합니다!"
        />
    );
}

특수화는 범용적인 컴포넌트를 만들어놓고 특수한 상황에 대한 것을 만들면 된다. 

 

🚩 Inheritance

상속의 의미로
객체지향에서 나온 의미이다.
다른 컴포넌트로부터 상속을 받아서 새로운 컴포넌트를 만드는 것이다.

 

  • 리액트에서는 잘 사용하지 않는다.

복잡한 컴포넌트를 쪼개서
여러 개의 컴포넌트로 만들고, 만든 컴포넌트를 조합해서
새로운 컴포넌트를 만들자!

📝Context

컴포넌트는 props를 통해 데이터를 단방향 전달을 한다.
그러나 이것이 너무 많아지면 코드가 복잡해지고 효율성이 떨어진다.
Context는 props로 내려가면 전달하는 것이 아닌 곧바로 데이터를 전달한다.

 

 

사용 범위
  • 여러 개의 컴포넌트가 접근하는 데이터
    • 로그인 여부, 로그인 정보, UI 테마, 현재 언어 등등
사용 예제

이처럼 테마 하나를 위해 깊이가 깊은 코드를 내려가야 한다. 이것을 해결하는 것이 Context이다. 

 

const ThemeContext = React.createContext("light");

function App(props){
    return (
        <ThemeContext.Provider value="dark">
            <Toolbar/>
        </ThemeContext.Provider>
    );
}

function Toolbar(props){
    return (
        <div>
            <ThemedButton />
        </div>
    );
}

function ThemedButton(props){
    return (
        <ThemeContext.Consumer>
            {value=> <button theme={value}/>}
        </ThemeContext.Consumer>
    );
}

 

사용하기 전에 고려할 점

무조건 Context를 사용하는 것이 좋은 것은 아니다.

  • 다른 레벨에 많은 컴포넌트가 특정 데이터를 필요로 할때 사용해야 한다.
  • 재사용성이 떨어지기 때문

 

📝 Context API

리액트가 제공하는 Context에 대해 알아보자
React.createContext()
const MyContext = React.createContext(기본값);
  • 상위 레벨에 매칭되는 Provider가 없다면 기본값이 사용된다.!
Context.Provider 

하위 컴포넌트에게 데이터를 제공해주는 컴포넌트를 지정한다.

<MyContext.Provider value = {}>
  • Provider의 value는 하위 컴포넌트에게 제공된다. 
  • Provider의 value가 바뀌면 하위 컴포넌트는 재렌더링 된다.

🚩 주의점

  • Provider 컴포넌트가 재렌더링될 때마다 모든 하위 consumer 컴포넌트가 재렌더링된다.
  • 의도치 않은 재렌더링이 될 수 있다.
function App(props){
    return (
        <MyContext.Provider value={{something : 'someThing'}}>
            <Toolbar/>
        </MyContext.Provider>
    );
}
  • 위 코드는 해당 컴포넌트가 재렌더링 될 때마다 모든 하위 컴포넌트가 재렌더링된다.
    • 그 이유는 something 벨류가 매번 새롭게 생성되기 때문이다.
function App(props){
    const [value,setValue] = useState({something: 'something'})
    return (
        <MyContext.Provider value={value}>
            <Toolbar/>
        </MyContext.Provider>
    );
}

state를 사용해서 불필요한 랜더링을 막을 수 있다.

 

Context.Consumer

상위 컴포넌트의 Context를 구독하여 사용할 수 있게 한다.

<MyContext.Comsumer>
	{value => /* */}
</MyContext.Consumer>
  • 만약 상위 Provider가 없다면 기본값을 사용한다.
Context.displayName
  • 개발자 도구에서의 이름을 지정한다.