오늘 문제는 인덱스 바꾸기다. 문제 설명을 첨부하자면, 아래와 같다.
문자열 `my_string`과 정수 `num1`, `num2`가 매개변수로 주어질 때, `my_string`에서 인덱스
`num1`과 인덱스 `num2`에 해당하는 문자를 바꾼 문자열을 return 하도록 solution 함수를 완성해보세요.
my_string | num1 | num2 | result |
---|---|---|---|
"hello" | 1 | 2 | "hlelo" |
"I love you" | 3 | 6 | "I l veoyou" |
이 문제를 처음에는 배열에 어떤 기막힌 메서드가 있을까라는 생각으로 접근했다가, string을 replace하는 방법도 있어서, 그 부분으로 접근해봤다가 실패했다. 그리고 나서는 temp 변수를 선언해서 바꿀까하다가, for문으로 해결하는 방법을 사용했다.
function solution(my_string, num1, num2) {
const str1 = my_string[num1]
const str2 = my_string[num2]
let answer = []
for (let i = 0; i < my_string.length; i++) {
if (i === num1) {
answer.push(str2)
} else if (i === num2) {
answer.push(str1)
} else {
answer.push(my_string[i])
}
}
return answer.join('')
}
temp를 사용하는 게 좀 더 간결했을 거라는 생각이 드는데, 아무튼 이렇게만 풀고 넘어가면 안되고 다른 사람들 풀이를 봐야 한다. 오늘은 한 두개만 봤는데, 그 중에 배열 구조분해 할당을 해서 풀이한 부분이 인상적이여서 그 부분을 남겨본다.
function solution(my_string, num1, num2) {
my_string = [...my_string]
;[my_string[num1], my_string[num2]] = [my_string[num2], my_string[num1]]
return my_string.join('')
}
평소 세미콜론을 사용하고 있지 않아서 구조 분해 할당하는 곳 앞에 세미 콜론이 붙어 있지만, 아무튼 이 코드에서 중점적으로 봐야할 부분은 아래 부분이다.
;[my_string[num1], my_string[num2]] = [my_string[num2], my_string[num1]]
이 코드는 my_string 배열의 num1 인덱스와 num2 인덱스의 값을 서로 교환하는 구조분해 할당 패턴을 사용하고 있다.
컴포넌트에서 같은 단계에 있는 여러 요소(태그)는 반드시 하나의 요소로 감싸져 있어야 한다. 그때 사용할 수 있는 것이 Fragment다. <React.Fragment></ React.Fragment> , <></>
import React from 'react'
const MyComponent = () => {
return (
<React.Fragment>
<h1>안녕하세요</h1>
<p>React Fragment를 사용하는 예제입니다.</p>
</React.Fragment>
)
}
export default MyComponent
key를 전달해야 하는 경우가 아니라면 React.Fragment를 굳이 사용할 필요는 없고, 빈 태그로 감싸도 된다.
portal을 사용하기 전
{
error && (
<ErrorModal
title={error.title}
message={error.message}
onConfirm={errorHandler}
/>
)
}
portal을 사용하고 난 후 ErrorModal 컴포넌트가 id가 error-modal-root인 요소 안으로 들어간다.
<div id="error-modal-root"></div>
{error &&
createPortal(
<ErrorModal
title={error.title : ''}
message={error.message : ''}
onConfirm={errorHandler}
/>,
document.getElementById('error-modal-root')
)}
컴포넌트가 특정 정보를 ‘기억’하도록 하고 싶지만 해당 정보가 새 렌더링을 촉발하지 않도록 하려는 경우 ref를 사용할 수 있다.
즉, useState로 상태 관리하지 않고 직접 DOM을 참조하고 싶을 때 사용할 수 있다.
import { useRef } from 'react'
const nameRef = useRef()
<input
id="username"
type="text"
value={enteredUsername}
onChange={usernameChangeHandler}
ref={nameRef}
/>
nameRef.current.focus()
useEffect는 리액트의 생명주기에 따라 실행하고 싶은 코드를 정의한다.
useEffect(() => {
// 부수 효과를 수행하는 코드
// 데이터 가져오기, DOM 조작 등
return () => {
// 클린업(clean-up) 코드
// 컴포넌트가 언마운트되거나 의존성 배열이 변경될 때 실행
}
}, [dependencies])
의존성 배열이 빈 배열이면 처음에 컴포넌트가 마운트 될 때 한 번 실행되고 의존성 배열에 값이 들어 있으면 해당 값이 변경될 때마다 부수 효과를 수행하는 코드가 실행된다.
useLayoutEffect는 컴포넌트가 마운트되기 직전에 실행할 로직들을 관리한다.
useState의 한계
복잡한 상태를 다루려면 여러 개의 snapshot을 관리해야 함
그 과정에서 상태 변화가 꼬일 수도 있음
MVC패턴 vs Flux 패턴
import { useReducer, useState } from 'react'
const Counter = () => {
const [number, setNumber] = useState(0)
// 2. 은행(useReducer)은 고객의 요구사항의 타입에 맞는 서비스를 제공하기 위해 회계 직원(countReducer)를 통해 업무 처리
const countReducer = (oldState, action) => {
if (action.type === 'DOWN') {
// reducer는 순수함수여야 하기 때문에 oldState - number 처럼 number를 reducer 함수 내부에서 변경해서 외부에 영향을 받도록 작성할 수 없다.
return oldState - Number(action.number)
} else if (action.type === 'RESET') {
return (oldState = 0)
} else if (action.type === 'UP') {
return oldState + Number(action.number)
}
}
// 3. countReducer가 반환한 새로운 상태는 count 변수에 의해 나타내어지고, 화면에 렌더링
const [count, countDispatch] = useReducer(countReducer, 0)
// 1. 은행(useReducer)에서 고객(이벤트(down, reset, up))은 창구직원(countDispatch)에게 요구사항을 전달
const down = () => {
countDispatch({ type: 'DOWN', number: number })
}
const reset = () => {
countDispatch({ type: 'RESET' })
}
const up = () => {
countDispatch({ type: 'UP', number: number })
}
const handleNumber = (e) => {
setNumber(e.target.value)
}
return (
<>
<input type="button" value="-" onClick={down} />
<input type="button" value="0" onClick={reset} />
<input type="button" value="+" onClick={up} />
<input type="text" value={number} onChange={handleNumber} />
<span>{count}</span>
</>
)
}
export default Counter
createContext로 context를 생성할 수 있도록 한다.
import { createContext } from 'react'
const themeDefault = { border: '3px solid #d6c596' }
const themeContext = createContext(themeDefault)
export const ThemeContextProvider = (props) => {
return (
<themeContext.Provider value={themeDefault}>
{props.children}
</themeContext.Provider>
)
}
export default themeContext
createContext에 기본값 정의하고 children요소에 쉽게 감쌀 수 있도록 provider(우산)를 아래와 같이 정의하고 export 시켜서 외부에서 import 해서 쉽게 사용한다.
export const ThemeContextProvider = (props) => {
return (
<themeContext.Provider value={themeDefault}>
{props.children}
</themeContext.Provider>
)
}
function Sub1() {
const blueTheme = useContext(blueThemeContext)
return (
// value를 전달하지 않으면 아무리 위에 우산을 씌워놔도 가로막힘.. 현재 우산에 가로막힘
<themeContextProvider>
<div style={blueTheme}>
<h1>Sub1</h1>
<Sub2 />
</div>
</themeContextProvider>
)
}
사용하고 싶은 곳에서 정의한 Provider로 감싸놓으면 그 하위 컴포넌트들을 해당 Context에서 정의한 value들을 사용할 수 있다.
상위 컴포넌트에 있는 ref 요소를 하위 컴포넌트에 전달하려고 할 때 사용한다. 하지만 prop으로도 전달해서 사용이 가능한데, 왜 굳이 forwardRef를 사용하는지 아직은 잘 모르겠음..
import './App.css'
import { useRef } from 'react'
import MyInput from './components/MyInput'
function App() {
const ref = useRef(null)
const handleClick = () => {
ref.current.focus()
}
return (
<div className="App">
<MyInput ref={ref} />
<button onClick={handleClick}>집중</button>
</div>
)
}
export default App
useImperativeHandle은 하위컴포넌트에서 정의한 메서드나 변수를 forwardRef로 상위 컴포넌트로 노출시킬 수 있다. 이것도 아직은 이렇게 사용해야 할 이유를 못 찾고 있음..
import { forwardRef, useImperativeHandle, useRef } from 'react'
const MyInput = (props, ref) => {
const inputRef = useRef(null)
useImperativeHandle(
ref,
() => {
return {
focus() {
inputRef.current.focus()
}
}
},
[]
)
return <input type="text" {...props} ref={inputRef} />
}
export default forwardRef(MyInput)
리액트는 가상 DOM을 이용해서 DOM전체를 다시 렌더링하지 않고 변경된 부분만 감지해서 DOM을 변경시킬 수 있다.
Re-evaluating vs re-rendering
쉽게 이야기해서 useEffect의 의존성 배열에 특정 값을 전달해서 해당 값이 변경되면 다시 실행되는 로직이 있다고 한다면, 그건 Re-evaluating이 되는 것이다. 하지만 그것이 곧 리렌더링이 되는 로직은 아닐 수도 있다. 그렇기 때문에 모든 Re-evaluating이 Re-rendering으로 이어지는 것은 아니다. Re-rendering은 가상DOM이 변경사항을 감지해서 변경된 부분을 다시 렌더링 시키는 부분만을 의미한다.
memo는 컴포넌트를 메모이제이션하여 컴포넌트의 전체 렌더링을 최적화한다.
이것은 컴포넌트 렌더링을 조건에 따라 방지하거나 제어한다. 그러나 전체를 캐시하거나 메모이제이션 하는 것은 아니다.
useCallback은 함수를 메모이제이션하여 렌더링 간에 함수가 새로 생성되는 것을 방지한다. 이것은 함수의 참조가 변경되지 않도록 한다.
useMemo는 값을 메모이제이션하여 계산 비용이 높은 연산을 최적화한다.
HTTP라는 프로토콜을 이용해 프론트 개발자는 데이터베이스에 있는 정보를 직접가지고 오지 않고 서버와 통신을 통해 가져와서 사용할 수 있다. 이 때 클라이언트에서 서버에 요청을 하고 서버가 클라이언트로 응답을 보낸다.
이때 상황에 따라 상태코드를 내려준다.
상태 코드 중에 200번대는 주로 성공과 관련된 부분, 300번대는 리다이렉션 관련, 400번대는 클라이언트 오류, 500번대는 서버 오류와 관련이 있다.
상태 코드 중에 특히 오류와 관련된 부분에 대해서 프론트에서 적절하게 처리를 해줘야한다. 그냥 간단하게 alert으로 처리하기 보다는 오류에 맞는 적절한 UI를 보여줘야 한다.
이처럼 사용자가 불편함을 느끼지 않게 오류를 적절하게 처리하거나 로딩이 길어질 때도 로딩과 관련한 UI를 보여줘야 한다.
그리고 클라이언트에서 요청을 보내고 응답을 받을 때마다 데이터를 동기적으로 처리하게 되면 비효율적이기 때문에 브라우저에서는 Ajax를 이용해서 비동기 요청을 웹 페이지르 새로고칠 필요없이 처리한다. 이렇게 비동기 요청을 처리하는 방식에는 3가지가 있다. 콜백 함수, Promise, async/await
콜백 지옥을 해결하기 위해 Promise가 나왔고 Promise도 체이닝이 길어지면 가독성이 나빠지기 때문에 async/await로 동기적으로 보이는 코드지만 비동기적으로 처리가 가능하게 할 수 있다.