Techniques for dealing with REACT_finally, Hooks

the Other Object·2023년 3월 27일
0
  • 함수컴포넌트에서도 상태관리할수있다 : useState
  • 렌더링 직후 작업을 설정하는 : useEffect
  • 리액트 내장 Hooks 사용하기 → 커스텀 Hooks 만들기

useState

  • 가장 기본적인 훅
  • 함수컴포넌트에서도 가변적인 상태를 가질 수 있게 해줌
//useState 사용해서 숫자카운터 구현하기
//Counter.js

import React, {useState} from 'react';

const Counter = () => {
  
  //useState함수의 파라미터에 상태의 기본값 넣어줌 '0'으로 설정
  // 이 함수가 호출되면 배열을 반환한다, 그 배열의 첫번째 원소는 상태값, 두번째 원소는 상태를 설정하는 함수
  // 이 함수에 파라미터를 넣어서 호출하면 전달받은 파라미터로 값이 바뀌고 컴포넌트가 정상 리렌더링된다.
  const [value, setValue] = useState(0);
  
  const plusBtn = () => {
    setValue(value +1)
  }
  
  const minusBtn = () => {
    setValue(value -1)
  }
  
  return (
    <div>
      <p>
    	현재 카운터 값은 <b>{value}</b>입니다.
      </p>
	  <button
		onClick={plusBtn}
      > +1 </button>
	  <button
		onClick={minusBtn}
      > -1 </button>
    </div>
  );
};

export default Counter;
//App.js

import Counter from './Counter';

const App = () => {
  return <Counter />
}
  
export default App;

useState를 여러번 사용하기

  • 하나의 useState함수는 하나의 상태값만 관리할 수 있따.
  • 컴포넌트에서 관리해야 할 상태가 여러개라면 useState를 여러번 사용하면 됨.
//Info.js

import React, {useState} from 'react';

const Info = () => {
  
  const [name, setName] = useState('');
  const [nickName, setNickName] = useState('');
  
  const onChangeName = (e) => {
    setName(e.target.value);
  }
  
  const onChangeNickName = (e) => {
    setNickName(e.target.value);
  }


  return (
    <div>
       <div>
    	  <input 
    		 value={name}
			 onChange={onChangName} />
    	  <input 
    		 value={nickName}
			 onChange={onChangNickName} />
          <div>
             <b>이름:</b> {name}   
          </div>
          <div>
             <b>닉네임:</b> {nickName}  
          </div>
       </div>
    </div>
  )
}

export default Info;
//App.js

import Info from './Info';

const App = () => {
  
  return <Info />;
};

export default App;

useEffect

  • useEffect는 리액트컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다.
  • componentDidMount 와 componentDidUpdate 를 합친형태
  • componentDidMount : 컴포넌트를 만들고 첫렌더링을 다 마친 후 실행 됨, 이 안에서 다른 자바스크립트라이브러리 또는 프레임워크의 함수를 호출하거나 이벤트등록, setTimeout, setInterval, 네트워크요청 같은 비동기작업 처리
  • componentDidUpdate(prevProps, prevState, snapshot){} : 리렌더링 완료 후 실행, DOM관련 처리가능, prevProps 또는 prevState 사용해서 컴포넌트가 이전에 가졌던 데이터에 접근가능, getSnapshotBeforeUpdate에서 반환한 값이 있다면 여기서 snapshot값을 전달 받을 수 있다.
//Info.js

import React, {useState, useEffect} from 'react';

const Info = () => {
  
  const [name, setName] = useState('');
  const [nickName, setNickName] = useState('');
  
  useEffect(() => {
    console.log('렌더링이 완료되었습니다');
    console.log({
      name,
      nickName
    })
  })
  
  const onChangeName = (e) => {
    setName(e.target.value);
  }
  
  const onChangeNickName = (e) => {
    setNickName(e.target.value);
  }


  return (
    <div>
       <div>
    	  <input 
    		 value={name}
			 onChange={onChangName} />
    	  <input 
    		 value={nickName}
			 onChange={onChangNickName} />
          <div>
             <b>이름:</b> {name}   
          </div>
          <div>
             <b>닉네임:</b> {nickName}  
          </div>
       </div>
    </div>
  )
}

