공부/ReactNative

[ReactNative] To-Do-List 애플리케이션 | 처음 배우는 리액트네이티브 5장

hyunh404 2025. 1. 22. 02:19
728x90

 

 

To-Do-List 만들기

 

 

  • 등록 : 할 일항목을 추가하는기능
  • 수정 : 완료되지 않은 할 일 항목을 수정하는 기능
  • 삭제 : 할 일항목을 삭제하는기능
  • 완료 : 할 일 항목의 완료 상태를 관리하는 기능

 

프로젝트를 진행하기 앞서 styled-components/native를 사용하는 데 있어 아래와 같은 오류가 발해서 추가로 패키지를 설치해주었다.

Could not find a declaration file for module 'styled-components/native'.

 

npm install --save-dev @types/styled-components-react-native

 

 

1. SafeAreaView 컴포넌트

: 외부 요인에 의해 컴포넌트 일부가 가려지는 것을 방지하기 위해 사용

: 자동으로 padding 값 적용

: iOS 기기에만 적용됨

 

const Container = styled.SafeAreaView`
    flex: 1;
    background-color: ${({ theme }) => theme.background};
    align-items: center;
    justify-content: flex-start;
`;

 

 

2. StatusBar 컴포넌트

: 외부 요인에 의해 컴포넌트 일부가 가려지는 것을 방지하고 상태 바를 변경해 잘보이게 하는 데 사용

: 안드로이드에 적용

 

export default function App() {
    return (
        <ThemeProvider theme={theme}>
            <Container>
                <StatusBar
                    barStyle="light-content"
                    backgroundColor={theme.background}
                />
                <Title>TODO List</Title>
            </Container>
        </ThemeProvider>
    )
}

 

 

3. Dimensions

: 현재 화면의 크기를 알 수 있다.

: 다양한 크기의 기기에 동일한 모습으로 적용될 수 있도록 코드 작성 가능

: 처음 값을 받아왔을 때의 크기로 고정된 되기 때문에 변화된 화면의 크기와 일치하지 않을 수 있다. => 이를 대비 하기 위해 이벤트 리스너를 등록해 변화에 대응할 수 있도록 기능을 제공한다.

 

  • useWindowDimensions : 리액트 네이티브에서 제공하는 Hooks, 화면의 크기 가 변경되면 화면의 크기, 너비, 높이를 자동으로 업데이트
import React from 'react'
import styled from 'styled-components/native'
import { Dimensions } from 'react-native';

const StyledInput = styled.TextInput`
    width: ${({ width }) => width - 40}px;
    height: 60px;
    margin: 3px 0;
    padding: 15px 20px;
    border-radius: 10px;
    background-color: ${({ theme }) => theme.itemBackground};
    font-size: 25px;
    color: ${({ theme }) => theme.text};
`;

const Input = () => {
    const width = Dimensions.get('window').width;

    return (
        <StyledInput width={width} />
    )
}

export default Input
 const width = useWindowDimensions().width;

 

 

4. Textinput 컴포넌트

: 첫 글자가 대문자로 나타나고 오타 입 력 시 자동으로 수정하는 기능이 존재

  • autoCapitalize : 첫 글자 대문자
  • autoCorrect : 오타 자동 수정
  • returnKeyType : iOS에서 키보드 완료 버튼 설정
  • keyboardAppearance : iOS 키보드 색상 변경
<StyledInput
    width={width}
    placeholder={placeholder}
    maxLength={50}
    autoCapitalize="none"
    autoCorrect={false}
    returnKeyType="done"
    keyboardAppearance="dark"
/>

 

 

728x90

 

실습

 

 

 

1️⃣ 추가 기능

 

useState를 이용해 to do list 를 저장하고 관리할 tasks 변수를 생성 후 초기값을 입력했다.

 

또한 최신 항목이 가장 앞에 보이도록 역순으로 렌더링 했고,

고유의 key 값도 설정했다.

 

{Object.values(tasks)
    .reverse()
    .map(item => (
        <Task
            key={item.id}
            text={item.text}
        />
))}

 

 

_addTask 함수를 정의하고 추가 기능을 넣어주겠다.

 

id는 타임스탬프를 이용하고 text는 입력된 값을 지정해준 후 새 입력값은 completed는 항상 false 가 된다.

마지막으로 newTask 값을 빈 문자열로 지정해 input을 초기화한 후 기존 목록은 유지하고 새 항목이 추가되도록 정의했다.

 

const _addTask = () => {
        const ID = Date.now().toString();
        const newTaskObject = {
            [ID]: { id: ID, text: newTask, completed: false },
        };
        setNewTask('');
        setTasks({ ...tasks, ...newTaskObject });
};

 

항목 추가

 

 

 

2️⃣ 삭제 기능

 

_deleteTask 함수에서 id를 이용해 해당항목을 삭제하고

onPressOut 이벤트를 정의해 아이콘에 기능을 적용해주었다.

 

const _deleteTask = id => {
        const currentTasks = Object.assign({}, tasks);
        delete currentTasks[id];
        setTasks(currentTasks);
}
<TouchableOpacity onPressOut={_onPressOut}>
	<Icon source={type} completed={completed} />
</TouchableOpacity>

 

항목 삭제

 

 

3️⃣ 완료 기능

 

completed 값을 통해 완료 - 미완료 상태가 전환되도록 함수를 정의했다.

 

id를 이용해 해당 항목의 완료 상태를 전달하도록 _toggleTask 함수를 만들었고

삼항연산자를 사용해 아이콘을 상태에 따라 불러와주었다.

 

 

또한,

명확한 구분을 위해 complete 값에 따른 스타일을 조정해서 기능을 완성했다.

 

