Hooks는 리액트 v16.8에 새로 도입된 기능으로서, 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 렌더링 직후 작업을 설정하는 useEffect 등의 기능등을 제공하여 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해준다.
if(counter) {
const [sample, setSample] = useState(0);
}
useState를 조건문 안에서 호출하면 안된다는 Error
window.onload = function () {
useEffect(() => {
// do something...
}, [counter]);
}
window.onload라는 함수에서 useEffect를 호출하면 안 된다는 Error
react에서 사용자의 상태에 따라, 화면을 바꿔주기 위해 사용되는 트리거 역할을 하는 변수
react의 state를 감시하고, 바뀐 정보에 따라 화면을 표시해준다.
(state와 setState의 반환값을 비교)
// React에 기본적으로 내장되어 있는 useState 훅을 사용하면, state를 만들 수 있다.
import { useState } from "react";
// const [state, state변경함수] = useState(기본 state값);
const [isLoggedIn, setIsLoggedIn] = useState(false);
// 전에 만든 "isLoggedIn" state의 값을 true로 변경한다.
setIsLoggedIn(true);
// ** useState함수를 사용해 만든 "state 변경 함수"를 사용하여야 한다.
리액트 컴포넌트가 랜더링 될 때마다 특정 작업을 수행하도록 설정할 수 있다.
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 (
(...)
);
};
export default Info;
만약 useEffect에서 설정한 함수가 컴포넌트가 화면에 처음 랜더링 될 때만 실행되고 업데이트 할 경우에는 실행 할 필요가 없는 경우엔 함수의 두번째 피라미터로 빈 배열을 넣으면 된다.
useEffect(() => {
console.log('마운트 될 때만 실행됩니다.');
}, []);
useEffect(() => {
console.log(name);
}, [name]);
배열 안에는 useState을 통해 관리하고 있는 상태를 넣어줘도 되고, props로 전달받은 값을 넣어줘도 된다.
useEffect : 비동기 방식
useLayoutEffect : 동기 방식 (끝날때까지 react가 기다려줌)
// newContext.js
import { createContext } from "react" // createContext 함수 불러오기
// context안에 homeText란 변수를 만들고, 공백("") 문자를 저장한다.
const newContext = createContext({
homeText: "",
})
// App.js
import React from "react";
import Home from "./Home"; // 자식 컴포넌트 불러오기
import { newContext } from "./newContext"; // context 불러오기
export default function App() {
// context에 저장할 정보를 입력한다.
const homeText = "is Worked!"
// NewContext.Provider로 우리가 만든 context를 사용할 부분을 감싸준다.
return (
<newContext.Provider value={{ homeText }}>
<Home
</newContext.Provider>
);
}
// Home.js
import React from "react";
import { useContext } from "react";
import { newContext } from "../newContext";
export default function Home() {
// useContext hook 사용해서, newContext에 저장된 정보 가져오기
const { homeText } = useContext(newContext);
// 불러온 정보 사용하기!!
return (
<React.Fragment>
<p>{homeText}<p>
</React.Fragment>
);
}
리액트에서 context 없이 변수나 함수를 다른 컴포넌트로 전달하려면 부모자식간에만 전달이 가능하므로, 컴포넌트가 많아질수록 불필요한 컴포넌트를 많이 거쳐야한다. (props drilling 발생)
context를 이용하면 중간과정을 재치고 직접적으로 전달 가능
전달하고자하는 컴포넌트에 context를 만들면 불필요한 호출이 추가될 수 있으므로 context 전용 파일을 만들어야 한다. (전역변수는 통상적으로 store 라는 폴더에 저장해서 관리)
useState는 useState보다 컴포넌트에서 더 다양한 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용한다.
리듀서라는 개념은 Redux를 배우게 될 때 더 자세히 알아 보자.
리듀서는 현재 상태와, 업데이트를 위해 필요한 정보를 담은 액션(action)값을 전달 받아 새로운 상태를 반환하는 함수이다.
리듀서 함수에서는 새로운 상태를 만들 때는 꼭 불변성을 지켜주어야 한다
상태를 업데이트하는 로직을 컴포넌트 밖에 작성해야한다
function reducer(state, action) {
return { ... }; // 불변성을 지키면서 업데이트한 새로운 상태를 반환합니다
}
액션값은 주로 다음과 같은 형태로 이루어져있다.
{
type: 'INCREMENT',
// 다른 값들이 필요하다면, 추가적으로 들어감
}
Redux에서는 액션 객체에서는 어떤 액션인지 알려주는 type 필드가 꼭 있어야 하지만, useReducer에서 사용하는 액션 객체는 꼭 type를 지니고 있을 필요가 없다. 심지어 객체가 아닌 문자열이나, 숫자여도 상관이 없다.
// dispatch에게 인자를 전달받음
function reducer(state, action){
// state에 이전 상태값 들어있음
// action에 {키:값, ...} 형식의 객체(action)가 들어있음
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
// 아무것도 해당되지 않을 때 기존 상태 반환
return state;
}
}
const Counter = () => {
// 1번째 : reducer 함수, 2번째 : 초기 상태값
const [state, dispatch] = useReducer(reducer, { value: 0 });
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b> 입니다.
</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
</div>
);
};
export default Counter;
HTML요소(태그)나 컴포넌트의 메모리주소를 가져와, 객체(레퍼런스) 형식으로 관리할 수 있다.
부모 컴포넌트와, 자식 컴포넌트가 함수형 컴포넌트로 정의 되었을때,
부모 컴포넌트에서, 자식 컴포넌트를 객체 형식으로 관리할 수 있다.
// 자식 컴포넌트
import {forwardRef} from 'react'
// 부모 컴포넌트의 ref를 인자로 받아올 수 있다.
const _TextInput = (
props,
ref
) => {
// 자식 컴포넌트의 ref에 useRef()객체 전달
return (
<div>
<p>Hello</p>
<TextInput
ref={ref}
{...props}
/>
</div>
);
};
// forwardRef()으로 감싸주기
export const MyTextInput = forwardRef(_TextInput);
// 부모 컴포넌트
import {useRef} from 'react'
const Parent = () => {
const testRef = useRef(null);
const setFocus = () => testRef.current?.focus();
// 자식 컴포넌트에게 ref 전달
return <ImperativeTextInput ref={testRef} />
};
import {useRef, useImperativeHandle} from 'react'
const ImperativeTextInput = (props, ref) => {
const textInputRef = useRef<TextInput | null>(null);
// ref을 전달받아, 메소드를 만들어준다. ( 다른 ref 호출 가능 )
useImperativeHandle(
ref,
() => ({
focus() {
textInputRef.current?.focus();
},
dismiss() {
Keyboard.dismiss();
},
}),
[],
);
return <TextInput ref={textInputRef} {...props} />;
};
export default forwardRef(ImperativeTextInput);
Memoization : 과거에 계산한 값을 반복해서 사용할때, 그 값을 캐시에 저장하는 것
export default function App(){
const data = useMemo(()=> "data",[]);
// 데이터 변수는 의존성 배열 []에따라 선언된다. ( []사용시, 첫 렌더링 시에 1번만 선언 )
return <></>
}
useMemo의 함수버전. 반복적으로 사용되는 함수를 캐시에 저장할 수 있다.
export default function App(){
const avatarPressed = useCallback(() => Alert.alert('avatar pressed.'), []);
return <></>
}
React.js는 컴포넌트 내부에서 전역변수를 사용할 수 있도록 cache 객체 안에, key:value 형식으로 저장하는 방식을 사용한다.
// 전역적인 캐시 생성
const cache: Record<string, any> = {};
export default function createOrUse<T>(key: string, callback: () => T) {
if (!cache[key]) {
cache[key] = callback();
// 키 : 값 저장
}
return cache[key];
// 입력한 키에 해당하는 값 반환
}
React.js의 캐시는, 상황에 따라 업데이트하도록 만드는 의존성 배열을 만들 수 있다.
( 배열 속의 값이 바뀔때마다, 캐시를 업데이트 한다. )
useEffect(() => {
console.log("dependency")
}, [의존성1, 의존성2 ...])
/*
1. 의존성 변경
2. cache내부에서, 의존성에 해당하는 key 찾아서 업데이트
3. React가 컴포넌트를 재렌더링함
*/