FrontEnd/React Native

[RN] Button 표현 방법, state, event 정리 _ 처음 배우는 리액트 네이티브 (24.05.13)

hail2y 2024. 5. 14. 03:19

Button 표현 방법

리액트 네이티브에서는 다양한 내장 컴포넌트(core components)를 제공한다.

ex. View, Text, Button..

 

같은 컴포넌트일지라도 iOS, 안드로이드에서 다르게 표현될 수 있기 때문에 두 플랫폼으로 다 테스트해 보는 것이 중요하다. 즉 컴포넌트 속성이 다르게 표현될 수 있기 때문에 커스텀 컴포넌트를 만들어 문제를 해결해 볼 수 있다. 

아래는 그 예시이다.

 

<Button>의 color 속성은 iOS에서 text color를 나타내지만, 안드로이드에서는 버튼의 배경색을 나타낸다. 따라서 의도한 결과와 다르게 나오기 때문에 Button 컴포넌트를 대체할 TouchableOpacity + Text 조합을 만들 수 있다.

 

Button   →   TouchableOpacity + Text      Pressable

import React from 'react'; // JSX는 React.createElement를 호출하는 코드로 컴파일되므로 컴포넌트 작성 시 반드시 작성해야 하는 코드
import { Text, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';

const MyButton = props => {
    console.log(props);
    return (
        <TouchableOpacity
            style={{
                backgroundColor: '#3498db',
                padding: 16,
                margin: 10,
                borderRadius: 8,
            }}
            onPress={() => props.onPress()}>
            <Text style={{ color: 'white', fontSize: 24 }}>{props.children || props.title}</Text> 
            {/*true이면 평가를 멈추고 해당 피연산자 변환 전의 값 반환*/}
        </TouchableOpacity>
    );
};

MyButton.defaultProps = { // default 값 전달
    title: 'Button',
};

MyButton.propTypes = { // 잘못된 타입이나 미입력 방지 위해 propTypes 사용
    title: PropTypes.string.isRequired,
    onPress: PropTypes.func.isRequired
};

export default MyButton;
/* App.js */

import React from 'react'; 
import { Text, View } from "react-native";
import MyButton from "./components/MyButton";

const App = () => {
    return (
        <View
        style={{
            flex: 1,
            backgroundColor: '#fff',
            alignItems: 'center',
            justifyContent: 'center'
        }}>
        <Text
        style={{
            fontSize: 30,
            marginBottom: 10
        }}
        >Props</Text>
        <MyButton title="Button" onPress={() => alert('props')}/>
        <MyButton title="Button" onPress={() => alert('children')}>Children Props</MyButton>
        <MyButton onPress={() => alert('default')} />
        </View>
    );
};

export default App;
  • TouchableOpacity 컴포넌트에는 사실 onPress 속성이 없지만, TouchableWithoutFeedBack 컴포넌트를 상속받으며 그의 속성인 onPress 속성을 사용할 수 있다.
  • props(properties): 부모 컴포넌트로부터 전달된 속성값 혹은 상속받은 속성값, 부모 컴포넌트가 자식 컴포넌트의 props를 설정하여 자식 컴포넌트에서 해당 props를 사용할 수 있지만 변경은 불가능하다. 변경이 필요한 경우 부모 컴포넌트에서 이루어진다.

 

props를 터미널에 찍어보면 이렇게 객체 형태로 들어온다. 

  • onPress: 이벤트 핸들러 함수로 버튼이 클릭되었을 때 실행되는 함수
  • children: 컴포넌트 내부에 포함될 자식 요소, 즉 태그 사이에 있는 텍스트
  • title: 버튼에 표시될 텍스트

객체 타입으로 전달되니 props.title, props.children.. 이런 식으로 사용된다. 

state

이전 포스팅에서도 적어두었지만 어느새 머릿 속에서 증발해버려 여기서도 짤막하게 기록해 두기... 흑

 

props는 부모 컴포넌트에서 받은 값으로, 변경할 수 없는 반면에 state는 컴포넌트 내부에서 생성되고 값을 변경할 수 있다. 다시 말하면, state는 컴포넌트에서 변화할 수 있는 값을 나타낸다.

 

컴포넌트는 크게 함수형 컴포넌트, 클래스형 컴포넌트가 있는데 전자는 상태를 관리할 수 없었고 후자는 가능했다. 하지만 후자는 class, render() 함수를 반드시 선언해야 했고, Component 상속 등을 해야 했어서 함수형 컴포넌트보다 더 복잡하다는 단점이 있었다. 그때 리액트 16.8버전 이후 Hooks 훅이 등장하며 상태를 관리할 수 있게 되면서 '함수형 컴포넌트 + Hooks' 조합을 일반적으로 사용하게 되었다. 

https://velog.io/@shinyejin0212/React-클래스형-컴포넌트에서-함수형-컴포넌트로-바뀐-이유

더보기

const [state, setState] = useState(initialState);  //  배열 형태로 반환

  • state: 상태를 관리하는 변수
  • setState: 그 변수를 변경할 수 있는 세터(setter) 함수

event

1. press 이벤트 

https://reactnative.dev/docs/pressable

  • onPressIn: 터치가 시작될 때 항상 호출
  • onPressOut: 터치가 해제될 때 항상 호출
  • onPress: 터치가 해제될 때 onPressOut 이후 호출
  • onLongPress: 터치가 일정 시간 이상 지속되면 호출 -- delayLongPress 값을 조절 가능

2. change 이벤트

 

변화를 감지하는 change 이벤트는 TextInput 컴포넌트에서 많이 사용된다. 

  • onChange(): 컴포넌트의 텍스트가 변경되었을 때 아래 박스와 같은 형태로 인자를 전달한다.
  • onChangeText(): 컴포넌트의 텍스트가 변경되었을 때 변경된 텍스트의 문자열만 인수로 전달한다.
{	
    ...,
    "nativeEvent": {
        "eventCount": ...,
        "target": ...,
        "text": ...,
    },
    ...
}
import { useState, React } from "react";
import { Text, TextInput, View } from "react-native";

const EventInput = () => {
    const [text, setText] = useState('');
    const _onChange = event => setText(event.nativeEvent.text);
    const _onChangeText = text => setText(text);
    
    return (
        <View>
            <Text style={{ margin: 10, fontSize: 30}}>text: {text}</Text>
            <TextInput 
                style={{ borderWidth: 1, padding: 10, fontSize: 20}}
                placeholder="Enter a text..."
                onChange={_onChange} /* nativeEvent 객체를 전달 */
                onChangeText={_onChangeText} /* 둘이 같은데 이건 컴포넌트의 텍스트가 변경되었을 때 "변경된 텍스트의 문자열만" 인수로 전달하며 호출 */
            />
        </View>
    );
};

export default EventInput;

 

onChange()는 객체 형태로 반환하기 때문에 세터 함수 안에 event.nativeEvent.text로 지정하지만, onChangeText()는 텍스트의 문자열만을 지정하여 간편하게 사용할 수 있다. 두 함수의 기능은 같다. 

 

3. Pressable 이벤트

https://reactnative.dev/docs/pressable

TouchableOpacity 컴포넌트처럼 위의 press 이벤트들을 사용할 수 있는데 기존의 컴포넌트들과 다른 점이 있다면,

  • HitRect -- hitSlop
  • PressRect -- pressRetentionOffset

이 있다. 버튼에서 약간 벗어난 곳을 클릭했을 때도 이벤트를 발생할 수 있게 하거나, 반대로 버튼을 실수로 눌렀을 때 버튼의 실제 위치로부터 어느 정도까지를 벗어났다고 봐줄 것인지를 정하기 위한 이벤트이다. PressRect의 범위는 HitRect의 범위 끝에서부터 시작되므로 hitSlop의 값에 따라 PressRect의 범위가 달라진다. 

import React from 'react';
import {View, Text, Pressable} from 'react-native';

const Button = (props) => {
  return (
    <Pressable
      style={{padding: 10, backgroundColor: '#1abc9c'}}
      onPressIn={() => console.log('Press In')}
      onPressOut={() => console.log('Press Out')}
      onPress={() => console.log('Press')}
      onLongPress={() => console.log('Long Press')}
      delayLongPress={3000}
      pressRetentionOffset={{bottom: 50, left: 50, right: 50, top: 50}}
      hitSlop={50}>
      <Text style={{padding: 10, fontSize: 30}}>{props.title}</Text>
    </Pressable>
  );
};

const App = () => {
  return (
    <View
      style={{
        flex: 1,
        justifyContent: 'center',
        backgroundColor: '#fff',
        alignItems: 'center',
      }}>
      <Button title="Pressable" />
    </View>
  );
};

export default App;