대외활동/Draconist-프론트

REACT NATIVE 스터디. 2주차 7단원. Context API DRACONIST

피스타0204 2025. 1. 29. 03:31

컴포넌트 단에서가 아닌 전체에서 상태(변수)를 관리하려면 어떻게 해야 할까요?
실습을 진행하며 알아봅시다.

npx create-expo-app react-native-context

cd react-native-context

npm install styled-components
import App from '../../src/App';  

export default App;


1. 전역 상태 관리

일반적인 리액트 네이티브 애플리케이션의 경우 데이터는 부모 컴포넌트에서 자식 컴포넌트로 전달됩니다.  만약 데이터를 사용하는 컴포넌트가 많다면, 최상위 컴포넌트인 App 컴포넌트에 서 상태를 관리하여 하위 컴포넌트 어디서 필요로 하든 전달할 수 있게 설정합니다.


하지만 그렇게 하면 위 그림에서처럼 F까지 도달하는데 App > A >B > D >F 로 전달해야 합니다.
또, 자식 컴포넌트에서는 부모로부터 받은 데이 터를 변경할 수 없으므로 데이터를 전달받은 과정의 역순으로 App 컴포넌트에 데이터 변경 요 청을 전달해야 합니다.

이런 방법으로 상태를 관리하면, 관리하는 상태가 추가되거나 변경될 경우 과정에 속한 모든 컴포넌트를 찾아서 수정해야 한다는 단점이 있습니다. 이렇게 최상위 컴포넌트에서 전역 상태 를 관리하는 방식은 개발 단계뿐만 아니라 유지보수에서도 매우 불편합니다

하지만 Context API를 이용하면 Context를 생성해 필요한 컴포넌트에서 데이터를 바로 받아올 수 있습니다. Context API는 데이터를 전역적으로 관리하기 위해 "context"라는 저장소를 만들어 사용하기 때문에 어떤 컴포넌트에서든 바로 접근할 수 있습니다.


2. Context API

1) Consumer 컴포넌트

context API의 context저장소는 createContext메서드로 생성할 수 있고 기본값을 부여할 수 있습니다.

const Context = createContext(defaultValue);

생성된 Context 오브젝트는 입력된 기본값 외에도 Consumer 컴포넌트와 Provider 컴포넌 트를 갖고 있습니다.

먼저 Consumer 컴포넌트Context의 내용을 읽고 사용하게 해줍니다.

Consumer 컴포넌트는 상위 컴포넌트 중 가장 가까운 곳에 있는 Provider 컴포넌트가 전달하는 데이터를 이용합니다. 만약 상위 컴포넌트 중 Provider 컴포넌트가 없다면 createContext 함 수의 파라미터로 전달된 기본값을 사용합니다.


src 폴더 안에 components 폴더를 생성하고 Consumer 컴포넌트를 이용해서 createContext 함수의 파라미터로 전달된 기본값을 출력하는 User 컴포넌트를 작성해보겠습니다

//context/User.js

import { createContext } from 'react';

const UserContext = createContext({ name: 'ullBbang college student' });

export default UserContext;

 

//component/User.js

import React from 'react';
import styled from 'styled-components/native';
import UserContext from './context/User'; // 경로 수정

const StyledText = styled.Text`
  font-size: 24px;
  margin: 10px;
`;

const User = () => {
  return (
    <UserContext.Consumer>
      {value => <StyledText>Name: {value.name}</StyledText>}
    </UserContext.Consumer>
  );
};

export default User;

 

//App.js

import React from 'react';
import styled, { ThemeProvider } from 'styled-components/native';
import { theme } from './theme';
import User from './components/User';

const Container = styled.View`
  flex: 1;
  background-color: ${({ theme }) => theme.background};
  align-items: center;
  justify-content: center;
`;

const App = () => {
  return ( 
      <Container>
        <User/>
      </Container>
  );
};

export default App;

 

provider가 없으므로 자동으로 기본값이 출력됩니다.

 


2) provider 컴포넌트

Context에 있는 Provider 컴포넌트는 하위 컴포넌트에 Context의 변화를 알립니다. Provider 컴포넌트는 value를 받아서 모든 하위 컴포넌트에 전달하고, 하위 컴포넌트는 Provider 컴포넌트의 value가 변경될 때마다 다시 렌더링됩니다.

//App.js

import React from 'react';
import styled, { ThemeProvider } from 'styled-components/native';
import { theme } from './theme';
import User from './components/User';
import UserContext from './context/User';

const Container = styled.View`
  flex: 1;
  background-color: ${({ theme }) => theme.background};
  align-items: center;
  justify-content: center;
`;

const App = () => {
  return (
    <UserContext.Provider  value={{ name: 'ulBbang' }}>
      <Container>
        <User />
      </Container>
    </UserContext.Provider>
  );
};

export default App;

 

Provider 컴포넌트로부터 value를 전달받는 하위 컴포넌트의 수에는 제한이 없습니다. Provider 컴포넌트를 사용할 때 반드시 value를 지 정해야 한다는 점과 Consumer 컴포넌트 는 가장 가까운 Provider 컴포넌트가 전달하는 값을 이용한다는 점을 꼭 기억해두기 바랍니다.

3) Context 수정하기

