[스터디] 1. JS, Redux, CSS

Joah·2022년 9월 1일
1

Team Study

목록 보기
1/3

JavaScript

⭐️ 불변성을 유지하려면 어떻게 해야하나요?

📌JS에서 제공하는 데이터 타입에는 2가지가 있습니다.

  1. 원시형 데이터(숫자, 문자열, 불린, 심볼, null, undefined)
  2. 참조형 데이터(객체)

📌이 둘의 차이는 다음과 같습니다.

  • 원시형은 변경 불가능한 값, 참조형은 변경 가능한 값입니다.
  • 원시형 데이터를 변수에 할당하면 실제 값이 저장되고 참조형 데이터를 변수에 할당하면 참조 값이 저장됩니다.

📌 원시형 데이터

원시형 데이터는 한번 생성되면 값으로서 변경할 수 없습니다.

원시 값은 변경 불가능한 값이기 때문에 값을 직접 변경할 수 없습니다.

따라서 변수 값을 변경하기 위해 원시 값을 재할당하면 새로운 메모리 공간을 확보하고 재할당한 값을 저장한 후 변수가 참조하던 메모리 공간의 주소를 변경합니다.

이러한 특성을 불변성이라고 합니다.

let age = 50;
let copy = age;

console.log(age === copy)
//true

age = 100; //값 재할당

console.log(age === copy) 
//false
console.log(age, copy)
//100, 50

왜 true가 출력될까?

식별자 age와 식별자 copy는 각각 다른 메모리 공간 즉, 다른 메모리 주소를 가지고 있습니다. 하지만 50과 50을 비교하면 같은 숫자형이며 50이기 때문에 true가 출력됩니다.

왜 재할당 후 age와 copy를 비교했을 때 false가 출력될까?

원시형 데이터는 불변하기 때문에 원래 변수 age가 가지고 있던 50이 저장된 메모리 공간에 새로운 값인 100 값으로 변경할 수 없습니다.

따라서 새로운 메모리 공간에, age라는 식별자와 연결된 새로운 메모리 주소에 100을 저장합니다.

하지만 copy는 여전히 50인 값이 담긴 메모리 공간, 메모리 주소를 가지고 있습니다. 따라서 false가 출력됩니다.

결국

두 변수의 원시 값은 서로 다른 메모리 공간에 저장된 별개의 값이 되어 어느 한쪽에서 재할당을 통해 값을 변경하더라도 서로 간섭할 수 없게 됩니다.

불변성을 갖는 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법은 없습니다.

📌 참조형 데이터

객체는 프로퍼티의 개수가 정해져 있지 않으며, 동적으로 추가되고 삭제할 수 있습니다.

따라서 원시 값처럼 확보해야 할 메모리 공간의 크기를 사전에 정할 수 없습니다.

참조형 데이터는 (이하 객체)는 변경 가능한 값입니다.

객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조 값에 접근할 수 있습니다. 참조 값은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체입니다.

따라서 객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있습니다.

즉, 재할당 없이 프로퍼티를 동적으로 추가할 수도 있고 프로퍼티 값을 갱신할 수도 있으며 프로퍼티 자체를 삭제할 수도 있습니다.

하지만 객체는 여러 개의 식별자가 하나의 객체를 공유합니다. 이것은 구조적인 단점에 따른 부작용이 됩니다.

const joah = { name: 'Eunkyeong Kim' };

const copiedJoah = joah;

console.log(copiedJoah === joah); //true
console.log(copiedJoah.name === joah.name) //true
console.log(copiedJoah.name, joah.name) //Eunkyeong Kim, Eunkyeong Kim

copiedJoah.name = "bumsoo";
//재할당

console.log(copiedJoah === joah); //true
console.log(copiedJoah.name === joah.name) //true
console.log(copiedJoah.name, joah.name) //bumsoo, bumsoo

왜 true가 출력될까요?

