@ 어디서 궁금했는가?
리엑트 프로젝트를 하고 있다보니까 useState, useReducer, useContext 등은 잘 사용했지만 계속 되는 오류로 건들지 못하고 어디서 본 걸로 사용을 하고 에러가 뜨면 더이상 건들지 않는 경우가 많았다. 그래서 제대로 좀 정리해 보고 이것 저것 실험을 해보면서 공부를 하기 위해서 작성하게 되었다.
@ 공부
## Hook 이란?
Hooks 는 리액트 v16.8 에 새로 도입된 기능으로서, 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능등을 제공하여 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해줍니다.
## 우선 Hook 에 대한 규칙
Hooks 규칙
1. 리액트 함수의 제일 상단에 작성해야합니다.
- 조건문, 내부 함수, 반복문 안에 사용하지 않습니다.
2. 리액트 함수 안에서만 사용합니다.
- custom 함수 안에서는 예외적으로 호출 할 수 있습니다.
ex)
const [name, setName] = useState("");
useEffect(()=>{
document.title = `search search search my ${name}`;
});
주의
React 16.8.0은 Hook를 지원하는 첫 번째 배포입니다. 업그레이드 할 때 React DOM을 포함한 모든 패키지를 업데이트 하는 것을 잊지 마세요. React Native는 v0.59부터 Hook을 지원합니다.
## LifeCycle
import React from "react";
class Content extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
console.log("Component WILL MOUNT!");
}
componentDidMount() {
console.log("Component DID MOUNT!");
}
componentWillReceiveProps(nextProps) {
console.log("Component WILL RECIEVE PROPS!");
}
shouldComponentUpdate(nextProps, nextState) {
return true;
}
componentWillUpdate(nextProps, nextState) {
console.log("Component WILL UPDATE!");
}
componentDidUpdate(prevProps, prevState) {
console.log("Component DID UPDATE!");
}
componentWillUnmount() {
console.log("Component WILL UNMOUNT!");
}
render() {
return (
<div>
<h1>{this.props.sentDigit}</h1>
</div>
);
}
}
export default Content;
LifeCycle을 알기 좋은 예시다.
주의할 점은, componentWillMount에서는 props나 state를 바꾸면 안 됩니다. Mount 중이기 때문이죠. 그리고 아직 DOM에 render하지 않았기 때문에 DOM에도 접근할 수 없습니다. componentDidMount에서는 DOM에 접근할 수 있습니다. 그래서 여기에서는 주로 AJAX 요청을 하거나, setTimeout, setInterval같은 행동을 합니다
## Mount vs Render
마운트 : 나타난다.
https://medium.com/@nancydo7/understanding-react-16-4-component-lifecycle-methods-e376710e5157
## useState
> 비동기 처리를 한다.
const onchange = e =>{
console.log(e.target.checked);
const{name,checked} = e.target;
if(checked){
setComs([...coms,name]);
}else{
setComs(coms.filter(t => t !== name));
}
// console.log(coms)
}
// console.log(coms)
console.log 위치에 따라서 coms 가 변화하는 것이 다르다.
값이 변화 하는 위치가 다르기 때문이다. 함수가 다 끝난 후에 값에 대한 변화가 일어 난다.
> 왜 setState는 비동기로 처리 되는가?
## useEffect
- If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
- If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument
- 상태가 바뀔때, 업데이트 전에도, 리렌더링 될때 마다도 작업을 할 수 있다.
useEffect([]) 이 있다면 마운트 될때 만 작동
componentWillMount - render - componentDidMount 합친 과정이라고 생각하면 된다.
- useEffect() 라면 렌더링 될때만 작동한다.
1. componentDidMount를 useEffect를 사용하여 대체할 수 있나요?
useEffect(fn, []) 를 대체하여 사용할 수는 있지만, 그렇다고componentDidMount와 useEffect가 완전히 똑같은 것은 아닙니다. useEffect는 componentDidMount와 달리 props와 state를 capture 할 수 있고, 그 안에 있는 callback에서는 초기 props값과 초기 state값을 볼 수 있습니다.
예를 들어, 만약 당신이 맨 마지막에 들어오는 값을 필요로 할 때, 당신은 ref 를 사용하여 코드를 작성할 수 있습니다.
하지만, 그럴 필요 없습니다. 더 간단하게 코드를 구조화 할 수 있는 방법이 있기 때문이죠. 여기서 이 문제를 해결하기 위해 당신이 꼭 명심해야 할 것은effect의 기본적인 모델은 componentDidMount,그 이외의 lifecycle의 기본적인 모델과 다르다는 것 입니다. 결론적으로 그 둘 사이의 정확히 일치하는 공통점을 찾으려고 하는 것은 당신이 혼란스러워 할 수 있는 일이 될 수 있다는 이야기입니다.
따라서, 생산적인 코드를 작성하려면, "think in effects"하세요.
effects의 기본 모델은 Lifecycle 이벤트에 대응하는 것보다 동기화를 구현하는데 가까운 모델이라고 할 수 있습니다.
2. useEffect 안에서 데이터를 어떻게 올바르게 fetch 할 수 있나요? 그리고 [] 이건 뭔가요?
이 글이 useEffect를 사용하여 데이터를 fetch 하는데 좋은 발판이 될 수 있을 것입니다. 이 글을 확실히 끝까지 읽으세요! 제가 지금 번역하고 있는 이 글보다 길지 않습니다.
[] 이 빈 대괄호의 뜻은 effect가 React 앱 안에서 데이터 흐름을 구성하고 있는 어떤 값을 아무것도 사용하고 있지 않다는 의미입니다. 그래서 궁금하면 그냥 한 번 써봐도 괜찮습니다. 하지만 이것은 흔하게 버그를 일으킬 수 있는 코드 이기도 합니다.
여러분은 다른 전략(? strategies)를 공부해봐도 좋을 것 같습니다. (주로 useReducer나 useCallback)
that can remove the need for a dependency instead of incorrectly omitting it. (한국어로 어떤 느낌으로 번역해야 할 지 모르겠습니다..)
3. 함수를 effect에 의존되게 작성해야 할까요?
추천하는 방법으로는, props나 state가 필요하지 않는 함수들은 컴포넌트 외부에서 호이스팅 하고, effect 안에서는 그 effect 안에서만 사용되는 것들로만 구성해야 합니다.
만약, 여러분이 만든 effect가 render 범위 안에서 계속 사용된다면, effect들이 정의된 곳에서 useCallback으로 감싼 뒤, 계속 진행하세요.
4. 왜 가끔가다 refetching loop에 걸리는 것 일 까요?
이것은 두번째 인자를 넣지 않고 effect를 사용하여 데이터를 fetch 할 때 일어날 수 있는 일 입니다. 두번째 인자가 없다면, render가 모두 마친 뒤에 effect가 실행되며, state를 설정하면 다시 effect는 트리거 될 것 입니다. 또한, 계속해서 바뀌는 값을 array에 넣어도 infinite loop가 발생합니다. 아마 하나 하나씩 제거하면서 찾아야 어떤 것이 문제였는지 알 수 있을 것 입니다.
하지만, 그렇게 하나하나씩 제거하는 것은 때때로 잘못된 수정방법이 될 수 있습니다. 그 방법 대신 근본적인 부분부터 고쳐나가세요.
예를 들어, 먼저 A 함수가 그 문제를 일으킨다면, 그 A 함수를 effects 안에 넣거나, 호이스팅 하거나, 또는 useCallback으로 감싸는 것이 도움이 될 수 있을 것 입니다.
object를 새로 재생성 하지 않으려면, useMemo를 유사한 용도로 사용할 수 있습니다.
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
function LifecycleDemo() {
// It takes a function
useEffect(() => {
// This gets called after every render, by default
// (the first one, and every one after that)
console.log('render!');
// If you want to implement componentWillUnmount,
// return a function from here, and React will call
// it prior to unmounting.
return () => console.log('unmounting...');
})
return "I'm a lifecycle demo";
}
function App() {
// Set up a piece of state, just so that we have
// a way to trigger a re-render.
const [random, setRandom] = useState(Math.random());
console.log(random);
// Set up another piece of state to keep track of
// whether the LifecycleDemo is shown or hidden
const [mounted, setMounted] = useState(true);
// This function will change the random number,
// and trigger a re-render (in the console,
// you'll see a "render!" from LifecycleDemo)
const reRender = () => setRandom(Math.random());
// This function will unmount and re-mount the
// LifecycleDemo, so you can see its cleanup function
// being called.
const toggle = () => setMounted(!mounted);
return (
<>
<button onClick={reRender}>Re-render</button>
<button onClick={toggle}>Show/Hide LifecycleDemo</button>
{mounted && <LifecycleDemo/>}
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
## useCallBack
useCallback 은 useMemo와 상당히 비슷한 함수입니다. 주로 렌더링 성능을 최적화해야 하는 상황에서 사용하는데요, 이 Hook을 사용하면 이벤트 핸들러 함수를 필요할 때만 생성 할 수 있습니다.
우리가 방금 구현한 Average 컴포넌트를 보면, onChange 와 onInsert 라는 함수를 선언해주었습니다. 이렇게 선언을 하게 되면 컴포넌트가 리렌더링 될 때마다 이 함수들이 새로 생성됩니다. 대부분의 경우에는 이러한 방식이 문제가 되지 않지만, 컴포넌트의 렌더링이 자주 발생하거나, 렌더링 해야 할 컴포넌트의 개수가 많아진다면 이 부분을 최적화 해주시는 것이 좋습니다.
useCallback 의 첫번째 파라미터에는 우리가 생성해주고 싶은 함수를 넣어주고, 두번째 파라미터에는 배열을 넣어주면 되는데 이 배열에는 어떤 값이 바뀌었을 때 함수를 새로 생성해주어야 하는지 명시해주어야 합니다.
만약에 onChange 처럼 비어있는 배열을 넣게 되면 컴포넌트가 렌더링 될 때 단 한번만 함수가 생성되며, onInsert 처럼 배열 안에 number 와 list 를 넣게 되면 인풋 내용이 바뀌거나 새로운 항목이 추가 될 때마다 함수가 생성됩니다.
const changeDate = useCallback( ( e ) => {
const{ name,value } = e.target;
if( name === "to" && new Date(range.from) > new Date(value) ){
alert("날짜가 앞섭니다.");
}
setRange({
...range,
[ name ]:value
})
}, [ range ]);
// 그냥 [] 으로 하게 되면 처음 렌더링 될 때만 함수 생성이 된다.
// range 를 적게 되면
## useRef
컴포넌트 안의 변수 만들기 그런데 변수를 만들면 리렌더링 하게 되면 그 변수 값은 초기화 값이 된다. 그래서 계속 유지하기 위해서 useState 를 사용하게 된다. 그런데 state 를 바꾸면 component가 리렌더링 하게 된다.
근데 굳이 리렌더링 할때 마다 할 이유기 없는 값 > setTimeout, setInterval 의 id , 외부라이브러리를 사용하여 생성된 인스턴스,
> useRef는 바뀌어도 component가 리렌더링 되지 않는다.
const [users,setUsers] = useState([
{
id:1,
username: 'lee1',
email:'test@name'
},
{
id:2,
username: 'lee2',
email:'test2@name'
},
{
id:3,
username: 'lee3',
email:'test3@name'
}
]);
const nextId = useRef(4);
//useRef 4가 바뀐다고 컴퍼넌트가 리렌더링 되지 않는다.
const onCreate =() =>{
const user ={
id:nextId.current,
username,
email,
}
...( 중략 )
import _ from 'lodash';
const onchange = useCallback( ( e ) => {
const { name,checked } = e.target;
if( !checked ){
const clonning = _.cloneDeep( coms )
_.remove( clonning, ( clone ) => clone === name );
setComs( clonning );
}else{
setComs([ ...coms, name ]);
}
}, [ coms ] );
useEffect(() => {
console.log("useEffect");
return
})
useCallback(()=>{
console.log("useCallback")
})
## [] 부분 정리
const [ num, setNum ] = useState(0)
let bb = 0;
console.log(num);
useEffect(()=>{
console.log("mount");
return console.log("unMount")
},[])
const onPress = useCallback(() =>{
console.log("useCallBack")
bb++;
setNum( num + bb );
console.log(num)
},[])
result
> 버튼을 눌렀을 때
useCallBack
App.js:21 0
App.js:10 1
App.js:18 useCallBack
App.js:21 0
App.js:10 2
App.js:18 useCallBack
App.js:21 0
App.js:10 3
결국 [] 하게 된다면 onPress() 는 mount 될 당시에 한번만 생성되게 된다. 그렇게 된다면 num 라는 값은 onPress에 종속되어 있는 값으로 설정되게 된다. 그래서 onPress() 에서의 num 과 useState() 안에 있는 num 을 같게 보면 안된다. class component 에서 this 로 구분 지었던 것과 같은 것이라고 생각하면 된다. 그렇기 때문에 [ num ] 을 쓰게 된다면 num 은 새로운 값으로 갱신 된다. 새로운 변수가 생긴는 것이라고 생각해도 좋지만 ( let _num = num 은 아니다 )
[ num ] 을 하게 되면 함수를 새롭게 만들게 된다.
const onPress = useCallback(() =>{
console.log("useCallBack")
//bb++;
//setNum( num + bb );
console.log(num)
},[])
App.js:18 useCallBack
App.js:21 0
App.js:18 useCallBack
App.js:21 0
결국 state 가 변화하지 않는다면 렌더링은 되지 않는다.
useEffect(()=>{
console.log("mount");
return console.log("unMount")
})
> 리렌더링 될때 호출 된다.
### 결론
[] 비어있다면 연관되어 있는 값이 없기 때문에 처음 마운트 될때 생성되고 존재한다.
[]가 없다면 리렌더링 될때 마다 새로 호출하게 된다.
[ value ] 가 있다면 value 값이 변할 때 마다 새로 함수가 생성된다.
함수 안에 있는 값은
function testStore(){
const [ num, setNum ] = useState(0);
console.log("check");
// rerendering 이 될때만 실행 하게 된다.
const onClick = () => {
//setNum( num + 1 );
}
return(
<div>
<h1>hi</h1>
<button onClick={onClick} />
</div>
)
}
console.log( "check") 값은 리렌더링 될때 다시 실행 하게 된다.
## useRef
const idReference = document.getElementById('id')
와 같이 특정 태그에 접근 하기 위해서 사용하는 경우가 있다. 많은 태그가 존재할 때 관리하기 힘들수 있다. 그래서 useRef() 훅을 통해서 쉬게 접근 할 수 있다. current를 통해서 해당 값에 접근 할 수 있습니다.
function App() {
let [name, setName] = useState("Nate");
let nameRef = useRef(null);
const submitButton = () => {
setName(nameRef.current.value);
};
return (
<div className="App">
<p>{name}</p>
<div>
<input ref={nameRef} type="text" />
<button type="button" onClick={submitButton}>
Submit
</button>
</div>
</div>
);
}
useRef(null) 로 설정하는 이유
> Ref.current is null because the ref is not set till after the function returns and the content is rendered
const refContainer = useRef(initialValue);
How can I measure a DOM node?
In order to measure the position or size of a DOM node, you can use a callback ref. React will call that callback whenever the ref gets attached to a different node. Here is a small demo:
> 참고
https://ko.reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
'FE > React & RN' 카테고리의 다른 글
[ React-Native ] 시작 (0) | 2019.10.21 |
---|---|
[ React ] Redux (0) | 2019.09.04 |
[ React ] Context API (0) | 2019.08.18 |
[React] 억음 표시 ( grave accent ) ` (0) | 2019.08.03 |
[React] 조건부 렌더링 (0) | 2019.08.03 |
댓글