zero-wiki Help

컨텍스트 스타일링

컨텍스트란 무엇일까?

기존의 리액트는 컴포넌트의 props를 통해서 부모에서 자식으로 단방향으로 전달되었습니다. 여러 컴포넌트에 걸쳐 자주 사용되는 데이터의 경우 기존 방식을 사용하면 너무 복잡해지고 불편함이 많습니다. 따라서 생긴게 컨텍스트입니다..

컨텍스트는 기존 리액트 컴포넌트 사이에서 props를 통해 전달하는 방식 대신 컴포넌트 트리를 통해 곧바로 컴포넌트에 전달하는 새로운 방식을 제공합니다.

프롭스 드릴링

루트 컴포넌트에서 E 컴포넌트에서 사용하려면 최소 2번 props를 넘겨야합니다. 만약 2차례가 아니라 10번이 중첩이 되어있다면 10번이나 넘겨주어야 합니다.

use context

만약 컨텍스트를 사용하면 바로 적용이 가능합니다. 일일이 props로 전달할 필요 없이 컴포넌트에 곧바로 데이터를 전달할 수 있습니다.

언제 컨텍스트를 사용해야 할까?

여러 컴포넌트에서 자주 필요로 하는 데이터로는 사용자의 로그인 여부, 로그인 정보, UI테마, 현재 선택된 언어등이 있습니다. 예를 들어 로그인 여부에 따라서 로그인 버튼과 로그아웃 버튼을 선택적으로 보여주고 싶은 경우, 현재 로그인 상태 데이터에 접근할 필요가 있습니다. 이와 마찬가지로 테마와 현재선택된 언어같은 데이터도 접근이 자주 일어날 가능성이 높은 데이터입니다.

기존 코드의 안좋은 예시

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

위 코드에서는 App 컴포넌트에서 Toolbar 컴포넌트로 Toolbar 컴포넌트에서 ThemedButton 버튼으로 Theme를 넘깁니다. props를 통해서 데이터를 전달하는 기존 방식은 실제 데이터를 필요로 하는 컴포넌트까지의 깊이가 깊어질수록 복잡해집니다.

const ThemeContext = React.createContext('light'); function App(props){ return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props){ return ( <div> <ThemeButton /> </div> ) } function ThemedButton(props){ return( <ThemeContext.Consumer> {value => <Button theme={value}/>} </ThemeContext.Consumer> ) }

위 코드에서는 React.createContext( ) 함수를 사용해서 ThemeContext 라는 이름의 컨텍스트를 하나 생성했습니다. 컨텍스트를 사용할 컴포넌트의 상위 컴포넌트에서 Provider로 감싸주어야 하는데 여기서는 최상위 컴포넌트인 App 컴포넌트를 ThemeContext.Provider로 감싸주었습니다. 이렇게 하면 Provider의 모든 하위 컴포넌트가 얼마나 깊이 위치해 있는지 관계없이 컨텍스트의 데이터를 읽을 수 있습니다.

컨텍스트를 사용하기 전에 고려할 점

컨텍스트는 다른 레벨의 많은 컴포넌트가 특정 데이터를 필요로 하는 경우에 주로 사용합니다. 하지만 무조건 컨텍스트를 사용 사용하는 것이 좋은 것은 아닙니다. 컴포넌트와 컨텍스트가 연계되면 재사용성이 떨어지고 해당 데이터가 변경될경우 전부 re-rendering 되기 때문입니다.

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

위 코드의 방식은 컴포넌트의 의존성을 상위 컴포넌트와 분리할 필요가 있는 대부분의 경우 적합한 방법입니다. 또한 렌더링 전에 하위 컴포넌트가 상위 컴포넌트와 통신해야 하는 경우 render props를 사용하여 처리할 수도 있습니다.

하지만 어떤 경우에는 하나의 데이터에 다양한 레벨에 있는 중첩된 컴포넌트들의 접근이 필요할 수 있습니다. 이러한 경우 위 방식은 사용할 수 없고 컨텍스트를 사용해야 합니다. 컨텍스트는 해당 데이터와 데이터의 변경사항을 모두 하위 컴포넌트에게 broadcast (널리 알려주는 것) 해주기 때문입니다.

