'Redux' 로 CRUD 기능 만들기

이정수·2023년 10월 1일
0
post-thumbnail

이번 주차에는 Redux를 모듈화하는 방법에 대해 공부해보았다.

Todo나 User 처럼 특정 상태에 따라 분리해서 관리할 수 있다.

먼저 이전의 User에 대한 경우는 ContextAPI로 관리하고 있기 때문에 투두리스트에 관한 리듀서를 만들어 보았다.

만드는 순서는 다음과 같다.
1. modules/todos.ts 파일에 다음을 선언한다.

  1. 액션타입
  2. 액션 생성 함수
  3. (타입스크립트라면 state에 관한 interface나 type선언)
  4. 초기상태 (initialState)
  5. 리듀서 함수
  1. moduls/index.ts 파일에 다음을 선언한다.
    1. rootReducer를 선언수 export 한다.
      • combineReducer를 통해 만들어진 여러개의 리듀서를 하나로 합친다.
  2. index.tsx 파일에 다음을 선언한다.
    1. store를 만든다.
      • createStore의 첫번째 인자로 rootReducer를 넘긴다.
      • (createStore의 두번째 인자로 composeWithdevTools()함수를 넘긴다.)
  3. 각 컴포넌트에서 사용한다~~

todo.ts

  • 액션타입을 선언한다.
const ADD_TODO = 'todos/ADD_TODO'

const TOGGLE_TODO = 'todos/TOGGLE_TODO' //투두 <-> done 상태 바꿈

const DELETE_TODO = 'todos/DELETE_TODO'

const UPDATE_TODO = 'todos/UPDATE_TODO' //수정하기
  • 액션 생성함수를 선언한다.

//액션 생성함수 선언

let nextId = 1

export const addTodo = (text: string) => ({

type: ADD_TODO,

todo: {

id: nextId++,

text,

},

})

export const toggleTodo = (id: number) => ({

type: TOGGLE_TODO,

id,

})

  

export const deleteTodo = (id: number) => ({

type: DELETE_TODO,

id,

})

  

export const updateTodo = (id: number, text: string) => ({

type: UPDATE_TODO,

text,

id,

})
  • [!] reducer함수를 선언한다.
    	- 일단 기능에 맞게 todos라고 선언후,state와 action 값을 전달한다.
export default function todos( state = initialState,action: { type: any; todo: ConcatArray<ITodo>; id: number; text: any }) {

switch (action.type) {

	case ADD_TODO:
	
	return state.concat(action.todo)
	
	
	case TOGGLE_TODO:
	
		return state.map(todo =>
		
		todo.id === action.id ? { ...todo, done: !todo.done } : todo )
	
	case DELETE_TODO:
	
		const targetIndex = state.findIndex(todo => todo.id === action.id)
		
		return [...state.slice(0, targetIndex), ...state.slice(targetIndex + 1)]
	
	case UPDATE_TODO:
	
		return state.map(todo =>
		
		todo.id === action.id ? { ...todo, text: action.text } : todo
		
		)
	
	default:
	
		return state

}}

TodoList 컴포넌트에서 사용하기

기존에 Wrapper에 몽땅 때려박아 가독성이 안 좋았던 코드를 리팩토링해 보았다.
이전에는 Contents의 인풋이 변경되면 날짜를 표시하던 헤더까지 전부 리랜더링이 되었다. 게다가 헤더는 외부 라이브러리인 moment.js를 이용해서 이를 분리해야겠다는 생각이 들었다.

Todolist를 3개의 세부 컴포넌트로 분리시켜 주었다.

export function TodoList() {

	return (
	
	<>
		
		<S.Wrapper>
		
			<TodosHeader />
			
			<TodosContainer />
			
			<TodosFooter />
		
		</S.Wrapper>
	
	</>
	
	)

}

헤더

  • ==날짜==에 해당하는 정보들은 모두 헤더 컴포넌트에 담아서 분리시켰다.