마운트 될 때만 실행하고 싶을 때

  • useEffect에서 설정한 함수를 컴포넌트가 화면에 맨 처음 렌더링 될 때만 실행하고, 업데이트 될 때는 실행하지 않으려면??
  • 함수의 두번째 파라미터에 빈 배열을 넣어주면 된다.
//Info.js

import React, {useState, useEffect} from 'react';

const Info = () => {
  
  (...)
  
  useEffect(() => {
    console.log('마운트 될 때만 실행됩니다.');
  }, [])
  
  (...)
}

특정 값이 업데이트 될 때만 실행하고 싶을 때

  • useEffect를 사용할 때, 특정 값이 변경 될 때만 호출
  • 클래스형이라면?
    componentDidUpdate(prevProp, prevState) {
    if(prevProps.value !== this.props.value) {
    doSomething();
    }
    }
    : props 안에 들어있는 value값이 바뀔 때만 특정작업 수행한다, 이걸 useEffect에서 해야한다면??
  • useEffect의 두번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값ㅇ르 넣어주면 된다.
//Info.js

import React, {useState, useEffect} from 'react';

const Info = () => {
  
  (...)
  
  useEffect(() => {
    console.log(name);
  }, [name])
  
  (...)
}

- 배열 안에는 useState를 통해 관리하고 있는 상태를 넣어줘도 되고, props로 전달받은 값을 넣어줘도 된다.

cleanup함수 (뒷정리)

  • useEffect는 기본적으로 렌더링 된 직후마다 실행되고,
  • 두번째 파라미터 배열에 뭘 넣는지에 따라 실행되는 조건이 달라진다.
  • 컴포넌트가 언마운트 되기 전이나 업데이트되기 직전에 어떤 작업을 수행하고 싶다면, useEffect에서 cleanup함수를 반환해줘야함.
useEffect (() => {
  console.log('effect');
  console.log(name);
  return () => {
    console.log('cleanup');
    console.log(name);
  }
}, [name])
//App.js

import React, {useState} from 'react';
import Info from './Info';

const App = () => {

  const [visible, setVisible] = useState(false);
  
  const handleClickVisible = () => {
    setVisible(!visible);
  }

  return (
    <div>
    	<button
    		onClick={handleClickVisible}
    	>
		    {visible ? '숨기기' : '보이기'}
    	</button>
		{visible && <Info />}
    </div>
  );
};

export default App;

* 업데이트할때마다 발생하는 effect 와 cleanup
* 컴포넌트가 나타날 때 콘솔에 : effect
* 사라질 때 : cleanup
* 렌더링 될 때마다 뒷정리함수가 계속 나타난다. 클린업함수가 호출될 때는 업데이트되기 직전 값을 보여준다.
* 오직 언마운트 될 때만 클린업함수를 호출하고 싶다면 useEffect함수의 두번째 파라미터에 비어있는 배열을 넣으면 된다.
* (언마운트 : 페이지에서 컴포넌트가 사라짐)

useEffect(() => {
  console.lof('effect');
  return () => {
    console.log('unmount');
  }
}, []);

useReducer

  • useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hook
  • Reducer는 현재상태, 그리고 업데이트를 위해 필요한 정보를 담은 action값을 전달받아 새로운 상태를 반환하는 함수
  • 리듀서함수에서 새로운 상태를 만들 떄는 반드시 불변성을 지켜줘야한다.
function reducer(state, action) {
  return {...}
}
  
{
  type: 'INCREMENT',
}

