코딩하는 문과생
[React.js] React Hook 본문
[서론]
리액트의 가장 큰 특징은 화면의 효율적인 렌더링을 위해 가상 DOM을 이용하며, 상태(State)라는 객체(Object)를 통해 컴포넌트를 제어한다는 점이다. 과거 동아리 교육 당시에는 State를 관리하기 위해 클래스 컴포넌트가 필수적이었고, this.setState를 이용해 상태를 제어할 수 밖에 없었지만, 한 화면 내 여러 상태의 관리의 필요성이 대두되면서 중앙저장소에 상태를 관리할 수 있게 하는 Redux가 나오게 되었다. Redux의 가장 큰 특징은 상태(State)에 각 컴포넌트의 직접 접근이 허용된다는 점이다.
그러나 클래스 컴포넌트는 여전히 상태관리를 하기에는 복잡도가 너무 높았고, 그에 따른 대안으로 함수형 컴포넌트에서 쉽게 상태를 관리하기 위해 훅(Hook)이 점차 주목받기 시작했다.
[React Hook]
함수형 컴포넌트에서 상태를 관리하기 위해 사용되고 있다. 기본적으로 제공되는 것 이외에도 개발자 본인이 커스터마이징하여 필요한 훅을 정의하여 사용할 수도 있다. 대표적으로 사용되는 훅으로는 useState와 useEffect가 있으며, apollo에 한해 사용되는 useQuery와 useMutation 등이 있다.
- useState
가장 대표적인 리액트 훅이다. 상태에 대한 세팅을 useState로 간단하게 지정할 수 있다.
export default function App() {
const [loading, setLoading] = useState(true); //1. state 초기화
const onFinish = () => setLoading(false); //2. onFinish 함수 호출 시 loading값 변경 함수 정의
//...
// 3. 로딩시 출력되는 화면
if (loading) {
return (
<AppLoading startAsync={preload} onError={console.warn} onFinish={onFinish} />
);
}
// 4. 로딩 완료 시 출력되는 화면
return (
<ApolloProvider client={client}>
<NavigationContainer>
{isLoggedIn ? <LoggedInNav /> : <LoggedOutNav />}
</NavigationContainer>
</ApolloProvider>
);
}
- useEffect
렌더링이 완료되고 나서 실행되는 훅이다. 아래 예시는 렌더링 후 이전과 변경되는 data가 있는 경우 subscribeToMore 함수가 실행된다.
//...
//1. 렌더링 이후 작업을 정의한다. 두번째 파라미터에 변수를 추가하면, 해당 변수에 값이 변경될 때마다 useEffect 훅이 동작한다.
useEffect(() => {
if (data?.seeRoom) {
subscribeToMore({
document: ROOM_UPDATES,
variables: { id: route?.params?.id, },
updateQuery, //이후 실행할 것을 정의한다.
});
}
}, [data]);
//...
[Apollo Hook]
- useQuery
apollo client를 사용하는 경우 쿼리에 대한 작업을 쉽게 할 수 있도록 정의된 훅이다.
// 1. fetch에 실행될 QUERY를 정의한다.
const SEE_PROFILE_QUERY = gql`
query seeProfile($username: String!) {
seeProfile(username: $username) {
firstName
lastName
username
bio
avatar
photos
{ ...PhotoFragment }
totalFollowing
totalFollowers
isMe
isFollowing
}
}
${PHOTO_FRAGMENT} `;
//...
// 2. useQuery 훅을 이용해 쉽게 fetch와 이후 작업을 제어할 수 있다.
const { data, loading } = useQuery(SEE_PROFILE_QUERY, {
variables: { username, },
}); //query를 fetch 하는 동안은 loading값이 true가 되며, fetch가 완료되면 loading값이 false가 되면서 data변수에 값이 담긴다.
-useLazyQuery
: Query를 상황에 따라 나중에 호출해야 하는 경우가 있다. (ex. 버튼클릭시 query날리는 경우) 이 때 사용하는 hook이 useLazyQuery이다. 아래 예시는 useLazyQuery를 사용하는 일부 코드를 가져온 것으로, location과 history 변수에 변경이 있을 때, useEffect hook이 동작하여 callQuery(useLazyQuery)를 동작하는 구조이다. 중간에 예외처리를 하고 쿼리를 호출하기 위해 useLazyQuery를 사용하였다.
const [callQuery, { data, loading, called }] = useLazyQuery<searchRestaurant, searchRestaurantVariables>(
SEARCH_RESTAURANT,
);
useEffect(() => {
const [_, query] = location.search.split('?term=');
if (!query) {
history.replace('/');
}
callQuery({
variables: {
input: {
page: 1,
query,
},
},
});
}, [location, history]); //location과 history 의존
- useMutation
Apollo 를 이용하여 mutation fetch시 사용하는 훅이다. loading중이나, 이후 작업할 내용을 정의하기 쉽다.
// MUTATION 정의
const CREATE_COMMENT_MUTATION = gql`
mutation createComment($photoId: Int!, $payload: String!) {
createComment(photoId: $photoId, payload: $payload) {
ok
error
id # 캐시에서 댓글을 삭제하기 위해서는 db로부터 id를 받아야한다.
}
}
`;
// ...
//3. CREATE_COMMENT_MUTATION 작업이 진행되고, 이후 update에 추가작업을 명시할 수 있다. (ex. cache 업데이트)
const [createCommentMutation, { loading }] = useMutation( CREATE_COMMENT_MUTATION, {
update: createCommentUpdate,
} );
// ...
// 2. form으로 받은 데이터를 이용해 mutation작업 진행
const onValid = (data) => {
const { payload } = data;
if (loading) {
return;
}
createCommentMutation({ variables: { photoId, payload, }, }); };
// 1. onSubmit 클릭 후 onValid호출
return (
<form onSubmit={handleSubmit(onValid)}>
<PostCommentInput
{...register("payload", { required: true, })}
name="payload"
type="text"
placeholder="Write a comment..." />
</form>
);
}
-useForm
Form 태그를 쉽게 제어하기 위해 사용하는 훅이다. register를 이용해 form태그를 참조할 수 있고, handleSubmit를 이용하여 제출 시 호출하는 함수를 정의할 수 있다. 또한 getValues, setValue, watch 등을 이용하여 input에 있는 값을 이용할 수 있다.
function SignUp() {
const onCompleted = (data) => {
const { username, password } = getValues();
const { createAccount: { ok }, } = data;
if (!ok) { return; }
};
const onSubmitValid = (data) => {
if (loading) { return; }
createAccount({
variables: {
...data,
},
});
};
const { register, handleSubmit, formState, getValues }
= useForm({
mode: "onChange",
});
const [createAccount, { loading }] = useMutation(CREATE_ACCOUNT_MUTATION, { onCompleted, });
return (
<AuthLayout>
<PageTitle title="Sign up" />
<FormBox>
<HeaderContainer>
<FontAwesomeIcon icon={faInstagram} size="3x"></FontAwesomeIcon>
<SubTitle> Sign up to see photos and videos from your friends. </SubTitle>
</HeaderContainer>
<form onSubmit={handleSubmit(onSubmitValid)}>
<Input {...register("firstName", { required: "First Name is required" })} name="firstName" type="text" placeholder="First Name" />
<Input {...register("lastName")} name="lastName" type="text" placeholder="Last Name" />
<Input {...register("email", { required: "Email is required" })} name="email" type="text" placeholder="Email" />
<Input {...register("username", { required: "Username is required" })} name="username" type="text" placeholder="Username" />
<Input {...register("password", { required: "Password is required" })} name="password" type="password" placeholder="Password" />
<Button type="submit" value={loading ? "Loading.." : "Sign Up"} disabled={!formState.isValid || loading} />
</form>
</FormBox>
<BottomBox cta="Have an account?" link={routes.home} linkText="Log in" />
</AuthLayout>
);
}
- useReactiveVar
apollo client에서 전역상태 관리를 위해 사용되는 훅이다. (ex. 로그인 여부 파악)
// apollo.js
// 1. 값 초기화
export const isLoggedInVar = makeVar(false);
// 2. 값 변경 시 사용
isLoggedInVar(true);
// App.js
// 3. 다른 화면에서 사용할 때 사용한다.
const hasToken = useReactiveVar(isLoggedInVar);
- 커스텀 훅(useMe)
여러 훅을 조합하여 커스텀 훅을 만들 수 있다. 아래 예시는 useQuery를 이용하여 유저정보를 가져오는 useMe 훅이다. 로직상 2번 호출하더라도, 서버로 request를 두번 보내는 것이 아닌, 두번째 request에서는 캐시에 정보를 요청한다.
import React from 'react';
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';
import { meQuery } from '../__generated__/meQuery';
const ME_QUERY = gql`
query meQuery {
me {
id
email
role
verified
}
}
`;
export const useMe = () => {
return useQuery<meQuery>(ME_QUERY);
}
...
const { data, loading, error } = useMe();
...
[React Router Dom Hook]
- useHistory
화면 이동에 사용 가능한 History 인스턴스에 접근가능하게 해준다. redirect 시에 많이 사용된다.
- push: 이전에 호출했던 화면에 접근가능, 호출한 페이지의 히스토리를 남긴다.
- replace: 완전히 새로운 화면으로 대체, 호출한 페이지의 히스토리를 남기지 않는다.
push 내 파라미터로 pathname, search, state를 부여해 화면을 리다이렉트 할 수 있다.
const history = useHistory();
...
history.push('/');
history.replace('/');
...
history.push({
pathname: '/search',
search: `?term=${searchTerm}`,
// state 인 경우 Post로 보내는 것과 동일하다.
});
- useLocation
사용자가 현재 머물러있는 페이지에 대한 정보를 알려주는 hooks이다. defaultProps 하나인 location 객체를 대체하는 react-router-dom hooks 이다
const location = useLocation();
const { register, handleSubmit, } = useForm({
// 초기값 세팅 defaultValues: {
username: location?.state?.username || "",
password: location?.state?.password || "",
},
});
- useParams
path parameter의 정보를 얻을 수 있는 hooks이다.
// App.js
// path 내 사용된 파라미터를 useParams를 이용해 받을 수 있다.
<Route path={`/users/:username`}>
<Layout>
<Profile />
</Layout>
</Route>
// Profile.js
const { username } = useParams();
이외에 useRef(RN TextInput 참조시 사용), useApolloClient(apollo Client 참조 시 사용), useNavigation(RN에서 사용), useLazyQuery(바로 호출하는 경우가 아닌 경우) 등의 훅이 있다.
'웹 프로그래밍 > React.js' 카테고리의 다른 글
[React.js] Life Cycle API (0) | 2020.06.21 |
---|---|
[React.js] Props & State (0) | 2020.06.21 |
[React.js] 프로젝트 구성, JSX (0) | 2020.06.21 |
[React.js] React 개발환경설정 (2) | 2020.06.21 |