처음 만난 리액트(React) : section 14. Context

꿀돼질래·2022년 9월 7일
0
post-thumbnail

💡 Context란?

Context

컴포넌트의 props를 통한 데이터 전달

  • Context를 사용시 곧바로 데이터 전달

  • 기존 방식은 props로 데이터 전달 → props로 데이터 전달 → props로 데이터 전달 → ... (자식 → 자식 → 자식 → ...)

언제 Context를 사용해야 할까?

  • 여러 개의 Component들이 접근행햐 하는 데이터
    • 로그인 여부, 로그인 정보, UI 테마, 현재 언어 등 ...
// 상위 App Component
function App(props) {
  	return <Toolbar theme="dark" />;
}

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

function ThemedButton(props) {
  	return <Button theme={props.theme} />;
}
// 컨텍스트는 데이터를 매번 컴포넌트를 통해 전달할 필요 없이 컴포넌트 트리로 곧바로 전달
// 여기에서는 현재 테마를 위한 컨텍스트를 생성하며 기본값은 'light'
const ThemeContext = React.createContext('light');

// Provider를 사용하여 하위 컴포넌트들에게 현재 테마 데이터를 전달
// 모든 하위 컴포넌트들은 컴포넌트 트리 하단에 얼마나 깊이 있는지에 관계없이 데이터를 읽을 수 있음
// 여기에서는 현재 테마값으로 'dark'를 전달
function App(props) {
  	return (
      	<ThemeContext.Provider value="dark">
      		<Toolbar />
      	</ThemeContext.Provider>
    );
}

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

function ThemedButton(props) {
    // 리액트는 가장 가까운 상위 테마 Provider를 찾아서 해당되는 값을 사용
    // 만약 해당되는 Provider가 없을 경우 기본값(여기에서는 'light')을 사용
    // 여기에서는 상위 Provider가 있기 때문에 현재 테마의 값은 'dark'
  	return (
      	<ThemeContext.Consumer>
      		{value => <Button theme={value} />}
      	</ThemeContext.Consumer>
     );
}

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

사용자 정보와 AvatarSize를 단계를 거쳐 하위 컴포넌트 Link와 Avatar로 전달

// Page컴포넌트는 PageLayout컴포넌트를 렌더링
<Page user={user} avatarSize={avatarSize} />
  
// PageLayout컴포넌트는 NavigationBar컴포넌트를 렌더링
<PageLayout user={user} avatarSize={avatarSize} />
  
// NavigationBar컴포넌트는 Link컴포넌트를 렌더링
<NavigationBar user={user} avatarSize={avatarSize} />
  
// Link컴포넌트는 Avatar컴포넌트를 렌더링
<Link href={user.permalink}>
  	<Avatar user={user} size={avatarSize} />
</Link>

// Avatar 최하위컴포넌트, user와 avatarSize가 필요
// 여러 단계를 거쳐 props를 통해 전달
// 하지만 불필요하고 번거로움

Context를 사용하지 않고 변수에 저장하여 직접 넘겨주기

Element Variable

function Page(props) {
  	const user = props.user;

  	const userLink = (
      	<Link href={user.permalink}>
      		<Avatar user={user} size={props.avatarSize} />
        </Link>
	);

	// Page Component는 PageLayout Component를 렌더링
	// 이때 props로 userLink를 함께 전달
  	return <PageLayout userLink={userLink} />;
}

// PageLayout Component는 NavigationBar Component를 렌더링
<PageLayout userLink={...} />
// NavigationBar Component는 props로 전달받은 userLink element를 return
<NavigationBar userLink={...} />
  
/* 상위 레벨의 Page Component만 Avatar Component에서
필요로 하는 user와 avatarSize만 알고있으면 됨*/
/* 중간 레벨의 Component를 통해 전달해야하는 Props를
없애고 코드를 간결하게 해줌 */
/* 최상위에 있는 Component 많은 권한을 부여 */

하위 컴포넌트를 여러 개의 변수로 나눠서 전달

// 하위 컴포넌트 의존성을 상위 컴포넌트와 분리

