useEffect 사용 방법
종속성 배열을 잘못 입력하면, 버그로 이어질 수 있으므로 사용하지 않는 것이 좋습니다.
useEffect 내에서 사용되는 값은 변경 사항을 적용하려면 종속성 배열에 추가해야 합니다.
function Profile({ userId }) {
const (user, setUser) = useState();
useEffect(() => {
fetchUser(userId).then(data => setUser(data));
}, (userId));
}
import { useState, useEffect } from "react";
export default function Profile({ userId }) {
const (user, setUser) = useState();
// 여기서는 Profile 컴포넌트가 렌더링 될 때마다 노출되기 때문에, 서버의 API를 호출하는 코드가 항상 실행
useEffect(() => {
fetchUser(userId).then((data) => setUser(data));
});
// 그래서 아래와 같이 빈배열을 입력해서, 마운트 된 후에 한번만 호출되도록 할 수 있음
// 근데 이는 userId가 변경되어도 새로운 사용자 정보를 가져오지 못하기 때문에
useEffect(() => {
fetchUser(userId).then((data) => setUser(data));
}, ());
// 2.js로!!!!!
// ...
}
마운트 지점달리고만 싶다면 별도의 후크사용하기가 더 직관적입니다~의 장점이 있다
function Profile({ userId }) {
const (user, setUser) = useState();
useOnMounted(() => fetchUser(userId).then(data => setUser(data)))
}
import { useState, useEffect } from "react";
import useOnMounted from './useOnMounted';
export default function Profile({ userId }) {
const (user, setUser) = useState();
// 아래와 같이 (userId)를 입력해서, userId가 변경되었을때 실행될 수 있도록
// 이것이 의존성 배열
useEffect(() => {
fetchUser(userId).then((data) => setUser(data));
}, (userId));
// ...
}
export default function Profile2({ userId }) {
const (user, setUser) = useState();
const (needDetail, setNeedDetail) = useState(false);
// 매개변수값이 추가되고 이를 상태값으로 관리한다면
// 상태값이기 때문에 의존성 배열에 추가를 해줘야하는데
// 기존에 있던 코드를 수정할 때에는 의존성 배열에 입력하는것을 깜빡하는 경우가 많다
// 그래서 eslint에서 사용할 수 있는 룰을 만들어 제공 >> 보통 CRA에는 포함되어있음 >> 터미널에 뜨게 되어있음
useEffect(() => {
fetchUser(userId, needDetail).then((data) => setUser(data));
}, (userId, needDetail));
// ...
}
// 근데 userId와 같은것이 변경되지않는다고 확신하게 된다면, useEffect를 사용하기 보다 hook을 추천
// 그래서 위의 것을 대신하여
function Profile({userId}){
// useEffect를 만들어서 빈배열을 넣는것 보다 아래와 같이 훅을 만들어 하면 훨배 나음
useOnMounted(()=>(fetchUser(userId).then((data) => setUser(data))));
return null;
}
// 의존성 배열에 필요한 변수를 입력하지 않았을때, 어떤 문제가 발생할까
// 아래에서는 value2값을 입력하지않았다
// value2가 변경이 되어도 부수효과 함수는 새로 생성이 되겠지만, 의존성 배열에는 v2가 없기 때문에 리액트는 방금 생성된 부수효과 함수를 무시하고,
// 이전에 생성된 부수효과 함수를 계속 사용
// 함수가 생성될 때는 그 함수가 생성될 당시의 지역변수를 기억하고 있음
// 이를 실행 컨텍스트라고 함!!!!!!!!!!!!
import { useState, useEffect } from "react";
export default function MyComponent() {
const (value1, setValue1) = useState(0);
const (value2, setValue2) = useState(0);
useEffect(() => {
const id = setInterval(() => console.log(value1, value2), 1000);
return () => clearInterval(id);
}, (value1)); // 여기에 안넣음
return (
<div>
<button onClick={() => setValue1(value1 + 1)}>value1 증가</button>
<button onClick={() => setValue2(value2 + 1)}>value2 증가</button>
</div>
);
}
useEffect 이내 비동기/대기 기능 사용
useEffect는 항상 함수를 반환당신이해야하기 때문에 별도의 함수 정의 및 사용하다.
function Profile({ userId }) {
const (user, setUser) = useState();
async function fetchAndSetUser () {
const data = await fetchUser(userId);
setUser(data);
}
useEffect(() => {
fetchAndSetUser();
}, (fetchAndSetUser));
return (...);
}
// 부수효과 함수를 async await를 아래와 같이 하면 문제!
// 부수효과 함수의 반환값은 항상 함수 타입이어야 하기 때문
// async await 함수는 Promise 객체를 반환하기 때문에! >> 부수효과 함수가 될 수 없음
// 부수효과 함수는 함수만 반환할 수 있음
// 반환된 함수는 부수효과 함수가 호출되기 직전과 컴포넌트가 사라지기 직전에 호출
useEffect(async () => {
const data = await fetchUser(userId);
setUser(data);
}, (userId));
// 위의 것은 잘못된것!
// async await를 사용하고 싶다면
// 함수를 하나 만들어서 호출해주는 방식을 사용해야 함
useEffect(() => {
async function fetchAndSetUser() {
const data = await fetchUser(userId);
setUser(data);
}
fetchAndSetUser();
}, (userId));
종속성 배열 사용을 줄이는 방법
- if 문을 사용한 제어 호출
import { useState, useEffect } from "react";
export default function Profile({ userId }) {
const (user, setUser) = useState();
async function fetchAndSetUser(needDetail) {
const data = await fetchUser(userId, needDetail);
setUser(data);
}
// 의존성 배열을 되도록 사용하지 않도록 하되,
// 함수의 실행시점을 의존성 배열로 관리하지 않고 부수효과 함수 내에서 처리를 하면
// if (!user || user.id !== userId)
// 부수효과 함수 안에서 사용하는 모든 변수는 최신화 된 값을 참조함으로 안심할 수 있다
// 그래서 useCallback같은 것을 사용하지않아도 되는 것이지!
useEffect(() => {
if (!user || user.id !== userId) {
fetchAndSetUser(false);
}
});
// ...
}
- 상태값 변경 함수의 파라미터로 함수 사용
setCount(카운트 + 1);
=>
setCount((이전) => 이전 + 1);
function MyComponent() {
const (count, setCount) = useState(0);
useEffect(() => {
function onClick() {
setCount(prev => prev + 1);
}
window.addEventListener('click', onClick);
return () => window.removeEventListener('click', onClick);
});
// ...
}
- useReducer 후크 사용
// dispatch는 변하지 않는 값 (의존성 배열로 빈 배열을 입력했다)
// 현재는 의존성 배열이 전혀 입력되지않은 상태이다
// 값을 변경하는 로직은 reducer에서 작성
// useReducer를 사용하면, 다양한 액션과 상태값을 관리하기 용이하고, 상태값 변경 로직을 여러곳에서 재사용하기에도 좋음
function Timer({ initialTotalSeconds }) {
const (state, dispatch) = useReducer(reducer, {
hour: initialTotalSeconds / 3600
minute: (initialTotalSeconds % 3600) / 60
second: initialTotalSeconds % 60
});
const { hour, minute, second } = state;
useEffect(() => {
const id = setInterval(dispatch, 1000);
return () => clearInterval(id);
});
//...
}
function reducer(state) {
const { hour, minute, second } = state;
if(second) {
return { ..state, second: second - 1 };
} else if(second) {
return { ..state, minute: minute - 1, second: 59 };
} else if(second) {
return { ..state, hour: hour - 1, minute: 59, second: 59 };
} else {
return state;
}
}
- userRef 후크 사용
// 만약 이를 해결하는 마땅한 방법이 떠오르지않는다면,useRef가 손쉬운 해결책이 될 수 있음
// (아래쪽 의존성 배열로 빈 배열을 입력하면된다)
// 이렇게 렌더링 될때마다, ref객체에 onClick함수를 넣어주면 됨
// 그리고 원래 사용하려고 한곳에서 ref객체를 이용해 함수를 호출하는 방식 onClickRef.current();
// 부수효과 내에서 사용된 ref객체는 의존성 배열에 추가할 필요가 없다
// 그런데, 의존성 배열에 이 함수를 입력하지않으려고 애써보이는데
// 불필요한 코드와 연산이 추가되기도 했음
// 하지만 이 모든 것은 의존성 배열을 관리하는 규칙을 지키기 위한 것
// 이것이 리액트 훅의 단점
import { useRef, useEffect } from "react";
export default function MyComponent({ onClick }) {
const onClickRef = useRef();
useEffect(() => {
onClickRef.current = onClick;
});
useEffect(() => {
window.addEventListener("click", () => {
onClickRef.current();
// ...
});
// ...
});
// ...
}
// ref 객체는 이렇게 컴포넌트 함수에서직접 변경해도 된다고 생각할 수 있지만, 문제가 있음
// 부수효과 함수에서 ref를 수정하는 이유는 onClickRef.current = onClick;
// 나중에 도입될 리액트의 concurrent mode 때문
// concurrent mode : 컴포넌트가 시행되었다 하더라도, 중간에 렌더링이 취소될 수 있음
// 렌더링은 취소되었는데 ref 객체에는 잘못된 값이 저장될 수 있음으로
// ref 객체는 이렇게 컴포넌트 함수에서 직접 수정하면 안됨 8.js onClickRef.current = onClick;
// 단 concurrent mode로 동작하지 않는 리액트 버전에서는 문제가 되지는 않음
// Concurrent / Concurrency : 동시적으로 / 동시성
// Parallel / Parallelism : 병렬적으로 / 병렬
// 나중을 위해 7.js 처럼 작성하는 것이 좋다
// useEffect(() => {
// onClickRef.current = onClick;
// });
렌더링 속도를 높이기 위해 성능을 최적화하는 방법
렌더링은 대부분의 CPU 리소스를 사용합니다.
리액트 렌더링 프로세스

