Context

-  기존 컴포넌트의 props를 통한 데이터 전달방식 대신 컴포넌트 트리를 통해 곧바로 컴포넌트로 전달함

 

 

언제 Context를 사용해야 할까?

- 여러 개의 컴포넌트들이 접근해야 하는 데이터 : 로그인 여부, 로그인 정보, UI 테마, 현재 언어 등...

이 처럼 props를 통해 데이터 전달하는 방식은 실제 데이터를 필요한 컴포넌트가 깊어질 수록 복잡해진다.

 

 

Context를 사용한 방식

 

 

 

Context를 사용하기 전에 고려할 점

- 무조건 Context를 사용하는 것이 좋은게 아니다. 왜냐하면 컴포넌트와 Context가 연동되면 재사용성이 떨어진다.

- 다른 레벨의 많은 컴포넌트가 데이터를 필요로 하는 경우가 아니라면, 기존 사용방식인 props 를 사용하는게 좋다.

 

 

 

Context API

React.createContext() : context를 생성

const MyContext = React.createContext(기본값);

- 만약 상위 레벨에 매칭되는 Provider가 없다면 기본값이 사용 됨!

- 기본값으로 undefined를 넣으면 기본값이 사용되지 않음.

 

 

Context.Provider : 제공자라는 뜻, 데이터를 제공해주는 컴포넌트

<MyContext.Provider value={/* some value */}>

- value 는 Provider 하위에 있는 컴포넌트들에게 전달된다.

- Provider value에서 주의해야 할 사항

Provider 컴포넌트가 재렌더링될 때마다 모든 하위 consumer 컴포넌트가 재렌더링 됨.

 

 

이와 같이 state를 사용하여 불필요한 재렌더링을 막는다.

 

 

 

Class.contextType

- Provider 하위에 있는 클래스 컴포넌트에서 context 데이터에 접근하기 위해 사용함

- 지금은 사용하지 않기 때문에 이런게 있다라는 것만 알아주기

 

 

 

Context.Consumer

- context의 데이터를 구독

<MyContext.Consumer>
	{value => /* 컨텍스트의 값에 따라서 컴포넌트들을 렌더링 */}
</MyContext.Consumer>

 

 

function as a child

- 컴포넌트의 자식으로 함수를 사용하는 방법

// children이라는 prop을 직접 선언하는 방식
<Profile children={name => <p>이름: {name}</p>} />

// Profile 컴포넌트로 감싸서 children으로 만드는 방식
<Profile>{name => <p>이름: {name}</p>}</Profile>

 

 

Context.displayName

- Context 객체는 displayName이라는 문자열 속성을 가진다.

- 크롬의 React 개발자 도구에서는 Context의 Provider나 Consumer를 표시할 때 이 DisplayName을 함께 표시해준다.

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

// 개발자 도구에 "MyDisplayName.Provider"로 표시됨
<MyContext.Provider>

// 개발자 도구에 "MyDisplayName.Consumer"로 표시됨
<MyContext.Consumer>

 

 

 

 

여러개의 Context 사용하기

다음과 같이 Provider를 중첩해서 사용 가능

 

 

 

useContext() Hook

- 함수 컴포넌트에서 Context를 보다 쉽게  사용할 수 있게 함

사용시 파라미터에 Context 객체를 넣어줘야 함

 

 

 

 

 

실습 : Context를 사용하여 테마 변경 기능 만들기

import React from "react";

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

export default ThemeContext;

ThemeContext라는 Context를 만든다. 기본값은 없다.

 

 

 

import { useContext, userContext } from "react";
import ThemeContext from "./ThemeContext";

function MainContent(props) {
    const { theme, toggleTheme } = useContext(ThemeContext);

    return (
        <div
            style={{
                width: "100vw",
                height: "100vh",
                padding: "1.5rem",
                backgroundColor: theme == "light" ? "white" : "black",
                color: theme == "light" ? "black" : "white",
            }}
        >
            <p>안녕하세요, 테마 변경이 가능한 웹사이트 입니다.</p>
            <button onClick={toggleTheme}>테마 변경</button>
        </div>
    );
}

