diff --git "a/01\355\232\214/.gitkeep" "b/01\355\232\214/.gitkeep" deleted file mode 100644 index e69de29..0000000 diff --git "a/02\355\232\214/.gitkeep" "b/02\355\232\214/.gitkeep" deleted file mode 100644 index e69de29..0000000 diff --git "a/03\355\232\214/.gitkeep" "b/03\355\232\214/.gitkeep" deleted file mode 100644 index e69de29..0000000 diff --git "a/03\355\232\214/\354\235\264\353\217\204\355\230\204/README.md" "b/03\355\232\214/\354\235\264\353\217\204\355\230\204/README.md" new file mode 100644 index 0000000..5aaaaae --- /dev/null +++ "b/03\355\232\214/\354\235\264\353\217\204\355\230\204/README.md" @@ -0,0 +1,12 @@ +[3화] UI표현하기 ~ 상호작용 더하기 전반부 + +# 조건부 렌더링 + +# 리스트 렌더링 + +1. key가 왜 중요할까 + +# 컴포넌트 순수성 유지 + +1. mutation(변이) + diff --git "a/04\355\232\214/\354\235\264\353\217\204\355\230\204/Readme.md" "b/04\355\232\214/\354\235\264\353\217\204\355\230\204/Readme.md" new file mode 100644 index 0000000..e25bf72 --- /dev/null +++ "b/04\355\232\214/\354\235\264\353\217\204\355\230\204/Readme.md" @@ -0,0 +1,469 @@ +[4화] Adding Interactivity (상호작용 추가하기) + +## 랜더링하고 커밋하기 +- step 1 . 촉발!! + - 렌더링이 일어나는데는 두가지 이유가 있다. + 1. 컴포넌트의 초기 렌더링인 경우 + - 대상 DOM 노드로 createRood를 호출 후 render메서드를 호출한다 + + 2. 컴포넌트의 state (또는 상위요소중의 하나)가 업데이트된 경우 + - state를 업데이트하면 set함수로 업데이트하여 추가렌더링을 일으킨다.(업데이트할때마다!!) + + 추가. 부모에서 자식한테 prop이 없을때는? + - **렌더링은 되지만** 바뀌는 데이터가 없으니 그대로 있는다 + +- step 2. 컴포넌트 렌더링 + - 촉발 후 React는 **컴포넌트를 호출**하여 화면에 표시할 내용을 파악 + - 이 과정은 재귀적으로 일어나는데 + 1. 업데이트된 컴포넌트가 다른 컴포넌트를 반한 + 2. React는 다른 컴포넌트 렌더링 + - 예제는 간단해서.. 패스 + - 첫 렌더링을 하는동안 각 태그에 대한 DOM노드(html요소)를 생성 +### 함정 +- 렌더링은 항상 순수한 계산이어야 합니다. + - 동일한 입력에는 동일한 출력을 나와야 한다. + - 이전의 state를 변경해서는 안됩니다. 렌더링 전에 존재했던 객체나 변수를 변경해서는 안됩니다. + - (이 경우는 코드의 복잡해짐을 겪는 경우가 너무 많았어서 꼭 지켜야 될거 같아요) +### 성능 최적화 (state 관리 챕터에서 깊게 다룰 예정) +- npm build를 통해 성능 최적화를 할수 있지만 성급하게 해서는 안된다 (다 따져보고 해야하는데 다음챕터에서 언급) + +- step 3. React가 DOM에 변경사항을 커밋 + ```js + export default function Clock({ time }) { + return ( + <> +

{time}

+ + + ); + } + ``` +타임의 초가 변경될때마다 렌더링이 되는 모습을 확인할수 있다. +이 경우 컴포넌트를 각자 사용하면 될거 같긴 하지만 더 좋은 경우가 있을지 확인해보자 + +## 스냅샷으로서의 state +- 스냅샷 (한번찍은 정보에 대해 쭉 사용한다 (변경하기 전까지)) +1. state를 설정하면 렌더리이 실행됨 + +- 인터페이스가 이벤트(불린 값에 의하던지 일정 숫자,클릭)에 반응하려면 state를 업데이트해야 합니다. + - 렌더링은 함수를 호출한다는 뜻인데 모든 변수(prop,이벤트,로컬변수)는 렌더링 당시의 state를 사용합니다. + ```js + import { useState } from 'react'; + + export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> +

