세번째 강의의 첫 섹션은 이전과 마찬가지로
첫 번째 과제에 대한 피드백이 이어졌습니다.
(해당 게시글은 실제 강의 내용을 그대로 옮겨적은것은 아니며, 주관적인 이해 및 판단으로 요약되어진 글입니다.)
상수화,이름으로 의미 부여하기
컴포넌트 에서 특정 조건을 렌더링 하려할때, 단순히 Idx값이나. 주어진 props 등을 이용하기보단, 그 값이 고정된 값이라면 상수를 사용하는것이 좋습니다.
(idx ===4 라는 조건보다는, idx === AD_INDEX 등..)
상수화 및 그룹화
일부 함수, 또는 리듀서와 같은 비슷한 변수나 함수를 생성하는경우, 그룹화를 통해 관리하는것이 좋습니다. 상수를 관리하는 객체의 경우, 이름또한 객체로 하는것이 좋다고 하셨습니다. 또한, 상수를 만들때는 타입값을 따로 명시적으로 적어주어 중복되는 상황을 막아야합니다.
Commit의 내용부족
커밋의 제목만으로는 커밋의 변경내용을 표현하기 충분하지 않은 경우가 많습니다.
때문에 Commit은 해당 커밋에 대한 설명 또한 가능한 자세히 적어두는 습관이 필요합니다.
컴포넌트의 추상화
컴포넌트는 View만을 담당하는게 맞다고 생각합니다. 때문에 컴포넌트에서는 추상화를 가능한 많이 하는것이 맞다고 생각하였습니다. 예를들어 Dispatch의 사용, 3항 연산의 다수 사용 등으로, 컴포넌트의 본래 역할과 멀어진다면, 추상화를 생각해봐야 한다고 생각이 들었습니다.
++ 추상화는 어렵기도 한 문제입니다. 이러한 부분은 확실한 정답이라 부를만한것도 없다고 생각합니다. 추상화는 많은 고민이 되는것이 맞는것이고, GitHub 등에서 각 년도 star를 가장 많이 받은 BestPractice 등을 따라 해보는것이 많은 도움이 될것이라 말씀하셨습니다.
렌더링이란, 화면에 특정한 요소를 그려내는것을 의미합니다.
기존의 바닐라 JS와 같은경우에는 DOM에 직접 접근하여 수정하는 방법을 채택하였습니다. 하지만 이러한 방식의 경우 DOM에 접근하는것이 많아지면 많아질수록 수정이 복잡해집니다. (명령형 방식)
때문에 이러한 복잡해지는 단점을 핵심UI만 관리하는 방식으로 복잡함을 보완해주고, 렌더링 또한 기존 방식보다 처리해주기 때문에 React와 같은 라이브러리를 사용하는것입니다.
일반적인 DOM의 렌더링 과정인 CRP(Critical Rendering Path)은 아래와 같습니다.
이러한 과정은 DOM 또는 CSSOM이 수정될때마다 매번 반복됩니다. 적게 변하든 많이 변하든, 각각의 변경시마다 이러한 과정은 매번 반복이 되는것입니다.
이러한 단점을 보완하기 위하여, React는 가상돔(Virtual DOM)을 사용하였습니다.
가상돔의 사용이란, 간단히 말하여 기존 DOM의 값을 데이터상으로서 보관하고, 모든 변경 UI과정을 가상 DOM을 통해 먼저 계산합니다. 그리고 계산된 값을 실제 DOM에 넘겨주도록 설계되었습니다.
이러한 가상돔을 사용하기때문에, 기존 CRP를 통해 여러번 실행될 과정을 리액트는 1번만 실행할 수 있는것입니다.
리액트는 이러한 가상돔을 활용하기 위하여, 단순하지만 강제적인 규약이 정해져있습니다.
state가 변경되면, 무조건 리렌더링 한다.
이것이 리액트의 가장 기본적이지만, 핵심적인 명제입니다.
React는 우선 이벤트가 발생된다면 아래와 같은 방식이 시작됩니다.
1. 해당 컴포넌트에 변화가 없는지, 없다면 재사용할것인지 결정합니다.
2. state가 변경되면, 함수 컴포넌트를 호출합니다.
3. 2의 결과를 통해서, 가상돔을 생성합니다
4. 가상돔을 통해 모든 UI 계산값을 실제 DOM에 전달합니다.
이러한 상황에서 React를 다루는 개발자가 할 수 있는 최적화는 몇가지로 단축됩니다. 우선적으로 기존 컴포넌트의 UI를 재사용할 수 있을것인지, 그리고 가상돔의 차이를 최대한 적게 하는것입니다. 이번 강의에서는 기존 컴포넌트의 재사용에 대하여 다루었습니다.
React는 부모 컴포넌트의 state가 변할경우, 자식 컴포넌트 또한 모두 리렌더링 합니다.
하지만 부모 state가 변하더라도, 자식 컴포넌트에서 사용하는 state 및 상태,UI는 변하지 않는 경우도 있을것입니다.
그렇다면, 자식 컴포넌트의 props가 변하지 않았다면 자식 컴포넌트를 그대로 재사용하는것이 효율적일수도 있습니다. 이럴때 사용하는것이 React.memo입니다.
React.memo는 HOC(Higher Order Component)입니다.
HOC이란것은, 컴포넌트를 props로 받아 컴포넌트를 리턴하는 컴포넌트를 의미합니다.
React.memo를 사용한다면, 만약 자식 컴포넌트의 props가 변하지 않는다면, 해당 자식 컴포넌트는 리렌더링 되지 않습니다.
React.memo는 2번째 props로 callback함수를 가지고 있습니다. 만약 함수의 리턴값이 true일 경우 무조건 이전 결과를 재사용하고, false일경우 리렌더링을 수행합니다. 기본값은 false입니다.
const ReturnFalseMemo = React.memo(ChildComponent, () => false);
const ReturnTrueMemo = React.memo(ChildComponent, () => true);
React에서 props의 변화를 감지한다는것은 기존의 개념과는 조금 다른 관점입니다.
설명에 앞서, 자바스크립트는 원시형과 참조형의 타입으로 나뉘어집니다. 원시형의 타입으로는 string,boolean,number,null 등의 값이 있고, 참조형이란 원시형을 제외한 타입. obj가 있습니다. obj에서 파생된 arr등도 참조형입니다.
원시형과 참조형의 가장 큰 타입은. 원시형의 데이터는 불변이라는것입니다.
원시형은 데이터가 할당되는 공간(주소)이 변하지 않습니다. 만약 도중에 값이 변경이 된다고 하더라도, 그것은 새로운 데이터 공간(주소)이 생겨나는것일 뿐입니다.
하지만 참조형의 경우는 다릅니다. 기본적으로 할당된 공간(주소)이 변하지 않습니다. 이는 참조형 타입인 객체를 유연하게 사용할수있게 하는데 도움이 되기도 하지만, 때때로 결과를 예측하게 힘들게 하기도 합니다. 각각의 객체는 다른 공간을 가지고 있기 때문에 값의 예측을 하기 위해선 객체의
property를 모두 비교해줘야 합니다.
다시 React로 돌아와서, 리액트는 state가 변경되면 rerender를 실행시킵니다. 이때 기존의 모든 객체값을 그대로 불변 상태로 둔채, 새로운 객체를 생성합니다. 그리고 이것을 비교합니다.
React는 이러한 비교를 shallow compare(얕은 비교)를 이용하여 비교하고있습니다.
shallow compare란 객체의 모든 deep의 property를 일일히 비교 검사하진 않고, 첫 deep의 property를 비교한다는것을 의미합니다.
때문에, React의 최적화 함수를 사용할때는 자신이 제대로 최적화 함수를 사용하고 있는것인지 알고있어야 합니다.
리액트에서 사용되는 함수 컴포넌트는 기본적으로 함수입니다. 때문에 기본적으로 이전에 사용된 함수값과 새롭게 생성된 함수값은 공유될수 없습니다.
하지만 공유가 필요한 상황이 있을수도 있기 때문에, Reat는 몇가 API를 제공합니다.
useMemo를 사용한다면, 기존의 '값'의 공간을 기억해둡니다. 그리고 그 값을 재사용하게 됩니다.
하지만 이러한 값은 저장한 값이 변경 되는 경우, useMemo의 값 또한 변경이 필요할수 있습니다. 때문에 첫번째 인자로는 저장할 값을 지정하고, 두번째 인자, 의존성 배열에는 해당 값이 변경될경우 다시 useMemo를 통해 값의 공간을 재할당 한다는 의미를 갖고 있습니다.
useMemo의 사용법
useMemo(() => computeExpensiveValue(a, b), [a, b]);
react에서는 리렌더링시, 함수 컴포넌트내에 있는 함수 또 한 모두 재 생성됩니다. 이는 곧 React.memo등의 props 비교를 방해하게 됩니다. 때문에 useCallback을 사용하여 함수 컴포넌트에서 사용되는 함수의 공간값을 저장하여 재사용할 수 있습니다.
기본적으로 useMemo를 활용한 함수입니다.
아래의 두 함수는 같은 의미를 가지고 있습니다.
const memorizedFunction = useMemo(() => () => console.log("Hello World"), []);
const memorizedFunction = useCallback(() => console.log("Hello World"), []);
메모이제이션 기능은 언뜻보면 효율적으로 보일수도 있습니다. props 변화가 없는 함수는 재활용을 한다면 가상돔의 계산도 적어질것이고, 이는 곧 성능의 향상이라고 생각할수도 있기 때문입니다.
하지만 최적화를 위한 과정 또한 리소스를 소모시킨다는것을 간과해서는 안됩니다. 메모이제이션 기능을 수행하기 위해선 우선, 값을 저장하고. 함수를 호출하고. 의존성을 비교한다. 이 3가지 과정을 거쳐야 합니다.
이 과정은 오히려 다루는 데이터가 적을 경우에는 불필요한 함수실행으로 인해, 성능적인 면에서 비효율적일수도 있는 부분입니다.
하지만 다루는 데이터가 많을경우, 예컨데 1만개에 가까운 데이터를 저장하는 컴포넌트를 가정한다면, 이 컴포넌트가 props의 변화가 없이 매 호출시마다 렌더링되는것은 매우 비효율적일것이고, 이러한 상황에서는 메모이제이션 기능이 매우 효율적일것입니다.
즉, 최적화란 만능적인 기능이 아니며, 상황에 따라 신중하게 접근해야 하는 부분입니다.
최적화는 자동으로 되어지지 않습니다. 실제 개발자가 단순히 최적화 함수를 적어주는것으로는 최적화의 의미가 크게 없을 것입니다. 최적화 또한 많은 고민을 통해 결정하여야 되는 작업이기 때문입니다. 하지만 이러한 최적화는 실질적인 결과물을 나타내지는 않습니다. 기업은 체감이 크지 않은 최적화 보다는, 새로운 기능을 구현해내는 것을 선호할것입니다. 때문에 모든 작업물에 대하여 최적화를 해줘야 겠다는 생각은 좋지 않은 생각일수있습니다. 최적화가 필요한 시점은 대량의 데이터를 처리하여야 해서 UX적인 성능 증가가 필요한 시점등에서, 다른 구성원들의 동의를 얻어 작업을 진행하는것이 옳은 행동일것입니다.
useEffect에 감싸여 있는 함수는, 특정 상황에서 실행을 보장받습니다. useEffect는 빈 배열을 넘길경우 첫 실행시에만 작동하며, 아무것도 넘기지 않을경우 매 컴포넌트가 호출될때 실행되게 됩니다.
useEffect의 모습
useEffect(effect, 의존성)
또는, 의존성 배열에 추가적인 변수 및 함수들을 설정한다면, 의존성 배열에 들어있는 값이 변경될때 useEffect내부의 실행이 보장됩니다.
useEffect 함수를 잘 설정하기 위해선 분명, 모든 의존하는 값을 의존성 배열에 명시해야 할것입니다.
하지만, 여기에 더해 가능한 의존성 배열을 적게 하는것 또한 버그가 발생하는 가능성을 줄여줄것입니다.
useState의 set함수는 기본적으로 메모이제이션 되어있습니다. 때문에 의존성 배열에 들어가지 않습니다.
context란 맥락을 전달하는, React에서 제공하는 내장 API입니다.
많은 상황에서, 전역 값을 관리하는 형식으로 사용되기도 하지만, 그것만을 위해 만들어진 기능은 아닙니다.
기본적으로 React는 컴포넌트가 여러개의 자식 컴포넌트를 가지고 있습니다. 이러한 컴포넌트들의 deep가 깊어질수록 필요한 데이터를 넘겨주는데 많은 단계들을 거쳐야 할 수 있습니다. 이러한 맥락을 쉽게 넘겨주기 위해서 contextAPI는 구상되어졌습니다.
contextAPI를 통해 props를 관리하고, 전달하기 까다로울때 context를 통해 바로 값을 넘겨줄수있는것입니다.
contextAPI는 context를 필요로 하는 컴포넌트에서 부분적으로만 사용될수있습니다.
createContext를 사용하여 context 객체를 생성합니다.
const fooContext = createContext(defaultValue)
context에 있는 값을 전달하기 위해서는 Provider 컴포넌트를 이용해야 합니다. 이 값이 지정되어 있지 않으면, context 생성시 설정된 기본값이 제공됩니다.
const state = {foo:"bar"}
<fooContext.Provider = {state}>
<Child>
</fooContext.Provider>
const foo = useContext(fooContext)
리액트를 꽤 공부하였다고 생각하엿는데도, 이번 강의는 새로웠습니다. 개발을 하다보면 기존에 하던 방식의 개발방식에 고착되고, 때문에 다른 방식을 찾게되는 사고방식이 굳게 되는 느낌이 없잖아 있었는데, 이러한 수업을 통해서 기존의 코드에서 조금 더 좋게 사용될 수 있었던 부분을 발견할 수 있게되고, 기억에 잘 남아 있지 않던 개념 부분등, 많은 것을 배울 수 있었습니다.
또한, 강사님의 피드백이나 타인의 코드를 통해서도 얻어가는 점이 있는 느낌이 좋았습니다. loading이라는 변수보다는 is를 붙이거나, if문에서 사용되는 의미가 있는 변수값이면 상수로 표현하는 방법 등, 단순하지만 생각지 못했던 부분을 생각하고 이후에 쉽게 적용할수있을것 같다는 부분도 좋았습니다.