비구조화 할당이라고도 부르며, 배열이나 객체의 속성을 개별 변수로 쉽게 추출할 수 있게 해주며 코드의 가독성 향상과 변수 초기화 과정을 직관적으로 확인
// 배열 선언
const numbers = [1, 2, 3];
// 구조분해 할당
const [one, two, three] = numbers;
console.log(one);
console.log(two);
const numbers = [1, 2, 3];
const [one, two, three] = numbers;
// 특정 요소 생략 시 빈 자리로 두고 넘기기
const [one, , three] = numbers;
console.log(one); // 1
console.log(two); // 2
console.log(three); // 3
const person = {
name: 'John Doe',
age: 30,
address: {
city: 'New York',
country: 'USA'
}
};
// 객체에서 분해하고 싶은 속성을 지정
const { name, age, address: { city, country } } = person;
// 구조분해 할당 시 새로운 이름을 지정하고 싶은 경우 ':' 사용
const { name: myName, age: myAge } = person;
console.log(name); // John Doe
console.log(age); // 30
console.log(city); // New York
console.log(country); // USA
function introduce({ name, age }) {
console.log(`My name is ${name} and I am ${age} years old.`);
}
const person = { name: 'Jane Doe', age: 28 };
// 함수 호출
introduce(person); // My name is Jane Doe and I am 28 years old.
구조 분해 할당은 코드의 가독성을 올려주는 아주 유용한 방법이다. 특히, 컴포넌트의 props 또는 state를 다룰 때 유용하게 사용된다. 선택적으로 사용하고자하는 데이터의 추출이 가능하기 때문에 가장 많이 사용되는 문법 중 하나이다.
배열이나 객체의 속성을 쉽게 복사하거나 병합하는 경우 사용
const array = [1, 2, 3, 4, 5];
// 스프레드 연산자
...array
const firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
const mergedArray = [...firstArray, ...secondArray];
console.log(mergedArray); // [1, 2, 3, 4, 5, 6]
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];
console.log(copiedArray); // [1, 2, 3]
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
const originalObject = { a: 1, b: 2 };
const extendedObject = { ...originalObject, c: 3 };
console.log(extendedObject); // { a: 1, b: 2, c: 3 }
const copiedObject = { ...originalObject };
console.log(copiedObject); // { a: 1, b: 2 }
스프레드 연산자는 다음과 같은 역할을 하며, React에서 상태를 업데이트 하거나 props에 전달 시 불변성을 유지하며 데이터를 관리할 필요가 있는 경우 효과적으로 사용 될 수 있다.
스프레드 연산자와 같은 '...'기호를 사용하지만 함수의 매개변수에서 사용될 때, 전달받은 인자들을 배열로 묶어주는 역할
// 요소의 갯수와 상관없이 모든 인자를 배열로 처리 가능
function sum(...numbers) {
return numbers.reduce((acc, current) => acc + current, 0);
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4, 5)); // 15
const [first, second, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(others); // [3, 4, 5]
const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a); // 1
console.log(b); // 2
console.log(rest); // { c: 3, d: 4 }
나미저 연산자는 함수 매개변ㅅ 처리, 배열 및 객체 작업에서 매우 유용하게 사용되며 특히, 가변적인 인자를 처리하거나 특정 요소를 제외한 나머지 요소를 다룰 때 코드의 유연성을 높여줄 수 있다.
비동기 프로그래밍을 위한 문법으로
Promise
를 더 쉽고 직관적으로 사용할 수 있게 해주며, 코드의 가독성과 유지보수성이 크게 향상
// Promise 사용 시
function fetchData() {
return new Promise(resolve => {
setTimeout(() => resolve("Data loaded"), 2000);
});
}
// async/await 사용 시
// 일반함수
async function fetchData () {
const response = await api();
return response.data;
}
// 화살표 함수
const fetchData = async () => {
const response = await api();
return response.data;
};
// 예외 처리 필요 시
const fetchData = async () => {
try {
const response = await api();
return response.data;
} catch (error) {
throw new Error('something wrong...');
}
};
// 비동기 함수 선언
const getProductList = async () => {
const response = await axios.get('url');
return response.data.productList;
};
const productList = getProductList();
asycn/await은 다음과 같은 장점을 가진다.
배열의 각 요소에 대해 주어진 함수를 수행하며, 반환 값이 없다.
const array = [1, 2, 3, 4, 5];
array.forEach(item => console.log(item));
// 출력 : 1 2 3 4 5
배열의 각 요소에 대해 주어진 함수를 실행하고, 그 결과로 새 배열을 생성한다. 주의할 점은 map()을 실행한 배열과 반드시 동일한 길이의 배열이 생성된다.
const array = [1, 2, 3, 4, 5];
const squared = array.map(item => item * item);
console.log(squared);
// 출력 : [1, 4, 9, 16, 25]
map()의 경우 사용할 때 주의해야 할 부분은 위에서도 언급했지만 함수를 호출하는 배열과 반드시 동일한 길이의 배열을 생성한다. 그렇기 때문에 특정 조건을 가진 배열의 갯수만 반환해야한다면 다음에 배울 filter()를 사용하면 된다.
배열의 각 요소에 대해 주어진 함수를 실행하고, 그 함수의 조건이 참일 경우 반환하는 모든 요소로 새 배열을 생성한다.
const array = [1, 2, 3, 4, 5];
const even = array.filter(item => item % 2 === 0);
console.log(even);
// 출력: [2, 4]
filter()는 조건에서 참인 경우에만 반환되는 배열을 생성하기 때문에 호출한 배열과 길이가 다를 수 있다는 점이 특징이다.
배열의 각 요소에 대해 주어진
리듀서(reducer)
함수를 실행하고, 하나의 결과값을 반환한다. 배열을 축약할 때 유용
const array = [1, 2, 3, 4, 5];
const sum = array.reduce((accumulator, current) => accumulator + current, 0);
console.log(sum);
// 출력: 15
reduce()의 경우 첫번째 인자를 함수를 전달하고, 두번째 인자로는 초기값을 설정한다. 다음 예시를 통해 자세히 알아보자.
예시는 각 객체를 요소로 가지는 배열에서 name(key)과 example(value)만을 보유한 객체를 반환하는 예시이다.
// 배열 생성
const exampleArray = [
{name: 'test1', example: 'example1', age: 20, desc: '...'},
{name: 'test2', example: 'example2', age: 21, desc: '...'},
{name: 'test3', example: 'example3', age: 22, desc: '...'},
{name: 'test4', example: 'example4', age: 23, desc: '...'},
];
// reduce 사용
// prevObject : 이전 요소
// currentObject : 현재 요소
const nameAndExampleObject = exampleArray.reduce((prevObject, currentObject) => {
prevObject = {
...prevObject, // 스프레드 연산자 활용(객체에 누적된 데이터)
currentObject[name]: currentObject.example
};
return prevObject
}, {}); // 두번째 인자로 빈 배열 부여
console.log(nameAndExampleObject);
// 결과 : {test1: example1, test2: example2, test3: example3, test4: example4 }
배열 내에서 주어진 팔변 함수를 만족하는 첫 번째 요소의 값을 반환한다. 해당하는 요소가 없는 경우
undifined
를 반환한다.
const array = [1, 2, 3, 4, 5];
const found = array.find(item => item > 3);
const wrongFound = array.find(item => item === 8);
console.log(found);
console.log(wrongFound);
// 출력 : 4
// 출력 : undifined
find()를 사용할 때 주의할 점은 찾고자하는 조건의 요소가 복수개일 경우 가장 첫 번째 요소가 반환된다는 것을 인지해야하며, 데이터가 없는 경우 undifined
가 반환되기 때문에 조건문을 통한 분기처리 등에 주의깊게 사용해야한다.
배열의 적어도 하나의 요소가 주어진 판별 함수를 통화하는지 테스트하고 결과를 불린값을 반환한다.
const array = [1, 2, 3, 4, 5];
// 짝수가 있는지 확인
const hasEvenNumber = array.some(item => item % 2 === 0);
console.log(hasEvenNumber);
// 출력: true
some과는 반대로 배열의 모든 요소가 주어진 판별 함수를 통과하는지 테스트하여 결과를 반환한다.
const array = [2, 4, 6, 8, 10];
// 모든 요소가 짝수인지 확인
const allEven = array.every(item => item % 2 === 0);
console.log(allEven);
// 출력: true
부모 컴포넌트로부터 자식 컴포넌트로 데이터를 전달하는 경우 사용하며, 읽기 전용이다. 함수의 매개변수와 유사한 개념으로서 다양한 인스턴스에서 재사용 가능한 컴포넌트 생성이 가능하다.
// props 객체 자체 전달
function SomeComponent (props) {...}
const SomeComponent = (props) => {...};
// 비구조화 할당 전달
function SomeComponent ({text, color}) {...}
const SomeComponent = ({text, color}) => {...};
// typescript 사용
interface SomeComponentProps {
text: string;
color: string;
size: number;
}
const SomeComponent = ({
text,
color,
size
}: SomeComponentProps):JSX.Element => { // 중요
...
return (...);
};
타입스크립트를 함께 사용하는 경우에는 //중요
라는 표시가 되어있는 라인처럼 Props
의 타입과 반환 값을 정확히 명시해주어야 컴포넌트에 대한 이해가 명확해지므로 반드시 정확한 타입을 매핑하고, 반환 값을 명시해주자.
// props 인터페이스 정의(typescript 한정)
interface IButtonProps {
text: string; // 버튼에 표시할 텍스트
onClick: () => void; // 버튼의 이벤트 핸들러 함수
}
// 버튼 컴포넌트 정의
const CommonButton = ({text, onClick}:IButtonProps): JSX.Element => {
return <button onClick={onClick}>{text}</button>
}
// 호출
const SomeComponent = (): JSX.Element => {
const handleClick = () => {
console.log('버튼 클릭');
};
return (
<div>
...
<CommonButton
text='버튼1'
onClick={handleClick}
/>
</div>
);
}
props는 반복되어 생성되는 컴포넌트 혹은 초기 값을 부모 컴포넌트로부터 전달받아야 하는 경우 사용한다. 여기서 주의할 점은 props가 변경되면 컴포넌트는 리-렌더링 되기 때문에 props의 필요성을 잘 판단해서 사용해야한다.
컴포넌트의 상태를 관리하는 데이터로 컴포넌트 내외부에 선언될 수 있으며 컴포넌트의 생명주기 동안 변경이 가능하다. 또한, State가 변경 시 컴포넌트는 리-렌더링된다.
const [값, 세터] = useState(초기값);
// 지역 State : 컴포넌트 내부
const [count, setCount] = useState(0);
// 전역 State : Context API, 라이브러리 등 활용
const [count, setCount] = useRecoilState(0);
// typescript 사용
const [count, setCount] = useState<number>(0);
setCount('2'); // type Error
// 버튼 클릭 시 수량 증가 기능 구현
const CountComponent = ():JSX.Element => {
// 지역 상태 선언 : 변경 시 마다 컴포넌트 리-렌더링
const [count, setCount] = useState<number>(0);
const handleCount = () => {
// 연산이 필요한 state의 경우 아래와 같이 비동기적으로 업데이트 수행
setCount(prev => prev + 1);
};
return (
<div>
<span>{count}</span>
<button onClick={handleCount}>증가</button>
</div>
);
};
위 예시에서 count가 변경되면 CountComponent는 리-렌더링이 발생한다. 아주 단순한 예시이기 때문에 state를 하나만 사용했지만 복합적인 컴포넌트에서 여러개의 state를 다룰 때는 리-렌더링이 불필요하게 일어날 수 있기 때문에 가급적이면 꼭 필요한 위치에서만 state를 선언해야한다.
만약, count가 CountComponent를 호출하는 상위 또는 차상위 컴포넌트에 있을 경우 count의 변경으로 인해 그와 무관한 다른 자식 컴포넌트들까지 리-렌더링이 발생할 수 있다. 이런 부분을 잘 고려해서 사용할 수 있도록 하자.
컴포넌트가 렌더링 될 때마다 특정 작업을 수행할 수 있게 하며, 첫 렌더링과 업데이트 시마다 호출되는 내장 훅 함수이다. 또한, 특정 데이터를 의존성 배열에 추가하여 해당 데이터 변경시에도 호출이 가능하다.
// 생김새
useEffect(함수, 의존성배열);
// 최초 렌더링 시 한번만 수행 : 빈 배열
useEffect(() => {...}, []);
// 특정 데이터 변경 시 수행 : props, state 등
const [disabled, setDisabled] = useState<boolean>(false);
useEffect(() => {setDisabled(!disabled)}, [disabled]);
// 복수 데이터 변경 시 수행
useEffect(() => {
if(데이터1) {
console.log(데이터1);
}
....
}, [데이터1, 데이터2, 데이터3]);
// 클린 업 함수 수행 : 컴포넌트 언마운트(제거) 시
useEffect(() => {
// return 값에 실행하고자하는 작업을 반드시 함수형으로 선언
return () => {
console.log('컴포넌트 언마운트 시 수행');
}
}, []);
const SomeComponent = ():JSX.Element => {
// 초기 값 미부여
const [text, setText] = useState<string>('');
// 최초 렌더링 시 데이터 변경
useEffect(() => {
setText('useEffect 실행');
}, []);
return (
<div>{text}</div>
);
};
useEffect는 리액트의 클래스형 컴포넌트에서 지원하던 생명주기 함수와 유사한 기능을 하는 내장 훅 함수라고 인식하면 된다. 컴포넌트가 최초 마운트 되거나 언마운트시에 수행할 동작을 정의해주고, 특정 데이터의 변경에 따라 자동으로 실행되도록 의존성 배열을 추가하면되니 굉장히 도움이 많이되고 유용한 내장 훅 함수이다. 하지만 무분별하게 사용할 경우 데이터 변경에 따른 동작을 유추하기가 어려워지고, 의도하지 않은대로 동작하여 사이드 이펙트가 발생할 가능성이 높아지기 때문에 꼭 필요한지 여부를 여러번 검토하고 적용해야할 필요성이 있다.