export function TodosHeader() {

	const dayOfWeek = moment().format('dddd')
	
	const dateOfMonth = moment().format('MMMM Do')
	
		return (
		
		<S.TodoListHeader>
		
			<S.Button> {'<'} prev </S.Button>
			
			<S.TodoListDate>
			
				<span className="day">{dayOfWeek.toUpperCase()} ,</span>
				
				<span className="date">{dateOfMonth}</span>
			
			</S.TodoListDate>
			
			<S.Button> next {'>'} </S.Button>
		
		</S.TodoListHeader>
		
	)

}
  • Footer는 새로운 Todo를 만드는 기능만 담당한다.
export function TodosFooter() {

const [isInputOpen, setIsInputOpen] = useState<boolean>(false)

const todos = useSelector((state: RootState) => state!.todos)

const dispatch = useDispatch()

const onCreate = (text: string) => dispatch(addTodo(text))


return (

	<S.TodoListFooter>
	
	{todos.length} tasks
	
	<S.AddButton
	
	isOpen={isInputOpen}
	
	onClick={() => setIsInputOpen(!isInputOpen)}>	
	
	ADD NEW +{' '}
	
	</S.AddButton>
	
		<CreateTodo isOpen={isInputOpen} onCreate={onCreate} />
	
	</S.TodoListFooter>

)}

container / todoItem

const TodoItem = React.memo(function TodoItem({

todo,onToggle,onDelete,onUpdate,
}){

	const [isUpdate, setIsUpdate] = useState(false)
	
	const [text, setText] = useState(todo.text)

	
	const onSubmit = (e: any) => {
	
		e.preventDefault()
		
		onUpdate(todo.id, text)
		
		setIsUpdate(false)
	
	}

	const handleUpdateClick = () => {setIsUpdate(true)}
	

console.log('TodoItem 랜더링~~~')

return (

	<S.Item>
	
	{isUpdate 
	? 
	( <input
	
		placeholder={todo.text}
	
		value={text}
	
		onChange={e => { setText(e.target.value)}}/>
	
	) 
	: 
	(
		<li onClick={() => {onToggle(todo.id)}}>	
	
			<span 
				style={{ textDecoration: todo.done ? 'line-through' : 'none' }}> 
				{todo.text} </span>
	
		</li> )}
	
	<div>
	
	<button onClick={() => {onDelete(todo.id)}}> DEL </button>
	
	{isUpdate ? (
	
		<button onClick={onSubmit}> DONE </button>
	
	) : (
	
		<button onClick={handleUpdateClick}> MODI </button>
	
	)}
	
	</div>
	
	</S.Item>
	
	)})

궁금했던 점

예시 코드에서 주어진 [[React.memo]]에 대해 공부해보고 난 후 궁금한 점이 생겼다.

memo는 오직 props에 대해서만 변경사항을 체크한다. 기존의 예시 코드에서는 update 기능이 들어가 있지 않았기에 부모컴포넌트에서 받아온 함수들을 사용해서 큰 문제가 되지 않았다.

이번에는 update 기능을 추가하게 되면서

  • 사용자가 수정 버튼을 눌렀는지에 대한 상태값
    - isUpdate
  • 수정할 text를 담는 상태값
    - text

두가지를 TodoItem 컴포넌트 내에서 사용하게 되었다.

그러면 useState hook을 사용했을 때에도 memo의 최적화 방법이 유효할 것인가 ?

todolist에 차례로 1,2,3,4 를 입력해본 후 렌더링여부를 체크하면

memo 사용 X

새로운 할일을 추가 할 때마다 이전에 작성되었던 할일 목록들도 중복되어서 리랜더링이 일어난다.

이후 수정 버튼을 누르고 값을 수정하면 예상대로 isUpdate와 text가 변경된 컴포넌트에서 리랜더링이 일어난다.
수정 후 완료버튼을 누르면 기존의 모든 할일들이 다시 랜더링 된다.

memo 사용 O

반면 할일이 추가 될때만 해당 아이템에 대해서만 랜더링이 일어난다.

이후 수정 버튼을 누르고 값을 수정하면 예상대로 isUpdate와 text가 변경된 컴포넌트에서 리랜더링이 일어난다.
수정 후 완료 버튼을 누르면 ==수정된 컴포넌트만== 다시 랜더링 된다!

profile
keep on pushing

0개의 댓글