코딩테스트 React,Redux

juno·2022년 11월 30일
0

문제

  • 아래의 그림에서 start 버튼을 누르면, 1초마다 number counter가 오릅니다
  • stop & added 버튼을 누르면 타이머가 멈추고,
    text, number, 그리고 가장 낮은번호의 고유의 id값을 하나의 객체로 묶어서,
    전역상태로 관리합니다. (redux를 사용하여 값을 저장합니다.)
  • 전역으로 관리되는 이 데이터는 배열로 되어 있고, 이 전부를 Tasks List에 렌더합니다.
  • 아래의 리스트를 렌더할때는 저장된 배열을에서 text값이 같은 것 끼리 합하여 렌더하는데,
    이때 id는 제일 낮은 값을 쓰고 timer는 합하여 보여줍니다.
  • 허나 그렇다고, 전역상태로 관리되는 데이터는 수정하지 않습니다.
  • 또한 Total Time에는 모든 time의 합이 나와야 됩니다.

key point

1. start버튼을 누를시 setInterval로 숫자 증가

2. stop & added 버튼 누를시 clearInterval 및 데이터 전역상태관리 중인 배열에 업데이트

3. List를 렌더할때 filter 과정을 거쳐서 List를 압축

풀이

Step 1. input 하나의 state로 관리

	
    // 컴포넌트 return 위
	const [inputValues, setInputValues] = useState({
		text: '',
		time: 0,
	});

	const inputHandler = ({ target: { name, value } }) => {
		setInputValues(prev => ({ ...prev, [name]: value }));
	};
	// 컴포넌트 return 아래
	return (
		<div>
			<h1>Tasks 추가</h1>
			<span>text: </span>
			<input
				type='text'
				name='text'
				value={inputValues.text}
				onChange={inputHandler}
			/>
			<span> number: </span>
			<input type='number' name='time' value={inputValues.time} readOnly />
			<span> start :</span>
			<button>start </button>
			<span> added task : </span>
			<button>stop & added</button>
		</div>
	);

	

input에 대한 상태값은 name property로 하나로 관리하면 state 선언을 줄일 수 있다.

Step2. start, stop & added 버튼 함수 추가


	const [timer, setTimer] = useState(null);

	const startTimer = () => {
		if (!timer) {
			setTimer(
				setInterval(() => {
					setInputValues(prev => ({ ...prev, time: prev.time + 1 }));
				}, 1000)
			);
		}
	};

	const addedTask = () => {
		clearInterval(timer);
		const data = { ...inputValues, id: createdId() };
		dispatch(pushTask(data));
		setInputValues({ text: '', time: 0 });
		setTimer(null);
	};
	
	

리액트에서 setInterval 관련 함수를 사용할때, 컴포넌트 밖에서

'let timer ;' 이런식의 변수 선언 후 할당하는 경우를 많이 찾아보았는데,

저는 오히려 state로 관리하면, 예기치 못한 sideEffect를 막을 수 있지 않을까 싶어서 해당 방식을 사용해 보았다.

지금생각해보니 useRef로 관리해도 될 것같다.

Step 1~2. Edit컴포넌트 완성

위의 두 과정을 통해 Edit 컴포넌트 완성


import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { pushTask } from './redux/tasksSlice';

export default function Edit() {
	const dispatch = useDispatch(); // 아래의 설명 있습니다.
	const [inputValues, setInputValues] = useState({
		text: '',
		time: 0,
	});

	const [timer, setTimer] = useState(null);

	const inputHandler = ({ target: { name, value } }) => {
		setInputValues(prev => ({ ...prev, [name]: value }));
	};

	const startTimer = () => {
		if (!timer) {
			setTimer(
				setInterval(() => {
					setInputValues(prev => ({ ...prev, time: prev.time + 1 }));
				}, 1000)
			);
		}
	};

	const addedTask = () => {
		clearInterval(timer);
		const data = { ...inputValues, id: createdId() };
		dispatch(pushTask(data));
		setInputValues({ text: '', time: 0 });
		setTimer(null);
	};

	return (
		<div>
			<h1>Tasks 추가</h1>
			<span>text: </span>
			<input
				type='text'
				name='text'
				value={inputValues.text}
				onChange={inputHandler}
			/>
			<span> number: </span>
			<input type='number' name='time' value={inputValues.time} readOnly />
			<span> start :</span>
			<button onClick={startTimer}>start </button>
			<span> added task : </span>
			<button onClick={addedTask}>stop & added</button>
		</div>
	);
}

let unique = 0;

function createdId() {
	unique++;
	return unique;
}

Step 3. Redux

Redux toolkit을 사용하였다.

