일반적으로 생성된 DOM 노드 또는 컴포넌트에 직접 접근할 때 주로 사용된다.
current속성을 이용하여 접근한다.
state와 다르게 값을 변경해도 재랜더링 되지 않는다.
따라서 화면에 보여주기 위한 용도로는 적합하지 않다.
forwardRef()
ref 속성을 이용해서 자식 컴포넌트를 참조할 때 사용된다.
부모 컴포너트에서 자식 컴포넌트 접근시 ref 속성을 이용해야한다.
ref는 일반 Props로 처리되지 않기 때문에 forwardRef()를 사용하여 자식 컴포넌트를 감싸야한다.
useImperativeHandle()
부모가 호출할 자식함수를 지정하기 위해서는 forwardRef 함수 내에서 useImperativeHandle()로 감싸야한다.
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import {React, forwardRef, useRef, useImperativeHandle} from 'react';
const Box = forwardRef((props, ref)=>{
// 반드시 forwardRef로 감싸로 ref 받음
const modal = useRef();
useImperativeHandle(ref, () => ({ open})); //부모에게 노출
function open(){ // 부모가 호출할 함수
modal.current.showModal(); // 모달창 띄우기
}
const handleClose = () => {
modal.current.close();
}
return (
<>
<dialog ref={modal}>
<h2>모달창</h2>
<form>
아이디: <input></input>
비번: <input></input>
<button onClick={handleClose}>close</button>
</form>
</dialog>
</>
)
})
function App(props) {
const box = useRef();
function show_modal(){
box.current.open();
}
return (
<div>
<button onClick={show_modal}>모달창 보기</button>
<Box ref={box}></Box>
</div>
);
}
export default App;
ref.current => null이 된다.
따라서 참조하는 태그가 사라졌을 경우 current.value와 같이 참조하려는 경우 널포인터 에러 발생. 예외처리 필요
import React, { useRef, useState } from 'react';
function App(props) {
const inputRef = useRef();
const [showText, setShowText] = useState(true);
return (
<div>
{/* {showText?<input type='text'></input>:null} */}
{showText && <input type='text' ref={inputRef}></input>}
<button onClick={() => setShowText(()=>!showText)}>텍스트 보이기/가리기</button>
<hr></hr>
<button onClick={()=>inputRef.current.focus()}>텍스트 1로 이동 에러</button>
<button onClick={()=>inputRef.current&&inputRef.current.focus()} >텍스트로 이동2</button>
<button onClick={()=>showText&&inputRef.current.focus()} >텍스트로 이동3</button>
</div>
);
}
export default App;
계층(트리) 구조의 자손들에게 공통된 데이터를 Prop Drilling 중복코드 없이 전달하고자 할 때 유용하게 사용할 수 있다.
하나의 값만 전달이 가능하므로 여러개의 값 전달을 위해선 배열이나 json객체를 사용한다.
기본적으로 createContext로 생성한 컨텍스트를 사용하기 위해서는 자손컴포넌트에서도 해당 컨텍스트를 임포트할 수 있어야한다.
따라서 const UserContext = createContext({}); 이런 createContext 구문은 따로 파일로 빼놓는 것이 적절하다.
이러한 트리구조에서 props를 계속 상속하는 형식 Prop Drilling은 중복이 많아지게 된다.
useContext hook을 이용해 중복을 줄일 수 있다.
(생성)createContext
(자손으로 전달)<컨텍스트이름.Provider value={값}>
(자손에서 사용)useContext(컨텍스트이름)