useCallback

  • 주로 렌더링 성능을 최적화해야 하는 상황에서 사용한다. 만들어놨던 함수를 재사용할 수 있다.
  • useCallback의 첫번째 파라미터 : 생성하고 싶은 함수를 넣고
  • 두번째 파라미터 : 배열
  • 함수 내부에서 상태 값에 의존해야 할 때는 그 값을 반드시 두번째 파라미터 안에 포함
  • 예를들면, onChange : 기존 값을 조회하지 않고 바로 설정만 해서 배열이 비어도 상관없으나,
  • onInsert : 기존의 number와 list를 조회해서 nextList를 생성하기 때문에 배열 안에 number와 list를 꼭 넣어주어야한다.
//Average.js

import {useState, useMemo, useCallback} from 'react';

const getAverage = number => {
  console.log('평균값 계산 중');
  if (numbers.length === 0) {
    return 0;
  }
  const sum = numbers.reduce((a,b) => a+b);
  
  return (
    sum/numbers.length;
  )
}

const Average = () => {
  
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('')
  
  const onChange = useCallback(e => {
    // useCallback의 첫번째 파라미터 = 생성하고 싶은 함수
    // 두번째 파라미터 : 배열(이 배열에는 어떤값이 바뀌었을때 함수를 새로 생성해야하는지 명시해야한다.)
    setNumber(e.target.value);
  //(1) 빈배열넣으면 : 컴포넌트가 렌더링될때 만들었던 함수를 계속해서 재사용하게 되며
  }, []);  //컴포넌트가 처음 렌더링 될 때만 함수 생성
  
  const onInsert = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber('');
  //(2) 배열 안에 number와 list를 넣게되면 인풋내용이 바뀌거나 
    // 새로운 항목이 추가 될 때 새로 만들어진 함수를 사용하게 된다.
    // onInsert는 기존의 number와 list를 조회해서 nextList를 생성하므로 
    // 배열 안에 number와 list를 꼭 넣어줘야 한다.
  }, [number, list]);  //number 혹은 list가 바뀌었을 때만 함수 생성
  
  
  return (
    <div>
       <input 
    	  value={number}
		  onChange={onChange} />
       <button onClick={onInsert}> 등록 </button>
       <ul>
          {list.map((value, index) => (
            <li key={index}> {value} </li>
          ))}
       </ul>
       <div>
          <b> 평균값: </b> {avg}
       </div>
    </div>           
  )
}

export default Average;

useRef

  • useRef Hook은 함수컴포넌트에서 ref를 쉽게 사용할 수 있도록한다.
//Average.js 등록버튼 눌렀을 때 focus가 input쪽으로 넘어가도록 코드 짜보기

import {useRef} from 'react';

(...)
 
const Average = () => {

  (...)
   const inputEl = useRef(null);
  (...)
   return (
    <input value={number} onChange={onChange} ref={inputEl} />
    // useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트 가리킴
  )
}

로컬변수 사용하기

  • 컴포넌트 로컬변수를 사용해야 할 때도 useRef 활용 가능.
  • 로컬변수 : 렌더링과 상관없이 바뀔 수 있는 값을 의미한다.
//로컬변수를 사용해야할때 함수형컴포넌트는 이렇게 사용한다.

import React, {useState} from 'react';

const RefSample = () => {
  
  const id = useRef(1);
  const setId = (n) => {
    id.current = n;
  }
  const printId = () => {
    console.log(id.current);
  }
  
  
  return (
    <div>
       refsample
    </div>
  );
};

export default RefSample

// ref 안의 값이 바뀌어도 컴포넌트가 렌더링되지 않는다. 렌더링과 관련되지 않은 값을 관리할때만 사용
  • 리액트에서 Hooks패턴을 사용하면 클래스형 컴포넌트를 작성하지 않고도 대부분의 기능구현가능
//App.js

