Context

tintwo·2022년 12월 15일
0

Context

context를 이용하면 data를 하나하나 props로 넘겨주지 않아도 컴포넌트 트리 전체에 data를 제공할 수 있다.

context는 React 컴포넌트 트리 안에서 전역적이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법

참고 Context Doc

기존 방식으로 이용하면 Root Node의 data를 C 컴포넌트로 전달하려면 최소 2번의 props를 전달해야 한다.

만약 data를 이용하려는 컴포넌트가 한참 아래(10단계) 아래 있다면 그 만큼 props로 전달이 필요하기 때문에 많이 비효율 적이다.

반면에 context를 사용하면 전역적으로 data를 사용할 수 있게 된다.

따라서 코드도 깔끔해지고, data도 한 곳에서 관리하기 때문에 디버깅도 더 쉬워진다.

Context는 언제 사용할까?

여러 개의 Component들이 접근해야 하는 데이터가 있을 때

로그인 여부, 로그인 정보, Ui 테마, 현재 선택된 언어 등등

ex) 사용자의 로그인 여부에 따라 로그아웃 버튼을 보여줄지 말지

기존 방식의 data 전달

function App(props) {
	return <Toolbar theme="dark" />;
}

function Toolbar(props) {
	// 이 Toolbar 컴포넌트는 ThemeButton에 theme를 넘겨주기 위해 'theme' prop을 가져야만 합니다.
    // 현재 테마를 알아야 하는 모든 버튼에 대해서 props로 전달하는 것은 굉장히 비효율 적입니다.
    
    return (
    	<div>
        	<ThemeButton theme={props.theme} />
        </div>
    );
}

function ThemeButton(props) {
	return <Button theme={props.theme} />
}

총 3개의 컴포넌트가 있다.

가장 상위 App 컴포넌트에서 Toolbar 컴포넌트를 사용하고 있고, theme=dark 라는 props를 넘기고 있다.

Toolbar 컴포넌트에서 props로 받은 theme를 ThemeButton으로 다시 또 넘기고 있다.

이제야 ThemeButton 컴포넌트에서 props로 theme=dark라는 data를 받아서 사용하고 있다.

이 처럼 props를 통해 data를 전달하는 기존 방식은 실제 data를 필요로 하는 컴포넌트 까지의 깊이가 깊어질수록 복잡해지고, 반복적인 코드를 계속 작성해야하는 단점이 있고, 가독성도 떨어진다.

Context를 이용한 data 전달

// 컨텍스트는 데이털르 매번 컴포넌트를 통해 전달할 필요 없이 컴포넌트 트리를 곧바로 전달하게 해줍니다.
// 여기에서는 현재 테마를 위한 컨텍스트를 생성하며, 기본값은 'light'입니다.

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

// Provider를 사용하여 하위 컴포넌트들에게 현재 테마 데이털르 전달합니다.
// 모든 하위 컴포넌트들은 컴포넌트 트리 하단에 얼마나 깊이 있는지에 관계없이 데이터를 읽을 수 있습니다.
// 여기에서는 현재 테마값으로 'dark'를 전달하고 있습니다.

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

// 이제 중간에 위치한 컴포넌트 테마 데이터를 하위 컴포넌트로 전달할 필요가 없습니다.
function Toolbar(props) {
	return (
    	<div>
        	<ThemeButton />
        </div>
    );
}

function ThemeButton(props) {
	// 리액트는 가장 가까운 상위 테마 Provider를 찾아서 해당되는 값을 사용합니다.
    // 만약 해당되는 Provider가 없을 경우 기본값 (여기서는 'light')을 사용합니다.
    // 여기에서는 상위 Provider가 있기 때문에 현재 테마의 값은 'dark'가 됩니다.
    
    return (
    	<ThemeContext.Consumer>
        	{value => <Button theme={value} />}
        </ThemeContext.Consumer>
   );
}
  1. React.createContext를 이용해 context 생성
  2. Context를 사용할 상위 컴포넌트에서 Provider을 이용해 감싸준다.

Context 사용 시 유의사항

무조건 Context를 사용하는 것이 좋지는 않다.

컴포넌트와 Context가 연동되면 재사용성이 떨어지기 때문이다.

그래서 다른 레벨의 많은 컴포넌트가 data를 필요로 하는 경우가 아니라면

기존의 방식대로 props를 통해 data를 전달하는 컴포넌트 composition 방법이 더 적합하다.

Context를 사용하지 않은 element variable의 사용

fcuntion Page(props) {
	const user = props.user;
    
    const unserLink = (
    	<Link href={user.permalink}>
        	<Avatar user={user} size={props.avatarSize} />
        <Link />
    );
    
    // Page 컴포넌트는 PageLayout 컴포넌트를 렌더링 
    // 이때 props로 userLink를 함께 전달
    
    return <PageLayout userLink={userLink} />;
}

// PageLayout 컴포넌트 NabigationBar 컴포넌트를 렌더링
<PageLayout userLink={...} />

// NavigationBar 컴포넌트는 props로 전달받은 suerLink element를 리턴
<NavigationBar userLink={...} />

user와 avatarSize가 props로 들어있는 avatar 컴포넌트를 userLink라는 변수에 저장한 뒤에 해당 변수를 하위 컴포넌트에 넘겨주고 있다.

