React 오프라인 4강 - 배열(map, filter, slice, concat, spread 연산자)

어쩌다·2022년 7월 4일
0

React 오프라인 4강 - 배열(map, filter, slice, concat, spread 연산자)


불변함수

깊은 복사를 하는 함수

  1. 나무와 해가 있는 그림이 있다고 하자.
  2. 여기에서 해를 달로만 바꿔달라는 요청이 들어왔다.
  3. 달만 바꾸면 될 텐데, 나무도 함께 '다시' 그려지게 된다.
  4. 그림 하나가 함수의 return 값이라고 한다면 해당 리턴값은 JSX 문법을 통해 rendering이 된다.
  5. return값 하나로는 함수가 하나로 묶여있기 때문에 부분 변경이 아니라 re-rendering이 되는 셈이다.
  6. 이렇게 하면 어떨까.
  7. 해와 나무를 따로 그려놓고 하나의 도화지에 합쳐보자.
    1. 이렇게 되면 해를 달로 바꿨을 때 해가 그려진 도화지만 바꾸면 부분 변경이 가능해지는 셈이다.
    2. 그러면 해와 나무에 따로 return값이 각자 있을 것이고 따로 렌더링이 가능해진다.
  8. 이를 compoent화 시키는 것이라고 할 수 있다.
  9. A가 해라고 하고, B가 나무라고 한다면 A(B)가 있다고 할 수 있겠다.
    1. 결국은 A를 렌더링하게 되면 B를 자동으로 렌더링을 해야될지 안 해야될지 결정해야 한다.
    2. 이를 위해서 component화를 시키는 것이다.

component

return (
	<div>
  	<div></div>
    <div>나무</div>
  </div>
);
  1. 이를 각자 나눠서 컴포넌트화 시키고 싶다.
function Wood () {
  return (
  	<div>나무</div>
  );
}
import Wood from './Wood';

function App() {
  return(
  	<div>
    	<div></div>
      <Wood></Wood>
    </div>
  );
}
  1. 이렇게 App() 안에 <Wood />를 import하여 또 다른 return값을 가진 컴포넌트가 생겼다.
  2. App()이 렌더링될 때 <Wood /> 또한 return을 할지 결정할 수 있다.
  3. 결정을 하고 안 하고는 실행시키거나 안 시키거나 하면 되는 것으로 결정된다.
  4. 따라서 부모가 다시 그려지면 자식도 다시 그려야 할지 React가 연산하게 된다.
    1. 연산에 대한 결정은 데이터가 변경이 되었는가로 결정된다.
    2. 부모와 자식 모두 해이고 달로 바꿔달라고 한다면 다시 그리게 되는 연산이 될 것이다.
    3. 수천개의 컴포넌트 안에서도 해를 찾아서 달로 바뀌게 될 것이다.
    4. 이러한 연산을 최적화 시키려면 깊은 복사를 해야 한다.
    5. 깊은 복사를 하면 레퍼런스를 통해 비교하고 끝내버리면 연산이 최적화될 것이다.
  5. A가 해고 B가 나무라고 했을 때, A가 부모가 되고 B가 자식이 된다.
    1. 이는 의존하고 있는 관계라고 할 수 있다.
    2. 상태 데이터를 공유하고 있는 거라면 의존하는 관계라고 할 수 있고, 상태 데이터를 공유하지 않는다고 한다면 의존하지 않는 관계라고 할 수 있다.

의존관계

  1. 댓글 쓰기 컴포넌트와 알림을 띄우는 종 컴포넌트가 있다.
  2. 이 둘은 의존관계라고 할 수 있다.
  3. 왜냐하면 댓글 쓰기에서 댓글 데이터가 하나 들어올 때, 상태가 바뀔 것이고 바뀌는 상태값에 따라서 알림 종이 반응할 것이기 때문이다.
  4. 이렇게 상태 데이터를 공유하고 있기 때문에 이는 의존관계라고 할 수 있다.
  5. 따라서 리액트는 이러한 의존관계의 집합이라고 할 수 있다.
  6. 싱글 페이지에서의 컴포넌트들이 집합되어있고 이러한 집합된 컴포넌트들 중에서 데이터가 의존적이라면 의존관계가 생긴다.
  7. 항상 부모가 변경되면 자식은 반드시 따라오게 되어있다.
  8. 이렇게 연산이 끝나면 re-rendering을 하게 되는데 연산을 최적화하는 방법을 찾아야 한다.
  9. 따라서 깊은 복사를 사용해야 한다.

불변 함수들

전개 연산자

const a = [1, 2, 3];
const b = [...a]; // a라는 배열을 전개한다.
  1. b라는 새로운 공간에 a라는 배열을 전개시켰다.
  2. 간단한 문법인 ...를 통해서 전개한다.
b.push(4); // 배열에 데이터를 추가
console.log(`a의 값은 : ${a}`); // 1, 2, 3
console.log(`b의 값은 : ${b}`); // 1, 2, 3, 4
  1. b의 데이터를 변경했기 때문에 불변이 아니게 되었다.
  2. push() 함수를 통해서 배열에 새로운 데이터를 추가하는 것이다.
  3. 이렇게 되면 b는 [1, 2, 3, 4]가 되어야 할 것이고 a는 [1, 2, 3]이 되어야 깊은 복사를 하게 되는 것이다.
  4. 얕은 복사를 하는 거라면 const b = a;를 통해서 a와 b 모두 [1, 2, 3, 4]가 될 수 있지만, 변수를 달리하여 다른 heap 공간에 '깊은 복사'를 하는 것이다.

