elevne's Study Note
React Native 로 To-Do App 만들기 (2) 본문
앱에 기능들을 추가해주기 위해 Redux 를 사용한다. 우선 Redux 관련 모듈들을 아래 명령어를 통해 전부 추가해준다.
npm install @reduxjs/toolkit react-redux redux --save
그 후 slices 를 담을 slices 폴더, todoSlice.js, store.js 파일을 아래와 같은 구조로 생성해준다.
todoSlice.js 는 아래와 같이 작성해준다.
const { createSlice } = require("@reduxjs/toolkit");
const todoSlice = createSlice({
name: "todo",
initialState: {
currentId: 4,
todos: [],
},
reducers: {}
});
export default todoSlice.reducer;
작성한 reducer 을 store 에 등록을 해줘야한다. store.js 파일은 아래와 같이 작성한다. (또, 작성한 store 은 App.js 에서 Provider 컴포넌트로 감싸줄 때 사용한다)
import { configureStore } from "@reduxjs/toolkit";
import { todoReducer } from "./slices/todoSlice"
export const store = configureStore({
reducer: {
todo: todoReducer
}
})
그 다음으로는 Action type 에 매칭되는 리듀서 함수를 생성해줘야 한다. addTodo, updateTodo, deleteTodo 액션을 만들어야 했다. 이는 todoSlice.js 에서 작성한 slice 의 reducers 부분에 작성해준다.
import { createSlice } from "@reduxjs/toolkit";
const todoSlice = createSlice({
name: "todo",
initialState: {
currentId: 4,
todos: [],
},
reducers: {
addTodo: (state, action) => {
state.todos.push({
id: state.currentId++,
text: action.payload.trim(),
state: 'todo'
})
},
updateTodo: (state, action) => {
const item = state.todos.findIndex((item) => item.id === action.payload); // Key값이 같은 TODO 찾기
state.todos[item].state = state.todos[item].state === "todo" ? "done" : "todo";
state.todos.push(state.todos.splice(item, 1)[0]);
},
deleteTodo: (state, action) => {
const item = state.todos.findIndex((item) => item.id === action.payload);
if (item > -1) { // ITEM 이 있을 경우를 뜻함
state.todos.splice(item, 1)
}
}
}
});
export default todoSlice.reducer;
export const {addTodo, updateTodo, deleteTodo} = todoSlice.actions;
reducer 들을 정의해준 후 export 해주는 과정을 거쳤다. Reducer 내부에서 splice 라는 메서드가 사용되었다. 이는 배열로부터 특정 범위를 삭제하거나 새로운 값을 추가 또는 기존 값을 대체할 수 있는 메서드이다.
이제 Redux 를 사용할 준비는 마무리가 되었다. 이제 컴포넌트들을 수정해야할 차례였다. 우선 InputForm 컴포넌트부터 아래와 같이 수정하였다.
import { KeyboardAvoidingView, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'
import React, { useState } from 'react'
import { useDispatch } from 'react-redux';
import { addTodo } from '../redux/slices/todoSlice';
const InputForm = () => {
const [currentValue, setCurrentValue] = useState("");
const dispatch = useDispatch();
const handleSubmit = () => {
if (currentValue !== '') {
dispatch(addTodo(currentValue));
setCurrentValue('');
}
}
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.addFormContainer}>
<TextInput
style={styles.inputField}
placeholder="할 일을 작성해주세요."
value={currentValue}
onChangeText={setCurrentValue}
onSubmitEditing={handleSubmit}
/>
<Pressable style={styles.addButton} onPress={handleSubmit}>
<Text style={styles.addButtonText} >+</Text>
</Pressable>
</KeyboardAvoidingView>
)
}
export default InputForm
const styles = StyleSheet.create({
addFormContainer: {
flexDirection: 'row',
marginTop: 'auto',
marginBottom: 30,
paddingHorizontal: 20,
backgroundColor: '#f7f8fa'
},
inputField: {
flex: 1,
height: 42,
padding: 5,
marginRight: 25,
borderRadius: 4,
borderColor: 'rgba(0, 0, 0, 0.2)',
borderWidth: 1,
color: '#000000',
fontSize: 15,
textAlignVertical: 'center'
},
addButton: {
justifyContent: 'center',
alignItems: 'center',
width: 42,
height: 42,
borderRadius: 4,
backgroundColor: 'rgba(0,0,0,0.7)',
shadowColor: '#000000',
shadowOpacity: 0.14,
shadowRadius: 8,
shadowOffset: {
width: 0,
height: 4
}
},
addButtonText: {
color: 'white',
fontSize: 25
}
})
useDispatch, dispatch 메서드가 사용되었다. 또, TextInput 내에서 Enter 키로 Form 이 Submit 되는 경우를 다루기 위해서 onSubmitEditing 도 추가해주었다. 이제 추가한 Todo 들을 화면에 리스트로 보여줄 차례였다.
Web 에서 React 를 사용하여 목록을 보여줄 때는 <li> 혹은 <ul> 등의 태그로 구성해볼 수 있다. React Native 에서는 FlatList 와 ScrollView 를 사용할 수 있는데, 이 둘에는 큰 차이가 있다고 한다. ScrollView 는 Component 가 로드된 직후 Item 들을 로드한다. 즉, 모든 데이터는 RAM 에 저장되며 성능 저하로 인해 그 안에 있는 수많은 항목들을 사용할 수 없게될 수 있다고 한다. 반대로 FlatList 에는 10 개의 Item (기본값) 을 화면에 탑재하고 사용자가 보기를 스크롤하면 다른 Item 이 탑재되게 한다. 이러한 점에서 ScrollView 대신에 FlatList 를 사용하는 편이 좋다고 한다. (적은 수의 Item 일 때는 ScrollView, 많을 때는 FlatList 를 사용하면 된다고 한다)
MainScreen.js 를 아래와 같이 작성한다. 아래에서 사용된 FlatList 는 data, renderItem, keyExtractor 을 받게된다. data 는 리스트들을 위한 데이터, renderItem 은 렌더링하는 부분을 넣어준다. 그 다음 keyExtractor 은 지정된 인덱스에서 지정된 항목에 대한 고유 키를 추출하는데 사용된다. 키는 캐싱에 사용되며 item re-rendering 을 추적하기 위한 반응 키로 사용된다고 한다. 이 때, store 에 접근하기 위해 useSelector 메서드가 사용된 것을 확인할 수 있다.
import { View, Text, SafeAreaView, Platform, FlatList } from 'react-native'
import React from 'react'
import { StyleSheet } from 'react-native'
import { StatusBar } from 'expo-status-bar'
import InputForm from '../components/InputForm'
import TodoItem from '../components/TodoItem'
import { useSelector } from 'react-redux'
const MainScreen = () => {
const todos = useSelector(state => state.todo.todos);
const todoTasks = todos.filter((item) => item.state === "todo");
const completedTasks = todos.filter((item) => item.state === "done");
return (
<SafeAreaView style={styles.container}>
<StatusBar
barStyle={"default"}
/>
<Text style={styles.pageTitle}>ToDo App</Text>
<View style={styles.listView}>
<Text style={styles.listTitle}>TO DO</Text>
{todoTasks.length !== 0 ? (
<FlatList
data={todoTasks}
renderItem={({item}) => <TodoItem {...item} />}
keyExtractor={(item) => item.id}
></FlatList>
) : (<Text style={styles.emptyListText}>NOTHING TO DO!</Text>)}
</View>
<View style={styles.separator}/>
<View style={styles.listView}>
<Text style={styles.listTitle}>DONE</Text>
{completedTasks.length !== 0 ? (
<FlatList
data={completedTasks}
renderItem={({item}) => <TodoItem {...item} />}
keyExtractor={(item) => item.id}
></FlatList>
) : (<Text style={styles.emptyListText}>NOTHING DONE YET</Text>)}
</View>
<InputForm/>
</SafeAreaView>
)
}
export default MainScreen
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Platform.OS === 'android' ? 20 : 0, // ANDROID 와 IOS 의 분기처리 (이유: IOS 에서는 이미 SafeAreaView 를 사용했기 때문)
backgroundColor: "#f7f8fa"
},
pageTitle: {
marginBottom: 35,
paddingHorizontal: 15,
fontSize: 50,
fontWeight: "600"
},
separator: {
marginHorizontal: 10,
marginTop: 25,
marginBottom: 10,
borderBottomWidth: 1,
borderBottomColor: "rgba(0,0,0,0.2)"
},
listView: {
flex: 1,
},
listTitle: {
marginBottom: 25,
paddingHorizontal: 15,
fontSize: 40,
fontWeight: "500"
},
emptyListText: {
paddingTop: 10,
paddingBottom: 15,
paddingHorizontal: 15,
fontSize: 15,
lineHeight: 20,
color: "#737373"
}
})
그 다음으로는 TodoItem 컴포넌트를 아래와 같이 수정해준다.
import { View, Text, Pressable, StyleSheet } from 'react-native'
import React from 'react'
import CheckboxUnchecked from "../assets/checkbox-unchecked.svg"
import CheckboxChecked from "../assets/checkbox-checked.svg"
import DeleteIcon from "../assets/delete.svg"
import { useDispatch } from 'react-redux'
import { updateTodo, deleteTodo } from '../redux/slices/todoSlice'
const TodoItem = (props) => { // PROPS 를 가져올 수 있는 것은 FlatList 에서 item 넣어주는 부분이 있기 때문
const dispatch = useDispatch();
return (
<View style={styles.itemContainer} >
<Pressable style={styles.itemCheckbox} hitSlop={10}
onPress={() => dispatch(updateTodo(props.id))}>
{props.state === "todo" ?
<CheckboxUnchecked/> :
<CheckboxChecked style={styles.itemCheckboxCheckedIcon} /> }
</Pressable>
<Text style={[styles.itemText,
props.state === "done" ? styles.itemTextChecked : "" ]}>
{props.text}
</Text>
<Pressable style={[styles.deleteButton,
props.state === "done" ? styles.deleteButtonDone : ""]} hitSlop={10}
onPress={() => dispatch(deleteTodo(props.id))}>
<DeleteIcon />
</Pressable>
</View>
)
}
export default TodoItem
const styles = StyleSheet.create({
itemContainer: {
flexDirection: "row",
alignItems: "center",
paddingTop: 10,
paddingBottom: 15,
paddingHorizontal: 15,
backgroundColor: "#f7f8fa"
},
itemCheckbox: {
justifyContent: "center",
alignItems: "center",
width: 20,
height: 20,
marginRight: 15,
borderRadius: 5,
},
itemCheckboxCheckedIcon: {
shadowColor: "#000000",
shadowOpacity: 0.2,
shadowRadius: 8,
shadowOffset: {
width: 0,
height: 4
}
},
itemText: {
marginRight: "auto",
paddingRight: 25,
fontSize: 15,
lineHeight: 20,
color: "#737373",
},
itemTextChecked: {
opacity: 0.5,
textDecorationLine: "line-through"
},
deleteButton: {
opacity: 0.8
},
deleteButtonDone: {
opacity: 0.3
}
})
여기까지 작성하고 실행하면 모든 의도한 기능들이 정상 동작하는 것을 확인할 수 있다.
이제 모든 기능이 잘 동작하는 것도 확인해보았다. 기본 기능들은 완료되었고, 다음 번에는 추가적으로 회원 기능 등을 만들어볼 예정이다.
Reference:
'Frontend > React Native' 카테고리의 다른 글
React Native 로 To-Do App 만들기 (4) (0) | 2023.04.24 |
---|---|
React Native 로 To-Do App 만들기 (3) (0) | 2023.04.23 |
React Native: Redux (2) (0) | 2023.03.25 |
React Native: Redux (0) | 2023.03.24 |
React Native 로 To-Do App 만들기 (1) (0) | 2023.03.23 |