이 방식은 가장 상위 레벨에 있는 Page 컴포넌트만 avatar 컴포넌트에서 필요하는 user와 avatarSize의 data를 가지고 있으면 된다.

이 방식은 코드를 더 간결하게 만들어주고, 최상위 컴포넌트에 더 많은 권한을 부여해준다.

하지만 항상 좋은건 아니다 data가 많아질수록 상위 컴포넌트만 복잡해지고, 관리해야할 data가 많아진다.

그럴땐 아래의 방법으로 의존성을 좀 덜어줄 수 있다.

하위 컴포넌트의 의존성을 상위 컴포넌트와 분리가 필요할 때

function Page(props) {
	const user = props.user;
    
    const topBar = (
    	<NavigationBar>
        	<Link href= {user.permalink}>
            	<Avatar user={user} size={props.avatoarSize} />
            </Link>
        </NavigationBar>
    );
    
    const content = <Feed user={user} />;
    
    return (
    	<PageLayout
        	topBar = {topBar}		// 하위 컴포넌트를 여러개의 변수로 나눠 전달
            content = {content}
        />
    );
}

ContextAPI

Context 생성

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

예제 코드에 나온것 처럼 기본값을 넣어주면 된다.
react에서 렌더링이 일어날 때 context를 구독하는 하위 컴포넌트가 나오면 현재 Context의 값을 가장 가까이 있는 상위 레벨에 Provider로 부터 받아오게 된다.

만약 상위 레벨에 매칭되는 Provider가 없다면 이 경우에만 기본값이 사용된다.

기본값은 Provider 없이 컴포넌트를 테스트할 때 유용하며,

기본값 대신 undefined를 넣으면 기본값이 사용되지 않는다.

위의 방법으로 Context 객체를 만들었다면 이제 하위 컴포넌트들이 해당 Context의 data를 받을 수 있도록 설정 해줘야 한다.

이를 위해 사용하는 것이 Context.Provider

Context.Provider로 하위 컴포넌트들을 감싸주면, 하위 컴포넌트들이 해당 Context의 data에 접근할 수 있다.

Context.Provider의 사용

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

Provider에는 value 라는 prop가 있으며, provider의 하위 컴포넌트들이 이 data에 접근 할 수 있다.
하위 컴포넌트들이 이 data를 소비한다는 뜻에서 Consumer컴포넌트 라고 부른다.

value prop의 값이 변경되면 재 렌더링 되며, 여러 컴포넌트와 관계를 가질 수 있다.

Provider value 사용 시 주의사항

Context는 재 렌더링 여부를 결정 할 때 레퍼런스 정보를 사용하기 때문에 Provider의 부모 컴포넌트가 재 렌더링 되었을 경우, 의도치 않게 Consumer컴포넌트가 재 렌더링 일어날 수 있다.

왜냐하면

function App(props) {
	return (
    	<MyContext.Provider value={{ somethig: 'something' }}>
        // 이 경우 App 컴포넌트가 재 렌더링 될 때 마다,
        // value prop이 재 렌더링 되기 때문에 모든 하위 Consumer도 재 렌더링 된다.
        	<Toolbar />
        </MyContext.Provider>
    );
}

이를 방지 하기 위해선 state를 사용해 불필요한 재렌더링을 막을 수 있다.

function App(props) {
	const [value, setValue] = useState({ something: 'something' });
    // state를 선언 후 
    
    return (
    	<MyContext.Provider value={value}>
        // state의 값을 value에 넣는다.
        	<Toolbar />
        </MyContext.Provider>
    );
}

Context.consumer

Context를 구독할 때 사용한다.

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

MyContext.Consumer로 감싸주면 자식으로 들어간 함수가 현재 Context의 value를 받아 ReactNode로 return 하게 된다.

이 때 함수로 전달되는 value는 Provider의 value prop과 동일하다.

만약 상위 컴포넌트에 value prop가 없다면 value 파라미터는 Create Context를 호출할 때 넣는 기본값과 동일한 역할을 한다.

function as a child

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

react에서는 기본적으로 하위 컴포넌트들을 children이라는 prop으로 전달 해주는데 children 대신 function을 전달 할 수 있다.

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

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

Context.displayName 속성 (문자열)

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

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

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

useContext Hook

함수 컴포넌트에서 Context를 쉽게 사용할 수 있게 해준다.

매번 Consumer 컴포넌트로 감싸주지 않아도 된다.

function MyComponent(props) {
	const value = useContext(MyContext);
    //react.createContext로 생성된 객체를 인자로 받고 현재 Context의 값을 리턴한다.
    
    return(
    		...
    )
}

useContext Hook을 사용하면 Context값을 다른 방식과 동일하게 컴포넌트 트리 상에서 가장 가까운 상위 Provider에서 받아오게 된다.

만약 Context의 값이 변경되면 변경된 값과 함께 useContext hook을 사용하는 컴포넌트가 재 렌더링 된다.

그렇기 때문에 useContext hook을 사용하는 컴포넌트가 꽤 무거운 작업을 한다면 별도로 최적화 작업을 해줄 필요가 있다.

또한 파라미터로 Context객체를 넣어줘야 한다.

// 올바른 사용법
useContext(MyContext);

// 잘못된 사용법
useContext(MyContext.Consumer);
useContext(MyContext.Provider);
profile
study records of beginner developer

0개의 댓글