{number}

+ + + )} + //3개의 큐가 생성이 되지만 각각 set값에 들어갈때는 렌더링 초기의 값 (0)에 들어가므로 결과적으로 1이 출력된다. + //strict모드에서는 2번 , 없이는 1번 렌더링 되는것을 확인할수 있다. + ``` + - 하지만 시점을 미뤄도 그대로 number의 값이 0으로 표시가 될까? + - 아니다! 지정된 state는 버튼을 클릭했을때 변경이 되었을수 있지만 보여지는 변수의 값은 예약되어있는 값을 출력하기 때문이다. + - state변수의 값은 이벤트핸들러의 코드가 비동기적이라도 렌더링내에서 절대 변경되지 않습니다!! + - 렌더링 내에서 변수를 변경하고 싶다면 다음 챕터인 state업데이트를 공부하자! + - 이벤트 헨들러의 state관계는 렌더링된 jsx와의 관계와 동일하다고 볼수 있다.(위의 jsx문 참고) + - 렌더링시점에서의 state변수와 jsx문 안에서의 state변수와 같다는 말 + +- 문제 1 + - alert의 위치가 크게 의미가 없는 이유는 snapshot의 영향인데 바뀐 state변수의 값은 예약어 일뿐 현재 상태에서는 어떠한 영향도 끼치지 않기 때문이다. + + +## 여러 state 업데이트를 큐에 담기 +- state변수를 여러번 업데이트 하고 싶다면 전 챕터의 "number+1"이 아닌 아래의 업데이트 함수를 이용하자 + - 아래의 방법은 number의 값을 예약하며 개별적으로 처리되는게 아닌 일괄적으로 데이터를 저장하여 처리 + - 따라서 업데이트 함수는 "다음 state값"을 전달하는 것이 아닌 "state값을 계산하는 함수"를 "지시"하는 방법입니다. + - 전달하는 것은 다음 전달값이 초기화 되며 값을 출력하는 대신 + - 업데이트 함수는 큐를 순회하여 업데이트되는 state값을 제공하는것이 다릅니다. + +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> +

{number}

+ + + ) +} +// +``` +```js +import { useState } from 'react'; + +export default function Counter() { + const [number, setNumber] = useState(0); + + return ( + <> +

{number}

+ + + ) +} +//위와 같은 방법은 number에 저장된 state값을 함수에 전달하여 6이 나오는것을 확인할수 잇는데 +// "number+5"와 "n=>n+1"의 순서를 바꾼다면 함수로 지시한 값은 초기화 되고 "1"만 출력되는 것을 확인할수 있습니다. +//이는 jsx문에서 위에서 아래로 순회한다는 것을 보여준다. +``` +#### 문제 2 +- for of 문과 forEach문을 사용할수 있는데 배열안의 값을 나열하여 타입비교를 통해 배치시킬수 있다. + - 갠적으로 for문보다는 forEach문이 더 간결해 보여 좋을거 같습니다. 복사보다는 그대로 출력시키는게 좋아보입니다. + +## 객체 state 업데이트 +- state는 객체를 포함해서 어떠한 js값이든 저장할수 있는데! + - state에 있는 객체를 직접 변이(mutation)해서는 안됩니다. + - 대신 객체를 업데이트하려면 새 객체를 생성또는 복사를 통해 state를 업데이트 해야합니다.**중요** +- js객체의 값에는 number,boolean,string,symbol,null,undefined의 기본값이 있는데 이러한 종류의 값은 읽기전용의 값입니다. + - 하지만 렌더링을 촉발하여? 값을 바꿀수 있습니다(업데이트? 복사?) + +```jsx +const [x, setX) = useState(0); + +setX(5); +//위와 같이 선언된 x의 값이 0에서 5로 변경은 되었지만 0(디폴트여서 인지? 예약어라서?)자체는 변경되지 않습니다. + +const [position,setposition] = useState({x:0,y:0}); + +position.x = 5; // 이건 변이! 왜냐 초기값을 변경하기 때문에 //순수성을 지켜야 한다. +``` +```js +import { useState } from 'react'; +export default function MovingDot() { + const [position, setPosition] = useState({ + x: 0, + y: 0 + }); + return ( +
{ + position.x = e.clientX; + position.y = e.clientY; + }} + style={{ + position: 'relative', + width: '100vw', + height: '100vh', + }}> +
+
+ ); +} +//위의 jsx문은 원이 커서를 따라다니는 문인데 position의 값이 계속 초기화가 되며 초기값을 표출하기 때문에 원이 고정되어 있는 것처럼 보인다. +//또 state함수에 할당되지 않는다면 react는 객체가 변이되었다는 사실을 모르기 때문이다. +//리렌더링을 촉발하려면 새 객체를 생성하거나 복사하여 state함수에 전달하자 + +// onPointerMove={e => { +// setPosition({ +// x: e.clientX, +// y: e.clientY +// }); +// }} +// 위와 같은 방법으로 말이다!! +//이 경우는 기존 객체의 state를 수정하는게 아닌 지역적으로 객체를 생성하여 변이한것이기 때문에 괜찮습니다!이것을 지역변이라고 합니다 +``` +### 또 다른 방법으로는 전개구문을 사용하여 객체를 복사하는 방법이 있습니다. +```js +import { useState } from 'react'; + +export default function Form() { + const [person, setPerson] = useState({ + firstName: 'Barbara', + lastName: 'Hepworth', + email: 'bhepworth@sculpture.com' + }); + + function handleFirstNameChange(e) { + person.firstName = e.target.value; + } + + function handleLastNameChange(e) { + person.lastName = e.target.value; + } + + function handleEmailChange(e) { + person.email = e.target.value; + } + + return ( + <> + + + +

