jineecode
todolist 본문
*데이터 타입을 타입스크립트로 정의할 때 같은 파일에서 정의하면 해당 파일 안에서만 타입을 정의할 수 있다.
그러나 @types/index.d.ts 를 만들고 해당 파일 안에서 타입을 정의하면 프로젝트 전반에 걸쳐 타입을 사용할 수 있다.
context은 전반에 걸쳐 사용될 예정이므로 @types/index.d.ts 파일에 따로 저장하는 것이 좋다.
1. AsyncStorage 설치
2. Context
1) src/Context/TodoListContext/@types/index.d.ts
interface ITodoListContext {
todoList: Array<string>;
addTodoList: (todo: string) => void;
removeTodoList: (index: number) => void;
}
interface 명: ITodoListContext
todoList: string으로 이루어진 배열
2) src/Context/TodoListContext/index.tsx
import React, {createContext, useState, useEffect} from 'react';
import AsyncStorage from '@react-native-community/async-storage';
interface Props {
children: JSX.Element | Array<JSX.Element>;
}
const TodoListContext = createContext<ITodoListContext>({
todoList: [],
addTodoList: (todo: string): void => {},
removeTodoList: (index: number): void => {},
});
const TodoListContextProvider = ({children}: Props) => {
const [todoList, setTodoList] = useState<Array<string>>([]);
const addTodoList = (todo: string): void => {
const list = [...todoList, todo];
setTodoList(list);
AsyncStorage.setItem('todoList', JSON.stringify(list));
};
const removeTodoList = (index: number): void => {
let list = [...todoList];
list.splice(index, 1);
setTodoList(list);
AsyncStorage.setItem('todoList', JSON.stringify(list));
};
const initData = async () => {
try {
const list = await AsyncStorage.getItem('todoList');
if (list !== null) {
setTodoList(JSON.parse(list));
}
} catch (e) {
console.log(e);
}
};
useEffect(() => {
initData();
}, []);
return (
<TodoListContext.Provider
value={{
todoList,
addTodoList,
removeTodoList,
}}>
{children}
</TodoListContext.Provider>
);
};
export {TodoListContextProvider, TodoListContext};
2-1) creatContext로 Context를 생성하고 useState로 생성한 state 데이터를 Context 안에 저장할 예정.
이렇게 하여, Context의 데이터를 수정할 수 있다.
2-1-1) createContext 함수에 초기값을 할당하여 context를 생성할 수 있다. 이때 미리 정의한 ts 타입을 사용하여 Context의 데이터 타입을 지정한다.
const TodoListContext = createContext<ITodoListContext>({
todoList: [],
addTodoList: (todo: string): void => {},
removeTodoList: (index: number): void => {},
});
todoList 가 빈 배열인 이유는, 실제 구현은 context의 프로바이더 컴포넌트에서 할 예정이기 때문이다.
2-1-2)
context를 사용하기 위해서는 우선 공통 부모 컴포넌트에서 context의 프로바이더를 사용한다.
공통 부모 컴포넌트에서 프로바이더를 사용하기 위해서는, context의 프로바이더 컴포넌트를 만들고, 공통 부모 컴포넌트의 부모 컴포넌트로서 사용한다.
TodoListContextProvider는 context의 프로바이더 컴포넌트로서, 공통 부모 컴포넌트의 부모 컴포넌트가 될 예정이다.
즉, 자식 컴포넌트를 children 매개변수를 통해 전달받는다.
이렇게 전달받은 자식 컴포넌트는 createContext로 생성한 context의 프로바이더인 TodoListContext.Provider의 하위에 위치한다.
2-2) useEffect를 가지고 asyncstorage에 저장된 데이터를 가져와 설정할 예정
2-2-1) useEffect는 라이프사이클 함수와 같은 것
useEffect(() => {
initData();
}, []);
두 번째 매개변수에 빈 배열을 넣으면 componentDidMount와 같은 역할을 수행한다.
(컴포넌트가 처음 화면에 표시된 후 useEffect가 한 번만 호출)
두 번째 매개변수에 아무것도 넣지 않으면 componentDidMount, componentDidUpdate를 동시에 수행.
즉, props, state가 변경되고 리렌더링 된 후에도 또 실행된다.
useEffect(() => {
...
return () => {
};
...
});
첫 번째 매개변수 함수는 함수를 반환할 수 있다. 이 함수는 componentWillUnount와 같은 역할을 한다.
(컴포넌트가 화면에서 사라진 후, 이 함수가 호출되며, 라이브러리와 연동을 해제하거나 타이머를 해제할 때 사용)
useEffect(() => {
...
}, [todoList]);
두 번째 매개변수로 배열을 전달할 수 있다. 특정 변수를 설정하여 전달하면, 전달된 변수가 변경될 때만 함수가 호출된다.
useEffect(() => {
...
}, [todoList]);
useEffect(() => {
...
});
한 컴포넌트에 여러 번 정의하여 사용할 수 있다.
예를 들어, componentDidMount의 역할을 하는 useEffect와, 특정변수 값이 변경될 때 실행되는 로직에 useEffect를 정의할 수 있다.
2-3) 프로바이더 설정
프로바이더는 context를 공유할 컴포넌트들의 최상단 공통 부모 컴포넌트에 사용한다.
여기선 src/App.tsx에서 사용하도록 했다.
import React from 'react';
import Styled from 'styled-components/native';
import {TodoListContextProvider} from '../src/Context/TodoListContext';
const Container = Styled.View`
flex: 1;
background-color: #EEE;
`;
const App = () => {
return (
<TodoListContextProvider>
...
</TodoListContextProvider>
);
};
export default App;
2-4) todo 컴포넌트
src/Screens/Todo/index.tsx
mport React from 'react';
import Styled from 'styled-components/native';
import TodoListView from './TodoListView';
import AddTodo from './AddTodo';
const Container = Styled.View`
flex: 1;
`;
interface Props {}
const Todo = ({ }: Props) => {
return (
<Container>
<TodoListView />
<AddTodo />
</Container>
);
};
export default Todo;
한 컴포넌트에 종속되는 컴포넌트는 분리하지 않고 종속된 컴포넌트 하위 폴더에 생성한다.
이렇게 관리하면 해당 컴포넌트에 종속된 컴포넌트를 쉽게 찾을 수 있으며 공통 컴포넌트와 구별되는 장점이 있다.
Todo 컴포넌트에는 TodoListView 컴포넌트와 AddTodo 컴포넌트가 import 되어 있다.
2-5) TodoListView
src/Screens/Todo/TodoListView/index.tsx
import React from 'react';
import Styled from 'styled-components/native';
import Header from './Header';
import TodoList from './TodoList';
const Container = Styled.SafeAreaView`
flex: 1;
`;
interface Props {}
const TodoListView = ({ }: Props) => {
return (
<Container>
<Header />
<TodoList />
</Container>
);
};
export default TodoListView;
TodoListView 에는 Header 컴포넌트와 TodoList 컴포넌트가 포함되어 있다.
2-5-1) Header
src/Screens/Todo/TodoListView/Header/index.tsx
import React from 'react';
import Styled from 'styled-components/native';
const Container = Styled.View`
height: 40px;
justify-content: center;
align-items: center;
`;
const TitleLabel = Styled.Text`
font-size: 24px;
font-weight: bold;
`;
interface Props {}
const Header = ({ }: Props) => {
return (
<Container>
<TitleLabel>Todo List App</TitleLabel>
</Container>
);
};
export default Header;
단순히 Todo List App 이라는 문자를 화면에 표시할 뿐인 컴포넌트.
2-5-2) TodoList
src/Screens/Todo/TodoListView/TodoList/index.tsx
실제로 할 일 리스트를 표시하는 컴포넌트로, Context를 사용하여 저장된 할 일 데이터를 화면에 표시한다.
import React, { useContext } from 'react';
import { FlatList } from 'react-native';
import Styled from 'styled-components/native';
import { TodoListContext } from '../../../../Context/TodoListContext';
import EmptyItem from './EmptyItem';
import TodoItem from './TodoItem';
const Container = Styled(FlatList)`
`;
interface Props {}
const TodoList = ({ }: Props) => {
const { todoList, removeTodoList } = useContext<ITodoListContext>(
TodoListContext
);
return (
<Container
data={todoList}
keyExtractor={(item, index) => {
return `todo-${index}`;
}}
ListEmptyComponent={<EmptyItem />}
renderItem={({ item, index }) => (
<TodoItem
text={item as string}
onDelete={() => removeTodoList(index)}
/>
)}
contentContainerStyle={todoList.length === 0 && { flex: 1 }}
/>
);
};
export default TodoList;
TodoList 컴포넌트에서는 데이터가 없을 때 표시하는 EmptyItem 컴포넌트와 데이터가 있을 때 해당 데이터를 표시하는 TodoItem 컴포넌트가 있다.
(이쯤되니 컴포넌트 관리가 너무 힘들다 -_-;)
* FlatList
const Container = Styled(FlatList)`
`;
...
const TodoList = ({ }: Props) => {
...
return (
<Container
data={todoList}
keyExtractor={(item, index) => {
return `todo-${index}`;
}}
ListEmptyComponent={<EmptyItem />}
renderItem={({ item, index }) => (
<TodoItem
text={item as string}
onDelete={() => removeTodoList(index)}
/>
)}
contentContainerStyle={todoList.length === 0 && { flex: 1 }}
/>
);
};
RN의 리스트 뷰 중 하나인 FlatList. FlatList 컴포넌트는 Props를 전달하여 사용할 수 있다.
data: 데이터 배열
keyExtractor: key 값
ListEmptyComponent: 주어진 배열에 데이터가 없을 경우 표시되는 컴포넌트
renderItem: 주어진 배열에 데이터를 사용하여 반복적으로 표시될 컴포넌트
contentContainerStyle=... : 표시할 데이터가 없는 경우 ListEmptyComponent가 화면에 표시된다. 그러나 이것도 하나의 리스트 아이템으로 표시되므로 전체 화면으로 표시하기 위해 스타일 flex: 1을 부여하였다.
const TodoList = ({ }: Props) => {
const { todoList, removeTodoList } = useContext<ITodoListContext>(
TodoListContext
);
...
};
export default TodoList;
TodoList에서 Context를 사용하기 위해서, useContext 를 불러와 TodoListContext를 초기값으로 설정한다.
TodoListContext에서 사용하고자 하는 todoList 변수와, removeTodoList 함수를 불러왔다.
2-5-2-1) EmptyItem 컴포넌트
src/Screens/Todo/TodoListView/TodoList/EmptyItem/index.tsx
import React from 'react';
import Styled from 'styled-components/native';
const Container = Styled.View`
flex: 1;
align-items: center;
justify-content: center;
`;
const Label = Styled.Text``;
interface Props {}
const EmptyItem = ({ }: Props) => {
return (
<Container>
<Label>하단에 "+" 버튼을 눌러 새로운 할일을 등록해 보세요.</Label>
</Container>
);
};
export default EmptyItem;
2-5-2-2 ) TodoItem 컴포넌트
src/Screens/Todo/TodoListView/TodoList/TodoItem/index.tsx
import React from 'react';
import Styled from 'styled-components/native';
const Container = Styled.View`
flex-direction: row;
background-color: #FFF;
margin:4px 16px;
padding: 8px 16px;
border-radius: 8px;
align-items: center;
`;
const Label = Styled.Text`
flex: 1;
`;
const DeleteButton = Styled.TouchableOpacity``;
const Icon = Styled.Image`
width: 24px;
height: 24px;
`;
interface Props {
text: string;
onDelete: () => void;
}
const TodoItem = ({ text, onDelete }: Props) => {
return (
<Container>
<Label>{text}</Label>
<DeleteButton onPress={onDelete}>
<Icon source={require('../../../../../Assets/Images/remove.png')} />
</DeleteButton>
</Container>
);
};
export default TodoItem;
onDelete는 TodoList/index.tsx 에서 전달받은 함수이다.
이 컴포넌트에는 text와 삭제 버튼이 존재한다.
2-6) AddTodo
src/Screens/Todo/AddTodo/index.tsx
import React, { useState } from 'react';
import AddButton from './AddButton';
import TodoInput from './TodoInput';
interface Props {}
const AddTodo = ({ }: Props) => {
const [showInput, setShowInput] = useState<boolean>(false);
return (
<>
<AddButton onPress={() => setShowInput(true)} />
{showInput && <TodoInput hideTodoInput={() => setShowInput(false)} />}
</>
);
};
export default AddTodo;
AddTodo 에는 AddButton 컴포넌트와 TodoInput 컴포넌트가 포함되어 있다.
useState를 사용하여, AddButton을 눌렀을 때 할일을 입력하는 TodoInput 컴포넌트를 화면에 표시하기 위해, showInput state를 생성했다.
할일을 입력하는 컴포넌트에서 할일 입력을 완료하면 해당 컴포넌트를 숨길 수 있도록 hideTodoInput을 설정했다.
2-6-1) AddButton
src/Screens/Todo/AddTodo/AddButton/index.tsx
import React from 'react';
import Styled from 'styled-components/native';
const Container = Styled.SafeAreaView`
position: absolute;
bottom: 0;
align-self: center;
justify-content: flex-end;
`;
const ButtonContainer = Styled.TouchableOpacity`
box-shadow: 4px 4px 8px #999;
`;
const Icon = Styled.Image``;
interface Props {
onPress?: () => void;
}
const AddButton = ({ onPress }: Props) => {
return (
<Container>
<ButtonContainer onPress={onPress}>
<Icon source={require('../../../../Assets/Images/add.png')} />
</ButtonContainer>
</Container>
);
};
export default AddButton;
onPress?:() => void를 이미지 버튼이 선택되었을 때 호출할 수 있게 연결하였다.
2-6-2) TodoInput
src/Screens/Todo/AddTodo/TodoInput/index.tsx
import React from 'react';
import {Platform} from 'react-native';
import Styled from 'styled-components/native';
import Background from './Background';
import TextInput from './TextInput';
const Container = Styled.KeyboardAvoidingView`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
justify-content: flex-end;
`;
interface Props {
hideTodoInput: () => void;
}
const TodoInput = ({hideTodoInput}: Props) => {
return (
<Container behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
<Background onPress={hideTodoInput} />
<TextInput hideTodoInput={hideTodoInput} />
</Container>
);
};
export default TodoInput;
TodoInput 에는 입력 받을 때 화면을 살짝 어둡게 처리할 Background 컴포넌트와, 할 일을 입력받을 TextInput 컴포넌트를 가지고 있다.
KeyboardAvoidingView 컴포넌트는 키보드가 활성화되는 컴포넌트인데, ios에서는 입력창을 가리는 문제가 있으므로 padding옵션을 추가해준다.
2-6-2-1) Background 컴포넌트
src/Screens/Todo/AddTodo/TodoInput/Background/index.tsx
import React from 'react';
import Styled from 'styled-components/native';
const Container = Styled.TouchableWithoutFeedback`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
`;
const BlackBackground = Styled.View`
background-color: #000;
opacity: 0.3;
width: 100%;
height: 100%;
`;
interface Props {
onPress: () => void;
}
const Background = ({ onPress }: Props) => {
return (
<Container onPress={onPress}>
<BlackBackground />
</Container>
);
};
export default Background;
2-6-2-1) TextInput 컴포넌트
src/Screens/Todo/AddTodo/TodoInput/TextInput/index.tsx
import React, { useContext } from 'react';
import Styled from 'styled-components/native';
import { TodoListContext } from '../../../../../Context/TodoListContext';
const Input = Styled.TextInput`
width: 100%;
height: 40px;
background-color: #FFF;
padding: 0px 8px;
`;
interface Props {
hideTodoInput: () => void;
}
const TextInput = ({ hideTodoInput }: Props) => {
const { addTodoList } = useContext<ITodoListContext>(TodoListContext);
return (
<Input
autoFocus={true}
autoCapitalize="none"
autoCorrect={false}
placeholder="할일을 입력하세요!"
returnKeyType="done"
onSubmitEditing={({ nativeEvent }) => {
addTodoList(nativeEvent.text);
hideTodoInput();
}}
/>
);
};
export default TextInput;
데이터가 추가되는 컴포넌트로, Context를 사용할 필요가 있다.
역시 useContext를 사용하여, TodoListContext를 전달하고, TodoListContext의 addTodoList 함수를 할당 받는다.
*TextInput
RN의 컴포넌트인 TextInput 컴포넌트.
onSubmitEditing은 키보드의 완료 버튼을 누르면 호출되는 TextInput의 함수이다. 이것을 누르면 Context에 데이터를 저장하고, TodoInput 컴포넌트를 숨기도록 hideTodoInput 함수를 호출했다.
'React native' 카테고리의 다른 글
React-Navigation 사용법 (0) | 2021.08.10 |
---|---|
RN 새로운 프로젝트 생성하기 (0) | 2021.07.10 |
AsyncStorage 사용 (0) | 2021.07.03 |
props & state (0) | 2021.07.03 |
개발환경 세팅 (0) | 2021.06.26 |