const _toggleTask = id => {
        const currentTasks = Object.assign({}, tasks);
        currentTasks[id]['completed'] = !currentTasks[id]['completed'];
        setTasks(currentTasks);
}
const Contents = styled.Text`
  flex: 1;
  font-size: 24px;
  color: ${({ theme, completed }) => (completed ? theme.done : theme.text)};
  text-decoration-line: ${({ completed }) => (completed ? 'line-through' : 'none')}
`;

 

완료 미완료

 

 

4️⃣ 수정 기능

 

수정은 버튼을 클릭했을 때 해당 항목이 Input 컴포넌트로 변경되면서 내용을 수정하도록 만들었다.

 

_updateTask 함수에서 id를 통해 해당 항목을 전달했고

isEditing 변수로 상태 변화를 감지해 Input 컴포넌트를 불러오도록 정의했다.

 

const _onSubmitEditing = () => {
        if (isEditing) {
            const editedTask = Object.assign({}, item, { text });
            setIsEditing(false);
            updateTask(editedTask);
        }
}
return isEditing ? (
        <Input
            value={text}
            onChangeText={text => setText(text)}
            onSubmitEditing={_onSubmitEditing}
            onBlur={_onBlur}
        />
    ) : (
        <Container>
            <IconButton
                type={item.completed ? images.completed : images.uncompleted}
                id={item.id}
                onPressOut={toggleTask}
                completed={item.completed}
            />
            <Contents completed={item.completed}>{item.text}</Contents>
            {item.completed || (
                <IconButton
                    type={images.update}
                    onPressOut={_handleUpdateButtonPress}
                />)
            }
            <IconButton
                type={images.delete}
                id={item.id}
                onPressOut={deleteTask}
                completed={item.completed}
            />
        </Container>
    )

 

항목 수정

 

 

5️⃣ 입력 취소 기능

 

onBlur 함수를 정의해 focus가 input에서 사라졌을 시 입력이 취소되도록 했다.

 

_onBlur 함수에 setNewTask를 빈 값으로 줌으로써 간단히 정의해주었다.

 

const _onBlur = () => {
        setNewTask('');
}
const _onBlur = () => {
        if (isEditing) {
            setIsEditing(false);
            setText(item.text);
        }
}

 

수정 취소

 

 

 

데이터 저장하기

 

 

리액트 네이티브에서는 AsyncStorage를 이용해 로컬에 데이터를 저장하고 불러오는 기능을 구현할 수 있다.

 

  • AsyncStorage : 비동기로 동작, 문자열로 된 키-값 형태 데이터를 기기에 저장하고 불러옴

아직 사용할 수 있지만 공식문서에서는 Deprecated라 되어 있다.

AsyncStorage대신 react- native-community 에서 관리하는 async - storage를 설치해서 사용하는 것이 권장된다.

 

 

+ @react-native-community/async-storage는 더 이상 유지보수되지 않기 때문에

@react-native-async-storage/async-storage 패키지를 사용하는 것을 권장한다.

 

npm install @react-native-async-storage/async-storage

 

 

tasks 초기값은 삭제 후 AsnycStorage를 이용해 tasks 라는 문자열로 키를 전달해 항목들을 문자열로 변환해서 저장하는

_saveTasks 함수를 정의했다.

 

값이 변경될때마다 저장해야하므로 setTasks 세터함수를 이용하는 곳에서 호출하도록 수정해주었다.

 

const [tasks, setTasks] = useState({});

    const _saveTask = async tasks => {
        try {
            await AsyncStorage.setItem('tasks', JSON.stringify(tasks));
            setTasks(tasks);
        } catch (e) {
            console.log(e);
        }
    };

    const _addTask = () => {
        const ID = Date.now().toString();
        const newTaskObject = {
            [ID]: { id: ID, text: newTask, completed: false },
        };
        setNewTask('');
        _saveTask({ ...tasks, ...newTaskObject });
    };

    const _deleteTask = id => {
        const currentTasks = Object.assign({}, tasks);
        delete currentTasks[id];
        _saveTask(currentTasks);
    }

    const _toggleTask = id => {
        const currentTasks = Object.assign({}, tasks);
        currentTasks[id]['completed'] = !currentTasks[id]['completed'];
        _saveTask(currentTasks);
    }

    const _updateTask = item => {
        const currentTasks = Object.assign({}, tasks);
        currentTasks[item.id] = item;
        _saveTask(currentTasks);
    }

 

 

 

저장된 데이터를 불러오기 위해 _loadTasks 함수를 정의해주었고 객체로 변환해 tasks에 입력하도록 해주었다.

 

첫 화면이 나타나기 전에 불러온 항목이 렌더링 완료되게 하려면 Expo에서 제공하는 AppLoading 컴포넌트를 이용해 쉽게 구현할 수 있다.

 

 

+ AppLoading 컴포넌트는 expo 버전 44 이후부터는 expo-app-loading 패키지로 분리되었기 때문에 별도의 패키지를 설치해야한다.

npm install expo-app-loading

 

  • AppLoading : 특정 조건에서 로딩 화면이 유지되도록 하는 기능, 렌더링 전에 처리해야하는 작업 수행할 때 유용
  • startAsync: AppLoading 컴포넌트가 동작하는 동안 실행될 함수
  • onFinish: startAsync가 완료되면 실행할 함수
  • onError: startAsync에서 오류가 발생하면 실행할 함수
const _loadTasks = async () => {
    const loadTasks = await AsyncStorage.getItem('tasks');
    setTasks(JSON.parse(loadTasks || '{}'));
};
<AppLoading
    startAsync={_loadTasks}
    onFinish={() => setIsReady(true)}
    onError={console.error}
/>

AppLoading

 

 

https://github.com/BB545/ReactNative_ExpoProject/tree/master/react-native-todo
728x90