컨텍스트를 사용하기 적합한 데이터의 대표적인 예로 현재 지역 정보, UI 테마, 그리고 캐싱된 데이터 등이 있습니다.

컨텍스트 API

  1. React.createContext

    컨텍스트를 사용하기 위해서 가장 먼저 해야 할 일은 컨텍스트를 생성하는 것입니다. 컨텍스트를 생성하기 위해서 React.createContext( ) 함수를 사용합니다. 이후 함수의 파라미터로 기본값을 넣어주면 컨텍스트 객체가 생성이 됩니다.

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

    리액트에서 렌더링이 일어날 때 컨텍스트 객체를 구독하는 하위 컴포넌트가 나오면 현재 컨텍스트의 값을 가장 가까이에 있는 상위 레벨의 Provider로부터 받아오게 됩니다. 만약 상위 레벨에 매칭되는 Provider가 없다면, 이 경우에만 기본값이 사용됩니다. 그렇기 때문에 기본값은 provider 없이 컴포넌트를 테스트할 때 유용합니다. 기본값을 undfined를 넣으면 사용되지 않습니다.

  2. Context.Provider

    컴포넌트를 만들었다면 하위 컴포넌트들이 해당 컨텍스트의 데이터를 받을 수 있도록 설정해줘야 합니다. 이를 위해서 사용하는 것이 Provider입니다. Provider는 “제공자”라는 뜻을 가지고 있습니다. 여기에서 제공자는 컴포넌트라고 이해하면 됩니다.

    모든 컨텍스트 객체는 Provider라는 리액트 컴포넌트를 갖고 있습니다.

    Context.Provider 컴포넌트로 하위 컴포넌트들을 감싸주면 모든 하위 컴포넌트들이 해당 컨텍스트의 데이터에 접근할 수 있습니다.

    provider는 다음과 같이 작성합니다.

    <Mycontext.provider value={/*some value*/}>

    Provider 컴포넌트에는 value라는 prop이 있으며 Provider 컴포넌트 하위에 있는 컴포넌트 들에게 전달됩니다. 하위 컴포넌트들은 이 값을 사용하고 consumer 컴포넌트라고 부릅니다. 하나의 Provider 컴포넌트는 여러 개의 consumer 컴포넌트와 연결될 수 있으며 여러 개의 Provider 컴포넌트는 중첩되어 사용될 수 있습니다.

    Provider 컴포넌트로 감싸진 모든 consumer 컴포넌트는 Provider의 value prop이 바뀔때마다 재렌더링 됩니다. 값이 변경되었을 때 상위 컴포넌트가 업데이트 대상이 아니더라도 하위에 있는 컴포넌트가 컨텍스트를 사용한다면 하위 컴포넌트에서 업데이트가 일어납니다.

    Provider value에서 주의해야 할 사항

    컨텍스트가 재렌더링 여부를 결정할 때 레퍼런스 정보를 사용하기 때문에 Provider의 부모 컴포넌트가 재렌더링되었을 경우, 의도치 않게 consumer 컴포넌트의 재렌더링이 일어날 수 있습니다. 왜냐하면 valu prop을 위한 새로운 객체가 매번 새롭게 생성되기 때문입니다.

    function App(props){ return ( <MyContext.Provider value={{something: 'something'}}> <Toobar /> </MyContext.Provider> ) }

    이를 방지하기 위해선 value의 직접 넣는게 아니라 컴포넌트의 state로 옮기고 해당 state 값을 넣어 주어야 합니다.

    function App(props){ const [value, setValue] = useState({ something: 'something' }); return ( <MyContext.Provider value={value}> <Toobar/> </MyContext.Provider> ); }
  3. Class.ContextType

    Provider 하위에 있는 클래스 컴포넌트에서 컨텍스트 데이터에 접근하기 위해 사용하는 것입니다. 클래스 컴포넌트는 현재 거의 사용하지 않기 때문에 이런 방법이 있다는 정도로만 참고하기 바랍니다.

    MyClass.contextType = MyContext; 라고 해주면 MyClass 라는 클래스 컴포넌트는 MyContext에 접근할 수 있습니다.

    class MyClass extends Ract.Componet{ componentDidMount(){ let value = this.context; } componetDidUpdate(){ let value = this.context; } componentWillUnmount(){ let value = this.context; } render(){ let value = this.constext; } } MyClass.contextType = MyContext;
  4. Context.Consumer

  5. Context.displayName