export const UserContext = createContext(""); <UserContext.Provider value={userName}>
<Profile/>
</UserContext.Provider>
const username = useContext(UserContext)
로직 :
App에서는 카트리스트 배열을 useState로 관리
문자열을 인자로 받아서 카트리스트에 추가하는 메서드 생성
App에서 CartModel컴포넌트를 호출
- useContext에 카트리스트를 담아서 전달
- 카트추가 메서드 전달
CartModel에서 input 태그를 useRef로 참조
버튼 클릭시 input태그를 참조하는 useRef의 current.value 값을 인자로 카트추가메서드 호출
Cart컴포넌트 호출
Cart컴포넌트에서는 useContext로 값을 받아서 map으로 순회하여 리스트 출력
여기서 활용된 개념은 부모->자식으로 이벤트메서드를 전달하여 사용하게끔 하는 기법과
useContext를 이용하여 props drilling 없이 자식에게 데이터 전달하는 방식을 사용했다.
코드 :
App.js
import React, { useState } from 'react';
import { CartContext } from './store/cart-context';
import CartModel from './components/CartModel';
function App(props) {
const [shoppingCart, setShoppingCart] = useState([]);
function handleAddItemtoCart(cart){
console.log("카트추가: ", cart);
setShoppingCart([...shoppingCart, cart]);
}
return (
<div>
<CartContext.Provider value={shoppingCart}>
<CartModel onAddtoCart={handleAddItemtoCart}></CartModel>
</CartContext.Provider>
</div>
);
}
export default App;
CartModel.js
import React, { useContext, useRef } from 'react';
import Cart from './Cart';
function CartModel(props) {
const {onAddtoCart} = props;
const cart = useRef();
return (
<div>
<input type='text' ref={cart}></input>
<button onClick={()=>onAddtoCart(cart.current.value)}>Add to Cart</button>
<ul>
<Cart></Cart>
</ul>
</div>
);
}
export default CartModel;
Cart.js
import React, { useContext } from 'react';
import { CartContext } from '../store/cart-context';
function Cart(props) {
const list = useContext(CartContext);
return (
<div>
{list.map((v, i)=>{
return (
<li key={i}>
{v}
</li>
)
})}
</div>
);
}
export default Cart;
실습 1코드에 useContext에 카트리스트와 이벤트함수를 담아서 넘길거임
하지만 하나의 값만 넘길 수 있으므로 json 객체에 담아서 전달할 것
주의점 : 여러개의 값을 가진 json를 데이터로 전달받기 때문에 객체분해할당을 이용해 key값을 일치시켜 값을 받은뒤 사용하면 편리함
import React, { useState } from 'react';
import { CartContext } from './store/cart-context';
import CartModel from './components/CartModel';
function App(props) {
const [shoppingCart, setShoppingCart] = useState([]);
const ctxValue = {
items: shoppingCart,
addItemToCart : handleAddItemtoCart
};
function handleAddItemtoCart(cart){
console.log("카트추가: ", cart);
setShoppingCart([...shoppingCart, cart]);
}
return (
<div>
<CartContext.Provider value={ctxValue}>
<CartModel></CartModel>
</CartContext.Provider>
</div>
);
}
export default App;
import React, { useContext, useRef } from 'react';
import Cart from './Cart';
import { CartContext } from '../store/cart-context';
function CartModel(props) {
const {addItemToCart} = useContext(CartContext);
const cart = useRef();
return (
<div>
<input type='text' ref={cart}></input>
<button onClick={()=>addItemToCart(cart.current.value)}>Add to Cart</button>
<ul>
<Cart></Cart>
</ul>
</div>
);
}
export default CartModel;
import React, { useContext } from 'react';
import { CartContext } from '../store/cart-context';
function Cart(props) {
const {items} = useContext(CartContext);
return (
<div>
{items.map((v, i)=>{
return (
<li key={i}>
{v}
</li>
)
})}
</div>
);
}
export default Cart;
import React, { useContext } from 'react';
import { createContext } from 'react';
function Profile(){
console.log("Profile render");
return <Greeting></Greeting>;
}
function Greeting(){
const theme = useContext(ThemeContext);
const username = useContext(UserContext);
console.log(theme, username);
return (
<div>
<ul>
<li>{theme}</li>
<li>{username}</li>
</ul>
<p style={{color: theme==="dark"?"gray":"green"}}>
{`${username}님 안녕하세요?!`}
</p>
</div>
)
}
const UserContext = createContext();
const ThemeContext = createContext();
function App(props) {
return (
<div>
<ThemeContext.Provider value={"light"}>
<UserContext.Provider value={"mike"}>
<Profile></Profile>
</UserContext.Provider>
</ThemeContext.Provider>
</div>
);
}
// const UserContext = createContext({});
// function App(props) {
// const v1 = {name:"mike", theme:"light"}
// return (
// <div>
// <UserContext.Provider value={v1}>
// <Profile></Profile>
// </UserContext.Provider>
// </div>
// );
// }
export default App;


