[React Native] props drilling 막는 방법, 글로벌 상태 관리 Context ?

fejigu·2023년 2월 9일
1

React Native

목록 보기
4/11


🔥 props drilling 문제.. 수정 필요

👉🏻 전일에 이어 로그인 여부에 따라 화면을 다르게 보여주는 부분에 있어, props drilling을 막고 데이터를 전달하는 방법으로 구현하기 위해 수정이 필요해보였다.

그 과정에서 Context에 대해 학습이 필요했고, Context를 사용하여 수정한 내용을 정리해보고자 한다.


🔎 Context란 ?

👉🏻 Context는 모든 수준에서 수동으로 전달하지 않고도 구성 요소 트리를 통해 데이터를 전달하는 방법을 제공한다.

일반적인 React 애플리케이션에서 데이터는 props를 통해 하향식으로 전달되지만, 애플리케이션 내의 많은 구성 요소에 필요한 특정 유형의 props에 대해 번거로운 문제가 있다.

그래서 prop을 명시적으로 전달하지 않고도 구성 요소 간에 값을 공유하는 방법을 제공한다는 Context를 사용하고자 했다.


⭐️ 해결 방법

👉🏻 해결하고자 했던 문제 : 유저의 로그인 여부에 따라 다른 화면으로 이동시키는데, 전일 작성했던 코드에서 생긴 props drilling 문제.
👉🏻 해결에 사용한 방법 : UserContext(UserContext.Provider, UserContext.Consumer)
👉🏻 사고 과정 :

1. UserContext.Provider가 제공하는 데이터를 사용 
2. UserContext.Provider는 자식으로 있는 모든 컴포넌트에 value에 작성된 데이터를 전달 
3. 데이터를 받는 곳, UserContext의 Consumer를 사용해서 render props 패턴으로 받기
4. UserContext.Consumer에서 파라미터로 전달된 setUser를 받기
5. setUser를 사용하는 onSubmit 함수에 전달하기 위해 
6. onSubmit 함수를 호출하는 곳애서 setUser를 파라미터로 전달
7. onSubmit 함수에서는 전달된 setUser를 사용해서 
8. signIn 함수가 성공했을때 전달된 데이터로 
9. setUser(유저 상태 변수)를 수정
10. useState(유저 상태 변수)가 변경
11. AuthStack 컴포넌트 대신에 MainStack 컴포넌트를 사용해서 ListScreen 화면이 나오는 것 
12. 문제 해결!

⭐️⭐️ 적용 및 코드 수정

👉🏻 전일 작성했던 코드를 수정해보자!

//App.js
import { NavigationContainer } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar';
import { useState } from 'react';
import UserContext from './contexts/UserContent';
import AuthStack from './navigations/AuthStack';
import MainStack from './navigations/MainStack';

const App = () => {
  //10. 유저 상태 변수가 변경되니 
  const [ user, setUser ] = useState(null);

  return (
  //1. UserContext.Provider가 제공하는 데이터를 사용
  //2. UserContext.Provider는 자식으로 있는 모든 컴포넌트에 value에 작성된 데이터를 전달 
  //11. AuthStack 컴포넌트 대신에 MainStack 컴포넌트를 사용해서 ListScreen 화면이 나오는 것 
  <UserContext.Provider value={{setUser}}>
    <NavigationContainer>
      <StatusBar style="dark" />
      
      {user ? <MainStack /> : <AuthStack />} 
    </NavigationContainer>
  </UserContext.Provider>
  );
};

export default App;
//SignInScreen.js
import { useEffect, useRef, useState } from 'react';
import { Alert, Image, Keyboard, StyleSheet, View } from 'react-native';
import { signIn } from '../api/auth';
import Button from '../components/Button';
import Input, {
  IconNames,
  KeyboardTypes,
  ReturnKeyTypes,
} from '../components/Input';
import SafeInputView from '../components/SafeInputView';
import PropTypes from 'prop-types';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import UserContext from '../contexts/UserContent';

const SignInScreen = ({ navigation }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  //useRef은 값이 변해도 리렌더링 되지 않는다
  //valueRef.current에 값이 들어간다 
  const passwordRef = useRef(null);
  const [disabled, setDisabled] = useState(true);
  const [isLoading, setIsLoading] = useState(false);

  const insets = useSafeAreaInsets();

  useEffect(() => {
    setDisabled(!email || !password);
  }, [email, password]);

  //5. setUser를 사용하는 onSubmit 함수에 전달하기 위해 
  //7. onSubmit 함수에서는 전달된 setUser를 사용해서 
  const onSubmit = async (setUser) => {
    if (!disabled && !isLoading) {
      Keyboard.dismiss();
      setIsLoading(true);
      try {
        //8. signIn 함수가 성공했을때 전달된 데이터로 
        const data = await signIn(email, password);
        setIsLoading(false);
        //9. 유저 상태 변수를 수정했다 
        setUser(data);
        //화면 이동 
        navigation.navigate('List');
      } catch (e) {
        Alert.alert('SignIn Failed', e, [
          {
            text: 'OK',
            onPress: () => setIsLoading(false),
          },
        ]);
      }
    }
  };

  return (
    //3. 데이터를 받는 곳, UserContext의 Consumer를 사용해서 render props 패턴으로 받으면 된다
    //4. 파라미터로 전달된 setUser를 받아와서 
    <UserContext.Consumer>
      {({ setUser}) => {
        return (
    <SafeInputView>
      <View
        style={[
          styles.container,
          { paddingTop: insets.top, paddingBottom: insets.bottom },
        ]}
      >
        <Image
          source={require('../../assets/main.png')}
          style={styles.image}
          resizeMode={'cover'}
        />
        <Input
          value={email}
          onChangeText={(text) => setEmail(text.trim())}
          title={'email'}
          placeholder={'your@email.com'}
          keyboardType={KeyboardTypes.EMAIL}
          returnKeyType={ReturnKeyTypes.NEXT}
          iconName={IconNames.EMAIL}
          onSubmitEditing={() => passwordRef.current.focus()}
        />
        <Input
          ref={passwordRef}
          value={password}
          onChangeText={(text) => setPassword(text.trim())}
          title={'password'}
          secureTextEntry
          iconName={IconNames.PASSWORD}
          onSubmitEditing={() => onSubmit(setUser)}
        />
        <View style={styles.buttonContainer}>
          <Button
            title={'LOGIN'}
            // 6. onSubmit 함수를 호출하는 곳애서 setUser를 파라미터로 전달
            onPress={() => onSubmit(setUser)}
            disabled={disabled}
            isLoading={isLoading}
          />
        </View>
      </View>
    </SafeInputView>
        );
      }}
    </UserContext.Consumer>
  );
};
SignInScreen.propTypes = {
  navigation: PropTypes.object,
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  image: {
    width: 200,
    height: 200,
  },
  buttonContainer: {
    width: '100%',
    paddingHorizontal: 20,
    marginTop: 20,
  },
});
export default SignInScreen;
profile
console.log(frontendjigu( ☕️, 📱); // true

0개의 댓글