반응은 데이터+컴포넌트 함수로 화면을 그립니다.
여기서 대부분의 작업은 “구성 요소 함수 실행 및 가상 DOM“에서 발생
데이터 : 컴포넌트의 속성 값 및 상태 값 >> 변경 시 화면 다시 그리기
+가상 돔
기존 돔과 비교하여 변경된 부분만 렌더링하는 버추얼 돔.
반응.메모
리액트 메모 기능 속성 값이 변경될 때만 렌더링 이 기능은 그렇게 하는 데 도움이 됩니다.
메모 함수는 구성 요소를 첫 번째 인수로 사용합니다. 두 번째 인수로 비교 함수받다.
두 번째 인수가 없는 경우, 얕은 비교를 수행하는 함수실행됩니다.
얕은 비교에서도 속성 값이 변경되지 않으면 실제 DOM에서는 변경되지 않으므로 큰 문제는 없습니다.
function MyComponent(props) {
// ...
}
function isEqual(prevProps, nextProps) {
// true 또는 false를 반환
// 참을 반환하면 이전 렌더링 결과를 재사용
// 거짓이라면 컴포넌트 함수를 이용해 가상돔을 업데이트하고 변경된 부분만 실제 돔에 반영
// 만약 이렇게 속성 값 비교함수를 입력하지 않으면, 얕은 비교를 수행하는 기본함수가 사용이 됨
//
// 컴포넌트를 memo 함수로 감싸지 않았다면, 항상 거짓을 반환하는 속성값 비교함수가 사용된다고 생각할 수 있다
// 이때는 속성값이 변경되지않아도 부모컴포넌트가 렌더링 될때마다 자기자신도 렌더링될것이다
// 속성값이 항상 거짓을 반환하더라도, 속성값이 변경되지 않았다면, 실제돔도 변경되지않을 것이기때문에 대체로 문제가 되지는 않음
//
// 하지만 렌더링 성능이 중요한 상황에서는 memo함수를 사용해서 컴포넌트 함수의 실행과 가상돔의 계산을 생략할 수 있기 때문에 렌더링 성능상의 이점은 있음
}
React.memo(MyComponent, isEqual);
이전 부동산 가치와 주 가치에 대해 포스팅했을 때,
불변 값은 속성 값과 가변 상태 값을 말합니다.
상태 값을 불변 값으로 관리하는 것이 좋다고 말한 이유가 여기에 있습니다.
값을 이전 값과 비교할 때 === 연산자비교할 개체 불변 객체로 관리하는 것이 좋기 때문에
const state = {};
const newState = {};
state === {...state, ...{name: 'horong'}}; // false
const prevProps = {
todos: (
{ title: "fix bug", priority: "high" },
{ title: "meeting with jone", priority: "low" },
// ...
),
// ...
};
const nextProps = {
todos: (
{ title: "fix bug", priority: "high" },
{ title: "meeting with jone", priority: "high" },
// ...
),
// ...
};
prevProps.todos === nextProps.todos;
prevProps.todos(0) === nextProps.todos(0);
// memo함수를 사용하면, 속성값 비교함수를 통해 컴포넌트 렌더링 과정을 생략할 수 있는데
// 속성값 비교함수를 입력하지않으면, 리액트가 기본으로 갖고있는 함수가 사용됨
// 이전/이후 속성 값을 가지고 있을 때, 속성값 변경여부는 어떻게 알게될까?
// priority == low>> high로 변경됨
// prevProps.todos === nextProps.todos >>로 비교가능
// 객체를 불변객체로 관리
// 데이터를 불변 데이터로 관리하면, 이전이후 값 단순 비교로 컴포넌트의 속성값이 변경되었는지 알수있음
// 상태 값을 불변객체로 관리하면, 렌더링 성능에 큰도움
// 새로운 객체를 만든다고 생각하면됨
const prevTodos = (1, 2, 3);
const nextTodos = (...todos, 4);
prevTodos === nextTodos;
// ----------------------------------------------------------------------------------------------------------
const prevProps2 = {
todos: (
{ title: "fix bug", priority: "high" },
{ title: "meeting with jone", priority: "low" },
// ...
),
friends: (),
// ...
};
const nextProps2 = {
todos: (
{ title: "fix bug", priority: "high" },
{ title: "meeting with jone", priority: "high" },
// ...
),
friends: (),
// ...
};
// 얕은 비교
const isEqual =
prevProps.todos === nextProps.todos &&
prevProps.friends === nextProps.friends;
함수 속성 값
함수를 속성 값으로 자식에게 전달할 때, React.memo를 사용해도
기능은 부모가 렌더링될 때마다 새로 만들기따라서 자식도 다시 렌더링됩니다.
// 속성값으로 함수를 자식에게 넘겨줄 시, React.memo를 사용하였다 할 지라도
// 함수는 부모가 렌더링 될 떄마다 새로 생성되기 때문에 자식 또한 다시 렌더링
// fruit가 변경되었을때 계속 다시 렌더링
import { useState } from "react";
function Parent() {
const (selectedFruit, setSelectedFruit) = useState("apple");
const (count, setCount) = useState(0);
return (
<div>
<p>{`count: ${count}`}</p>
<button onClick={() => setCount(count + 1)}>increase count</button>
<SelectFruit
selected={selectedFruit}
onChange={(fruit) => setSelectedFruit(fruit)}
/>
</div>
);
}
import { useState } from "react";
function Parent() {
// 상태값 변경함수는 한번생성되고 다시 생성되지않기 때문에
const (selectedFruit, setSelectedFruit) = useState("apple");
const (count, setCount) = useState(0);
return (
<div>
<p>{`count: ${count}`}</p>
<button onClick={() => setCount(count + 1)}>increase count</button>
{/* setSelectedFruit 와 같이 상태값 변경함수를 그대로 입력해주면 간단히 해결 */}
<SelectFruit selected={selectedFruit} onChange={setSelectedFruit} />;
</div>
);
}
import { useState } from "react";
function Parent() {
const (selectedFruit, setSelectedFruit) = useState("apple");
const (count, setCount) = useState(0);
return (
<div>
<p>{`count: ${count}`}</p>
<button onClick={() => setCount(count + 1)}>increase count</button>
<SelectFruit
selected={selectedFruit}
// 하지만 아래와 같이 다른방법이 있다면 이 방식은 힘들겠다
onChange={(v) => {
//...
setSelectedFruit(v);
}}
/>
;
</div>
);
}
useCallback 훅 를 사용하여 해결
useCallback은 함수입니다!!!!!
이 경우 한번 생성된 값으로 고정!
function Parent() {
const (v1, setV1) = useState();
const onChangeCallback = useCallback(v => {
// ...
setV1(v);
}, ())
return <Child onChange={onChangeCallback}>
}
개체 속성 값
객체도 함수와 마찬가지로 매번 새로운 객체로 인식됩니다.하다.
불변객체일 경우 별도로 생성
function Parent() {
return <Child data={DATA}>
}
const DATA = (1, 2, 3);
가변 객체인 경우 useMemo 후크 사용 ( :필요한 경우에만 변경)
필터라면 또 계속 불러야지!!!
function Parent() {
const (maxValue, setMaxValue) = useState(0);
const data = useMemo(() => DATA.filter(e => e<=maxValue), (
maxValue
));
return <Child data={data}>
}
const DATA = (1, 2, 3);
import React, { useState } from "react";
export default function App() {
return <SelectFruit />;
}
function SelectFruit({ selectedFruit, onChange }) {
const (fruits, setFruits) = useState(("apple", "banana", "orange"));
const (newFruit, setNewFruit) = useState("");
function addNewFruit() {
// 아래와 같이 하면안됨 >> 추가가 안됨
// fruits.push(newFruit);
// setFruits(fruits);
// 이렇게 해야함!
// 선택값은 불변 변수로 해야함
setFruits((...fruits, newFruit));
setNewFruit("");
}
// ...
return (
<div>
<Select options={fruits} />
<input
type="text"
value={newFruit}
onChange={(e) => setNewFruit(e.target.value)}
/>
<button onClick={addNewFruit}>추가하기</button>
{/* ... */}
</div>
);
}
const Select = React.memo(({ options }) => (
<div>
{options.map((item, i) => (
<p key={i}>{item}</p>
))}
</div>
));
useMemo, useCallback, React.memo >> 너무 애쓰지 마세요. 성능 문제가 있는 경우에만 최적화하는 것이 좋습니다.
원천 : https://keeper.37?category=940178
(React.js) React의 useEffect 사용법 및 성능 최적화 방법
useEffect 사용 방법 의존성 배열은 잘못 입력 시 버그가 발생할 수 있으므로 가급적 사용하지 않는 것을 권장합니다 useEffect 내에서 사용되는 값은 종속성 배열에 추가되어야 변경 사항이 적용됩니다. 재미있는
keeper.tistory.com