다시공부하는 React
상태와 관련된 로직을 재사용하기 어려움과 this.state는 로직에서 레퍼런스를 공유하기 때문에 문제가 발생할 수 있다는 문제점이 있었다.
useState
: state를 대체 할 수 있다.
useEffect
: 라이프 사이클 훅을 대체 할 수 있다. (componentDidMount, componentDidUpdate, componentWillUnmount)
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>click</button>
</div>
);
}
function Example() {
const [state, setState] = useState({ count: 0 });
return (
<div>
<p>clicked {state.count} times</p>
<button
onClick={() => setState({ count: state.count + 1 })}
// onClick={click}
>
click
</button>
</div>
);
function click() {
setState((state) => {
return {
count: state.count + 1,
};
});
}
}
컴포넌트가 렌더링 이후에(DOM을 업데이트한 이후) 어떤 일을 수행해야하는지를 알려준다.
function Example5() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("componentDidMount");
}, []);
useEffect(() => {
console.log("componentDidMount & componentDidUpdate by count", count);
return () => {
// cleanup uesEffect가 실행되기전에 먼저 실행된다.
console.log("cleanup by count", count);
};
}, [count]);
return (
<div>
<p>clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>click</button>
</div>
);
}
state
를 재활용하기에 좋다.
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const resize = () => {
setWidth(window.innerWidth);
};
window.addEventListener("resize", resize);
return () => {
window.removeEventListener("resize", resize);
};
}, []);
return width;
}
function App() {
const width = useWindowWidth();
return(
<div>{width}<div/>
)
}
withHasMounted는 props
를 통해 보내진다.
export default function withHasMounted(Component) {
class NewComponent extends React.Component {
state = {
hasMounted: false,
};
render() {
const { hasMounted } = this.state;
return <Component {...this.props} hasMounted={hasMounted} />;
}
componentDidMount() {
this.setState({ hasMounted: true });
}
}
NewComponent.displayName = `withHasMounted(${Component.name})`;
return NewComponent;
}
function App({hasMounted}) {
console.log(hasMounted); // 결과 : false, true
return(
<div className="App"> ... <div/>
)
}
export default withHasMounted(App)
useHasMounted는 상태(state)
를 통해 사용된다.
export default function useHasMounted() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
function App(){
const useHasMounted = useHasMounted();
console.log(useHasMounted); // 결과 : false, true
return(
<div className="App"> ... <div/>
)
}
다수의 하윗값을 가지는 로직을 가지는경우,
다음 state가 이전 state에 의존적인 경우에 주로 사용된다.
const [state, dispatch] = useReducer(reducer, initialArg, init)
state
: 컴포넌트에서 사용 할 수 있는 상태reducer
: state를 변경하는 로직이 담겨있는 함수dispatch
: action객체를 넣어서 실행한다.(action을 발생시킨다.)action
: 필수 프로퍼테로 type을 가지고있는 객체이다.const reducer = (state, action) => {
if (action.type === "ADD") {
return {
count: state.count + 1,
};
}
return state;
};
export default function Example() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>clicked {state.count} times</p>
<button onClick={click}>click</button>
</div>
);
function click() {
dispatch({ type: "ADD" });
}
}
콜백의 의존성이 변경되었을 때에만 변경된다.
함수
를 메모제이션해, 새로운 함수를 반환한다.
const memoizedCallback = useCallback(() => {
doSomething(a, b);
},[a, b],);
const click = useCallback(() => {
console.log(value);
}, []);
// 빈배열일 경우, 최초 한번만 진행되기 때문에 초기값만을 기억하게된다.
useCallback안에서 사용되는 함수를 언제 실행할껀지 dependency에 조건에 따라 결정되도록 한다.
export default function Example({ List }) {
return (
<div>
{List.map((item) => (
<ListItem
key={comment.title}
/>
))}
</div>
);
}
<ListItem
key={comment.title}
onClick={()=>{console.log("click")}}
/>
계속해서 새로운 함수가 만들어져 props
로 전달되기때문에 <ListItem/>
컴포넌트 에서 useMemo를 통해 메모제이션하는 의미가 없어진다.
const handleClick = () => {
console.log("click")
}
<ListItem
key={comment.title}
onClick={handleClick}
/>
<Example/>
컴포넌트에서 전달받은 List가 변경될경우, 함수도 새로 생성되고 있으므로 인라인함수와 똑같다.
➡ <ListItem/>
컴포넌트 안에서 함수를 만들어 사용한다.
const handleClick = useCallback(() => {
console.log("click")
},[]}
<ListItem
key={comment.title}
onClick={handleClick}
/>
의존성이 변경되었을때에멘 메모이제이션된 특정 값
만 다시 계산한다.
렌더링 중에 실행되며, 렌더링 중에는 하지 않는 것을 useMemo
안에서 사용하면 안된다. (side effects는 useEffect
에서 사용된다.)
배열이 없는경우, 매 렌더링 때마다 새 값을 계산하게 된다.
동일한 props
로 렌더링을 할 경우, React는 컴포넌트를 렌더링 하지않고 마지막으로 렌더링된 결과를 재사용한다.
React가 렌더링하는 빈도와, 렌더링 비용을 측정한다. (메모제이션 같은 성능 최적화 방법을 활용할 수 있는 부분을 식별할때 사용한다.)
가까운 미래에 React에서는, 이전 메모이제이션된 값들의 일부를 “잊어버리고” 다음 렌더링 시에 그것들을 재계산하는 방향을 택할지도 모르겠습니다.
useMemo를 사용하지 않고도 동작할 수 있도록 코드를 작성하고 그것을 추가하여 성능을 최적화하세요.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
import { useMemo, useState } from "react";
function sum(persons) {
console.log("sum");
return persons.map((person) => person.age).reduce((l, r) => l + r, 0);
}
export default function Example() {
const [value, setValue] = useState("");
const [persons] = useState([
{ name: "Mark", age: 10,},
{ name: "Hanna", age: 20,},
]);
// const count = sum(persons); input에 값이 입력될 때마다 렌더가 다시 일어난다.
// persons 의 값이 변화가 없는데도 불필요한 렌더, persons에 의존적으로 만들기 위해 useMemo를 사용한다.
const count = useMemo(() => {
return sum(persons);
}, [persons]);
return (
<div>
<input type="text" value={value} onChange={change} />
<p>{count}</p>
</div>
);
function change(e) {
setValue(e.target.value);
}
}
react에서는 Virtual DOM을 통해 DOM을 그리기때문에, querySelector
,getElementById
를 지양하고 useRef
를 사용한다.
useRef()
는매번 렌더링을 할 때 동일한 ref객체를 제공한다.
.current
를 변형하는 것이 리렌더링을 발생시키지 않는다.
createRef()
는 렌더가 될때마다 레퍼런스를 새로 만들어 지정해준다.
export default function Example() {
const input1Ref = createRef();
const input2Ref = useRef();
console.log(input1Ref.current, input2Ref.current);
// 결과 : null, undefined / null, <input>
return (
<div>
<input type="text" ref={input1Ref} />
<input type="text" ref={input2Ref} />
</div>
);
}
컴포넌트 트리 안에서 전역적으로 데이터를 공유할 수 있는 방법
props drilling의 한계를 해소하지만, 재사용하기 어렵다는 단점이 있다.
Provider
: 상위 컴포넌트(props
를 정의한다.)
const MyContext = React.createContext();
const value = ;
<MyContext.Provider value ={value}> </MyContext.Provider>
<PersonContext.Consumer>
{(persons) => persons.map((person) => <div>{person.name}</div>)}
</PersonContext.Consumer>
Context.Consumer
의 자식은 함수여야한다.
static contextType = PersonContext;
render() {
const persons = this.context;
return (
<ul>
{persons.map((person) => (
<li key={person.id}>{person.name}</li>
))}
</ul>
);
}
context
가 한개일때만 사용하며, 여러개일경우 사용이 불가능하다.
const persons = useContext(PersonContext);
return (
<ul>
{persons.map((person) => (
<li key={person.id}>{person.name}</li>
))}
</ul>
);
useContext
를 호출한 컴포넌트는 context
값이 변경되면 항상 리렌더링된다.