* App컴포넌트에서 todos배열에 새 객체를 추가하는 onInsert함수를 만들기
	- 이 함수에서는 새로운 객체를 만들 때마다 id값에 1씩 더해주어야 한다.
    - id값은 useRef를 사용하여 관리.
    - useState가 아닌 useRef를 사용하여 컴포넌트에서 사용할 변수를 만드는 이유는???
    - id값은 렌더링되는 정보가 아니기 때문이다.
    - 이 값은 화면에 보이지도 않고, 이 값이 바뀐다고 해서 컴포넌트가 리렌더링 될 필요도 없음
    - onInsert함수는 컴포넌트의 성능을 아낄 수 있도록 useCallback으로 감싸준다.
    - props로 전달해야 할 함수를 만들 때는 useCallback을 사용해서 함수를 감싸는 것이 좋다.

import {useState, useRef, useCallback} from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';

const App = () => {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: '리액트의기초알아보기',
      checked: true
    },
    {
      id: 2,
      text: '컴포넌트스타일링',
      checked: true
    },
    {
      id: 3,
      text: '일정관리앱만들기',
      checked: false
    }
  ]);
  
  const nextId = useRef(4);
  
  const onInsert = useCallback(
    text => {
      const tode = {
        id: nextId.current,
        text,
        checked: false
      };
      setTodos(todos.concat(todo));
      nextId.current += 1;
    }, [todos]);
  
  
  return(
    <TodoTemplate>
       <TodoInsert onInsert={onInsert} />
	   <TodoList todos={todos} />
    </TodoTemplate>
  )
};


export default App;
//TodoInsert.js

import {useState, useCallback} from 'react';
import {MdAdd} from 'react-icons/md';
import './TodoInsert.scss';

const TodoInsert = ( {onInsert} ) => {
  const [value, setValue] = useState('');
  const onChange = useCallback(e => {
    setValue(e.target.value);
  }, []);
  
  // onSubmit()함수 만들고 이를 form의 onSubmit으로 설정
  // 이 함수가 호출되면 props로 받아온 onInsert함수에 현재 value값을 파라미터로 넣어서 호출하고 현재 value값을 초기화한다. 추가로 onSubmit이벤트는 브라우저를 새로고침하는데, e.preventDefault()함수를 호춣서 새로고침을 방지함.
  // onSubmit 대신에 버튼의 onClick이벤트로도 처리가능.
  const onSubmit = useCallback(e => {
    onInsert(value);
    // setValue값 초기화
    setValue('');
    // submit이벤트는, 브라우저에서 새로고침을 발생시킴
    // 이를 방지하기 위해 이 함수를 호출한다.
    e.preventDefault();
  }, [onInsert, value])
  
  
  return (
    <form
       className="TodoInsert"
       onSumit={onSubmit}
    >
       <input 
		  placeholder='할일입력'
		  value={value}
		  onChange={onChange}
	   />
       <button
		  type="submit"
	   >
    	  <MdAdd/>
       </button>
    </form>
  );
};

export default TodoInsert;
// onSubmit 대신 onClick

  const onClick = useCallback(() => {
    onInsert(value);
    setValue('');
  }, [onInsert, value])

  return (
    <form className="TodoInsert">
       <input 
		  placeholder='할일입력'
		  value={value}
		  onChange={onChange}
	   />
       <button
		  onClick={onClick}
	   >
    	  <MdAdd/>
       </button>
    </form>
  );
};


- onClick이벤트로 가능한데 form과 onSubmit이벤트를 사용한 이유 ???
- onSubmit 이벤트 쓰면 Enter 때문에 onKeyPress 따로 안써도 됨
- 지우기 기능 : 배열의 불변성을 지키면서 배열원소를 제거해야할 경우, 배열 내장 함수인 filrer 사용
- filter함수 : 기존 배열은 그대로 둔 상태에서 특정 조건 만족하는 원소들만 따로 추출하여 새로운 배열을 만들어준다.

// filter 사용예제

const array = [1,2,3,4,5,6,7,8,9,10];
const biggerThanFive = array.filter(number => number > 5);