export default MainContent;

MainContent 컴포넌트는 ThemeContext로 부터 데이터를 가져와 해당 테마대로 렌더링을 하는 역할을 한다.

또한 테마 변경 버튼을 누를 경우 ThemeContext로 부터 받은 toggleTheme 함수를 호출하여 ThemeContext의 값을 변경하는 역할을 한다.

 

 

import { useState, useCallback } from "react";
import ThemeContext from "./ThemeContext";
import MainContent from "./MainContent";

function DarkOrLight(props) {
    const [theme, setTheme] = useState("light");

    const toggleTheme = useCallback(() => {
        if (theme == "light") {
            setTheme("dark");
        } else if (theme == "dark") {
            setTheme("light");
        }
    }, [theme]);

    return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            <MainContent />
        </ThemeContext.Provider>
    );
}

export default DarkOrLight;

DarkOrLight 컴포넌트에서 다음과 같이 작성한다.

Provider로 하위 컴포넌트에서 theme과 toggleTheme을 사용 가능하도록 만든다.

 

 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import DarkOrLight from './chapter_14/DarkOrLight';

ReactDOM.render(
  <React.StrictMode>
  <DarkOrLight />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

실행 결과, 테마 변경 버튼을 누르면 테마가 변경되는 것을 확인할 수 있다.

'React' 카테고리의 다른 글

[React] react-router-dom v6  (0) 2024.08.30
[React] styled-component  (0) 2024.08.23
[React] Composition 방법과 Inheritance  (0) 2024.08.20
[React] 실습 : 섭씨온도, 화씨온도 표시하기  (0) 2024.08.19
[React] Lifting State Up  (0) 2024.08.19

Composition

- 영어 뜻 : 구성
- 여러개의 컴포넌트를 합쳐서 새로운 컴포넌트를 만드는 것
- 그래서 구성 보다는 합성이라는 단어가 어울림

 

조합 방법

Containment

- 담다, 포함하다
- 하위 컴포넌트를 포함하는 형태의 합성 방법
- SideBar나 Dialog 같은 Box 형태의 컴포넌트는 자신의 하위 컴포넌트를 미리 알 수 없다.
- children이라는 prop을 사용해서 조합

children 이라는 prop은 리액트에서 기본적으로 제공한다. 이것을 사용하면 해당 컴포넌트의 하위 컴포넌트가 모두 children으로 들어오게 된다.
이전에 createElement를 배웠을 때, ...children이 하위 컴포넌트라는 것을 배웠었다.
FancyBorder 컴포넌트 안에 있는 모든 JSX 태그는 children으로 전달됨!!

 

 

 

여러개의 children 집합이 필요한 경우 : 별도로 props를 정해서 각각 원하는 컴포넌트를 넣어주면 된다.

다음은 화면을 왼쪽과 오른쪽으로 분할해서 보여주는 역할을 하는 SplitPane 컴포넌트이다. 

 

App 이라는 컴포넌트에서는 SplitPane 컴포넌트를 사용하고 있는데, 여기서 left, right라는 두개의 props를 정의해서 그 안에 각각 다른 컴포넌트를 넣어주고 있다.

 

SplitPane 에서는 이 left, right 라는 props를 이용해서 각각 왼쪽과 오른쪽 화면을 분리해서 렌더링하여 보여준다.

 

이 처럼 여러개의 children 집합이 필요한 경우에는 별도의 props를 정의해서 사용하면 된다.

 

 

 

Specialization

- 전문화, 특수화

- 예) Welcome Dialog는 Dialog의 특별한 케이스다.

- 범용적인 개념을 구별이 되게 구체화 하는 것

- 기존의 객체지향 언어에서는 상속을 사용하여 Specialization을 구현한다. 그러나! BUT!

- React에서는 합성(Composition)을 사용하여 Specialization을 구현한다.

Dialog라는 범용적인 컴포넌트와 WelcomeDialog라는 특별한 컴포넌트