변수 joah는 메모리 주소가 할당되어 있습니다. 그 메모리 주소를 따라가면 객체 {name : 'Eunkyeong Kim}이 저장되어 있습니다.

변수 copiedJoah에는 joah가 할당되어 있습니다. 따라서 copiedJoah와 joah는 같은 메모리 주소를 바라보고 있습니다.

copiedJoah 재할당 했을 때 왜 joah의 값도 bumsoo로 바뀌었나요?

객체는 참조형 데이터이므로 원시형과는 다르게 가변합니다. 따라서 copiedJoah의 name 속성의 값을 bumsoo로 바뀌면 하나의 객체를 바라보는 copiedJoah와 joah 둘다 값이 변경되는 겁니다.

이렇게 되면 원본이 수정되는 치명적인 일이 발생하네요 그래서 불변성을 유지하기 위해서는 어떻게 할 수 있죠?

참조형 데이터의 불변성을 부여하기 위해서는 2가지 방법이 있습니다.

⭐️ 깊은 복사와 얕은 복사에 대해 설명해주세요.

📌얕은 복사 shallow copy

얕은 복사는 한 단계까지만 복사하는 것을 말합니다.

얕은 복사는 객체에 중첩되어 있는 객체의 경우 참조 값을 복사합니다.

스프레드 연산자를 사용합니다.

const joah = { name: { first: "Eunkyeong", last: "Kim" } };
const shallowCopiedJoah = {...joah};

console.log(shallowCopiedJoah === joah); //false
console.log(shallowCopiedJoah.name.first === joah.name.first); //true
console.log("재할당 전 원본", joah.name.first); //Eunkyeong
console.log("재할당 전 복사본", shallowCopiedJoah.name.first); //Eunkyeong

shallowCopiedJoah.name.first = "bumsoo";

console.log(shallowCopiedJoah === joah); //false
console.log(shallowCopiedJoah.name.first === joah.name.first); //true
console.log("재할당 후 원본", joah.name.first); //bumsoo
console.log("재할당 후 복사본", shallowCopiedJoah.name.first); //bumsoo

왜 false가 출력되나요?

스프레드 연산자를 사용하여 얕은 복사를 하면 서로 다른 메모리 주소값을 가지게 됩니다. 따라서

console.log(shallowCopiedJoah === joah); //false 결과를 가져오죠! 복사가 되었다는 겁니다.

얕은 복사는 다른 메모리에 저장되어 원본 사본이 있는데 재할당을 shallowCopiedJoah.name에만 했는데 왜 joah.name도 수정된 값 bumsoo가 출력되나요?

joah와 shallowCopiedJoah의 메모리 주소는 다르지만 동일한 참조 값을 갖습니다. 다시 말 해, 원본과 복사본이 모두 동일한 객체를 가리킵니다.

즉, joah에는 참조값인 객체 데이터가 저장된 메모리 주소가 할당되어 있고 같은 주소가shallowCopiedJoah에도 동일하게 할당되어 있습니다.

두개의 식별자가 하나의 객체를 공유하고 있습니다.

따라서 원본 또는 사본 중 어느 한쪽에서 객체를 변경하면 서로 영향을 주고 받습니다.

그럼 중첩된 객체를 복사하고 따로 값을 할당하고 싶을 때는 어떻게 하면 될까요?

이때는 깊은 복사를 활용합니다. 깊은 복사는 변수를 또 생성하고 각각의 객체를 다시 할당하는 번거로움이 있어 보통은 모듈이나 라이브러리(immer)를 사용합니다.

📌깊은 복사 deep copy

const joah = { name: { first: "Eunkyeong", last: "Kim" } };

const _ = require("lodash"); //npm install lodash 필요

const deepCopiedJoah = _.cloneDeep(joah);
//lodash의 cloneDeep메소드 활용

console.log(deepCopiedJoah === joah); //false
console.log(deepCopiedJoah.name.first === joah.name.first); //true
console.log("재할당 전 원본", joah.name.first); //Eunkyeong
console.log("재할당 전 복사본", deepCopiedJoah.name.first); //Eunkyeong

deepCopiedJoah.name.first = "bumsoo";

console.log(deepCopiedJoah === joah); //false
console.log(deepCopiedJoah.name.first === joah.name.first); //false
console.log("재할당 후 원본", joah.name.first); //Eunkyeong
console.log("재할당 후 복사본", deepCopiedJoah.name.first); //bumsoo

깊은 복사는 중첩되어 있는 객체까지 모두 복사해서 원시 값처럼 완전한 복사본을 만듭니다.

즉, joah와 deepCopiedJoah는 완전히 다른 객체가 됩니다. 서로 같은 주소값을 참조하지 않습니다.

따라서 deepCopiedJoah.name.first 에 “bumsoo”를 재할당해도 원본인 joah.name.first의 값은 변하지 않습니다. 마치 원시형 데이터의 불변성과 같죠?


Redux

⭐️ Context API와 Redux를 비교해주세요.

📌 context API

리액트의 일반적인 데이터 흐름은 부모에서 자식으로 props를 넘겨주는 형태입니다.

만약 A라는 최상위 컴포넌트에서 E에게 전달할 데이터가 있다면, E의 부모 컴포넌트인 A<B<C<D를 거쳐야 E에게 A의 데이터가 도달할 수 있습니다.

그럼 A,B,C,D는 해당 데이터가 필요하지도 않은데 전달 받아야 하는 비효율성이 있으며, 데이터 양이 방대하고 복잡한 로직이라면 작은 실수라도 모래에서 바늘 찾기가 될 것입니다.

즉, 전역 상태를 자식 컴포넌트에게 효율적으로 전달하기 위해서 필요한 것이 context API 입니다.

리덕스나 MobX 같은 외부 라이브러리를 사용할 수 있지만 리액트 v.16.3 업데이트 이후 Context API가 많이 개선되었기 때문에 별도의 라이브러리를 사용하지 않아도 전역 상태를 손쉽게 관리할 수 있습니다. 보통은 user의 정보, Theme, Language 상태를 전달하기 위해 사용합니다.

사용법

context 파일을 생성한 후 최상위 폴더의 return문에 import한 context 파일을 컴포넌트로 작성합니다. 이때 모든 자식 컴포넌트를 감쌀 수 있게 작성하면 됩니다.

이후 전역 상태가 필요한 자식 컴포넌트는 useContext Hook을 활용하여 불러오기만 하면 됩니다.

주의할 점

context를 사용하면 컴포넌트의 재사용이 어려워질 수 있기 때문에 꼭 필요할 때 사용하길 권장합니다.

특징

  • context API는 리액트 내장 기능이기 때문에 따로 라이브러리를 설치할 필요가 없습니다.
  • 오직 React에서만 사용 가능합니다.
  • 비교적 사용하기 쉽습니다.

📌 Redux

개념은 context API와 동일합니다. 왜 사용하는지, 어떤 개념인지는 같은 맥락입니다.

Redux는 디자인 패턴인 Flux를 기반으로 생성된 구현체 입니다.

단방향 데이터 흐름을 이용해 예측가능하고 일괄적인 상태 컨테이너의 역할을 제공하는 라이브러리 입니다.

Redux의 구조

  • Action: 상태 변화에 대한 의도를 표현하는 단순한 JS 객체
  • Action Creator: Action 객체를 만드는 함수
  • Dispatcher: Action 객체를 Reducer 함수에 전달하는 역할을 담당
    • 비동기적인 처리가 필요할 경우 Middleware를 활용합니다.
  • Reducer: 이전 state와 Action객체를 바당서 새로운 state를 리턴하는 순수 함수
  • Store: Redux의 전체 state를 관리하는 하나의 객체

Redux의 데이터 흐름을 보면

view에서 event가 발생하면 Action 객체를 생성합니다.

이 객체를 Dispatch로 전달하며 중간에 middleWare가 Reducer에게 전달합니다. Store는 상태를 관리하는 곳인데 이곳에서 state를 변경합니다. state가 변경되면 UI에 반영이 됩니다.

특징

  • React, Vue와 같은 프레임워크 환경에서 사용할 수 있습니다.

📌 차이점

  • 결론적으로 보면 Context API와 Redux는 사용법과 그 구조에 조금 차이가 있을 뿐 전역 상태를 관리한다는 점에서는 유사합니다. 애초에 Redux가 Context API를 기반으로 만들어진 것이기 때문이기도 합니다.
  • 단순 전역 상태 관리만 있어도 된다면 Context API, 디버깅이나 로깅 등의 상태 관리 외의 기능이나 미들웨어가 필요하다면 Redux를 사용하는 것이 좋다고 여겨집니다.
  • Context API는 React를 사용할 때 추가 dependency 없이 사용할 수 있어서 가볍게 사용할 수 있다는 점에서 좋습니다. 하지만 상태를 넘겨줄 때 상태가 여러 개라면 Provider를 중첩해서 내려 줘야 하기 때문에 불편합니다.
  • Redux는 saga, thunk와 같은 미들웨어를 추가적으로 사용할 수 있어 비동기 처리를 따로 Util로 처리할 수 있지만. 추가 설정을 통해 디버깅을 가시적으로 할 수도 있어 효율적입니다. 하지만 미들웨어를 사용하기 위해 관련 개념을 이해해야 하기 때문에 어려운 점이 있습니다.

📌 결론

  • 단순 prop-drilling 을 피하는 것이 목적이라면 Context 를 사용합니다.
  • 적당히 복잡한 컴포넌트가 있거나 외부 라이브러리를 사용하고 싶지 않다면 Context + useReducer 를 사용합니다.
  • 특정 구성 요소만 re-render 시키거나, 사이드이펙트를 줄이기 위해 더 강력한 기능이 필요하다면 Redux + React-Redux 를 사용합니다.

CSS

⭐️ Cascading에 관해서 설명해주세요.

📌 Cascading = 위에서 아래로 흐르는, 폭포, 계단식

웹페이지의 디자인이 웹브라우저의 기본 디자인과 브라우저 사용자의 디자인 그리고 웹페이지 저자의 디자인이 결합될 수 있다는 점에 착안하고 있다고 할 수 있을 것 같습니다.

사실 웹브라우저<사용자<저자 순서대로 개발자가 지정하는 스타일링이 우선하는 것은 맞습니다. 미래에는 사용하는 사용자가 마음대로 스타일링을 할 수 있도록 하는 것이 궁극적인 목표가 될 것 같네요!

웹브라우저, 사용자, 컨텐츠 생산자의 조화를 이루기 위해서는 cascading을 위해서는 규칙이 필요합니다. 이 규칙은 우선순위를 정합니다.

하나의 태그에 여러 스타일링이 겹친다면

  1. !important
  2. 인라인 스타일
  3. id
  4. class
  5. tag

의 순서대로 우선순위를 갖습니다. CSS를 작성할 때 반드시 고려해야 하는 부분이며 무분별한 !important 사용은 지양해야 합니다.

⭐️ CSS 애니메이션과 JS 애니메이션의 차이에 대해 설명해주세요.

📌 CSS 애니메이션

간단하게 처리하는 애니메이션의 경우 CSS로 처리 합니다. transform / translate를 사용합니다.

브라우저 렌더링 과정에서 layout 이나 paint 단계를 거쳐야 할 경우가 생길 수 있기 때문에 성능 개선에 효율적이지 않을 수 있습니다.

특징

  • 외부 라이브러리를 필요로 하지 않습니다.
  • 어떤 요소가 애니메이션을 가져야 한다는 직관적인 표현이 가능 합니다.
  • 미디어쿼리를 사용해서 반응형으로 애니메이션을 구현 할 수 있습니다.

📌 JS 애니메이션

CSS로 처리하기에는 훨씬 복잡하고 무거운 애니메이션 작업들을 효율적이고, 세밀하게 다루기 위해 사용합니다. 또한 외부 라이브러리들로 하여금 성능 좋은 애니메이션을 구현 할 수 있습니다.

  • 애니메이션을 세밀하게 제어해야 하는 경우 JS를 사용 합니다.
  • 크로스 브라우징 측면에서 JS 애니메이션을 사용하는 것이 유리 합니다.(브라우저 호환성이 좋다.)
  • GPU를 통한 하드웨어 가속을 제어할 수 있씁니다. CSS 애니메이션의 경우 특정 속성에 의한 GPU가속이 됨으로서 저사양의 컴퓨팅인 경우에 성능 하락을 발생시킬 수 있으나 이를 막을수 있습니다.

📌 차이점

CSS 애니메이션은 낮은 버전의 브라우저에서는 지원을 하지 않는 경우가 있습니다.(특히, IE)

  1. 크로스 브라우징면에서는 JS 애니메이션을 사용하는 것이 낫습니다.
  2. 실행 로직을 브라우저 자체에서 실행하기 때문에 메모리 소비를 최적화 해줍니다.
  3. JavaScript에서는 css, 동작을 모두 관리해줘야하는 반면, CSS애니메이션은 CSS안에서 다 관리하기 때문에 관리에 용이합니다.
profile
Front-end Developer

0개의 댓글