여러 개의 컨텍스트 사용하기

여러개의 컨텍스트를 사용하고 싶을때에는 Context.Provider를 중첩해서 사용하는 방식으로 구현할 수 있습니다.

import React, { useContext } from 'react'; import { ThemeContext } from './ThemeContext'; import { UserContext } from './UserContext'; function MyComponent() { const { theme } = useContext(ThemeContext); const { user } = useContext(UserContext); // 이제 theme과 user에 대한 정보를 사용하여 컴포넌트를 렌더링할 수 있습니다. return ( <div> <h1 style={{ color: theme.primaryColor }}>Hello, {user.name}!</h1> </div> ); } export default MyComponent;

위 코드에서는 ThemeContext와 UserContext를 중첩하여 사용하고 있습니다. 이를 통해 theme와 user 두 개의 컨텍스트 값을 모두 사용할 수 있게 됩니다. 중첩된 컨텍스트의 경우, 상위 컨텍스트는 Provider를 사용하여 하위 컨텍스트에 값을 전달할 수 있습니다.

import React from 'react'; import { ThemeContext, themes } from './ThemeContext'; import { UserContext, users } from './UserContext'; import MyComponent from './MyComponent'; function App() { return ( <ThemeContext.Provider value={themes.dark}> <UserContext.Provider value={users[0]}> <MyComponent /> </UserContext.Provider> </ThemeContext.Provider> ); } export default App;

위 코드에서는 TemeContext.Provider와 UserContext.Provider를 모두 제공하고 있습니다. 이를 통해 MyComponent에서 theme와 user값 모두 사용할 수 있게 됩니다.

여러개의 컨텍스트를 동시에 사용할 수 있습니다. 하지만 두 개 또는 그 이상의 컨텍스트의 값이 자주 함께 사용될 경우 모든 값을 한 번에 제공해주는 별도의 render prop 컴포넌트를 직접 만드는것을 고려하는게 좋습니다.

useContext

함수 컴포넌트에서는 컨텍스트를 사용하기 위해 컴포넌트를 매번 Consumer 컴포넌트로 감싸주는 것보다 훅을 사용하면 쉽게 사용할 수 있게 해줍니다. useContext() 훅은 React.createContext() 함수 호출로 생성된 컨텍스트 객체를 인자로 받아서 현재 컨텍스트 값을 리턴합니다.

function MyComponent(props) { const value = useContext(MyContext); return ( ... ) }

useContext() 훅을 사용하면 다른 방식과 동일하게 컴포넌트 트리 상에서 가장 가까운 상위 Provider로부터 컨텍스트의 값을 받아오게 됩니다. 만약 컨텍스트의 값이 변경되면 변경된 값과 함께 useContext ( ) 훅을 사용하는 컴포넌트가 재 렌더링 됩니다. 그렇기 때문에 useContext() 훅을 사용하는 컴포넌트의 렌더링이 무거운 작업일 경우 별도로 최적화 작업(useMemo, useCallback)을 해줄 필요가 있습니다.

또한 useContext() 훅을 사용할 때에는 파라미터로 컨텍스트 객체를 넣어줘야 한다는 것을 기억하기 바랍니다. 아래 코드처럼 Consumer나 Provider를 넣으면 안 됩니다.

// good useContext(Mycontext); // bad useContext(Mycontext.Consumer); useContext(Mycontext.Provider);
Last modified: 08 August 2024