제목과 메세지를 WelcomeDialog에서 정의해서 제목과 메세지를 어떻게 사용하냐에 따라 인삿말 다이어로그가 되거나 경고 다이어로그가 된다.

 

 

 

Containment 와 Specialization을 같이 사용하기

children prop 을 통해 Dialog 하위 태그들을 Containment 방식 사용해서 불러 올 수 있으며, title 과 message prop으로 제목과 메세지 정의해서 특수화하여 사용하면 Specialization 역시 구현할 수 있다.

 

 

 

 

Inheritance

- Composition과 대비되는 개념

- 상속

- 다른 컴포넌트로부터 상속을 받아서 새로운 컴포넌트를 만드는 것

- 그러나 추천할 만한 사용은 아니다. 그래서 Composition 사용을 권장한다.

 

 

 

실습 : Card 컴포넌트 만들기

function Card(props) {
    const { title, backgroundColor, children } = props;

    return (
        <div
            style={{
                margin: 8,
                padding: 8,
                borderRadius: 8,
                boxShadow: "0px 0px 4px grey",
                backgroundColor: backgroundColor || "white",
            }}
        >
            {title && <h1>{title}</h1>}
            {children}
        </div>
    );
}

export default Card;

Card 컴포넌트는 하위 컴포넌트를 감싸서 카드 형태로 보여준다. Containment 와 Specialization 두가지 합성 방법을 모두 사용. children 부분이 Containment 부분이고, title과 backgroundColor 부분이 Specialization 부분이다.

 

 

import Card from "./Card";

function ProfileCard(props) {
    return (
        <Card title="Manse Kim" backgroundColor="#4ea04e">
            <p>안녕하세요, 만세입니다.</p>
            <p>리액트 공부 재미있네요.</p>
        </Card>
    );
}

export default ProfileCard;

ProfileCard 컴포넌트에서 Card 컴포넌트를 사용하여 타이틀에 이름을 넣고, 배경색깔 역시 넣는다.

children 으로는 간단한 자기소개 글을 넣었다.

 

 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import ProfileCard from './chapter_13/ProfileCard';

ReactDOM.render(
  <React.StrictMode>
  <ProfileCard />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

인덱스에서 렌더링하고 터미널에서 실행하면

 

 

제목과 배경색, 그리고 children이 합성되어 하나의 카드로 보여주는 것을 확인할 수 있다.

'React' 카테고리의 다른 글

[React] styled-component  (0) 2024.08.23
[React] Context  (0) 2024.08.21
[React] 실습 : 섭씨온도, 화씨온도 표시하기  (0) 2024.08.19
[React] Lifting State Up  (0) 2024.08.19
[React] Form과 Controlled Component  (0) 2024.08.18

지난 포스팅에서 Shared State를 배우기 위해 섭씨온도, 화씨온도 변환하는 것을 배웠는데, 직접 실습을 통해 확인해볼 것

 

 

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;

 

일단 하위 컴포넌트인 TemperatureInput 컴포넌트 작성

 

 

import React, { useState } from "react";
import TemperatureInput from "./TemperatureInput";

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;

 

상위 컴포넌트 Calculator에서 하위 컴포넌트 TemperatureInput 으로 정보를 전송하여 입력 후 온도 변환값이 나타나게 한다.

 

 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import Calculator from './chapter_12/Calculator';

ReactDOM.render(
  <React.StrictMode>
  <Calculator />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

터미널에서 npm start로 실행해본다.

 

 

 

섭씨 온도에 99를 입력하자 화씨 온도 부분에서 변환된 온도가 나타나게 된다.
섭씨 110도에서 물이 끓는다.
마찬가지로 화씨에서 온도를 입력하면 섭씨에서 변환된 온도값을 나타낸다.

'React' 카테고리의 다른 글

[React] Context  (0) 2024.08.21
[React] Composition 방법과 Inheritance  (0) 2024.08.20
[React] Lifting State Up  (0) 2024.08.19
[React] Form과 Controlled Component  (0) 2024.08.18
[React] List 와 Key  (0) 2024.08.13

+ Recent posts