function Page(props) {
  	const user = props.user;
  
  	const topBar = (
      	<NavigationBar>
      		<Link href={user.permalink>
      			<Avatar user={user} size={props.avatarSize} />
      		</Link>
		</NavigationBar>
	);
	const content = <Feed user={user} />;
    
    return (
      	<PageLayout
      		topBar={topBar}
    		content={content}
		/>
    );
}

// 하위 컴포넌트가 상위 컴포넌트가 통신해야 할 경우, renderProps를 사용
// 하나의 데이터에 다양한 레벨에 있는 중첩된 Component들이 접근할 필요가 있음 (Context사용)

💡 Context API

React.createContext()

const MyContext = React.createContext(기본값);
  • Context 생성
    • 상위 레벨에 매칭되는 Provider가 없다면 기본 값이 사용
    • 기본값으로 undefined를 넣으면 기본값이 사용되지 않음

Context.Provider

데이터 제공

<MyContext.Provider value={/* some value */}>
  
// value라는 prop, Provider Component의 하위 컴포넌트에 전달
// 하위 컴포넌트들이 이 값을 사용
// Consuming Component, 값이 변하면 재렌더링
  • React.createContext 함수를 사용해 Context를 생성하면
    하위 컴포넌트가 해당 Context 데이터를 받을 수 있게 설정
  • 모든 Context 객체는 Provider의 React Component를 갖음
  • 하위 컴포넌트를 감싸 주면 모든 하위 컴포넌트들이 해당 Context의 데이터에 접근

❕ 참고 링크
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#description

Provider value에서 주의해야 할 사항

Provider Component가 재렌더링될 때마다 모든 하위 Consumer Component가 재렌더링 됨

function App(props) {
  	return (
      	// value를 직접 넣는 것이 아니고 component state를 옮기고 해당 state 값을 넣어줌
      	<MyContext.Provider value={{ something: 'something' }}>
      		<Toolbar />
      	</MyContext.Provider>
	);
}

state를 사용하여 불필요한 재렌더링 막기

function App(prosp) {
  	const [value, setValue] = useState({ something: 'something' });
  
  	return (
      	<MyContext.Provider value={value}>
      		<Toolbar />
      	</MyContext.Provider>
	);
}

Class.contextType

  • Provider 하위에 있는 클래스 컴포넌트에서 context 데이터에 접근하기 위해 사용
    • 거의 사용하지 않기 때문에 이런 것이 있다고 참고
class MyClass extends React.Component {
  	componentDidMount() {
      	let value = this.context;
      	/* MyContext의 값을 이용하여 원하는 작업을 수행 가능 */
    }
  	componentDidUpdate() {
      	let value = this.context;
      	/* ... */
    }
  	componentWillnmount() {
      	let value = this.context;
      	/* ... */
    }
  	render() {
      	let value = this.context;
      	/* MyContext의 값에 따라서 컴포넌트들을 렌더링 */
    }
}
MyClass.contextType = MyContext;
// MyClass라는 Class Component는 MyContext 데이터에 접근
// Class Component에 있는 Context Type 속성에는 React.createContext 함수를 통해 생성된 context 객체가 대입될 수 있음
// this.context를 통해 상위에 있는 provider 중에서 가장 가까운 값을 가져올 수 있음

Context.Consumer

Context 데이터를 구독

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

/* Function as a child
 컴포넌트 자식으로 함수가 올 수 있음 */
/* Context.Consumer로 감싸주면 
자식으로 들어간 함수가 현재 Context value를 받아 React node로 return*/
/* 이 때 함수로 전달되는 value는 provider의 value prop과 동일 */
/* 만약 상위 컴포넌트의 provider가 없다면 이 value parmeter는 createcontext로 호출하는 기본값과 동일 */

function as a child

  • 사용하는 방법
    • 하위 컴포넌트들을 children prop으로 전달
    • children 컴포넌트 대신 함수를 사용
// children이라는 prop을 직접 선언하는 방식
<Profile children={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()

useContext() Hook을 사용

function MyComponent(props) {
  	const value = useContext(MyContext);
  	
  	return (
      	...
    )
}
  • react.createContext 함수 호출로 생성된 context 객체를 인자로 받아서 현재 context 값을 리턴

  • useContext Hook을 사용하면

    • context값을 다른 방식과 동일하게 컴포넌트 트리상에서 가장 가까운 상위 provider를 받아옴
  • 만약 context값이 변경되면 변경된 값과 함께 useContext hook을 사용하는 컴포넌트가 재렌더링

  • useContext hook을 사용하는 컴포넌트 렌더링이 무거운 작업일 경우

    • 별도로 최적화 작업
  • 파라미터로 context 객체를 넣어줘야함

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

// 잘못된 사용법
useContext(MyContext.Consumer);
useContext(MyContext.Provider);

마무리 하며...

🧠 Context 다시 읽어보고 이해해보자

0개의 댓글