티스토리 뷰

react-native를 설치한데 이어 간단한 Todo앱을 구현해보았다.

  • input에 할일을 적고 add 버튼을 누르면 하단 리스트에 추가된다.
  • checkbox를 클릭하면 todo에서 할일을 했다는 표시로 글자 가운데 줄이 그어진다.
  • 각 row는 delete 버튼이 있어서 리스트에서 제거할 수 있다.

 

레이아웃

react-native는 기본적으로 화면을 구성하기 위해 내장 컴포넌트인 View 컴포넌트를 사용하며, StyleSheet를 이용하여 스타일을 구성한다.

StyleSheet는 css의 스타일 기능을 제공하며, camel case 방식을 사용한다.

 

Container

앱 화면을 구성하는 container

react-native는 ios, android 모두 지원하므로 둘 다의 레이아웃을 적용해야 한다.

ios는 상단 노치에 의해 레이아웃이 가려지는 부분 때문에 추가적으로 설정을 해주어야 한다.

SafeAreaView를 이용하여 수동으로 레이아웃을 잡아주어야 한다.

https://reactnative.dev/docs/safeareaview 

 

SafeAreaView · React Native

The purpose of SafeAreaView is to render content within the safe area boundaries of a device. It is currently only applicable to iOS devices with iOS version 11 or later.

reactnative.dev

import React from 'react';
import {
  SafeAreaView,
  View,
  StyleSheet,
  Platform,
  type ViewStyle,
} from 'react-native';
import Contents from 'expo-constants';

const styles = StyleSheet.create({
  container: {
    paddingHorizontal: 16,
  },
});

type Props = {
  children: React.ReactNode;
  style?: ViewStyle;
};

function ViewTemplate({ children, style }: Props) {
  const padding = Platform.OS === 'ios' ? 16 : Contents.statusBarHeight + 16;

  return (
    <SafeAreaView>
      <View
        style={{
          ...styles.container,
          paddingTop: padding,
          ...style,
        }}
      >
        {children}
      </View>
    </SafeAreaView>
  );
}

export default ViewTemplate;

react-native에서 Platform을 통해 ios인지, android인지 확인할 수 있다.

이를통해 OS별 분기처리가 가능하다.

전체 레이아웃은 위 컴포넌트로 구성하였다.

 

Input

react-native의 TextInput컴포넌트를 이용하여 input을 구성한다.

사용법은 html의 input태그와 사용하는것이 크지는 않지만, onChange이벤트는 onChangeText라는 props로 넘겨준다.

 Button

react-native에서 제공하는 Button컴포넌트가 있지만 스타일 적용이 까다로운것 같아서 다른 컴포넌트를 사용하였다.

TouchableOpacity컴포넌트를 이용하여 스타일링이 용이한 컴포넌트를 만들었다.

그리고 클릭이벤트는 onPress를 이용하여 구현한다.

 

Checkbox

과거에 react-native에서 Checkbox가 있었지만 현재는 사라진것으로 보인다.

따라서 서드파티 모듈에서 Checkbox를 찾아야 헀는데 expo-checkbox라는 라이브러리를 통해 Checkbox를 구현할 수 있었다.

 

import React, { useRef, useState } from 'react';
import Typo from '@/components/Typo';
import ViewTemplate from '@/components/ViewTemplate';
import {
  StyleSheet,
  View,
  TextInput,
  Text,
  TouchableOpacity,
  Dimensions,
} from 'react-native';
import Checkbox from 'expo-checkbox';

const styles = StyleSheet.create({
  container: {
    paddingLeft: 12,
    paddingRight: 12,
  },
  title: {
    fontSize: 36,
    fontWeight: 'bold',
  },
  todoContainer: {
    rowGap: 16,
    width: '100%',
  },
  addArea: {
    flexDirection: 'row',
    columnGap: 16,
    height: 40,
  },
  input: {
    width: Dimensions.get('window').width - 140,
    borderRadius: 4,
    borderWidth: 1,
    height: 50,
    fontSize: 20,
    paddingHorizontal: 4,
  },
  addBtnBackground: {
    backgroundColor: '#d0d0d0',
    paddingVertical: 15,
    paddingHorizontal: 30,
    borderRadius: 4,
    marginBottom: 20,
    height: 50,
    justifyContent: 'center',
    alignItems: 'center',
  },
  addBtnText: {
    color: '#000',
    fontSize: 12,
  },
  todoListArea: {
    rowGap: 8,
  },
  row: {
    flexDirection: 'row',
  },
  left: {
    flexDirection: 'row',
    alignItems: 'center',
    columnGap: 4,
  },
  todoText: {
    width: Dimensions.get('window').width - 148,
  },
  deleteBtnBg: {
    backgroundColor: '#d0d0d0',
    justifyContent: 'center',
    alignItems: 'center',
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 4,
  },
  deleteBtnText: {
    color: '#000',
    fontSize: 12,
  },
});

export default function HomeScreen() {
  const inputRef = useRef<TextInput | null>(null);

  const [value, setValue] = useState('');
  const [list, setList] = useState<
    { key: number; value: string; checked: boolean }[]
  >([]);

  const onChangeText = (v: string) => {
    setValue(v);
  };

  const onAdd = () => {
    if (value === '') return;

    setList([
      ...list,
      {
        key: new Date().getTime(),
        value,
        checked: false,
      },
    ]);

    setValue('');

    inputRef.current?.blur();
  };

  const onDelete = (idx: number) => {
    setList((prev) => prev.filter((_, i) => idx !== i));
  };

  const onChangeCheck = (idx: number, checked: boolean) => {
    setList((prev) =>
      prev.map((state, i) => ({
        ...state,
        checked: i === idx ? checked : state.checked,
      })),
    );
  };

  return (
    <ViewTemplate
      style={{
        padding: 0,
        rowGap: 14,
        backgroundColor: '#fff',
        height: '100%',
      }}
    >
      <Typo typo='t'>Todo List</Typo>
      <View style={styles.todoContainer}>
        <View style={styles.addArea}>
          <TextInput
            style={styles.input}
            onChangeText={onChangeText}
            value={value}
            ref={inputRef}
          ></TextInput>
          <TouchableOpacity onPress={onAdd} style={styles.addBtnBackground}>
            <Text style={styles.addBtnText}>Add</Text>
          </TouchableOpacity>
        </View>
        <View style={styles.todoListArea}>
          {list.map(({ value, checked, key }, idx) => {
            return (
              <View style={styles.row} key={`${idx}-${key}`}>
                <View style={styles.left}>
                  <Checkbox
                    value={checked}
                    onValueChange={() => {
                      onChangeCheck(idx, !checked);
                    }}
                  ></Checkbox>
                  <Text
                    style={{
                      ...styles.todoText,
                      textDecorationLine: checked ? 'line-through' : 'none',
                    }}
                  >
                    {value}
                  </Text>
                </View>
                <TouchableOpacity
                  style={styles.deleteBtnBg}
                  onPress={() => {
                    onDelete(idx);
                  }}
                >
                  <Text style={styles.deleteBtnText}>Delete</Text>
                </TouchableOpacity>
              </View>
            );
          })}
        </View>
      </View>
    </ViewTemplate>
  );
}

 

 

왼쪽 안드로이드, 오른쪽 IOS

'front-end > react' 카테고리의 다른 글

react-native 설치해보기  (0) 2024.06.08
React query  (0) 2023.05.03
Redux 구현  (0) 2023.05.03
가상 돔, Virtual DOM  (0) 2023.05.03
React useState, useEffect Mechanism(with. closure)  (0) 2023.05.03
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함