- React 프레임워크에서 의미하는 콘텍스트와 이와 관련된
- createContext API
- useContext hook
- ref 속성
- forwardRef API
위 의 내용을 학습
- 콘텍스트와 ref 속성
- 목차
- 콘텍스트 이해하기
- 콘텍스트란?
- useRef 훅 이해하기
- useImperactiveHandle 훅 이해하기
-
React 패키지는
createContext
라는API
제공- →
createContext
함수 호출로 얻은 값 :context(콘텍스트)
- →
-
스위치 기능을 통해서 react-native-paper 패키지의 기본모드, 다크 모드 테마 설정 가능
설치
yarn add react-native-vector-icons react-native-paper color faker moment moment-with-locales-es6
yarn add @types/react-native-vector-icons @types/color @types/faker
yarn add react-native-appearance
src/screens/MainNavigator.tsx 구현
import React, { useState, useMemo } from 'react';
import { BottomNavigation } from 'react-native-paper';
import Home from './Home';
export default function MainNavigator() {
const [index, setIndex] = useState<number>(0);
const routes = useMemo(
() => [{ key: 'home', title: 'Home', icon: 'home' }],
[]
);
const renderScene = BottomNavigation.SceneMap({
home: Home,
});
return (
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
/>
);
}
컴포넌트의 속성
은부모 → 자식
컴포넌트로 어떤정보를 전달
- → 그러나 부모가 직계 자식이 아닌
손자, 증손자
컴포넌트에게 전달 - → → 번거로운
지속적인 속성 전달
- → 그러나 부모가 직계 자식이 아닌
<자식_컴포넌트 shared_props={shared_props} />;
({ shared_props }) => <손자_컴포넌트 shared_props={shared_props} />;
({ shared_props }) => <증손자_컴포넌트 shared_props={shared_props} />;
React는 이런 속성 전달의 번거로움을 덜고자
콘텍스트(context) 메커니즘
을 구현
- React, R/N에서 콘텍스트
createContext
useContext
- 위 둘을 통해서 이루어짐
// 부모 컴포넌트
// createContext
<Provider value={ }/> // 부모, value에 공유할 정보 설정
// 자식 컴포넌트
useContext → 공유 정보 취득
// 손자 컴포넌트
useContext → 공유 정보 취득
// 증손자 컴포넌트
useContext → 공유 정보 취득
-
콘텍스트 기능을 사용하는 React, R/N 코드는 항상 이름에
- →
Provider
자가 들어간 컴포넌트 - →
use콘텍스트_이름()
형태의커스텀 훅
을 사용
- →
-
콘텍스트 기능을 구현한
react-native-paper
같은 패키지 또한- →
Provider
자가 들어간 컴포넌트 - →
useTheme
형태의Provider가 제공하는 정보를 사용
할 수 있게 하는커스텀 훅
- →
이제 react-native-paper 패키지의 테마 기능을 사용하는 코드를 작성하면서 콘텍스트가 어떤 방식 동작하는지 학습
-
아이폰은 IOS 13
-
안드로이드는 버전 10(API29)
-
다크 모드(dark mode)라는 기능이 생겼음
-
낮 : 기본모드
-
밤 : 어두운 색상의 다크 모드
-
요즘 이를 사용하는 것이 매우 많음
- → 이를 사용하려면
context 기법
을 무조건 사용!!!
- → 이를 사용하려면
- R/N 패키지는 AppearanceProvider 컴포넌트와 useColorScheme 커스텀 훅을 제공
import { AppearanceProvider, useColorScheme } from 'react-native-appearance';
-
React, R/N 에서 제공자를 뜻하는
Provider
자가 들어간 컴포넌트는 항상- → 최상위 컴포넌트(root component)로 동작해야 함
-
그리고 이런 컴포넌트는 React framework가 제공하는 context 기능을 사용하여 만듬
-
다음 코드에서
AppearanceProvider
Provider를 포함- MainNavigator의 상위 컴포넌트로 만든다.
export default function App() {
return (
<AppearanceProvider>
<SafeAreaView>
<MainNavigator />
</SafeAreaView>
</AppearanceProvider>
);
}
-
Provider
컴포넌트를 제공하는 패키지는 항상 이 컴포넌트가 제공하는 기능 사용할 수 있도록- →
use기능()
형태의 커스텀 훅을 제공 - → 그러므로
AppearanceProvider
는useColorScheme
이란 이름의 커스텀 훅을 제공
- →
-
다음 코드는
useColorScheme
커스텀 훅을 호출하여 앱이다크모드
로 동작 중인지 확인
export default function App() {
const scheme = useColorScheme(); // 이 코드 패턴은
return (
<AppearanceProvider>
<MainNavigator />
</AppearanceProvider>
);
}
- 이 코드 패턴은 React, R/N에서 context 기능을 사용할 때 2개 패턴 중 하나로
- →
Provider
관련 커스텀 훅 호출을 한 컴포넌트에서 모두 이용해야 할 때의 패턴
- →
그런데 useColorScheme은 폰의 운영체제가 다크 모드인지만 알려줌
- 앞서 본 화면처럼 실제로 모드에 따라 바탕색과 글자 색을 바꾸려면 또 다른
Provider
컴포넌트 필요
- react-native-paper 패키지는 다음처럼 Provider 컴포넌트를 제공
import { Provider } from 'react-native-paper';
- 그런데 Provider는 너무 일반적인 이름이라 혼락스러울 수 있으므로 다음처럼 PaperProvider로 변경해서 사용
import { Provider as PaperProvider } from 'react-native-paper';
-
PaperProvider 또한
Provider
이므로 다음처럼 MainNavigator의 상위 컴포넌트- → 부모 컴포넌트로 동작해야 함
-
다음 코드에는 두 개의
Provider
컴포넌트가 존재Provider
의 순서는 JSX 구문만 따른다면 자유롭게 설정 가능
-
콘텍스트 기능은 완전히 독립적으로 동작하므로 기능이 다른 콘텍스트에 영향을 주지 않음!!!
다만 MainNavigator 처럼 특정
Provider
가 제공하는 기능을 커스텀 훅을 통해 사용하고 싶다면 해당 Provider의 자식 컴포넌트로 만들어야 함
import { Provider as PaperProvider } from 'react-native-paper';
export default function App() {
return (
<AppearanceProvider>
<SafeAreaView>
<MainNavigator />
</SafeAreaView>
</AppearanceProvider>
);
}
- PaperProvider는 theme이란 선택 속성을 제공
- react-native-paper 패키지는 theme 속성에 설정할 수 있는 값으로 두 가지 제공
import { DefaultTheme, DarkTheme } from 'react-native-paper';
위의 내용들을 바탕으로
import React, { useCallback, useState } from 'react';
import { SafeAreaView, StyleSheet } from 'react-native';
import { AppearanceProvider, useColorScheme } from 'react-native-appearance';
import { Provider as PaperProvider } from 'react-native-paper';
import { DefaultTheme, DarkTheme } from 'react-native-paper';
import MainNavigator from './src/screens/MainNavigator';
export default function App() {
const scheme = useColorScheme();
const [theme, setTheme] = useState(
scheme === 'dark' ? DarkTheme : DefaultTheme
);
const toggleTheme = useCallback(
() => setTheme((theme) => (theme.dark ? DefaultTheme : DarkTheme)),
[]
);
return (
<AppearanceProvider>
<PaperProvider theme={DarkTheme}>
<SafeAreaView style={[styles.safeAreaView]}>
<MainNavigator />
</SafeAreaView>
</PaperProvider>
</AppearanceProvider>
);
}
const styles = StyleSheet.create({
safeAreaView: { flex: 1 },
});
- R/N 패키지는 다음처럼 useTheme 이라는 커스텀 훅을 제공
import { useTheme } from 'react-native-paper';
- useTheme 커스텀 훅은 다음처럼
theme 객체를 반환
- → theme 객체는
PaperProvider
의theme 속성
에설정한 값
이콘텍스트를 통해 전달
된 것
- → theme 객체는
const theme = useTheme();
- 위에서 얻은 theme 에서 비구조화 할당 구문으로 fonts, colors 속성을 얻을 수 있음
const { fonts, colors } = theme;
theme 객체가 제공하는 속성을 알아보려면
`console.log(Object.keys(theme))` 실행해 보면 됨
dark mode로 실행
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { useTheme } from 'react-native-paper';
export default function Home() {
const theme = useTheme();
const { fonts, colors } = theme;
return (
<View style={[styles.view, { backgroundColor: colors.background }]}>
<View style={[styles.bar, { backgroundColor: colors.primary }]}>
<Text style={[styles.text, { color: colors.text }, fonts.medium]}>
TopBar
</Text>
</View>
<View style={styles.content}>
<Text style={[styles.text, { color: colors.text }, fonts.regular]}>
TopBar
</Text>
</View>
<View style={[styles.bar, { backgroundColor: colors.accent }]}>
<Text style={[styles.text, { color: colors.text }, fonts.light]}>
BottomBar
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
view: { flex: 1 },
bar: { height: 50, flexDirection: 'row', padding: 5, alignItems: 'center' },
content: { flex: 1, alignItems: 'center', justifyContent: 'center' },
text: { fontSize: 20, textAlign: 'center' },
});
-
앞의 코드에서 가장 흥미로운 부분
- →
useTheme
커스텀 훅의 사용 - → → 부모 컴포넌트 쪽에 구현한
Provider
컴포넌트 제공콘텍스트 기능
- → →
자식 컴포넌트에 사용
할 때의 패턴
- →
-
만약 useTheme 커스텀 훅이 없다면
- → 밑 코드와 같이 계속 해서 속성을 넘겨 받아야 함
- 훅이 있으면 코드가 간편해짐
훅 없이 구현 하기...
type HomeProps = {
theme: any;
};
function Home({ theme }: HomeProps) {}
- context 기능을 구현할 때
가장 먼저 해야할
일!- → react 패키지가 제공하는
createContextAPI
사용하여context 객체 생성
- → react 패키지가 제공하는
import React, { createContext } from 'react';
그러나 Ts 에서는 다음 코드 패턴으로 createContext API를 호출해야함
type ContextType = {
// 공유하려는 데이터 속성
};
const defaultContextValue: ContextType = {
// 공유하려는 데이터 속성 초기값
};
const SomeContext = createContext<ContextType>(defaultContextValue);
src/contexts sub dir 생성
ToggleThemeContext 생성
import React, { createContext } from 'react';
export type ToggleThemeContextType = {}; // -1-
const defaultToggleThemeContext = {}; // -2-
const ToggleThemeContext = createContext<ToggleThemeContextType>(
defaultToggleThemeContext // -3-
);
- 공유하려는
데이터 속성
- 공유하려는 데이터
속성의 초기값
설정 createContext
를 이용한 1, 2 번을 조합한Context
생성
-
createContext API
로생성된 객체
는- →
Provider
와Consumer
가 제공하는 기능을 사용하는 - → → 클래스 컴포넌트를 위한 컴포넌트
- →
-
하지만 여기서는
함수 방식으로 컴포넌트
를 구현- → 그렇기에
Consumer는 무시
해도 됨 - → → 함수 컴포넌트에서는 Consumer 보다 훨씬 사용법이 간단한 :
custom hook
사용!
- → 그렇기에
-
Provider 컴포넌트
는 다음처럼value
와children
속성이 존재하는- →
ProviderProps
속성을 제공 - 타입 변수 T :
createContext<T>
와 같아야 함 children
은 이전 children과똑같은 의미
의 속성value
:context Provider
가제공하는 기능
- →
interface ProviderProps<T> {
value: T;
children?: ReactNode;
}
위를 바탕으로 ToggleThemeContext 생성
import React, { createContext, FC } from 'react';
export type ToggleThemeContextType = {};
const defaultToggleThemeContext = {};
const ToggleThemeContext = createContext<ToggleThemeContextType>(
defaultToggleThemeContext
);
type ToggleThemeContextProps = {}; // -1-
export const ToggleThemeProvider: FC<ToggleThemeContextProps> = ({
children,
}) => {
const value = {};
return (
<ToggleThemeContext.Provider value={value}>
{children}
</ToggleThemeContext.Provider>
);
};
- FC 타입은 children 속성을 기본 제공하기 때문에 속성을 명시적으로 제공하지 않아도 됨
react 패키지는 useContext 훅을 제공
import { useContext } from 'react';
useContext
훅은 콘텍스트객체가 제공
하는- →
Provider
컴포넌트의value
속성값을 얻을목적으로 사용
하는 훅
- →
export const useToggleTheme = () => {
const value = useContext(ToggleThemeContext);
return value;
};
- 앞서 사용했던 useColorScheme, useTheme 커스텀 훅 또한 이런 코드 패턴으로 만들어진 커스텀 훅
App.tsx 에 내용추가
export default function App() {
const scheme = useColorScheme();
const [theme, setTheme] = useState(
scheme === 'dark' ? DarkTheme : DefaultTheme
);
const toggleTheme = useCallback(
// eslint-disable-next-line no-shadow
() => setTheme((theme) => (theme.dark ? DefaultTheme : DarkTheme)),
[]
);
return (
<AppearanceProvider>
<PaperProvider theme={theme}>
<ToggleThemeProvider toggleTheme={toggleTheme}>
<SafeAreaView style={[styles.safeAreaView]}>
<MainNavigator />
</SafeAreaView>
</ToggleThemeProvider>
</PaperProvider>
</AppearanceProvider>
);
}
- 아래 코드는 ToggleThemeProvider 컴포넌트에 toggleTheme 속성을 추가하고
- → 얻은 toggleTheme 함수를 ToggleThemeContext.Provider의 value 속성에 설정
type ToggleThemeContextProps = {
toggleTheme: () => void;
}
export const ToggleThemeProvider: FC<ToggleThemeContextProps> = ({
children, toggleTheme
}) => {
const value = {
toggleTheme
}
return <ToggleThemeContext.Provider value={value}>
}
- 그런데 value 속성의 타입은 제네릭 함수 createContext(defaultValue)의 타입 변수 T와 같아야함
- ToggleThemeContextType에 ()=> void 타입의 toggleTheme 선언,
- defaultToggleThemeContext에 toggleThem의 기본 형태를 구현
export type ToggleThemeContextType = {
toggleTheme: () => void;
};
const defaultToggleThemeContext = {
toggleTheme: () => {},
};
- 이럴 때
useToggleTheme
커스텀 훅의 구현 내용에 따르면toggleTheme
함수는 다음 코드 형태로 얻을 수 있음
const { toggleTheme } = useToggleTheme();
위의 내용을 바탕으로 구현
import React, { createContext, FC, useContext } from 'react';
export type ToggleThemeContextType = {
toggleTheme: () => void;
};
const defaultToggleThemeContext = {
toggleTheme: () => {},
};
const ToggleThemeContext = createContext<ToggleThemeContextType>(
defaultToggleThemeContext
);
type ToggleThemeContextProps = {
toggleTheme: () => void;
};
export const ToggleThemeProvider: FC<ToggleThemeContextProps> = ({
children,
toggleTheme,
}) => {
const value = { toggleTheme };
return (
<ToggleThemeContext.Provider value={value}>
{children}
</ToggleThemeContext.Provider>
);
};
export const useToggleTheme = () => {
const toggleTheme = useContext(ToggleThemeContext);
return toggleTheme;
};
- R/N은 다음처럼 Switch 컴포넌트를 제공
import { Switch } from 'react-native';
-
Switch 컴포넌트는
value
속성과onValueChange
이벤트 속성을 제공- →
value
: true, false - →
onValueChange : (boolean) => void
타입 이벤트 처리기 설정
- →
-
value 값 달라지면 이벤트 처리기를 호출
- react-native-paper 패키지는 Provider 컴포넌트의 theme 속성에
- → 설정한 값을 얻을 수 있는 useTheme 커스텀 훅을 제공
import { useTheme } from 'react-native-paper';
useTheme 훅을 사용해 theme 객체를 얻어오기
const theme = useTheme();
속성값 얻기
const { dark, colors, fonts } = useTheme();
dark 값을 Switch 코어 컴포넌트의 value 속성에 적용
<Switch value={dark} />
- 앞서 useToggleTheme 커스텀 훅을 만들었음
- Switch 컴포넌트 관련 코드에 useToggleTheme 훅을 호출해 얻은
- → toggleTheme 함수를 onValueChange 이벤트 속성에 설정
- → → 스위치를 옮길 때 마다 즉시 화면의 바탕색과 글자 색을 바꿀 수 있음
import { useToggleTheme } from '../contexts';
export default function Home() {
const { dark, colors, fonts } = useTheme();
const toggleTheme = useToggleTheme();
return <Switch value={dark} onValueChange={toggleTheme} />;
}
실제로 적용
import React from 'react';
import {StyleSheet, View, Text, Switch} from 'react-native';
import {useTheme} from 'react-native-paper';
import {useToggleTheme} from '../contexts';
export default function Home() {
const theme = useTheme();
const {fonts, colors, dark} = theme; // -1-
const toggleTheme = useToggleTheme(); // -2-
return (
<View style={[styles.view, {backgroundColor: colors.background}]}>
<View style={[styles.bar, {backgroundColor: colors.primary}]}>
<Text style={[styles.text, {color: colors.text}, fonts.medium]}>
TopBar
</Text>
<View style={[{flex: 1}]} />
<Switch value={dark} onValueChange={toggleTheme} />
</View>
- dark 속성 추가해서 비구조화 이용해서 가져오기
- useToggleTheme 함수 빼온것을 함수화 시켜서 변수에 할당
-
useRef
,useImperactiveHandle
흑은 ref 라는 속성에 적용하는 값을 만드는 훅- ↳ 사실 React, R/N가 제공하는 컴포넌트에는 모두 reference의 앞 3글자만 딴
- ↳ → ref 라는 속성이 존재
-
App 같은 사용자 컴포넌트도 ref 속성을 가질 수 있음
- ↳ ref 속성이 있는 사용자 컴포넌트는
forwardRef
함수로 생성해야 한다는 조건이 있음
- ↳ ref 속성이 있는 사용자 컴포넌트는
설치
yarn add react-native-vector-icons react-native-paper color faker moment moment-with-locales-es6
yarn add @types/react-native-vector-icons @types/color @types/faker
yarn add react-native-appearance react-native-localize
yarn add react-native-keyboard-aware-scroll-view
전 파일 복사
cp -r ../ch05_Context/src .
cp ../ch05_Context/App.tsx .
rm src/screens/*.*
MainNavigator 설정
import React, { useState } from 'react';
import { BottomNavigation } from 'react-native-paper';
import Home from './Home';
import Input from './Input';
import KeyboardAvoid from './KeyboardAvoid';
import KeyboardAware from './KeyboardAware';
import AutoFocus from './AutoFocus';
export default function MainNavigator() {
const [index, setIndex] = useState<number>(0);
const [routes] = useState([
{ key: 'home', title: 'Home', icon: 'home' },
{ key: 'input', title: 'Input', icon: 'apple-keyboard-caps' },
{ key: 'avoid', title: 'KeyboardAvoid', icon: 'keyboard-variant' },
{ key: 'aware', title: 'KeyboardAware', icon: 'keyboard-settings-outline' },
{ key: 'auto', title: 'AutoFocus', icon: 'home-automation' },
]);
const renderScene = BottomNavigation.SceneMap({
home: Home,
input: Input,
avoid: KeyboardAvoid,
aware: KeyboardAware,
auto: AutoFocus,
});
return (
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
/>
);
}
-
React, R/N 제공하는 코어 컴포넌트 중
- ↳
TextInput
,ScrollView
,FlatList
컴포넌트처럼메서드
를 제공하는 것이 있음
- ↳
-
TextInput : focus, blur 같은 메서드를 제공
-
ScrollView, FlatList : scrollToTop, scrollEnd 와 같은 메서드를 제공
-
그런데 컴포넌트의 메서드를 호출 하기 위해서는
- ↳
컴포넌트의 리액트 요소``(react element, 객체 지향 언어에서 클래스의 인스턴스)
를 얻을 수 있어야 - ↳ →
컴포넌트_객체.메서드()
형태로 호출이 가능하다.
- ↳
-
React, R/N 은 네이티브 컴포넌트가 제공하는 메서드를 호출할 수 있도록 ref 속성을 제공
- ↳ ref 속성으로
컴포넌트의 인스턴스(instance)
를 얻을 수 있으며 이를 이용하여 - ↳ →
ref.메소드()
형태로 호출 가능
- ↳ ref 속성으로
- ref 속성의 타입에 대해서 학습
- 앞서 구현 하였던 FlatList를 찾아서 ref 를 입력 후 → [정의로 이동] 클릭
VSCode 편집기에서는 ClassAttributes 라는 타입의 속성을 안내
지금은 함수 컴포넌트 관점에서 ref를 다루기 때문에 그 위 RefAttributes의 ref 속성 타입
Ref<T>
에 주목
RefAttributes의 ref 속성
interface RefAttributes<T> extends Attributes {
ref?: Ref<T> | undefined;
}
- 비슷한 방법으로 계속 정의를 찾다보면 ref 속성의 타입은
- ↳
current
속성이 있는 RefObject 제네릭 타입임을 확인 가능 - ↳ →
타입 변수 T
: FlatList, ScrollView, TextInput 처럼컴포넌트 이름
을 의미
- ↳
ref 속성의 타입
interface RefObject<T> {
readonly current: T | null;
}
- 그런데 앞의
RefObject<T>
에서current
속성의 타입은- ┣
T | null
임 - ┣ 즉
current = null 일 수 있음
- ┗ FlatList 등의
React 요소의 값
이 설정되어컴포넌트의 메서드 호출
가능
- ┣
React 패키지는 useRef 훅을 제공
import React, { useRef } from 'react';
-
[정의로 이동] 방식으로 useRef 함수 타입을 확인 하면
-
┣
MutableRefObject
-
┗
RefObject
-
여기서는
RefObject<T>
이므로 두 번재 버전의 useRef 훅을 사용
function useRef<T>(initialValue: T): MutableObject<T>;
function useRef<T>(initialValue: T | null): RefObject<T>;
위에서 두번 째 버전의 useRef 사용 → 다음 형태로 코드 작성
코드 형태
const flatList = useRef<FlatList | null>(null);
<FlatList ref={flatList} />;
- 이렇게 코드를 작성하여 flatList의 값을 얻으면 다음 코드 형태로 scrollEnd 메서드를 호출 가능하다.
옵션 체이닝 연산자 사용
flatList.current?.scrollToEnd();
- 여기서
scrollToEnd
메서드 호출 시- ┣
옵션 체이닝 연산자(option chaining operator) : ?.
- ┗ 사용한
이유
→ flatListRef의Type
:RefObject<FlatList | null>
- ┗ current 속성이 null 일 수 있기 때문
- ┣
다음에서 add 버튼을 눌러서 아이템을 추가하는 경우
추가 한 아이템을 보기 위해서 계속 스크롤을 해야함
추가할 때 마다 자동으로 스크롤 되는 기능을 구현
객체 깊숙한 곳에 사용하고 싶은 속성이 존재 할 경우
- 보통의 경우 우리는 . (체이닝 연산자)를 사용해서 배열이나 객체에 있는 값을 사용
- 만약 참조가
nullish (null 또는 undefined)
이라면- ┗ 에러가 발생하는 것 대신에 표현식의
리턴 값
은undefined
로 통일 됨
- ┗ 에러가 발생하는 것 대신에 표현식의
일반 적인 경우의 객체 값 얻기
let nestedProp = obj.first && obj.first.second;
옵션 체이닝 사용 시
let nestedProp = obj.first?.second;
- 이는 함수에서도 사용이 가능!!!
- 함수 호출과 optional chaining을 사용함으로써 메서드를 찾을 수 없는 경우에 예외를 발생시키는 것 대신에 그 표현식은 자동으로 undefined를 반환한다.
만약 속성 이름은 존재하지만 이게 함수가 아니라면 오류가 생기게 된다.
null 병합 연산자와 같이 사용하기
- 옵션 체이닝을 사용한 후 아무 값도 찾을 수 없을 때 기본 값 주기 위해 사용 가능
let customer = {
name: 'Carl',
details: { age: 82 },
};
const customerCity = customer?.city ?? 'Unknown city';
console.log(customerCity); // Unknown city
배열 항목에 접근
let arrayItem = arr?.[42];
ScrollView
,FlatList는
onContentSizeChange
이벤트 속성을 제공- ┗ 속성의 이름대로 콘텐츠의 넓이나 높이가 변하면 설정된 콜백 함수를 호출
onContentSizeChange 이벤트 속성
onContentSizeChange?: (w:number, h: number) => void;
FlatList
:아이템을 추가
하면 아이템의 높이 크기가 증가- ┗
onContentSizeChange
이벤트 발생
- ┗
onContentSizeChange
콜백 함수
에서scrollToEnd 메서드를 호출
하면 아이템 추가에 따른 자동 스크롤 기능 구현 가능
const onContextSizeChange = useCallback(() =>
flatListRef.current?.scrollToEnd(),
, [flatListRef.current]
);
<FlatListRef onContentSizeChange={onContentSizeChange}/>
실제 구현
const flatListRef = useRef<FlatList | null>(null); // -1-
// -2-
const onContentSizeChange = useCallback(
() => flatListRef.current?.scrollToEnd(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[flatListRef.current]
);
// -2-
return (
<View style={[styles.view, { backgroundColor: theme.colors.surface }]}>
<View style={[styles.topBar, { backgroundColor: theme.colors.accent }]}>
<Text onPress={add} style={styles.text}>
add
</Text>
<Text onPress={removeAll} style={styles.text}>
remove all
</Text>
<View style={{ flex: 1 }} />
<Switch value={theme.dark} onValueChange={toggleTheme} />
</View>
<FlatList
ref={flatListRef} // -3-
data={people}
renderItem={({ item }) => <Person person={item} />}
keyExtractor={(item) => item.id}
onContentSizeChange={onContentSizeChange} // -3-
/>
</View>
);
ref
설정flatList.current
를 의존성 배열에 설정하여서- ┗ 변경시 마다
스크롤이 자동으로 이동
하도록 설정
- ┗ 변경시 마다
- FlatList ref에 설정한
flatListRef
설정- ┗
onContentSizeChange
에 직접 만든 함수 설정
- ┗
export default function CopyMe() {
const [person, setPerson] = useState<D.IPerson>(D.createRandomPerson());
const { dark, colors } = useTheme();
const toggleTheme = useToggleTheme();
return (
<View style={[styles.view, { backgroundColor: colors.surface }]}>
<View style={[styles.topBar, { backgroundColor: colors.accent }]}>
<Text style={[styles.textButton]}>focus</Text>
<Text style={[styles.textButton]}>dismiss keyboard</Text>
<View style={{ flex: 1 }} />
<Switch value={dark} onValueChange={toggleTheme} />
</View>
<View style={[styles.textView]}>
<Text style={[styles.textInput, { color: colors.text }]}>email</Text>
<TextInput
style={[
styles.textInput,
{ color: colors.text, borderColor: colors.placeholder },
]}
value={person.email}
placeholder="enter your email"
onChangeText={(email) =>
setPerson((person) => ({ ...person, email }))
}
/>
</View>
</View>
);
}
만약 IOS 에서 키보드 올라오지 않으면
command
+k
- TextInput 컴포넌트는 focus 메서드를 제공
- ┗ 메서드를 호출하면 키보드가 올라 오도록 설정 가능
export default function Input() {
const textInputRef = useRef<TextInput | null>(null);
<TextInput ref={textInputRef}>
}
- 이제 textInputRef 값을 얻었으므로
- ┗ focus 버튼을 터치 시 TextInput에 포커스를 줄 수 있음
const setFocus = () => {
textInputRef.current?.focus();
};
<Text onPress={setFocus}>focus</Text>;
- R/N는 키보드 관련 프로그래밍이 가능하도록 Keyboard API 제공
import { Keyboard } from 'react-native';
- 화면에 나타난 키보드는 다음 코드를 실행 시 사라짐
키보드 내리기
Keyboard.dismiss();
위를 통한 키보드 내리기 프로그래밍
const dismissKeyboard = () => {
keyBoard.dismiss();
};
<Text onPress={dismissKeyboard}>dismiss keyboard</Text>;
더 간단하게 작성
<Text onPress={Keyboard.dismiss}>dismiss keyboard</Text>
- IOS
TextInput
이 다음 화면 왼쪽처럼 화면 아래에 있다면- ┣ 키보드가 올라왔을 때
TextInput을 기릴 수 있음
- ┗ 그래서 R/N이 이런 경우를 예방하고
KeyboardAvoidingView
제공
- ┣ 키보드가 올라왔을 때
import { KeyboardAvoidingView } from 'react-native';
KeyboardingAvoidingView
다음처럼TextInput
컴포넌트를 자식 요소로 하여- ┣
TextInput
가리지 않도록 하는 기능을 수행 - ┗
KeyboardingAvoidingView
→ View 이기 때문에flex : 1
같이 스타일링 가능
- ┣
<KeyboardAvoidingView>
<View style={{ flex: 1 }} />
<View>
<Text>email</Text>
<TextInput placeholder="enter your email" />
</View>
</KeyboardAvoidingView>
- React 공식문서는
KeyboardAvoidingView
의behavior
속성에 플렛폼에 따라- ┗
padding
,height
값을 줄 것을 권유
- ┗
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
<View style={{ flex: 1 }} />
<View>
<Text>email</Text>
<TextInput placeholder="enter your email" />
</View>
</KeyboardAvoidingView>
- 이제 src/screens/Input.tsx KeyboardAvoid.tsx 파일로 복사 생성
- ┗ KeyboardAvoid.tsx 파일을 다시 복사
하지만 아쉽게도 버그가 존재함 react-native-keyboard-aware-scroll-view 패키지가 제공하는
KeyboardAwareScrollView 사용하면 해결 가능
복사
cp keyboardAvoid.tsx KeyboardAware.tsx && cd ../..
- 이 패키지는 다음처럼
KeyboardAwareScrollView
제공
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
- 이제 KeyboardAwareScrollView 를 통해서 두 개의 TextInput 감싸면 됨
<KeyboardAwareScrollView>
<View style={{ flex: 1 }} />
<View style={[styles.textView]}>
<TextInput />
</View>
<View>
<Text>name</Text>
<TextInput />
</View>
</KeyboardAwareScrollView>
하지만 여전히 ios 버그 존재
KeyboardAwareScrollView
는scrollToFocusedInput
메서드 제공- ┣ 이 메서드는 이름처럼
포커스를 가진
- ┗
TextInput
을수직 방향 스크롤을 통해
항상 화면에 나타냄
- ┣ 이 메서드는 이름처럼
ref 속성값 얻기
const scrollViewRef = useRef<KeyboardScrollView | null>(null);
<KeyboardScrollView ref={scrollViewRef} />;
scrollViewRef
값을 사용하면- ┗
scrollToFocusedInput
메서드 호출 하는함수 작성가능
scrollToFocusedInput 메서드 호출
const scrollToInput = (reactNode: any) => {
scrollViewRef.current?.scrollToFocusedInput(reactNode);
};
autofocus 함수 구현
import { findNodeHandle } from 'react-native';
import type {
NativeSyntheticEvent,
TextInputFocusEventData,
} from 'react-native';
const autoFocus = (event: NativeSyntheticEvent<TextInputFocusEventData>) =>
scrollToInput(findNodeHandle(event.target));
- 이제
TextInput
의onFocus 이벤트 속성
에 앞서 구현한autoFocus
함수를 설정하면 이 TextInput은항상 화면에 나타남
이렇게 하면 버그는 안생기지만 항상 이렇게 KeyboardAwareScrollView를 사용 할 때마다
이런식 으로 코드를 짜는 것은 비 효율적 그렇기에 커스텀 훅을 이용
import { AutoFocusProvider, useAutoFocus } from '../contexts';
export default function AutoFocus() {
const autoFocus = useAutoFocus();
return <TextInput onFocus={autoFocus} />;
}
Provider 생성
import React, { createContext, useContext } from 'react';
import { View, Text } from 'react-native';
import { findNodeHandle } from 'react-native';
import type { FC, ComponentProps } from 'react';
import type {
NativeSyntheticEvent,
TextInputFocusEventData,
} from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
export type FocusContextType = {};
const defaultFocusContext = {};
const AutoFocusContext = createContext<FocusContextType>(defaultFocusContext);
export type AutoFocusProviderProps = ComponentProps<
typeof KeyboardAwareScrollView
>;
export const AutoFocusProvider: FC<AutoFocusProviderProps> = ({
children,
...props
}) => {
const value = {};
return (
<AutoFocusContext.Provider value={value}>
{children}
</AutoFocusContext.Provider>
);
};
export const useAutoFocus = () => {
const value = useContext(AutoFocusContext);
return value;
};
autoFocus 함수 구현
export type FocusEvent =
완성본
import React, { createContext, useCallback, useContext, useRef } from 'react';
import { findNodeHandle } from 'react-native';
import type { FC, ComponentProps } from 'react';
import type {
NativeSyntheticEvent,
TextInputFocusEventData,
} from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
export type FocusEvent = NativeSyntheticEvent<TextInputFocusEventData>;
export type FocusContextType = { autoFocus: (e: FocusEvent) => void };
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const defaultFocusContext = { autoFocus: (e: FocusEvent) => {} };
const AutoFocusContext = createContext<FocusContextType>(defaultFocusContext);
export type AutoFocusProviderProps = ComponentProps<
typeof KeyboardAwareScrollView
>;
export type AutoFocusContextType = {
autoFocus: (e: FocusEvent) => void;
};
export const AutoFocusProvider: FC<AutoFocusProviderProps> = ({
children,
...props
}) => {
const scrollRef = useRef<KeyboardAwareScrollView | null>(null);
const scrollInput = useCallback((reactNode: any) => {
scrollRef.current?.scrollToFocusedInput(reactNode);
}, []);
const autoFocus = useCallback((e: FocusEvent) => {
scrollInput(findNodeHandle(e.target));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const value = { autoFocus };
return (
<AutoFocusContext.Provider value={value}>
<KeyboardAwareScrollView
{...props}
style={{ flex: 1, width: '100%' }}
ref={scrollRef}
>
{children}
</KeyboardAwareScrollView>
</AutoFocusContext.Provider>
);
};
export const useAutoFocus = () => {
const { autoFocus } = useContext(AutoFocusContext);
return autoFocus;
};
- AutoFocus.tsx 파일 내용을 forwardRef API, useImperactive Handle 훅을 사용하여 다시 구현
- react-native-paper 테마 색생 관련 내용을 별도 테마 컴포넌트로 구현
- ┗ 코드 구현의 간결
설치
yarn add react-native-vector-icons react-native-paper color faker moment moment-with-locales-es6
yarn add @types/react-native-vector-icons @types/color @types/faker
yarn add react-native-appearance react-native-localize react-native-keyboard-aware-scroll-view
App.tsx 복사
-
여기는 앞에 내용의 useRef 뿐만 아닌
- ┣ forwardRef API
- ┣ useImperativeHandle
- ┗ 훅을 사용해서 만듬
-
앱의 모습은 똑같지만 구현 코드는 많이 다름
- ┗ 그렇기에 AutoFocus.tsx 파일 사용
몇 가지 테마 컴포넌트를 만들어서 코드를 더 간결하게 구성
생성
mkdir -p src/theme/paper
-
7장 에서는 src/theme dir에 navigation이란 이름의 서브 디렉터리 생성
- ┗ react-navigation 패키지의 테마 컴포넌트를 구현 할 예정
-
paper 디렉터리의 컴포넌트는
- ┣ AutoFocus 파일의 react-native-paper 관련 테마 코드를
- ┣ paper 쪽 컴포넌트로 옮겨서 결과적으로
- ┗ 파일의 테마 관련 코드를 좀 더 관결하게 구현할 목적으로 만든는 컴포넌트
-
테마 컴포넌트를 구현할 때 보통 네이티브 컴포넌트와 같은 이름으로
- ┣ 컴포넌트 이름을 지음
- ┗ 똑같은 이름으로 컴포넌트 이름을 짓는다.
import { View, Text, TextInput } from 'react-native';
import { View, Text, TextInput } from '../theme/paper';
-
react-native 코어 컴포넌트 TextInput 이름으로 가져온 이유
- ┗ 이번에 제작할 컴포넌트 이름도 TextInput 이기 때문
-
오류가 많이 생기는 이유
- ┣ onFocus
- ┣ value
- ┗ 이들은 TextInput 컴포넌트가 제공하는 속성
이 속성은 RNTextInput의 속성이므로 다음 코드로 간단히 오류를 없앨 수 있음
export type TextInputProps = ComponentPops<typeof RNTextInput & {}>
export const TextInput: FC<TextInputProps> = (props) => {
return <RNTextInput ref={textInputRef} style={textInputStyle {...props}}>
}
- useMemo로 구현한 부분 없애기
export const TextInput: FC<TextInputProps> = (props) => {
const { colors } = useTheme();
return (
<RNTextInput
ref={textInputRef}
style={[{ color: colors.text, borderColor: colors.placeholder }, style]}
{...props}
/>
);
};
style 속성 또한 FC 타입이 제공하는 속성
즉 굳이 TextInputProps에 style 속성을 선언하지 않아도 동작함
// -1-
export type TextInputProps = ComponentProps<typeof RNTextInput> & {
// style?: StyleProp<TextStyle>
};
// -1-
// -2-
export const TextInput: FC<TextInputProps> = ({ style, ...props }) => {};
// -2-
- style 속성을 굳이 지정하지 않아도 됨
- FC 속성을 지정하면 style 속성을 props에 지정이 가능함
ref 속성은 좀 더 특별함
원칙적으로 ref 속성은 R/N 코어 컴포넌트에만 적용할 수 잇는 속성 ref 속성은 {...props} 형태로 구현할 수 없음
- react 패키지는 다음처럼
forwardRef API
import React, { forwardRef } from 'react';
- 앞에서 ref 속성은 React, R/N가 제공하는 컴포넌트에만 사용할 수 있다고 설명
- forwardRef API로 생성한 사용자 정의 컴포넌트에서는 ref 속성을 설명한 바 있음
forwardRef API로 생성한 사용자 정의
ref 속성
을 사용할 수 있음┣
forwardRef API
는 이름대로부모 컴포넌트에서 생성한
┗ref를 자식 컴포넌트로 전달
부모 컴포넌트
const textInputRef = useRef();
<TextInputView ref={textInputRef} />;
자식 컴포넌트 - forwardRef 사용
const TextInputView = ({ textInputRef }) => {
return <TextInput ref={textInputRef} />;
};
forwardRef API 코어 컴포넌트가 아닌 사용자 컴포넌트에서 다음처럼 ref 속성을 할 수 있게 도와줌
import { TextInput as RNTextInput } from 'react-native';
import { TextInput } from '../theme/paper';
export default function Themed() {
const textInputRef = useRef<RNTextInput | null>(null);
return <TextInput ref={textInputRef} />;
}
하지만 Ts로 forwardRef API 사용하기 위해서는 조금 복잡한 타입 선언이 필요
- forwardRef의 타입
- ┣ 여기서
타입 변수 T
:ref의 대상 컴포넌트
- ┣
P 컴포넌트의 속성 타입
- ┣ 여기서
forwardRef 타입
function forwardRef<T, P = {}>(
render: ForwardRefRenderFunction<T, P>
): 반환_타입;
- 앞의 forwardRef 타입을 보면
ForwardRefRenderFunction
타입 매개변수를 입력받는 함수인 것을 알 수 잇음
ForwardRefRenderFunction 타입
interface ForwardRefRenderFunction<T, P={}> {
(props: PropsWithChildren<P>, ref: ((instance: T|null) | MutableRefObject<T| null> | null): ReactElement | null)
}
ForwardRefRenderFunction
에서 매개변수 부분의 복잡한 타입 부분을 생략하고- ┗ 단순하게 만든 것
interface ForwardRefRenderFunction {
(props, ref): ReactElement | null;
}
- 이 타입 선언을 보면 다음 코드 형태로 ref 속성을 얻을 수 잇음
ref 속성 얻기
const TextInput = (속성, re) => {...}
ref 속성에 설정된 값을 얻어야 하는 컴포넌트가 가져야 할 코드 패턴
- 컴포넌트 타입은
ForwardRefRenderFunction<속성_타입>
이어야 함- 컴포넌트 속성과 ref 속성은
({속성, ref})
형태로 수신forwardRef(컴포넌트_타입)
을 내보낸다.(export)
src/theme/paper/TextInput.tsx
import React, { forwardRef, ForwardRefRenderFunction } from 'react';
import type { ComponentProps } from 'react';
import { TextInput as RNTextInput } from 'react-native';
import { useTheme } from 'react-native-paper';
export type TextInputProps = ComponentProps<typeof RNTextInput>;
const _TextInput: ForwardRefRenderFunction<RNTextInput, TextInputProps> = (
{ style, ...props },
ref // -1-
) => {
const { colors } = useTheme();
return (
<RNTextInput
ref={ref}
style={[{ color: colors.text, borderColor: colors.placeholder }, style]}
{...props}
/>
);
};
export const TextInput = forwardRef(_TextInput);
- 지금 만드는 TextInput 컴포넌트는 forwardRef 대상 컴포넌트
- ┣ FC가 아닌
ForwardRenderFunction
타입을 import - ┣ 이렇게 타입이 바뀌어서
1번
과 같이 ref 속성 값을 - ┣ 두 번째 매개변수로 얻을 수 있게됨
- ┣ FC가 아닌
여기서 index.ts로 해서 내보내야 하기 때문에
forwardRef(_TextInput)
가 반환 한 값을 TextInput으로 설정
- Text 테마 컴포넌트는 TextInput과 달리 ref의 대상이 아님
- ┗ 단순히
FC
타입으로 구현해도 무방
- ┗ 단순히
Text.tsx 구현
import React from 'react';
import type { FC, ComponentProps } from 'react';
import { Text as RNText } from 'react-native';
import { useTheme } from 'react-native-paper';
export type TextProps = ComponentProps<typeof RNText>;
export const Text: FC<TextProps> = ({ style, ...props }) => {
const { colors } = useTheme();
return <RNText style={[{ color: colors.text }, style]} {...props} />;
};
- react-native-paper 패키지의 테마 색상 중 View 컴포넌트에 적용할 수 잇는 것에는
- ┗ background, surface, accent 등 몇 가지가 존재
import React from 'react';
import type { FC, ComponentProps } from 'react';
import { View as RNView } from 'react-native';
import { useTheme } from 'react-native-paper';
export type ViewProps = ComponentProps<typeof RNView> & {
accent?: boolean;
notification?: boolean;
primary?: boolean;
surface?: boolean;
background?: boolean;
};
export const View: FC<ViewProps> = ({
style,
accent,
notification,
primary,
surface,
background,
...props
}) => {
const { colors } = useTheme();
const backgroundColor = accent
? colors.accent
: notification
? colors.notification
: primary
? colors.primary
: surface
? colors.surface
: background
? colors.background
: 'transparent';
return <RNView style={[{ backgroundColor }, style]} {...props} />;
};
만든 3개의 theme을 index.tsx 파일에 적용
그 뒤 src/screens/Themed.tsx 파일에 구현 내용을 반영
react 패키지는 다음처럼 useImperativeHandle 훅을 제공
import React, { useImperativeHandle } from 'react';
-
프로그래밍 언어 분야에서는
- ┣
명령형 프로그래밍(imperative programming)
- ┣
선언형 프로그래밍(declarative programming
- ┗ 용어가 등장
- ┣
-
Ts
부분이명령형 프로그래밍
에 해당- ┗
Js
선언형 프로그래밍
에 해당
- ┗
-
핸들(handle)
이란 단어를 프로그래밍 용어로 사용할 때- ┣
불투명한 식별자(opaque identifier)
의미가 존재 - ┣ 식별자 : 무엇인가를 구분하는 용도로 사용하는 숫자나 문자열
- ┣
useImperativeHandle
훅은 JSX가 아닌 TSX 에서 컴포넌트 기능을 사용할 때 필요한 훅
- 다음 코드가 성립할 수 있는 이유는 TextInput 코어 컴포넌트가
- ┗
focus
라는메서드를 제공
한다는 것을미리 알고
있기 때문
- ┗
const textInputRef = useRef<TextInput | null>(null);
const setFocus = () => textInputRef.current?.focus();
하지만 만약 다음과 같은 타입의 객체가 있다고 가정
export type TextInputMethods = {
focus: () => void;
dismiss: () => void;
};
- 앞의
useRef<TextInput | null>(null)
부분 코드에 다음처럼- ┣
TextInput
대신TextInputMethods
를 사용하면 어떨까? - ┗ useImperativeHandle 훅의 탄생 배경
- ┣
TextInputMethods를 대신 사용
const methods = useRef<TextInputMethods | null>(null);
const setFocus = () => methodsRef.current?.focus();
const dismissKeyboard = () => methodsRef.current?.dismiss();
function useImperactiveHandle<T, R extends T>(
ref: Ref<T> | undefined,
init: () => R,
deps?: DependencyList
): void;
- 이 타입의 정의에서
- ┣ ref : forwardRef API 호출로 얻는 값을 입력하는 용도
- ┗ init : useMemo 훅 때와 비슷하게
() => 메서드_객체
형태의 함수를 입력하는 용도
다음 코드는 useMemo 훅과 useImperativeHandle 훅의 코드 사용법ㅂ이 비슷하다는 것을 말해줌
useMemo 훅과 useImperactiveHandle 훅의 사용법
const object = useMemo(() => ({}), []);
const handle = useImperactiveHandle(ref, () => ({}), []);
- useImperactiveHandle 훅은 사용법이 조금 복잡하기 때문에
- ┗ forwardRef API를 이용한 컴포넌트를 먼저 제작
src/screens 디렉터리에 ImperativeTextInput.tsx 파일을 생성
- ImperativeTextInput 컴포넌트는 ref 속성으로 동작
- ┗ 다음과 같은 초기 구현이 필요
import React, { forwardRef, useImperativeHandle } from 'react';
import type { ForwardRefRenderFunction, ComponentProps } from 'react';
import { TextInput as RNTextInput } from 'react-native';
export type TextInputMethods = {
focus: () => void;
dismiss: () => void;
};
export type ImperativeTextInputProps = ComponentProps<typeof RNTextInput>;
const ImperativeTextInput: ForwardRefRenderFunction<
TextInputMethods, // -2-
ImperativeTextInputProps
> = (props, ref) => {
useImperativeHandle(
ref, // -1-
() => ({
focus: () => {},
dismiss: () => {},
}),
[]
);
return <RNTextInput {...props} />;
};
export default forwardRef(ImperativeTextInput);
- forwardRef 로 전달받은 ref 값을
useImperativeHandle
의 첫 번째 매개변수로 입력- ┣ 그런데 이 ref의 타입은
2
의 TextInputMethods 임
- ┣ 그런데 이 ref의 타입은
-
이 때문에 TextInputMethods의 두 개의 메서드를 가진 객체를 반환하는 함수
- 두 번째 매개변수에 입력
-
다음 코드는 focus, dismiss 메서드를 구현한것
- ┗ useRef 훅의 사용 예와 동일
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import type { ForwardRefRenderFunction, ComponentProps } from 'react';
import { TextInput as RNTextInput, Keyboard } from 'react-native';
export type TextInputMethods = {
focus: () => void;
dismiss: () => void;
};
export type ImperativeTextInputProps = ComponentProps<typeof RNTextInput>;
const ImperativeTextInput: ForwardRefRenderFunction<
TextInputMethods,
ImperativeTextInputProps
> = (props, ref) => {
const textInputRef = useRef<RNTextInput | null>(null);
useImperativeHandle(
ref,
() => ({
focus: () => {
textInputRef.current?.focus();
},
dismiss: () => {
Keyboard.dismiss;
},
}),
[]
);
return <RNTextInput ref={textInputRef} {...props} />;
};
export default forwardRef(ImperativeTextInput);
- Ts는
focus: () => {}
형태의 코드를- ┣ 간결하게
focus() {}
형태로 사용하게 하는 구문을 제공 - ┣ 이 코드는
TextInputMethods
타입이 요구하는 두 메서드를 구현 - ┗ 앞서
src/theme/paper
디렉터리에 구현한TextInput
컴포넌트를 사용하지는 않음
- ┣ 간결하게
그러므로 구현하였던 TextInput 컴포넌트를 사용하여
RNTextInput
부분을 다시 구현
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import { TextInput as RNTextInput, Keyboard } from 'react-native';
import { TextInput } from '../theme/paper/';
import type { TextInputProps } from '../theme/paper';
export type TextInputMethods = {
focus: () => void;
dismiss: () => void;
};
export type ImperativeTextInputProps = TextInputProps;
const ImperativeTextInput: ForwardRefRenderFunction<
TextInputMethods,
ImperativeTextInputProps
> = (props, ref) => {
const textInputRef = useRef<RNTextInput | null>(null);
useImperativeHandle(
ref,
() => ({
focus: () => {
textInputRef.current?.focus();
},
dismiss: () => {
Keyboard.dismiss;
},
}),
[]
);
return <TextInput ref={textInputRef} {...props} />;
};
export default forwardRef(ImperativeTextInput);
- 위 코드에서는
- ┣
RNTextInput
대신테마 기능을 구현한
TextInput
을 사용 - ┣
RNTextInput
타입 대신TextInput
의 타입을 사용 - ┗ 이것으로
ImperativeTextInput
은 테마 기능을 가짐
- ┣
이제 src/screens 디렉터리의 Imperative.tsx 파일에
ImperativeTextInput 컴포넌트를 적용
만들었던 ImperativeTextInput 가져와서 사용
import ImperativeTextInput from './ImperativeTextInput';
import type { TextInputMethods } from './ImperativeTextInput';