필요한 패키지

 & npm install react-redux @reduxjs/toolkit

참고로 redux는 패키지를 설치 안해도 된다.

1. store 생성

  • 전역상태로 관리 할 여러 함수를 만들게 될껀데, 이를 하나의 변수로 묶어준다.
  • reducer는 어떤 key에 접근하면 무슨 변수를 쓸꺼다 라는 느낌이다.
  • 전역으로 관리할 데이터를 만들면 reducer안에 key(부를때), vlaue(이거쓴다)형태로 저장한다.
import { configureStore } from '@reduxjs/toolkit';
import tasksSlice from './tasksSlice'; // 아래서 만들 함수

const store = configureStore({
	reducer: {
		tasks: tasksSlice,
	},
});

export default store;

2. 제일 밖에 있는 컴포넌트에 가서 Provider로 감싸기

현재 저는 index.js에 Provider로 감쌌는데, 프로젝트의 관심사에 따라 app.js 혹은 Router.js에도 감싸도 된다.

store생성 및 Provider로 감싸기가 Redux(전역으로 값을 핸들링하기위한) 기본 세팅이다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import store from './redux/store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
	<React.StrictMode>
		<Provider store={store}>
			<App />
		</Provider>
	</React.StrictMode>
);

3. 준비 끝 사용할 변수 만들기

  • 해당값에 접근할때 쓸 name store의 reducder키 값이랑 맞춰준다.
  • 초기값인 initialState 부분은 value 라는 키값까지 명시해주고 초기값을 넣는다
  • reducers는 값을 업데이트할떄 사용한다. redux로 관리되는 값들은 reducers로만 바꿀수 있다고 생각하고 넘어가자
  • reducers 안의 함수의 파라미터를 보면 state, action이 있는데,
    state는 본인의 값을 받는 파라미터고,
    action은 해당 함수를 밖에서 실행할때 넣어주는 인자를 action.payload로 뱉어낸다
  • 2개의 export 해당함수가 어디서 import되는지 잘 확인해주자.
import { createSlice } from '@reduxjs/toolkit';

const tasksSlice = createSlice({
	name: 'tasks', // store의 reducer의 key값과 동일하게 
	initialState: {
		value: [], // 'value: ' 까지 꼭 있어야함
	},
	reducers: { // 여기 아래의 함수들로만 initialState를 바꿀수 있다.
		pushTask: (state, action) => {
          // state.value => initialState.value를 가르킴
          // action.payload => 밖에서 이 함수를 실행하게되면, 값이 일로 들어온다.
			state.value = [...state.value, action.payload];
		},
	},
});

// 함수부분을 destructurue 해서 export 한 것이다. 나중에 dispatch로 이 함수를 사용한다.
export const { pushTask } = tasksSlice.actions;

// store에서 import 한다. 상태값을 념겨준다!!
// store는 Provider가 모든 컴포넌트를 감싸면서 store를 넘겨주는데 
// 그렇기 때문에 어디서든 접근할 수 있도록 store에 전달
export default tasksSlice.reducer;
  1. 자 이제 Step 1~2의 redux 함수 사용법을 짓고 넘어가자.
  • reducers에 저장되어있는 함수를 사용할려면 useDispatch와 사용할 함수를 import해야한다.
  • 그리고 아래처럼 dispatch에 useDispatch()를 호출하여 리턴되는 무언가를 변수에 담아준다
  • reducers에 저장되어 있는 함수를 실행 할때는 아래처럼만 지켜주면 된다.
  • 그리고 주의할 점은 인자를 여러개 넘겨줘도 action.payload라는 하나의 경로로 저장되기 때문에
    인자는 1개의 값만 넘겨 줄 수 있다.
  • 여러값을 넘겨야 될것 같으면 객체로 잘 만들어서 넘겨줘 보자.
  • redux value 꺼내쓰기는 아래서 자연스럽게 나올 것이다.
	
	import { useDispatch } from 'react-redux';
	import { pushTask } from './redux/tasksSlice';

	const dispatch = useDispatch(); // useDispatch를 호출하여 변수에 담기

	
	const addedTask = () => {
		clearInterval(timer);
		const data = { ...inputValues, id: createdId() };
		// 아래처럼 실행 인자는 1개만 넘겨 받을 수 있다.
      	dispatch(pushTask(data));
		setInputValues({ text: '', time: 0 });
		setTimer(null);
	};

Step 4. redux 값 불러오기 및 Total Time 계산