요약 :useState와 동일하게 상태 관리할 때 사용
단, 상태(변수) 변경하는 로직들을 컴포넌트와 분리해서 구현해놓을 수 있다.
(생성)
1. reducer 메서드 구현
2. const [state, dispatch] = useReducer(reducer메서드, 초기값);
(사용)
1. action 구현 {type:"키워드", value:"전달값"}
{ type:'CHANGE_INPUT', key:'email', value:'zzzz@daum.net’}
2. dispatch(action)
요약2 : dispatch에 키워드와 전달값을 담은 액션을 인자로 주면 reducer에서 키워드에 맞게 값을 수정해서 상태변경 및 랜더링을 해주는 기능을 한다. reducer라는 사용자지정 필터를 내재하는 useState 느낌이라고 보면 될 것 같다.
특징 :
state값이 변경되면 자동으로 변경을 해줌
current_state값은 임의로 넣어주지 않아도 자동으로 매핑해서 들어간다.
사용 팁:
먼저 useState나 useRef를 통해 로직 개발을 하고, 후에 useReducer를 통해 코드를 간결화 하는 방식으로 차차 익숙해지는게 좋을 것 같다.
import React, { useReducer } from 'react';
/*
https://react.vlpt.us/basic/20-useReducer.html
상태관리방법
가. useState
나. useReducer : 컴포넌트와 상태 업데이트 로직을 분리 가능하도록 구현이 가능하다.
- 아키텍쳐
액션 => 리듀서 => 상태값(state)
<======뷰(이벤트 발생)<=======
1. reducer는 현재 상태값인 state와 action인자를 받아서새로운 nextState값을 반환함
import {useReducer} from 'react';
const reducer = {state, action}=>{
return nextState;
}
2. action의 형태는 다음과 같으면 dispatch함수로 전달한다.
{
type:'increment'
}
{
type: 'CHANGE_INPUT',
key: 'email',
value: 'ZZZ@daum.net'
}
{
type: 'ADD_TODO',
todo: {
id:1,
pw:2
}
}
//////////////////////////
실제 사용 방법은 다움과 같다
const [state, dispatch] = useReducer(reducer, initialState);
const onIncrease = (){
dispatch({type: 'INCREMENT'});
}
*/
function App(props) {
////////////////
const reducer = (current_state, action)=>{
switch (action.type){
case "INCREMENT":
console.log("increment");
return (++current_state);
case "DECREMENT":
console.log("decrement");
return (current_state-=1);
default:
console.log("default");
return current_state;
}
}
/////////////////
const [number, dispatch] = useReducer(reducer, 0);
const onIncrease = () =>{
dispatch({type:"INCREMENT"});
}
const onDecrease = () =>{
dispatch({type:"DECREMENT"});
}
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
);
}
export default App;
useState와 useReducer의 개념적인 차이점은
useState는 값의 변경 로직을 컴포넌트 내에서 한다는 것이고,
useReducer는 값의 변경 로직을 reducer라는 콜백함수 내에서 구현하여 컴포넌트와 독립적으로 구현할 수 있다는 점이다.
import React, { useReducer, useState } from 'react';
function App(props) {
const reducer = function(current_state, action){
console.log();
switch (action.type) {
case "ADD":
return [...current_state, action.xxx];
case "DEL":
return [...current_state.filter((v,i) => v !== action.xxx)]
default:
return current_state;
}
}
const [item, setItem] = useState("");
const [items, dispatch] = useReducer(reducer, []);
function handleChange(e){
setItem(()=>e.target.value);
}
function onIncrease(){
// setItems(()=>[...items, item])
dispatch({type:"ADD", xxx:item});
}
function onDecrease(){
// const items2 = items.filter((v, i)=>{
// return (v!=item);
// })
// setItems(()=>items2);
dispatch({type:"DEL", xxx:item});
}
return (
<div>
<input type='text' value={item} onChange={handleChange}></input>
<button onClick={onIncrease}>추가</button>
<button onClick={onDecrease}>삭제</button>
<hr/>
{items}
</div>
);
}
export default App;
useRef버전 코드
import React, { useReducer, useRef, useState } from 'react';
function App(props) {
const reducer = function(current_state, action){
switch (action.type) {
case "ADD":
return [...current_state, action.value];
case "DEL":
return current_state.filter((v,i)=>v!==action.value);
default:
return current_state;
}
}
const [items, dispatch] = useReducer(reducer, []);
const item = useRef();
// const [items, setItems] = useState([]);
function onIncrease(){
dispatch({type:"ADD", value:item.current.value});
}
function onDecrease(){
dispatch({type:"DEL", value:item.current.value});
}
return (
<div>
<input type='text' ref={item}></input>
<button onClick={onIncrease}>추가</button>
<button onClick={onDecrease}>삭제</button>
<hr/>
{items}
</div>
);
}
export default App;