-
React Native 003React/React Native 2022. 12. 30. 05:00728x90
Redux Slice 및 Store 생성하기
리덕스를 위해 필요한 모듈들을 터미널에서 설치해 줍니다.
npm install @reduxjs/toolkit react-redux redux
리덕스를 위한 파일 및 폴더 생성
redux폴더가 이미 있다면 안에 추가만 해줍니다. Todo를 위한 Slice 생성하기 (todoSlice.js에 입력하시오.)
import { createSlice } from "@reduxjs/toolkit"; const todoSlice = createSlice({ name: 'todo', //이름 initialState: { //처음 만들 todo의 state를 가지고 있다 currentId:1, //임의로 넣어준 숫자 1부터 시작 todos: [], //todos 배열에 모든 정보는 저장이 된다. }, reducers: {} }); export default todoSlice.reducer; //다른곳 에서 사용하려면
생성한 reducer를 Store에 등록하기(store.js)
import { configureStore } from "@reduxjs/toolkit"; import todoReducer from '@reduxjs/toolkit' export const store=configureStore({ reducer:{ todo:todoReducer } })
Action Type에 매칭되는 리듀서 함수 생성하기( 찐기능 넣기)
todoSlice.js에 reducers:{} 안에다 작성해 주시면 되시겠다.- addTodo
addTodo: (state, action) => { state.todos.push({ id:state.currentId++, //들어온 currentId에서 하나씩 올려주기 text: action.payload.trim(),//todo에 들어가는 내용이 들어간다. state:'todo' }) },
- updateTodo
updateTodo: (state, action) => { const item = state.todos.findIndex((item)=>item.id === action.payload); state.todos[item].state = state.todos[item].state =='todo'? 'done' : 'todo'; state.todos.push(state.todos.splice(item,1)[0]); //원래 들어있던 state를 todo에서 item을 없앤다음에 다시 push를 해주는건데 //todo에서 할일을 클릭했을 때 체크만 된 상태로 두는 것이 아니라 완료된 일쪽으로 옮겨주려고 하는 것 },
- deleteTodo
deleteTodo: (state, action) => { const item = state.todos.findIndex((item)=> item.id == action.payload); //id를 이용해 해당 인덱스를 가져옴 if(item > -1){ //item이 있다면 (= item>-1) state.todos.splice(item, 1);//배열안의 해당 아이템을 없앤다 } }
리듀서 함수의 Key 이름으로 액션이 생성이 된다.
다른 컴포넌트에서 액션을 사용할 수 있게 export까지 해주면 된다.
리덕스 장착 완료
적용 시키기
Input Value State 생성 (InputForm.js)
const InputForm = () => { const [currentValue, setcurrentValue]= useState("") return ( ... placeholder="할 일을 작성해 주세요" value={currentValue} /> ...
Input 에 value 입력 시 state 변경하기
return ( ... value={currentValue} onChangeText={setcurrentValue}//값을 입력했을 때 currentValue가 바뀜 /> ...
- onChangeText
텍스트 입력의 텍스트가 변경될 때 호출되는 콜백. 변경된 텍스트는 콜백 핸들러에 인수로 전달된다.
버튼을 눌렀을 때 Todo 생성하기
const InputForm = () => { const [currentValue, setcurrentValue]= useState("") const dispatch = useDispatch(); const handleSubmit = () => { if(currentValue !== ""){ //currentValue가 없지 않을 때 dispatch(addTodo(currentValue)); //원래 값없애기 setcurrentValue(""); } } ... return ( <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'paadding' : 'height'} style={styles.addFormContainer}> <TextInput style={styles.inputField} placeholder="할 일을 작성해 주세요" value={currentValue} onChangeText={setcurrentValue}//값을 입력했을 때 currentValue가 바뀜 onSubmitEditing={handleSubmit}//버튼 클릭 이외에 엔터키로 생성하기 위해 추가 /> <Pressable style={styles.addButton} onPress={handleSubmit}> <Text style={styles.addButtonText}>+</Text> </Pressable> </KeyboardAvoidingView> )
데이터 나열하기
Store에 있는 데이터를 해당 컴포넌트에 Provide 해주기
useSelector Hooks를 이용해서 리덕스 스토어에 있는 데이터 가져오기(MainScreen.js)
redux 스토어의 상태에 접근하기 위한 hook. 이 hook는 selector 함수를 인수로 사용한다. 선택자는 스토어 상태와 함께 호출된다.
... 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') ...
FlatList 컴포넌트를 이용해 Todo 리스트 하나씩 렌더링 해주기
- 웹에서는 ul li등을 이용해서 하나씩 렌더를 해주는데 React Native에서는ScrollView나 FlatList를 사용한다
- ScollView
컴포넌트가 load된 직후 Item(스크롤용 데이터)을 로드하고 결과적으로 모든 데이터는 ram에 저장되며 성능 저하로 인해 그 안에 있는 수백 수천개의 항목을 사용할 수 없게 된다. - FlatList
10개의 item(기본값)을 화면에 탑재하고 사용자가 보기를 스크롤 하면 다른 item이 탑재된다.
- ScollView
적은 수의 item에는 ScrollView를 사용하고 많은 수의 item에는 FlatList를 사용할 수 있다.
MainScreen.js
import { Platform, SafeAreaView, StyleSheet, Text, View} from 'react-native' import React from 'react' 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'}></StatusBar> <Text style={styles.pageTitle}>ToDo App</Text> <View style={styles.listView}> <Text style={styles.listTitle}>할 일</Text> {todoTasks.length !==0?( <FlatList data={todoTasks} renderItem={({ item }) => <TodoItem {...item} />} keyExtractor={item => item.id} /> ) : <Text style={styles.emptyListText}>할 일이 없습니다.</Text> } </View> <View style={styles.separator} /> <View style={styles.listView}> <Text style={styles.listTitle}>완료된 일</Text> {completedTasks.length!==0?( <FlatList data={completedTasks} renderItem={({ item }) => <TodoItem {...item} />} keyExtractor={item => item.id} /> ) : ( <Text>완료된 일이 없습니다.</Text> ) } </View> <InputForm /> </SafeAreaView> ) } export default MainScreen const styles = StyleSheet.create({ container:{ flex:1, paddingTop: Platform.OS==='android'? 20 : 0, //안드로이드일때는 20을 아니면 0 backgroundColor: '#F7f8fa', }, pageTitle:{ marginBottom:35, paddingHorizontal:15, fontSize:54, 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:41, fontWeight:'500', } })
실행하면 화면이 잘 나오는데 입력한 결과가 잘 나오지 않는 상황이 발생
하드코딩 탓인 듯 하다 TodoItem.js 역시 그랬다 실제 데이터를 넣어주자
import { Pressable, StyleSheet, Text, View } 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'; const TodoItem = (props) => { return ( <View style={styles.itemContainer}> <Pressable style={styles.itemCheckbox} hitSlop={10} > {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} > <DeleteIcon /> </Pressable> <Text> </Text> </View> ) } export default TodoItem ...
잘 나온다 이제 updateTodo 하고 deleteTodo를 하면 된다.
import { Pressable, StyleSheet, Text, View } 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 { deleteTodo, updateTodo} from '../redux/slices/todoSlice'; const TodoItem = (props) => { const dispatch = useDispatch(); return ( <View style={styles.itemContainer}> <Pressable hitSlop={10} style={styles.itemCheckbox} 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:{ width: 20, height: 20, marginRight: 10, borderRadius: 5 }, itemCheckboxCheckedIcon:{ shadowColor: 'black', shadowOpacity:0.14, shadoeRadius: 10, shadowOffset: { width: 0, height: 4 }, }, itemText:{ marginRight:'auto', paddingRight:'auto', fontsize:15, lineHeight:20, color:'#737373' }, itemTextChecked:{ opacity:0.3, textDecorationLine:'line-through' }, deleteButton:{ opacity:0.8 }, deleteButtonDone:{ opacity:0.3 } })
그런데 갑자기 추가가 안되고 에러가 떴다
ERROR Warning: Cannot update a component (`MainScreen`) while rendering a different component (`TodoItem`). To locate the bad setState() call inside `TodoItem`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render setState를 잘못했다고 하는 것 같은데
소괄호 어디갔니 onPress={=> dispatch(deleteTodo(props.id))} //위 코드때문에 안되는 거였다. 아래 코드로 바꿔주었고 잘 작동하게 되었다. onPress={()=> dispatch(deleteTodo(props.id))}
에러
ERROR Warning: Cannot update a component (`MainScreen`) while rendering a different component (`TodoItem`). To locate the bad setState() call inside `TodoItem`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
이 에러가 뜰 때는 아 내가 state를 잘 못 작성한게 있나보다 하고 잘 찾아보면 될 것 같다.
끝 이 정도면 표면 찍먹은 해본 것 같고,
이제 다시 주력으로 하려는 React를 하려고 한다.
한동안 정리해 둔 글이 있는데 전량 폐기하고
처음 시작하는 마음으로 설치부터 시작하려 한다.
React Native는 다시 필요하거나 궁금할 때 더 해야지.
점심은 뭐 먹지?
728x90'React > React Native' 카테고리의 다른 글
React Native 002 (1) 2022.12.30 React Native 001 (0) 2022.12.27 React Native 000 (0) 2022.12.26