+ {person.firstName}{' '} + {person.lastName}{' '} + ({person.email}) +

+ + ); +} +// 위의 문 역시 기존의 State값을 변경하여 react가 인식하지 못하고 있습니다. + +setPerson({ + ...person, + firstName: e.target.value +}); +//하지만 전개구문을 사용하여 복사하고 firstname객체만 덮어씌운다면 react가 인식할것입니다. +//주의할 점은 전개구문은 얕은 복사라는 점!! 한단계 깊은 복사를 하고 싶다면 두번 이상 사용해야 합니다. +``` +- 더 간단하게 하려면 중괄호를 사용하여 동적이름을 가진 프로퍼티를 지정할수 있다. +```js + function handleChange(e) { + setPerson({ + ...person, + [e.target.name]: e.target.value + }); + } +// 예시 +// +``` +### 중복된 객체 업데이트 하기 +위와 같은 방법으로 변경점이 있는 객체를 단일 이벤트 헨들러를 사용하면 더 간단히 적용시킬수 있다. +- 여기서 e.targer.name은 input tag의 name속성을 가리킨다. + +더 간단히 해보자면 immer라이브러리가 있다. +- immer는 proxy라는 개체로 사용자가 수행하는 작업을 기록합니다. 그래서 뭐가 됐든 draft를 사용하여 편집내용이 포함된 모든 객체를 새로운객체로 생성된다. +```js +export default function Form() { + const [person, updatePerson] = useImmer({ + name: 'Niki de Saint Phalle', + artwork: { + title: 'Blue Nana', + city: 'Hamburg', + image: 'https://i.imgur.com/Sd1AgUOm.jpg', + } + }); + + function handleNameChange(e) { + updatePerson(draft => { + draft.name = e.target.value; + }); + } + function handleImageChange(e) { + updatePerson(draft => { + draft.artwork.image = e.target.value; + }); + } + return ( + <> + + {person.artwork.title} + + )}; +//이러한 방법으로 깊이에 상관없이 image에 해당되는 값을 변경한다. +//immer는 redux에 사용된다고 하는데 사용되는 코드의 양이 방대해져서 그런게 아닐까 라는 생각이 드는데... 그 외에는... 언제 사용이 되는지 잘 모르겠다 +``` +[문제1](https://codesandbox.io/s/little-fast-tvkygl?file=/App.js) +[문제2](https://codesandbox.io/s/broken-dust-wslqtv?file=/App.js) +[문제3](https://codesandbox.io/s/hardcore-mahavira-47k3td?file=/App.js) + + + +## 배열 state 업데이트 +- 배열연산은 배열을 직접 변이하는 메서드 보다는 기존배열 복사후 생성하는 메서드를 사용하자 + 1. 추가: concat [...arr] + 2. 삭제: filter, slice (배열또는 배열의 일부를 복사) + 3. 교체: map + 4. 정렬: 배열 복사후 다음 처리 + +- 배열 변경하기 + - map 함수를 이용하여 새로운 배열을 만듭니다. ar[0] = 'bird' 와 같은 할당은 원래 배열을 변이하는 것으로 + 이 경우에 map함수를 통하여 변경하도록 하자 + +문제1 +```js +import { useState } from 'react'; + +const initialProducts = [{ + id: 0, + name: 'Baklava', + count: 1, +}, { + id: 1, + name: 'Cheese', + count: 5, +}, { + id: 2, + name: 'Spaghetti', + count: 2, +}]; + +export default function ShoppingCart() { + const [ + products, + setProducts + ] = useState(initialProducts) + + function handleIncreaseClick(productId) { + setProducts(products.map(product => { + if (product.id === productId) { + return { + ...product, + count: product.count + 1 + }; + } else { + return product; + } + })) + } + //맵함수를 통해 배열을 재생성하고 id값을 비교 하고 맞으면 +1 클릴할때마다 전체 렌더링 + + return ( + + ); +} + +``` + +문제2 +```js + +import { useState } from 'react'; + +const initialProducts = [{ + id: 0, + name: 'Baklava', + count: 1, +}, { + id: 1, + name: 'Cheese', + count: 5, +}, { + id: 2, + name: 'Spaghetti', + count: 2, +}]; + +export default function ShoppingCart() { + const [ + products, + setProducts + ] = useState(initialProducts) + + function handleIncreaseClick(productId) { + setProducts(products.map(product => { + if (product.id === productId) { + return { + ...product, + count: product.count + 1 + }; + } else { + return product; + } + })) + } + function handleDecreaseClick(productId) { + let nextProducts = products.map(product => { + if (product.id === productId) { + return { + ...product, + count: product.count - 1 + }; + } else { + return product; + } + }); + nextProducts = nextProducts.filter(p => + p.count > 0 + ); + setProducts(nextProducts) + } + + return ( + + ); +} +//필터를 사용하여 0이상인 count값만 렌더링한다. +``` \ No newline at end of file diff --git "a/07\355\232\214/\354\235\264\353\217\204\355\230\204/README.md" "b/07\355\232\214/\354\235\264\353\217\204\355\230\204/README.md" new file mode 100644 index 0000000..e3f68bd --- /dev/null +++ "b/07\355\232\214/\354\235\264\353\217\204\355\230\204/README.md" @@ -0,0 +1,276 @@ +[7화] UI표현하기 ~ 상호작용 더하기 전반부 + +### 이펙트가 필요하지 않을수 있습니다. + +#### ✅ 불필요한 Effect 처리 +1. 렌더링을 위해 데이터를 변환하는 경우 + - popst나 useState에 따라 바뀌는 state에서 계산할수 있는 값은 state에 넣지 말고 + - 대신 랜더링 중에 계산하세요 +- 간단한 계산 +- +```js +function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 이러지 마세요: 중복 state 및 불필요한 Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(firstName + ' ' + lastName); + }, [firstName, lastName]); + // ... + //대신 + // ✅ 좋습니다: 렌더링 과정 중에 계산 + const fullName = firstName + ' ' + lastName; +} +``` +- 복잡한 계산? +```js +function TodoList({ todos, filter }) { + const [newTodo, setNewTodo] = useState(''); + // ✅ getFilteredTodos()가 느리지 않다면 괜찮습니다. + const visibleTodos = getFilteredTodos(todos, filter); +} +// 하지만 이 getfilteredTodes함수가 느리거나 갖고있는 값이 많을경우 캐싱화를 생각해보자 +``` +```js +import { useMemo, useState } from 'react'; + +function TodoList({ todos, filter }) { + const [newTodo, setNewTodo] = useState(''); + const visibleTodos = useMemo(() => { + // ✅ todos나 filter가 변하지 않는 한 재실행되지 않음 + // memo는 다음렌더링때도 값을 가지고 있기 때문에(캐시에 저장) + // 의존성에 속한 값이 변할경우에만 실행이 됩니다! 그외는 기존에 저장된 데이터를 활용해요 + return getFilteredTodos(todos, filter); + }, [todos, filter]); + // Or 한줄로 표현하면 + const visibleTodos = useMemo(() => + getFilteredTodos(todos, filter), [todos, filter]); +} +``` +- 하지만 이 방법은 전체시간이 1ms이상이라면 memo를 사용하는 것을 고려해봐요 +- 왜냐면 memo는 첫렌더링시 기록단축에 도움이 되지 않고 재렌더링 시에만 도움이 되기 때문이에요 +- +#### ✅ prop이 변경되면 모든 state재설정 하기 +```js +export default function ProfilePage({ userId }) { + const [comment, setComment] = useState(''); + + // 🔴 이러지 마세요: prop 변경시 Effect에서 state 재설정 수행 + useEffect(() => { + setComment(''); + }, [userId]); + // ... +} +// 이것은 ProfilePage와 그 자식들이 먼저 오래된 값으로 렌더링한 다음 새로운 값으로 다시 렌더링하기 때문에 비효율적입니다. +//또 state가 있는 모든 컴포넌트에서 이 작업을 수행해야 하므로 복잡하죠! + +``` +- 그 대신 key를 전달하여 정확한 자식 컴포넌트에게 전달할수 있습니다. +```js +export default function ProfilePage({ userId }) { + return ( + + ); +} + +function Profile({ userId }) { + // ✅ This and any other state below will reset on key change automatically + // ✅ key가 변하면 이 컴포넌트 및 모든 자식 컴포넌트의 state가 자동으로 재설정됨 + const [comment, setComment] = useState(''); + // ... +} +``` +#### ✅ props가 변경될때 일부 state 조정하기 +- 하지만 이러한 경우보다는 렌더링 과정에서 계산을 하는것이 좋습니다. +- 굳이 한다면 key로 모든 state를 재설정하거나 렌더링중 모두 계산할수 있는지 고려해보자 +- 그렇지 않고 써야 한다면 무한루프를 어떻게 해결할건지(조건식으로던지?) 고려하고 써야한다. +- 일부 or 전체의 state및 props 조정은 할수 있겠지만 컴포넌트의 순수성을 유지하는것이 좋으므로 지양하는것이 좋은데ㅎㅎ;; 고려만이라도 해보자 + +#### 이벤트 핸들러간 로직 공유 +- Effect안에 특정 이벤트 로직 존재 +- 이런경우 페이지를 새로고침할때마다 effect가 실행되기 때문에 Effect내부가 아닌 +컴포넌트를 따로 빼서 사용자에게 필요한 경우에만 사용되도록 사용해야 합니다. +```js +function ProductPage({ product, addToCart }) { + // 🔴 이러지 마세요: Effect 내부에 특정 이벤트에 대한 로직 존재 + useEffect(() => { + if (product.isInCart) { + showNotification(`Added ${product.name} to the shopping cart!`); + } + }, [product]); + + function handleBuyClick() { + addToCart(product); + } + + function handleCheckoutClick() { + addToCart(product); + navigateTo('/checkout'); + } + // ... +} +function ProductPage({ product, addToCart }) { + // ✅ 좋습니다: 이벤트 핸들러 안에서 각 이벤트별 로직 호출 + function buyProduct() { + addToCart(product); + showNotification(`Added ${product.name} to the shopping cart!`); + } + + function handleBuyClick() { + buyProduct(); + } + + function handleCheckoutClick() { + buyProduct(); + navigateTo('/checkout'); + } + + //이렇게 하면 새로고침할때마다 가 아닌 필요한 경우에만 알람을 띄울수 있습니다. +} +``` +#### POST요청 보내기 + +- 버튼을 눌르는 post요청도 마찬가지로 제출을 클릭하였을때 요청을 보내므로 +이벤트행핸들러를 사용하여 코드문을 작성합니다. + +#### 연쇄 계산 +- 이 경우는 state값에 따라서 또 다른 state값을 조정하고 싶을때가 있는데 이경우도 effect가 아닌 +이벤트 헨들러에서 적용하는것이 좋습니다. + - 또 비효율적입니다. 간단한 계산같은 경우 체인의 각 값의 호출 사이에 렌더링을 다시 해야하기 때문에 비효율 적이죠! + - 공식문서의 코드문경우엔 무려 3번이나 렌더링을 해야죠 +- 하지만 드롭다운의 선택값의 따라 다음 드롭다운의 옵션이 달라지는 form을 작성한다 했을때! +이는 effect체인이 적절해 보입니다. 왜냐 선택되는 값에 따라 결과가 달리지기 때문이죠 + +#### 어플리케이션 초기화 하기 +- 일부 로직은 앱이 로드될때 한번만 실행되어야 할때도 있는데 그경우엔 Effect가 아닌 최상위 변수로 선언을 사용해보죠 +- Effect로 최상위 컴포넌트에 배치할 경우 개발중에는 두번 실행되는것을 확인할수 있습니다. +- 두번 실행될경우 예기치 못한 토큰무효화 라든지의 문제가 발생할수 있기 때문에 지양합시다 +- 하지만 prd환경같은 경우엔 1번 실행되어 다시 마운트 되지 않을수도 있지만 그래도! 동일한 제약조건을 따르면 코드를 이용하고 재사용하기 편하기 때문에 사용하지 말죠! + - window변수의 경우엔 nextjs를 다루는 환경에서는 다르니 사용에 주의하죠 + + +#### state변경을 부모컴포넌트에 알리기 +```js +function Toggle({ onChange }) { + const [isOn, setIsOn] = useState(false); + + // 🔴 Avoid: The onChange handler runs too late + // 🔴 이러지 마세요: onChange 핸들러가 너무 늦게 실행됨 + useEffect(() => { + onChange(isOn); + }, [isOn, onChange]) + + function handleClick() { + setIsOn(!isOn); + } + + function handleDragEnd(e) { + if (isCloserToRightEdge(e)) { + setIsOn(true); + } else { + setIsOn(false); + } + } + + +} +``` +- 이 경우엔 사용자의 동작(클릭,드래그)에 의해서 이뤄지는 코드문이기에 Effect는 맞지 않습니다.또 늦습니다. +- 그래서 각 코드들은 이벤트 핸들러에서 작성합시다. + +```js +function Toggle({ isOn, onChange }) { + function handleClick() { + onChange(!isOn); + } + + function handleDragEnd(e) { + if (isCloserToRightEdge(e)) { + onChange(true); + } else { + onChange(false); + } + } + + // 이렇게 ison과 onchange를 같이 관리하여 손쉽게 코드문을 이해할수 있게 해줄수도 있습니다. +} +``` +#### 부모에게 데이터 전달하기 +- 이경우엔 부모 -> 자식 으로 데이터흐름이 이어지고 있는데 자식에서 데이터를 페치하지말고 미리 부모에서 해당 데이터를 패치하고 전달하도록 하면 +- 데이터흐름이 깔끔해지고 정보의 출처도 한눈에 알아볼수 있다 (자식꺼는 이리저리 찾아봐야 하니까! 자식은 전달만 받자!) + +#### 외부 스토어 구독하기 +- 외부에서 가져오는 데이터api의 경우 react가 모르는 사이 변경될수 있는데 그럴땐 컴포넌트에서 수동으로 데이터를 구독하도록 해야합니다. +```js +function useOnlineStatus() { + // Not ideal: Manual store subscription in an Effect + // 이상적이지 않음: Effect에서 수동으로 store 구독 + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function updateState() { + setIsOnline(navigator.onLine); + } + + updateState(); + + window.addEventListener('online', updateState); + window.addEventListener('offline', updateState); + return () => { + window.removeEventListener('online', updateState); + window.removeEventListener('offline', updateState); + }; + }, []); + return isOnline; +} + +function ChatIndicator() { + const isOnline = useOnlineStatus(); + // ... +} +``` +- 이 경우엔 윈도우가 online일 경우에 값을 저장하라 라는 코드문인데 useEffect로 하기보다는 react에서 제공하는 훅인 useSyncExternalStore이걸 사용하자 +- 요것은 훅을 공부할때 더 자세히 해보자 + +#### 데이터 패칭하기 + +```js +function SearchResults({ query }) { + const [results, setResults] = useState([]); + const [page, setPage] = useState(1); + + useEffect(() => { + // 🔴 이러지 마세요: 클린업 없이 fetch 수행 + fetchResults(query, page).then(json => { + setResults(json); + }); + }, [query, page]); + + function handleNextPageClick() { + setPage(page + 1); + } + // ... +} +``` +- 이 경우 페이징의 값에따라 변경되는 페칭과 query에대한 패칭은 ?? 을 나타내는 구문입니다. +- 하지만 클린업이 없다는 점이 잘못된 점입니다. +- 그래서 ignore문을 넣어(클린없) 해결해보죠 +- 클린업을 하는 이유는 더이상 관련없는 페치가 앱에 계속 영향을 미치지 않아야 하기 때문이죠!! effect와 동기화 하기 참조!! + +*경쟁상태* +- 요건 서로 다른 요청이 서로 영향을 미치는 것을 의미합니다. 패치에서는 패칭으로 불러온 값이 계속 남아있어 앱에 영향을 주는 것을 의미할수 도 있죠 + +## 정리 + +1. 렌더링중에 계산은 effect가 필요 x +2. 비용이 많이 드는(시간이 소요되는) 계산을 저장하려면 usememo하자 +3. 전체 컴포넌트의 state변경은 key를 넣어 정확하게 수행하자 +4. 컴포넌트에 표시되는 코드는 effect 나머지는 이벤트핸들러!!! +5. 자식컴포넌트에서 데이터를 발생시키는게 아닌 부모에서 데이터를 발생시켜 자식한테 주자 +6. 부모컴포넌트에서 state를 끌어올리면 부모컴포넌트에서 더 많은 로직을 포함해야 하지만 전체적으로 걱정할 state가 줄어든다! + - 서로다른 state를 동기화 하려면 부모쪽에서 끌어올리기를 해보자 +7. effect에서 데이터를 fetch할수 있지만 경쟁조건을 피하기 위해 클립업(ignore)를 구현해야죠 \ No newline at end of file diff --git "a/12\355\232\214/\353\217\204\355\230\204.md" "b/12\355\232\214/\353\217\204\355\230\204.md" new file mode 100644 index 0000000..6940afe --- /dev/null +++ "b/12\355\232\214/\353\217\204\355\230\204.md" @@ -0,0 +1,34 @@ +<12회차 useMemo> + +- 컴포넌트 최상단에서 useMemo를 호출하여 리렌더링사이의 계산을 캐싱합니다. +```js + import { useMemo } from 'react'; + +function TodoList({ todos, tab }) { +const visibleTodos = useMemo( +() => filterTodos(todos, tab), +[todos, tab] +); +// ... +} +``` +- 매개변수는 2개의 인자가 있다 + 1. calculateValue + - 캐시하려는 값을 계산하는 함수입니다.이 함수는 순수해야 하며 ,인자를 받지않고 값을 반환해야합니다. + - 초기렌더링중 함수를 호출하는데 ,이후 렌더링에서는 의존성이 변경되지 않았다면 동일한 값을 반환합니다. 그렇지 않으면 함수를 호출하고 나중에 재사용할수있도록 저장합니다. + 2. dependencies + - calculateValue 코드 내에서 참조되는 모든 반응형 값들의 목록입니다. + - 값에는 모든 변수와 함수가 포함되어야 합니다.(배열형식으로) +- + + + + +상위 useMemo + 하위 memo를 함께 써야만 리렌더링을 하지 않는가? + +상위 useMemo는 데이터 일부 (todos,tab의 의존성에 따른 visibleTodos)에 대해서만 메모화 +그 밖의 데이터들(theme)의 변경에 대해서는 어차피 리렌더링을 피하지 못함. + +하위컴포넌트가 리렌더링을 피하는 방법은 momo를 해야만 한다. +상위는 useMemo를 써야하는 경우도 있고 그렇지 않은 경우도 있을수 있다. +useMemo + memo \ No newline at end of file