728x90
러닝 리엑트을 요약한 내용입니다.
- 프롭, 상태가 바뀌면 컴포넌트 트리가 다시 렌더링되면서 최신 데이터를 반영한다.
- useState를 사용해서 컴포넌트를 재렌터링을 하는 순간을 기술하는 주된 수단이였다.
- 6장에서 살펴본 useState, useRef, useContext 이외에 원래부터 제공하고 있는 useEffect, useLayoutEffect, useReducer를 살표 보자
- 컴포넌트 성능 최적화를 위한 useCallback과 useMemo를 살펴보자
7.1 useEffect 소개
- 렌더링이 끝난 다음에 무언가를 하고 싶으면 어떻게 해야할까?
Checkbox를 체크하거나 해제할때 alert를 띄워주는 예제
- App.js 컴포넌트 코드
- import './App.css'; import CheckBox from './components/CheckBoxHooks'; function App() { return ( <div className="App"> <CheckBox></CheckBox> </div> ); } export default App;
- useState를 사용해서 checked의 값을 변경해주는 예제
- alert를 클릭하지 않으면 렌더링이 되지 않는 문제가 있다.
- alert이 블러킹 함수이기 때문
- alert를 클릭하지 않으면 렌더링이 되지 않는 문제가 있다.
- import React, { useState } from 'react' export default function CheckBox() { const [checked, setChecked] = useState(false) alert(`checked: ${checked.toString()}`) return ( <> <input type="checkbox" value={checked} onChange={() => setChecked(checked => !checked)} /> {checked ? "checked" : "not checked"} </> ) }
- alert을 return 다음에 위치하는 예제
- 말이 안되는 코드
- import React, { useState } from 'react' export default function CheckBox() { const [checked, setChecked] = useState(false) return ( <> <input type="checkbox" value={checked} onChange={() => setChecked(checked => !checked)} /> {checked ? "checked" : "not checked"} </> ) alert(`checked: ${checked.toString()}`) }
- useEffect를 사용하는 예제
- 렌더가 부수효과로 무언가를 수행하게 하고 싶을 떄 useEffect를 사용한다.</aside>
- <aside> 💡 부수 효과: 컴포넌트가 렌더링 외에 다른 일을 수행해야 하는 일을 효과(Effect)라고 부른다.
- alert, console.log, 브라우저나 네이티브 API와 상호작용은 렌더링에 속하지 않는다.
- useEffect를 사용하면 렌더링이 끝나기를 기다렸다가 alert나 console.log 등에 값을 제공할 수 있다.
useEffect(() => { console.log(checked ? "Yes, checked" : "No, notchecked"); })
- checked 값을 검사해서 localStorage의 값을 설정 할 수도 있다.
useEffect(() => { localStorage.setItem("checkbox-value", checked); })
- useEffect를 사용해 DOM에 추가된 특정 텍스트 입력에 초점을 맞출 수 있다.
useEffect(() => { txtInputRef.current.focus(); })
- import React, { useState, useEffect } from 'react' export default function CheckBox() { const [checked, setChecked] = useState(false) useEffect(() => { alert(`checked: ${checked.toString()}`) }) return ( <> <input type="checkbox" value={checked} onChange={() => setChecked(checked => !checked)} /> {checked ? "checked" : "not checked"} </> ) }
7.1.1 의존 관계 배열
- useEffect는 useState, useReducer등의 다른 상태가 있는 훅스와 함께 작동하도로 설계됐다.
- useEffect는 렌더링이 끝난 다음에 호출 된다.
2가지의 서로 다른 상태 변수가 있는 곳에서 useEffect를 사용한 예제
import React, {useState, useEffect } from 'react'
function App() {
const [val, set] = useState("")
const [phrase, setPhrase] = useState("example phrase")
const createPhrase = () => {
setPhrase(val)
set("")
}
useEffect(() => {
console.log(`typing ${val}`)
})
useEffect(() => {
console.log(`saved phrase: ${phrase}`)
})
return (
<>
<label>Favorite phrase: </label>
<input
value={val}
placeholder={phrase}
onChange={e => set(e.target.value)}
/>
<button onClick={createPhrase}>send</button>
</>
);
}
export default App;
- 입력 필드가 바뀔 때마다 val이 변경되면서 컴포넌트가 다시 렌더링된다.
- Send 버튼을 클릭하면 텍스트 영역의 val이 ""로 재설정 된다. 이때도 다시 렌더링이 된다.
- 예제 코드는 예상대로 동작하지만 useEffect 훅이 쓸데 없이 많이 호출된다.
App.js:13 typing
App.js:17 saved phrase: example phrase
App.js:13 typing S
App.js:17 saved phrase: example phrase
App.js:13 typing Sh
App.js:17 saved phrase: example phrase
App.js:13 typing Shr
App.js:17 saved phrase: example phrase
App.js:13 typing Shre
App.js:17 saved phrase: example phrase
App.js:13 typing Shred
App.js:17 saved phrase: example phrase
App.js:13 typing
App.js:17 saved phrase: Shred
useEffect 훅을 구체적인 데이터 변경과 연동 하는 예제
useEffect(() => {
console.log(`typing ${val}`);
}, [val]);
useEffect(() => {
console.log(`saved phrase: ${phrase}`);
}, [phrase]);
- 의존 관계 배열을 Effect에 추가해서 호출 시점을 제어할 수 있다.
- val 값이 바뀔 때만 호출이 되고, phrase 값이 변경될 떄만 호출 된다.
App.js:13 typing
App.js:17 saved phrase: example phrase
App.js:13 typing S
App.js:13 typing Sh
App.js:13 typing Shr
App.js:13 typing Shre
App.js:13 typing Shred
App.js:13 typing
App.js:17 saved phrase: Shred
의존 관계 배열을 사용해 여러 값을 검사할 수 있다.
useEffect(() => {
console.log("either val or phrase has changed");
}, [val, phrase]);
초기 렌더링 직후 한번만 호출할 수 있다.
useEffect(() => {
console.log("only once after initial render");
}, []);
- 최초 렌더링시에만 호출되는 효관느 초기화에 아주 유용하게 쓰일수 있다.
컴포넌트가 트리에서 제거될 때 호출 된다.
useEffect(() => {
welcomeChime.play();
return () => goodbyeChime.play();
}, []);
- useEffect에 함수를 반환하면 컴포넌트가 제거 될때 호출 된다.
- useEffect는 생성과 제거시에 호출되기 때문에 설정과 정리에 사용할 수 있다.
뉴스 피드를 구독 및 해제 하는 코드
import React, {useState, useEffect } from 'react'
export default function NewsFeed() {
const [posts, setPosts] = useState([])
const addPost = post => setPosts(allPosts => [post, ...allPosts])
useEffect(() => {
newsFeed.subscribe(addPost)
welcomChime.play()
return () => {
newsFeed.unsubscribe(addPost)
goodbyeChime.play()
}
}, [])
}
- 랜더링시 뉴스 피드를 구독 할수 있다.
- 컴포넌트가 제거될 때 뉴스 피드를 구독 취소 할 수 있다.
뉴스 피드를 구독 및 해제 하는 코드 관심사 분리
import React, {useState, useEffect } from 'react'
export default function NewsFeed() {
const [posts, setPosts] = useState([])
const addPost = post => setPosts(allPosts => [post, ...allPosts])
useEffect(() => {
newsFeed.subscribe(addPost)
return () => {
newsFeed.unsubscribe(addPost)
}
}, [])
useEffect(() => {
welcomChime.play()
return () => {
goodbyeChime.play()
}
}, [])
}
- 기능을 여러 useEffect로 나눠 남는 것은 좋은 생각이다.
<aside> ❓ 호출 순서가 중요한 경우 우선 순위를 지정할 수 있을까???
</aside>
뉴스 피드를 구독 및 해제 하는 코드 커스텀 훅으로 구현
const useJazzyNews = () => {
const [posts, setPosts] = useState([]);
const addPost = post => setPosts(allPosts => [post, ...allPosts])
useEffect(() => {
newsFeed.subscribe(addPost)
return () => {
newsFeed.unsubscribe(addPost)
}
}, [])
useEffect(() => {
welcomChime.play()
return () => {
goodbyeChime.play()
}
}, [])
return posts
}
function NewsFeed() {
const posts = useJazzyNews()
return (
<>
<h1>{posts.length} articles</h1>
{posts.map(post => (
<Post key={ post.id } {...pros} />
))}
</>
)
}
7.1.2 의존 관계를 깊이 검사하기
- 지금까지 배열에 추가한 의존관계는 문자열 뿐이다
- 자바스크립트 기본 타입(문자열, 수)은 비교가 가능하다.
if ("gnar" === "gnar") {
console.log("gnarly!!");
}
- 객체, 배열, 함수 등을 비교하려면 비교 방법이 다른다.
- 길이나 원소가 모두 같지만 두 배열 [1, 2, 3]과 [1, 2, 3]은 같지 않다.
- 서로 다른 배열 인스턴스이기 때문에 배열의 값을 비교하려면 예상과 다른 결과를 얻음
if ([1, 2, 3] !== [1, 2, 3]) {
console.log("but they are the same");
}
- 배열에 값을 저장하는 변수를 만들고 비교하면 예상과 같은 결과를 얻을 수 있다.
- 자바스크립트에서는 배열, 객체, 함수는 같은 인스턴스일 때만 서로 같다
const array = [1, 2, 3]
if (array === array) {
console.log("but they are the same");
}
useEffect의 의존 관계 배열과 어떠한 관계가 있을까?
- 강제로 렌더링 할 수 있는 컴포넌트를 만들어 보자
- 키보드가 눌릴 때마다 컴포넌트를 다시 랜더링하는 훅
const useAnyKeyToRender = () => { const [, forcRender] = useState() useEffect(() => { window.addEventListener("keydown", forcRender) return () => window.removeEventListener("keydown", forcRender) }, []) }
- App 컴포넌트에서 강제로 렌더링 되는 훅을 사용해보자
- function App() { useAnyKeyToRender() useEffect(() => { console.log("fresh render") }) return <h1>Open the console</h1> }
- word 값을 참조하고, word가 변경 되면 App 컴포넌트를 다시 렌더링 하는 코드
- word가 변경이 되지 않음으로 재렌더링은 이루어지지 않음
- word가 변경되더라도 예상대로 동작함
function App() { useAnyKeyToRender() const word = "gnar" useEffect(() => { console.log("fresh render") }, [word]) return <h1>Open the console</h1> }
- 한 단어가 아닌 배열을 사용하면 어떠한 일이 벌어질까?
- 렌더링이 이뤄질 때마다 새로운 배열이 선언되기 때문에 자바스크립트는 words가 변경이 되었다고 가정하고 매번 재렌더링을 하게 된다.
- App 컴포넌트라 재렌더링 되면 words는 새로운 인스턴스가 생기기 때문에 발생하는 문제
function App() { useAnyKeyToRender() const words = ["sick", "powder", "day"] useEffect(() => { console.log("fresh render") }, [word]) return <h1>Open the console</h1> }
- 배열을 사용는 방법
- words를 App영역 밖에서 정의하면 문제가 해결
- 함수 밖에 선언된 words를 인스턴스를 가리킨다.
- 재렌더링이 된다고 하더라도 words를 새로 인스턴스를 생성하지 않기 때문에 해결 가능
- </aside>
const words = ["sick", "powder", "day"] function App() { useAnyKeyToRender() useEffect(() => { console.log("fresh render") }, [word]) return <h1>Open the console</h1> }
- words를 App영역 밖에서 정의하면 문제가 해결
- 컴포넌트 안에 변수를 만들어야하는 경우
import React from 'react' import WordCount from './components/WordCount'; function App() { return ( <> <WordCount>You are not going to believe this but...</WordCount> </> ); } export default App;
- WordCount 컴포넌트는 childern을 프로퍼티로 받아서 words를 배열로 만들었다.
- words가 변경될 때마다 컴포넌트를 다시 랜더링 할 수 있다.
- 하지만 키를 누르면 콘솔에 "fresh render"라는 단어가 출력된다.
- import React, { useState, useEffect } from 'react' const useAnyKeyToRender = () => { const [, forcRender] = useState() useEffect(() => { window.addEventListener("keydown", forcRender) return () => window.removeEventListener("keydown", forcRender) }, []) } export default function WordCount({ children = "" }) { useAnyKeyToRender() const words = children.split(" ") useEffect(() => { console.log("fresh render") }, [words]) return ( <> <p>{children}</p> <p> <strong>{words.length} - words</strong> </p> </> ) }
- 불필요한 추가적인 렌더링을 피할 수 있는 방법이 있다.
- useMem는 메모화된 값을 계산하는 함수를 호출 한다.
- 리엑트에서 useMemo를 사용하면 캐시된 값과 계산한 값을 비교해서 실제 값이 변경됐는지 검사해준다.
- useMemo에 의존관계를 전달하지 않으면 렌더링이 될때마다 값을 재계산한다.
- const words = useMemo(() => { const words = children.split(" ") return words }, [])
- useEffect와 마찬가지로 useMemo에도 의존관계 배열을 추가 해야함
- const words = useMemo(() => { const words = children.split(" ") return words }, [children])
- import React, { useState, useEffect, useMemo } from 'react' const useAnyKeyToRender = () => { const [, forcRender] = useState() useEffect(() => { window.addEventListener("keydown", forcRender) return () => window.removeEventListener("keydown", forcRender) }, []) } export default function WordCount({ children = "" }) { useAnyKeyToRender() const words = useMemo(() => { const words = children.split(" ") return words }, [children]) useEffect(() => { console.log("fresh render") }, [words]) return ( <> <p>{children}</p> <p> <strong>{words.length} - words</strong> </p> </> ) }
useCallback도 useMemo와 비슷하게 사용이 가능
- useCallback은 값 대신 함수를 메모화 한다.
- 예제 코드
- [ ] 어떠한 내용을 이야기하는지 잘 모르겠음... ㅠㅠ
- const fn = () => { console.log("hello") console.log("word") } useEffect(() => { console.log("fresh render") fn() }, [fn])
useJazzyNews 훅을 개선해보자
const useJazzyNews = () => {
const [_posts, setPosts] = useState([])
const addPost = post => setPosts(allPosts => [post, ...allPosts])
const posts = useMemo(() => _posts, [_posts])
useEffect(() => {
newPostChime.play()
}, [posts])
useEffect(() => {
newsFeed.subscribe(addPost)
return () => {
newsFeed.unsubscribe(addPost)
}
}, [])
useEffect(() => {
welcomChime.play()
return () => {
goodbyeChime.play()
}
}, [])
return posts
}
function NewsFeed() {
const posts = useJazzyNews()
return (
<>
<h1>{posts.length} articles</h1>
{posts.map(post => (
<Post key={ post.id } {...pros} />
))}
</>
)
}
728x90
'자바스크립트 > 러닝 리엑트' 카테고리의 다른 글
Suspense (0) | 2023.11.13 |
---|---|
데이터 포함시키기 (0) | 2023.11.13 |
리액트 상태 관리 (0) | 2023.11.13 |
JSX를 사용하는 리액트 (1) | 2023.11.13 |
리액트의 작동 원리 (0) | 2023.11.13 |