// 결과 : [6,7,8,9,10]

- filter함수에는 조건을 확인해주는 함수를 파라미터로 넣어줘야한다. 파라미터로 넣는 함수는 true/false 값을 반환해야하며, true 를 반환하는 경우만 새로운 배열에 포함된다.
* filter함수를 사용하여 onRemove함수를 작성하기
* App컴포넌트에 id를 파라미터로 받아와서 `같은 id를 가진 항목을` todos 배열에서 지우는 함수. 이 함수를 만들고 나서 TodoList의 props로 설정


//App.js

import {useState, useRef, useCallback} from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';

const App = () => {
  (...)
   
  const onRemove = useCallback(id => {
   setTodos(todos.filter(todo => todo.id !== id));
  }, [todos]);
  
  return(
    <TodoTemplate>
       <TodoInsert onInsert={onInsert} />
	   <TodoList todos={todos} onRemove={onRemove} />
    </TodoTemplate>
  )
};

export default App;
- TodoListItem에서 onRemove함수를 사용하려면, TodoList컴포넌트를 거쳐야 한다.
- props 로 받아온 onRemove함수를 TodoListItem에 그대로 전달하자,
  
//TodoList.js
  
import TodoListItem from './TodoListItem';
import './TodoList.scss';

const TodoList = ({todos, onRemove}) => {
  
  
  return (
    <div>
       {todos.map(todo => (
         <TodoListItem
        	todo={todo}
    		key={todo.id}
			onRemove={onRemove}
         />
       ))}
    </div>
  )
}
- '삭제'버튼을 누르면 TodoListItem에서 onRemove 함수에 현재 자신이 가진 id를 넣어서 삭제함수를 호출

//TodoListItem.js

import {MdCheckBoxOutlineBlank, MdCheckBox, MdRemoveCircleOutline} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';

const TodoListItem = ({todo, onRemove}) => {
  
  
  return (
    <div className="TodoListItem">
       <div className={cn('checkbox', {checked})}>
    	    {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
		    <div classname="text"> {text} </div>
       </div>
	   <div 
		  className="remove"
		  onClick={() => onRemove(id)}>
          	<MdRemoveCircleOutline/>
       </div>
    </div>
  )
};

export default TodoListItem

const App = () => {
  (...)
   
  const onToggle = useCallback(id => {
   setTodos(
   // 배열내장함수 map을 사용하여 특정 id를 갖고있는 객체의 checkde값을 반전시켜줌. 불변성을 유지하면서 특정 배열 원소를 업데이트해야할 때 map사용
   // map() : 배열을 전체적으로 새로운 형태로 변환하여 새로운 []을 생성해야할때 사용한다.
   // 딱 하나의 원소만 수정하는데 왜 map()함수를 사용했???
     todos.map(todo =>
   		todo.id === id ? {...todo, checked: !todo.checked} : todo,
    	// 삼항연산자 사용했다.
    	// todo.id 와 현재 파라미터로 사용된 id 값이 같을 때는 정해준 규칙대로 새로운 객체를 생성하지만, id값이 다를 때는 변화를 주지않고 처음 받아온 상태 그대로 반환한다는 것. 그렇기 때문에 map을 사용하여 만든 배열에서 변화가 필요한 원소만 업데이트되고 나머지는 그대로 남아있게 되는 것.
   )
  }, [todo])
  
  return(
    <TodoTemplate>
       <TodoInsert onInsert={onInsert} />
	   <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  )
};
// TodoList.js

import TodoListItem from './TodoListItem';
import './TodoList.scss';

const TodoList = ({todos, onRemove, onToggle}) => {
  
  return (
    <div className="TodoList">
      {todos.map(todo => (
  		<TodoListItem 
    		todo={todo}
			key={todo.id}
			onRemove={onRemove}
			onToggle={onToggle}
    	/>
  	  ))}
    </div>
  )
}

0개의 댓글