하지만 위에서처럼 Provider를 매번 생성하고 value값을 지정해주어야 하는 것은 오류발생빈도를 높입니다. UserContext 컴포넌트로 한번에 상태와 로직을 관리해보겠습니다.

//context/User.js

import React, { createContext, useState } from 'react';

const UserContext = createContext({
  user: { name: '' },
  dispatch: () => {},
});

const UserProvider = ({ children }) => {
  const [name, setName] = useState('Beomjun Kim');
  const value = { 
    user: { name }, 
    dispatch: setName 
  };

  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};

const UserConsumer = UserContext.Consumer;

export { UserProvider, UserConsumer };
export default UserContext;

//App.js

import React from 'react';
import styled, { ThemeProvider } from 'styled-components/native';
import { theme } from './theme';
import User from './components/User';
import { UserProvider } from './context/User';

const Container = styled.View`
  flex: 1;
  background-color: ${({ theme }) => theme.background};
  align-items: center;
  justify-content: center;
`;

const App = () => {
  return (
    <UserProvider>
      <Container>
        <User />
      </Container>
    </UserProvider>
  );
};

export default App;

//components/User.js

import React from 'react';
import styled from 'styled-components/native';
import { UserConsumer } from '../context/User';

const StyledText = styled.Text`
  font-size: 24px;
  margin: 10px;
`;

const User = () => {
  return (
    <UserConsumer>
      {({ user }) => <StyledText>Name: {user.name}</StyledText>}
    </UserConsumer>
  );
};

export default User;

 

이렇게 하면 context/user에서 값을 변경하고 관리하고, components/user에서 값을 잘 동봉한 컴포넌트를 바로 사용할 수 있습니다.

//components/Input.js

import React, { useState } from 'react';
import styled from 'styled-components/native';
import { UserConsumer } from '../context/User';

const StyledInput = styled.TextInput`
  border: 1px solid #606060;
  width: 250px;
  padding: 10px 15px;
  margin: 10px;
  font-size: 24px;
`;

const Input = () => {
  const [name, setName] = useState('');

  return (
    <UserConsumer>
      {({ dispatch }) => {
        return (
          <StyledInput
            value={name}
            onChangeText={text => setName(text)}
            onSubmitEditing={() => {
              dispatch(name);
              setName('');
            }}
            placeholder="Enter a name.."
            autoCapitalize="none"
            autoCorrect={false}
            returnKeyType="done"
          />
        );
      }}
    </UserConsumer>
  );
};

export default Input;


value={name}: StyledInput의 값으로 name 상태 변수를 바인딩합니다. 사용자가 텍스트를 입력하면 name이 갱신됩니다.

onChangeText={text => setName(text)}: 사용자가 텍스트 필드를 수정하면, 입력된 텍스트를 setName 함수로 상태에 업데이트합니다.

dispatch는 일반적으로 상태 관리에서 액션을 발생시키는 함수입니다.

onSubmitEditing은 사용자가 텍스트 입력 필드에서 엔터키를 누를 때 호출됩니다. 이때, 먼저 dispatch(name)이 실행되어 사용자가 입력한 name 값이 UserContext에 전달되어 상태가 갱신됩니다.

 

App.js에 Input을 추가하면 다음과 같은 화면에서 우리가 입력한 값으로 Context값을 변경할 수 있습니다.

 

3) UseContext

useState과 useContext Hook을 사용하면 Consumer 컴포넌트를 사용하는 대신 Context에서 제공하는 값을 간편하게 사용할 수 있게 해주는 함수입니다.

Consumer 컴포넌트를 사용할 때는 Consumer 컴포넌트의 자식으로 반드시 리액트 컴포넌 트를 반환하는 함수를 넣어야 하지만, useContext를 이용하면 Consumer 컴포넌트를 사용 했을 때보다 사용법이 훨씬 간편하고 코드의 가독성도 좋아지는 것을 볼 수 있습니다.

//components/User.js

import React, {useContext} from 'react';
import styled from 'styled-components/native';
import { UserConsumer } from '../context/User';
import UserContext from '../context/User';

const StyledText = styled.Text`
  font-size: 24px;
  margin: 10px;
`;

const User = () => {
  const { user } = useContext(UserContext);
  return <StyledText>Name: {user.name}</StyledText>;
};

export default User;

 

//components/Input.js

import React, { useState, useContext } from 'react';
import styled from 'styled-components/native';
import UserContext from '../context/User';

const StyledInput = styled.TextInput`
  border: 1px solid #606060;
  width: 250px;
  padding: 10px 15px;
  margin: 10px;
  font-size: 24px;
`;

const Input = () => {
  const [name, setName] = useState('');
  const { dispatch } = useContext(UserContext); // useContext로 Context 값을 가져옴

  return (
    <StyledInput
      value={name}
      onChangeText={text => setName(text)}
      onSubmitEditing={() => {
        dispatch(name); 
        setName('');
      }}
      placeholder="Enter a name.."
      autoCapitalize="none"
      autoCorrect={false}
      returnKeyType="done"
    />
  );
};

export default Input;

상태를 전달해야 하는 관계의 구조가 간단하다면 굳이 Context API를 사용할 필요가 없습니다. 하지만 프로젝트의 구조가 복잡하고 많은 컴포넌트가 이용하는 데이터를 전역적으로 관리할 때는 Context API를 사용하는 것이 좋습니다.