값 불러오기는 정말 쉽다.

  • useSelector를 콜백으로 본인이 설정한 key값에 접근 할 수 있다.
  • 아래는 destructurue를 해서 값을 꺼내왔다.
  • Total Time은 for문으로 꺼내 왔다.
	import React from 'react';
	import { useSelector } from 'react-redux';


	export default function Tasks() {
	const tasks = useSelector(({ tasks }) => tasks.value);
                  //  === useSelector((state) => state.tasks.value)
    
    let totalTime = 0;
	for (let i = 0; i < tasks.length; i++) {
		totalTime += tasks[i].time;
	}

    ......

Step 5. 데이터 압축하기

글만 읽어봤을떄는 쉬워 보였는데, 막상 해당 문제는 해결하지 못했다.
먼저 filter로 배열을 줄이고, 같은 text value값을 가진 time value는 하나로 합친다.

코딩테스트가 끝나고 해당 알고리즘을 구현하는데만 3시간을 더 사용하였다..

  • 아래의 함수는 전역으로 관리되는 데이터를 넣어서 압축시키는 로직을 구현한 것이다.
  • combine은 name의 키값과 value로 time의 합계를 만들어 주었다.
  • combineArray는 같은 name의 값을 가지면 처음 index외에 filter해준다.
  • 여기서 중요한 것은 time값은 신경쓰지 않는다. combine에서 가지고 있기 때문에
  • 마지막으로 comnine 키값이랑 일치하는 text는 time을 바꿔준다.
function combineTasks(tasks) {
  // name: sumTime
	let combine = {};
	for (let i = 0; i < tasks.length; i++) {
		if (combine[tasks[i].text]) {
			combine = {
				...combine,
				[tasks[i].text]: combine[tasks[i].text] + tasks[i].time,
			};
		}
		if (!combine[tasks[i].text]) {
			combine = {
				...combine,
				[tasks[i].text]: tasks[i].time,
			};
		}
	}
 // Array 필터 time 값은 위에서 저장하였기때문에 filter에만 집중한다.
	const combineArray = tasks.filter(
		(task, index, callback) =>
			index === callback.findIndex(t => t.text === task.text)
	);
  
  // combine의 키값을 확인하여 일치하는 키값에 time 바꿔주기
	let newArray = [...combineArray];
	for (const key in combine) {
		newArray = [...newArray].map(el => {
			if (el.text === key) {
				return { ...el, time: combine[key] };
			}
			return el;
		});
	}

	return newArray;
}

Step 4~5 TasksList 컴포넌트 완성

import React from 'react';
import { useSelector } from 'react-redux';

export default function Tasks() {
	const tasks = useSelector(({ tasks }) => tasks.value);
	let totalTime = 0;
	for (let i = 0; i < tasks.length; i++) {
		totalTime += tasks[i].time;
	}
	const combineArray = combineTasks(tasks);
	return (
		<div>
			<h1>Tasks List</h1>
			<h2>공통 압축한 리스트 </h2>
			<h3>Total Time : {totalTime} </h3>
			{combineArray.map(({ id, text, time }) => (
				<ul key={id} style={{ width: '200px', border: '1px solid black' }}>
					<li className=''>
						<h3>id: {id}</h3>
						<h3>text: {text}</h3>
						<h3>time: {time}</h3>
					</li>
				</ul>
			))}
		</div>
	);
}

function combineTasks(tasks) {
	let combine = {};
	for (let i = 0; i < tasks.length; i++) {
		if (combine[tasks[i].text]) {
			combine = {
				...combine,
				[tasks[i].text]: combine[tasks[i].text] + tasks[i].time,
			};
		}
		if (!combine[tasks[i].text]) {
			combine = {
				...combine,
				[tasks[i].text]: tasks[i].time,
			};
		}
	}

	const combineArray = tasks.filter(
		(task, index, callback) =>
			index === callback.findIndex(t => t.text === task.text)
	);

	let newArray = [...combineArray];
	for (const key in combine) {
		newArray = [...newArray].map(el => {
			if (el.text === key) {
				return { ...el, time: combine[key] };
			}
			return el;
		});
	}

	return newArray;
}

첫 코테 후기

주어진 시간은 많았지만, 너무 긴장해서 당장 눈앞에 보이는 문제부터 하나씩 헤쳐나가는식으로 진행 했었는데, 후에 빌드업을 잘못해서'아,, 이거 잘못됐다...' 라는 생각이 들었었다, 그때는 이미 늦었지만 ㅋㅋ,, 그래서, 해당 문제는 못풀었지만, 좋은 공부가 되었다.

profile
안녕하세요 인터랙션한 웹 개발을 지향하는 프론트엔드 개발자 입니다. https://kimjunho97.tistory.com => 블로그 이전 중

0개의 댓글