이용약관 UI 에 대해 생각해볼 것
1. '선택'과 '필수' 레이블에 대한 분리
2. 이용약관 글에 대한 배치
https://brunch.co.kr/@fbrudtjr1/34
좋은 글이 있어 공유합니닷..
회원가입 창을 구현하면서 이용약관에 대해 고민을 해보았다.
나는 어차피 이러한 UI 들을 기획자분들과 디자이너분들이 이미 다 설계를 해주셔서 상관이 없었고
짜여진 디자인에서 코드만 입혀보았다.
디자이너분께서 넘겨주신 디자인은 대충 이러한 디자인이었다.
따라서 이 디자인에 초점을 맞추고 코드를 작성해보겠다.
1. 이용약관 설명에 대한 파일 분리
이용약관 더보기를 눌렀을 때 약관에 대한 설명이 나와야한다.
이부분은 SignUp.tsx 코드에 같이 있으면 너무 지저분해보이기 때문에 약관에 대한 설명은 따로 분리시켜주었다.
terms.js 파일
//terms.js
// 이용약관 text 내용
export const termsData = {
terms2: `
나는 여기서 두 항목에 대한 이용약관이 필요하므로 terms2와 terms3 의 튜플형태로 만들어주었다.
2. 항목 생성
<View >
{[
{ name: 'terms1', label: '(필수) 약관 모두 동의' },
{ name: 'terms2', label: '(필수) 이용약관' },
{ name: 'terms3', label: '(필수) 개인정보 수집 및 이용에 대한 동의' }
].map((term, index) =>
각 항목을 입력 받고, name 과 label을 가진 객체 배열을 map 함수의 인자로 받아,
각 객체는 name과 label 속성을 가지고 있다.
🤔 콜백함수가 머였더라?
콜백함수: 인자로 넘겨지는 함수이다. 따라서 어떤 이벤트가 발생했거나 특정 시점에 도달했을 때 시스템에서 호출하는 함수이다.
즉 다른 함수의 인자로 넘겨진다는 의미는 다른 함수가 실행을 끝낸 뒤 실행이 된다는 의미이기도 하다.
call( 호출) + back (여기서 back 을 '뒤'로 생각하면 편하다.)= > 즉 뒤에서 실행이 된다는 의미!
😭 그럼 map 은 뭐야?
map 함수는 배열의 각 요소를 순회하면서 새로운 배열을 반환하는 함수이다.
const array = [1, 2, 3];
const newArray = array.map((element, index) => {
return element * 2;
});
console.log(newArray); // [2, 4, 6]
element는 현재 순회 중인 배열 요소를 나타내고 index는 현재 순회 중인 배열 요소의 인덱스를 나타낸다.
따라서 map 은 배열의 각 요소를 순회하며 '배열'을 반환하기 때문에
각 요소에 2를 곱한 새로운 배열을 반환할 수 있는 것이다.
다시 코드로 와보자.
나는 여기서 반복문으로 terms1이 terms label 에 이어져 있어야 하기 때문에 map 함수를 사용하여 약관정보를 담고 있는 객체 배열을 순회하면서 각 개체는 term, 해당 객체의 인덱스를 index 로 넣어준 것이다.
따라서 콜백함수로 map 을 사용하여 각 term 객체를 처리하여 JSX 요소를 반환할 수 있게 하였당
3. 각 약관에 대한 버튼 상태관리하기
const [terms, setTerms] = useState({ terms1: false, terms2: false, terms3: false });
const [showTerms, setShowTerms] = useState({ terms2: false, terms3: false });
const handleCheckboxChange = (name: string) => {
setTerms({ ...terms, [name]: !terms[name] });
};
terms: 각 약관에 대한 동의 여부 관리
terms1: false, terms2: false, terms3: false 로 설정해두어
각 객체의 초기상태는 선택하지 않은 상태로 설정해둔다.
showTerms: 약관의 세부내용 표시에 관한 관리
terms2: false, terms3: false로 두어 terms2와 terms3의 객체의 초기상태는 약관을 보이지 않는 상태로 설정해둔다.
handleCheckboxchange 함수: 특정 약관 체크박스의 상태를 반전시키는 함수
name 인자를 받음
문자열이라고 명시해두어서 확실히 해둘 수 있당
- terms 객체의 모든 속성을 새로운 객체로 복사하고.
- 예를 들어, terms가 { terms1: false, terms2: false, terms3: false }라면, { ...terms }는 { terms1: false, terms2: false, terms3: false }와 동일한 객체이다.
즉 그르니까~
terms 상태가 업데이트 된 setTerms 함수는 name 인자를 받아서,
각 terms array 들을 [name] 에서 !terms[name]으로 업데이트 시켜준다.
예를 들어, name이 'terms2'이고 terms.terms2가 false였다면, 새로운 객체는 { terms1: false, terms2: true, terms3: false }가 됩니다.
4. '필수로 선택'하도록 구현하기
약관에서 '필수' 라고 적혀있는 부분은 반드시 눌러야 한다.
반드시 누르게 구현하도록 하기 위하여 이 버튼이 눌려야만 다음 버튼이 눌러질 수 있도록 구현해보도록 하자.
항목의 버튼이 눌리는 상태가 업데이트가 되어야 다음 버튼이 인식되도록 하면 된다.
두개가 눌렸을 때는 동의버튼은 변화가 없다가
세개 버튼이 다 눌려야 동의버튼이 뜨는 것을 확인할 수 있다.
<TouchableOpacity
//버튼의스타일
style={terms.terms1 && terms.terms2 && terms.terms3 ? styles.button : styles.buttonDisabled}
onPress={onSubmit}
//버튼이 활성화되게끔
disabled={!terms.terms1 || !terms.terms2 || !terms.terms3}
>
<Text style={styles.buttonText}>동의</Text>
</TouchableOpacity>
terms.terms1 과 terms2.terms3가 다 눌려야 button.style을 가지고
하나라도 눌리지 않으면 buttonDisabled하게 두었다.
그리고 disabled를 사용하여 버튼이 비활성화되게 두었다.
disabled={terms1 이 아니거나 terms2가 아니거나 terms3가 아니면 -> 즉 하나라도 눌리지 않으면 버튼은 disabled=True
즉 비활성화가 된다.
조건 설명
- !terms.terms1: 첫 번째 약관에 동의하지 않으면 true, 동의하면 false
- || (OR 연산자): 여러 조건 중 하나라도 true이면 전체 조건은 true
- !terms.terms2: 두 번째 약관에 동의하지 않으면 true, 동의하면 false
- !terms.terms3: 세 번째 약관에 동의하지 않으면 true, 동의하면 false
- 모든 약관에 동의한 경우: terms.terms1, terms.terms2, terms.terms3 모두 true가 되어 논리가 false가 되므로, disabled는 false가 되고 버튼이 활성화
- 하나라도 약관에 동의하지 않은 경우: !terms.terms1 || !terms.terms2 || !terms.terms3 중 하나가 true가 되어 논리가 true가 되므로, disabled는 true가 되고 버튼이 비활성화
5. 약관 동의 체크박스 렌더링
RN에서는 어쨌든 화면에 렌더링 하기 위해서는 무조건 View 안에 감싸져 있어야 한다.
<View key={index}>
<View >
<View>
<TouchableOpacity
onPress={() => handleCheckboxChange(term.name)}
style={localStyles.checkboxContainer}
>
<View >
{terms[term.name] && <View style={styles.checkboxInner} />}
</View>
<Text style={[terms[term.name] ? styles.labelSelected : styles.labelUnselected, localStyles.label]}>
{term.label}
</Text>
</TouchableOpacity>
//더보기버튼: terms2 와 terms3 에만 보이게끔 설정
{term.name !== 'terms1' && (
<TouchableOpacity onPress={() => setShowTerms({ ...showTerms, [term.name]: !showTerms[term.name] })}>
<Text style={localStyles.moreButton}>{showTerms[term.name] ? '확인' : '더보기'}</Text>
</TouchableOpacity>
)}
</View>
</View>
(기본적인 스타일부분은 다 제거했다..너무 지저분해보임 ㅠ)
((여기서는 기본적인 구조만 익히도록 하자.)
TouchableOpacity: 클릭할 수 있는 영역 만들기
눌렀을 때 handleCheckboxChange 함수가 실행되도록 한다. (여기서 term.name 인자가 전달되었다.)
여기서는 name 과 체크박스가 다 TouchableOpacity 컨테이너 안에 들어가서 체크박스를 누르든, text를 누르든 다 설정이 되도록 했다.
"더보기" 버튼
term.name!=='terms1'
즉 term1 이 아닌경우에만 더보기버튼이 보이도록 했다.
만약에 terms1 에도 더보기버튼이 필요하다면 이 코드는 없애도 된다.
더보기 를 누르면 이용약관이 나오고, <ScrollView> 를 통해 약관을 내려볼 수 있게 했다..
아마 다른 페이지나 컨테이너로 분리시키긴 할 것 같다.
이건 스타일링 문제니 넘어가고
우리의 문제는 더보기 버튼을 누르면 이용약관이 나오고, 확인으로 버튼이 바뀐것을 확인할 수 있다.
{showTerms[term.name] && (
- 조건부 렌더링
- showTerms[term.name]가 true일 때만 아래 내용을 렌더링한다는 의미이다.
- 예를 들어, term.name이 terms2이고 showTerms.terms2가 true이면, terms2에 대한 세부 내용을 렌더링한다.
<ScrollView>
- 약관의 세부 내용이 길 경우 스크롤할 수 있도록 하는 컨테이너
- ScrollView는 스크롤 가능한 영역을 만든다.
전체코드
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
import { styles } from './styles';
import { termsData } from './terms';
interface SignUp {
nextStep: () => void;
}
const SignUp: React.FC<Step1Props> = ({ nextStep }) => {
const [terms, setTerms] = useState({ terms1: false, terms2: false, terms3: false });
const [showTerms, setShowTerms] = useState({ terms2: false, terms3: false });
const handleCheckboxChange = (name: string) => {
setTerms({ ...terms, [name]: !terms[name] });
};
const onSubmit = () => {
console.log(terms);
nextStep();
};
return (
<View style={styles.container}>
<Text style={styles.title}>서비스 이용에 동의해 주세요</Text>
<View style={styles.terms}>
{[
{ name: 'terms1', label: '(필수) 약관 모두 동의' },
{ name: 'terms2', label: '(필수) 이용약관' },
{ name: 'terms3', label: '(필수) 개인정보 수집 및 이용에 대한 동의' }
].map((term, index) => (
<View key={index}>
<View style={[styles.checkboxContainer, terms[term.name] && styles.checkboxContainerSelected]}>
<View style={localStyles.checkboxAndButtonContainer}>
<TouchableOpacity
onPress={() => handleCheckboxChange(term.name)}
style={localStyles.checkboxContainer}
>
<View style={[styles.checkbox, terms[term.name] && styles.checkboxChecked]}>
{terms[term.name] && <View style={styles.checkboxInner} />}
</View>
<Text style={[terms[term.name] ? styles.labelSelected : styles.labelUnselected, localStyles.label]}>
{term.label}
</Text>
</TouchableOpacity>
{term.name !== 'terms1' && (
<TouchableOpacity onPress={() => setShowTerms({ ...showTerms, [term.name]: !showTerms[term.name] })}>
<Text style={localStyles.moreButton}>{showTerms[term.name] ? '확인' : '더보기'}</Text>
</TouchableOpacity>
)}
</View>
</View>
{showTerms[term.name] && (
<View style={localStyles.scrollContainer}>
<ScrollView>
<Text style={localStyles.termsText}>{termsData[term.name]}</Text>
</ScrollView>
</View>
)}
</View>
))}
</View>
<TouchableOpacity
style={terms.terms1 && terms.terms2 && terms.terms3 ? styles.button : styles.buttonDisabled}
onPress={onSubmit}
disabled={!terms.terms1 || !terms.terms2 || !terms.terms3}
>
<Text style={styles.buttonText}>동의</Text>
</TouchableOpacity>
</View>
);
};
export default SignUp;
'모바일 > React Native' 카테고리의 다른 글
[RN] 기본 레이아웃 구성하기 (1) | 2024.07.24 |
---|---|
[RN] FlatList 알아보기 (1) | 2024.07.22 |
[RN] No bundle URL present 이슈 해결 (0) | 2024.07.17 |
[React] 쏟아지는 페이지 관리하기 (1) | 2024.07.12 |
[RN] 하단 버튼바 만들기 (0) | 2024.07.04 |