ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React Native 003
    React/React Native 2022. 12. 30. 05:00
    728x90

    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이 탑재된다. 

    적은 수의 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
designitlikesloth