[React-Native] State vs Props
https://reactnative.dev/docs/state
State · React Native
There are two types of data that control a component: props and state. props are set by the parent and they are fixed throughout the lifetime of a component. For data that is going to change, we have to use state.
reactnative.dev
컴포넌트를 제어하는 데이터엔 props, state 2개 유형이 있다. props는 부모가 설정하며 컴포넌트의 생명주기 동안 고정된다. 바뀌는 데이터는 state를 써야 한다
일반적으로 생성자에서 state를 초기화하고 바꿀 때 setState를 호출해야 한다. 항상 깜박이는 텍스트를 만든다고 가정한다. 깜박이는 컴포넌트가 생성될 때 텍스트 자체가 한 번 설정되므로 텍스트 자체는 소품이 된다. 텍스트가 현재 켜져 있는지 꺼져 있는지는 시간이 지나면서 변하기 때문에 그 상태를 유지해야 한다
import React, {useState, useEffect} from 'react';
import {Text, View} from 'react-native';
const Blink = props => {
const [isShowingText, setIsShowingText] = useState(true);
useEffect(() => {
const toggle = setInterval(() => {
setIsShowingText(!isShowingText);
}, 1000);
return () => clearInterval(toggle);
});
if (!isShowingText) {
return null;
}
return <Text>{props.text}</Text>;
};
const BlinkApp = () => {
return (
<View style={{marginTop: 50}}>
<Blink text="I love to blink" />
<Blink text="Yes blinking is so great" />
<Blink text="Why did they ever take this out of HTML" />
<Blink text="Look at me look at me look at me" />
</View>
);
};
실제 앱에선 타이머로 상태를 설정하지 않을 것이다. 서버에서 새 데이터를 받거나 유저 입력이 있을 때 상태를 설정할 수 있다. 리덕스, MobX 같은 상태 컨테이너를 써서 데이터 흐름을 제어할 수도 있다. 이 때 setState를 직접 호출하지 않고 리덕스, MobX를 써서 state를 수정한다
setState가 호출되면 BlinkApp은 해당 컴포넌트를 다시 렌더링한다. 타이머 안에서 setState를 호출하면 타이머가 틱할 때마다 컴포넌트가 다시 렌더링된다
state는 리액트와 같은 방식으로 작동하므로 state 처리의 자세한 내용은 React.Component API를 참조하라...(중략)
https://www.geeksforgeeks.org/react-native-state/
React Native State - GeeksforGeeks
Your All-in-One Learning Portal: GeeksforGeeks is a comprehensive educational platform that empowers learners across domains-spanning computer science and programming, school education, upskilling, commerce, software tools, competitive exams, and more.
www.geeksforgeeks.org
리액트 네이티브에서 변경될 데이터는 state를 써야 한다. state는 변수라고 생각하면 된다. 이걸로 데이터를 저장하고 원할 때 바꿀 수 있다. state를 정의할 때마다 초기값을 제공해야 한다. 그 후 리액트 네이티브의 setState()를 써서 원할 때마다 바꿀 수 있다. setState가 호출되고 상태가 바뀔 때마다 state가 사용 중인 컴포넌트를 다시 렌더링한다
state를 쓰려면 "react"에서 useState를 임포트해야 한다
const [stateName,setStateName] = useState(<initial_value>);
https://medium.com/@dev.rajangarg/react-native-props-1d6e76b04cf
React Native Props
In React Native, props (short for properties) are a mechanism for passing data from one component to another. They allow you to communicate…
medium.com
props는 property의 줄임말로 한 컴포넌트에서 다른 컴포넌트로 데이터를 전달하는 메커니즘이다. 부모 컴포넌트, 자식 컴포넌트 간에 통신할 수 있게 해 주며 재사용 가능한 모듈식 코드 작성에 필수다...(중략)...데이터는 보통 props를 통해 부모 컴포넌트에서 자식 컴포넌트로 이동한다. 하지만 자식에서 부모로 데이터를 다시 보내야 할 수 있다. 이 때 부모에서 자식으로 props로 전달된 콜백 함수를 쓸 수 있다. 그러면 자식은 이 콜백을 호출해서 데이터를 부모 컴포넌트로 다시 보낼 수 있다...(중략)
props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 보내기 위해 사용하는 요소다. 이와 비슷한 역할을 하는 요소로 state가 있지만 props는 수정할 수 없는 읽기 전용 프로퍼티(코틀린의 val, 자바의 static과 비슷하다)고 부모-자식 관계가 형성되어야 의미가 있는 요소다.
리액트 네이티브든 리액트든 데이터 흐름은 일방통행 형태의 단방향이다. 그래서 부모에서 자식에게로 데이터가 props에 담겨 전달되고, 데이터를 받은 자식 컴포넌트는 props에 담긴 값을 꺼내 사용한다.
props에 담을 수 있는 건 값, 함수 뿐 아니라 컴포넌트도 담을 수 있다. 아래는 예시 코드다.
import React, {Component} from 'react';
import {View, Text, TouchableOpacity, SafeAreaView} from 'react-native';
const HeartIcon = () => <Text>❤️</Text>;
const StarIcon = () => <Text>⭐</Text>;
const ThumbsUpIcon = () => <Text>👍</Text>;
const IconButton = props => {
return (
<TouchableOpacity onPress={props.onPress}>
<View style={{flexDirection: 'row', alignItems: 'center'}}>
<props.IconComponent />
<Text>{props.title}</Text>
</View>
</TouchableOpacity>
);
};
class App extends Component {
render() {
return (
<SafeAreaView>
<View>
<IconButton
IconComponent={HeartIcon}
title="하트"
onPress={() => console.log('하트 클릭')}
/>
<IconButton
IconComponent={StarIcon}
title="별표"
onPress={() => console.log('별표 클릭')}
/>
<IconButton
IconComponent={ThumbsUpIcon}
title="좋아요"
onPress={() => console.log('좋아요 클릭')}
/>
</View>
</SafeAreaView>
);
}
}
export default App;
이 코드를 실행하면 아래 화면이 표시된다.
이후 에뮬레이터에서 cmd + d를 누르고 Open devtools를 누르면 크롬 인스펙터와 같은 디버그 콘솔 창이 표시된다.
이제 각 텍스트를 클릭하면 아래처럼 콘솔 로그가 표시될 것이다.
작동을 확인했으니 코드를 본다.
const IconButton = props => {
return (
<TouchableOpacity onPress={props.onPress}>
<View style={{flexDirection: 'row', alignItems: 'center'}}>
<props.IconComponent />
<Text>{props.title}</Text>
</View>
</TouchableOpacity>
);
};
먼저 IconButton이란 컴포넌트를 정의했다. 이 컴포넌트에 부모 컴포넌트로부터 받은 데이터를 표시하기 위해선 앞서 말한대로 props를 받을 수 있어야 한다.
그래서 "= props" 형태로 써서 props라는 매개변수를 받도록 하고, 화살표 함수 뒤에 중괄호를 열어서 전달받은 props 안에 담긴 값들을 사용할 수 있게 TouchableOpacity와 View, Text를 사용해 UI가 어떤 형태로 표시될지 선언했다.
이후 props를 통해 onPress, IconComponent, title이라는 함수, 컴포넌트, 값들을 가져와 사용한다. 미리 props에 정의하지 않았어도 이 상태에선 컴파일 에러가 발생하지 않는다. App 클래스 안에서 실제로 사용할 때 저렇게 정의한 이름 대신 다른 이름을 사용하면 그 때 컴파일 에러가 발생한다.
이렇게 정의한 IconButton을 App 클래스 안에서 사용해 UI를 조립한다.
class App extends Component {
render() {
return (
<SafeAreaView>
<View>
<IconButton
IconComponent={HeartIcon}
title="하트"
onPress={() => console.log('하트 클릭')}
/>
<IconButton
IconComponent={StarIcon}
title="별표"
onPress={() => console.log('별표 클릭')}
/>
<IconButton
IconComponent={ThumbsUpIcon}
title="좋아요"
onPress={() => console.log('좋아요 클릭')}
/>
</View>
</SafeAreaView>
);
}
}
IconComponent에는 먼저 정의했던 아이콘을 표시하고 title, onPress에도 각각 적절한 값, 함수를 설정한다.
이후 저장하고 에뮬레이터를 확인하면 콘솔에 표시되는 걸 볼 수 있다.
이 코드는 아래와 같은 순서대로 작동해서 에뮬레이터에 UI를 렌더링한다.
- 부모 컴포넌트(여기선 App 클래스)에서 IconButton 컴포넌트 호출
- 리액트가 props 객체를 생성함. {IconComponent: HeartIcon, title: "하트", onPress: function} 형태로 만들어진다
- IconButton 컴포넌트의 props에 2번에서 만들어진 props 객체 전달. 이 시점에서 IconButton은 전달받은 props 객체 안의 값들을 맘대로 쓸 수 있다
- props에 담긴 값들로 JSX 구조를 만들고 화면에 렌더링
그러나 매번 "props."를 치고 뒤에 변수명을 쓰는 건 귀찮다. 그럴 때 구조 분해 할당 패턴을 써서 좀 더 편하게 구현할 수 있다.
const IconButton = ({IconComponent, title, onPress}) => {
return (
<TouchableOpacity onPress={onPress}>
<View style={{flexDirection: 'row', alignItems: 'center'}}>
<IconComponent />
<Text>{title}</Text>
</View>
</TouchableOpacity>
);
};
props 대신 ({IconComponent, title, onPress})를 사용하고 실제로 사용할 때도 "props." 없이 그대로 사용한다.
IconButton만 이렇게 수정한 후 저장하면 이전과 동일하게 작동한다.
state는 리액트 컴포넌트 안에서 관리되는 동적 데이터다. 변수처럼 시간이 지나면서 바뀔 수 있는 값들을 저장하고 관리하는 역할을 한다.
state는 render() 밖에 선언하는데, 주의할 것은 state는 변수라고 해서 막 바꿀 수 있는 값이 아니다. useState라는 함수를 사용해서만 state를 바꿀 수 있다.
아래는 위 코드에서 state와 useState를 추가한 예시다.
import React, {Component} from 'react';
import {View, Text, TouchableOpacity, SafeAreaView} from 'react-native';
const HeartIcon = () => <Text>❤️</Text>;
const StarIcon = () => <Text>⭐</Text>;
const ThumbsUpIcon = () => <Text>👍</Text>;
const IconButton = ({IconComponent, title, onPress, count, isSelected}) => {
return (
<TouchableOpacity onPress={onPress}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
backgroundColor: isSelected ? '#FFF000' : '#E0E0E0',
}}>
<IconComponent />
<Text>{title}</Text>
<Text style={{marginLeft: 8, color: 'blue'}}>{count}회 클릭</Text>
</View>
</TouchableOpacity>
);
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
heartClickCount: 0,
starClickCount: 0,
thumbsClickCount: 0,
totalClickCount: 0,
selectedIcon: null,
};
}
heartClick = () => {
console.log('하트 클릭');
this.setState({
heartClickCount: this.state.heartClickCount + 1,
selectedIcon: 'heart',
totalClickCount: this.state.totalClickCount + 1,
});
};
starClick = () => {
console.log('별표 클릭');
this.setState({
starClickCount: this.state.starClickCount + 1,
selectedIcon: 'star',
totalClickCount: this.state.totalClickCount + 1,
});
};
thumbsClick = () => {
console.log('좋아요 클릭');
this.setState({
thumbsClickCount: this.state.thumbsClickCount + 1,
selectedIcon: 'thumbs',
totalClickCount: this.state.totalClickCount + 1,
});
};
resetCount = () => {
this.setState({
heartClickCount: 0,
starClickCount: 0,
thumbsClickCount: 0,
selectedIcon: null,
totalClickCount: 0,
});
};
render() {
return (
<SafeAreaView>
<View>
<Text>현재 상태</Text>
<Text>총 클릭 수: {this.state.totalClickCount}번</Text>
<Text>마지막 선택한 아이콘: {this.state.selectedIcon || '없음'}</Text>
<Text>하트 클릭 횟수: {this.state.heartClickCount}번</Text>
<Text>별표 클릭 횟수: {this.state.starClickCount}번</Text>
<Text>좋아요 클릭 횟수: {this.state.thumbsClickCount}번</Text>
</View>
<View>
<IconButton
IconComponent={HeartIcon}
title="하트"
onPress={this.heartClick}
count={this.state.heartClickCount}
isSelected={this.state.selectedIcon === 'heart'}
/>
<IconButton
IconComponent={StarIcon}
title="별표"
onPress={this.starClick}
count={this.state.starClickCount}
isSelected={this.state.selectedIcon === 'star'}
/>
<IconButton
IconComponent={ThumbsUpIcon}
title="좋아요"
onPress={this.thumbsClick}
count={this.state.thumbsClickCount}
isSelected={this.state.selectedIcon === 'thumbs'}
/>
<TouchableOpacity
onPress={this.resetCount}
style={{
backgroundColor: '#ff6b6b',
padding: 15,
borderRadius: 10,
alignItems: 'center',
marginTop: 20,
}}>
<Text style={{color: 'white', fontWeight: 'bold'}}>모두 리셋</Text>
</TouchableOpacity>
{/* 10번 이상 클릭해야 렌더링되는 조건부 UI */}
{this.state.totalClickCount > 10 && (
<View
style={{
marginTop: 20,
padding: 15,
backgroundColor: '#4ecdc4',
borderRadius: 10,
}}>
<Text style={{color: 'white', textAlign: 'center'}}>
🎉 10번 이상 클릭!!! 🎉
</Text>
</View>
)}
</View>
</SafeAreaView>
);
}
}
export default App;
수정 후 저장하면 에뮬레이터에 아래 화면이 표시될 것이다.
App 클래스 안에서 constructor를 정의하고 this.state로 초기값을 설정한다. 각 클릭 카운트를 0으로 설정하고 selectedIcon을 null로 설정하는 걸 볼 수 있다.
class App extends Component {
constructor(props) {
super(props);
this.state = {
heartClickCount: 0,
starClickCount: 0,
thumbsClickCount: 0,
totalClickCount: 0,
selectedIcon: null,
};
}
그리고 각 뷰를 클릭했을 때 실행시킬 함수를 만든다. 현재 state를 리셋하는 함수도 만든다.
heartClick = () => {
console.log('하트 클릭');
this.setState({
heartClickCount: this.state.heartClickCount + 1,
selectedIcon: 'heart',
totalClickCount: this.state.totalClickCount + 1,
});
};
starClick = () => {
console.log('별표 클릭');
this.setState({
starClickCount: this.state.starClickCount + 1,
selectedIcon: 'star',
totalClickCount: this.state.totalClickCount + 1,
});
};
thumbsClick = () => {
console.log('좋아요 클릭');
this.setState({
thumbsClickCount: this.state.thumbsClickCount + 1,
selectedIcon: 'thumbs',
totalClickCount: this.state.totalClickCount + 1,
});
};
resetCount = () => {
this.setState({
heartClickCount: 0,
starClickCount: 0,
thumbsClickCount: 0,
selectedIcon: null,
totalClickCount: 0,
});
};
render() 안에선 기존 코드 위에 현재 상태를 표시하는 텍스트들을 띄우고, 기존 코드에선 count, isSelected를 사용하도록 수정한다.
그리고 기존 코드 밑에는 리셋 버튼과 10번 이상 클릭 시 표시될 UI도 추가한다. 조건부 UI는 state.totalClickCount가 10을 넘어선 순간부터 표시되는 UI로, 대충 막 클릭해 보면 어느 순간부터 아래처럼 표시된다.
마지막으로 클릭한 뷰의 배경색은 노란색으로 바뀌도록 설정했기 때문에 내가 마지막으로 클릭한 좋아요 텍스트 주변이 노란색이고 마지막 선택한 아이콘도 thumbs로 표시되는걸 볼 수 있다.