응용

const c = [...a, 4]; // 1, 2, 3, 4
console.log(`c의 값은 : ${c}`); // 1, 2, 3, 4

const c = [0, ...a, 4]; // 0, 1, 2, 3, 4
  1. 뒤에도 앞에도 데이터를 추가할 수 있다.
  2. 하지만 전개연산 사이에 데이터를 넣지는 못 한다.

concat 추가

  1. 기존 배열에 추가하는 것이 아닌 새로운 배열에서 데이터를 추가하도록 한다.
const a2 = [1, 2, 3];
const b2 = a2.concat(4);
console.log(`a2의 값은 : ${a}`); // 1, 2, 3
console.log(`b2의 값은 : ${b}`); // 1, 2, 3, 4

// push를 하게 되면?
const b2 = a2.push(4); // 4

// b2라는 변수를 지우면?
a2.concat(4); // 1, 2, 3
a2.push(4); // 4
  1. 결국에는 데이터 변경이 된 a2의 값이 b2라는 배열로 새로 들어가게 된다.
  2. concat() 또한 불변함수이기 때문에 a2.concat(4)을 통해 데이터를 변경하려 했는데, 데이터는 변경이 된 것은 맞지만 이렇게 깊은 복사를 한 데이터를 담을 공간이 없기 때문에 콘솔에서는 그대로 1, 2, 3이 나온다.

filter

const a3 = [1, 2, 3];
a3.filter((e)=>{ // n에 차례대로 배열의 데이터가 들어온다.
  return n !== 1;
});
// a3에 있는 데이터를 꺼내서 filter의 매개변수 안에 있는 함수에다 넣는다.
// boolean을 return한다.
console.log(a3); // [2, 3]
  1. true만 걸러내는 함수이다.
  2. return은 boolean 타입이다.
  3. props에 차례대로 배열의 데이터가 들어오고, 해당 조건에서 true만 걸러내는 함수라고 할 수 있다.
  4. 이렇게 필터링을 하여 보통 삭제할 때 사용한다.

slice와 전개 연산자 응용하기

const a4 = [1, 2, 3];
const b4 = a4.slice(0, 2); // index 번호
console.log(`b4의 값은 : ${b4}`); // [1, 2]

const c4 = [a4.slice(0, 2)]
console.log(`c4의 값은 : ${c4}`); // [[1, 2]]
// 배열 안에 배열이 들어가있음

// 이를 똑같은 답으로 나오게 하려면?
const c4 = [...a4.slice(0, 2), 4, ...a4.slice(2, 3)]
console.log(`c4의 값은 : ${c4}`); // [1, 2, 4, 3]
  1. slice()를 통해서 index 번호를 통해 배열을 자르도록 한다.
  2. 자르는 기준은 index 번호를 통해서 선별한다.
  3. const c4 = [a4.slice(0, 2)]이를 통해 a4를 slice한 배열의 배열을 c4 안에다 넣는다.
  4. 이렇게 중첩된 배열 안에서 데이터를 넣고 싶을 때, 전개 연산자를 사용하면 된다.
  5. 그러면 중첩된 배열은 전개가 될 것이고, 그 사이 안에 넣고 싶은 데이터를 넣어주면 된다.

map

const a5 = [1, 2, 3];
// 이를 반복하는 것은 for문이 가장 간단하다. 하지만 JSX에서는 사용할 수 없다.

a5.forEach((e)=>{ // 매개변수 값이 함수이다.
  console.log(e); // 1, 2, 3
});
// 리턴이 안 되기 때문에 깊은 복사가 안 된다.

const b5 = a5.map((e)=> e); // 데이터를 순환하여 리턴값을 b5에 담을 수 있다.
// return문 생략이 가능하다.
console.log(b5); // 1, 2, 3
  1. 전개 연산자와 다른 점은 : 전개 연산자는 그저 전개를 할 뿐이지만, map()은 배열에 대한 데이터를 가공할 수 있다는 점에서 다르다.

실습

import './App.css';

function App() {
  let list = [1, 2, 3];
  
  return (
  	<div>
    	<div>{list[0]}</div>
    </div>
  );
}

export default App;
  1. 여기에서 list를 forEach로 뿌리게 된다면 return이 되지 않기 때문에 화면이 아무것도 뜰 수 없을 것이다.
  2. 때문에 list를 map()을 통해서 데이터를 뿌리도록 하자.
import './App.css';

function App() {
  let list = [1, 2, 3];
  
  return (
  	<div>
    	<div>{list.map((n) => <h1>{n}</h1>)}</div>
    </div>
  );
}

export default App;
  1. 답은 [1, 2, 3]이 될 것이다.
  2. 하지만 이러한 용도로만 사용한다면 전개 연산자를 사용하면 되겠지만, map()을 사용하는 이유는 가공할 수 있다는 장점이다.
import './App.css';

function App() {
  let list = [1, 2, 3];
  
  return (
  	<div>
    	<div>{list.map((n) => n + 10)}</div>
    </div>
  );
}

export default App;
  1. 이를 App.js에 적용해보자.
import './App.css';

function App() {
  
  let list = [1, 2, 3];
  
  return(
  	<div>
    	<div>{list.map(n => <h1>{n}</h1>)}</div>
    </div>
  );
}

출처

React 오프라인 4강 - 배열(map, filter, slice, concat, spread 연산자) - 메타코딩

profile
혼자 공부하는 공간

0개의 댓글