useRef로 상태 관리를 할 수 있다구??

송은석·2021년 6월 7일
1
post-thumbnail

너무 재밌다.

솔직히 프로젝트를 시작하고 나서 즐겁지가 않았다. 시간이 급해서였을까. 리액트와 노드를 책으로 빠르게 배우고, 곳곳의 보일러 플레이트들을 따와서 코드 형식 맞추는데만 급급했다. 그래서 한동안 재미 없는 프로그래밍을 했던 것 같다.

그러나 오늘 강사님이 보여주신 리액트의 새로운 접근법은 나로 하여금 이전에 자바로 경험할 수 있었던 프로그래밍의 혁신적 즐거움을 다시금 맛볼 수 있게 해주었다.


오늘의 질문

오늘 강사님께 드렸던 질문은 자식 컴포넌트의 상태를 어떻게 부모 컴포넌트에서 사용할 수 있겠는가 하는 것이었다.

나는 단순히 리덕스와 유즈셀렉트를 통해 상태를 부모에게 전달하려고 했었다. 그런데 코드가 뭔가 지저분하고, 이렇게 하는게 맞나.. 하는 생각이 계속 들었던 것이다.

이에 강사님께서는 먼저 상태를 최소화해야하며, 리덕스보다 툴킷을 사용하는 것이 좋다는 이야기를 해주셨다.
그리고 useRef를 사용해서 부모 컴포넌트에서 자식 컴포넌트의 상태를 가져오는 방법을 알려주셨다. 강사님께서 만드신 코드를 참고 했는데, 다음의 깃허브 주소에서 볼 수 있다.
https://github.com/zerockcode1/route_test/tree/master/src/components


가르쳐주실 때, 그리고 이후에 혼자 코드를 보면서 공부를 하는데 얼마나 프로그래밍의 이러한 혁명적인(?) 모습에 재미가 있었는지 모른다. 너무 재밌었다!!

강사님의 코드를 참고해서 수정한 코드는 다음과 같다.

// 먼저 writeForm 부분의 코드
import React, { useCallback, useRef } from 'react';
import { Button, Form, Input } from 'antd';
import Router from 'next/router';
import TagBox from './TagBox';
import useInput from '../hooks/useInput';

const WriteMyRouteForm = () => {

  const back = useCallback(() => {
    if (confirm('작성 중이던 글이 모두 삭제됩니다. 돌아가시겠습니까?')) {
      Router.push('/');
    }
  }, []);

  const [title, onChangeTitle] = useInput('');
  const [content, onChangeContent] = useInput('');

  const childRef = useRef();
  let tags = null;

  const getTags = (tagList) => {
    tags = tagList;
  };

  const onSubmit = useCallback(() => {
    if (!title || !title.trim()) {
      return alert('제목을 작성하세요.');
    }
    if (!content || !content.trim()) {
      return alert('내용을 작성하세요.');
    }
    childRef.current.send();
    console.log(title, content, tags);
  }, [title, content, tags]);

  return (
    <div style={{ borderRight: '1px solid black', height: 700, marginLeft: '20px', paddingRight: '20px' }}>
      <Form encType="multipart/form-data">
        <Input value={title} placeholder="제목을 입력하세요" onChange={onChangeTitle} bordered={false} style={{ resize: 'none', height: '40px', fontSize: '37px', marginTop: '30px', fontWeight: 'bold' }} />
        <div style={{ height: '6px', width: '10rem', borderRadius: '1px', background: 'gray', margin: '10px' }} />
        <TagBox cref={childRef} getTags={getTags} />
        <div style={{ height: '1px', width: '10rem', borderRadius: '1px', background: 'gray', margin: '10px' }} />
        <Input.TextArea value={content} placeholder="내용을 입력하세요" onChange={onChangeContent} bordered={false} rows={21} style={{ resize: 'none' }} />

        <hr />
        <div style={{ margin: '20px 10px', padding: '0' }}>
          <Button type="primary" onClick={back} style={{ float: 'left' }}>뒤로가기</Button>
          <Button type="primary" onClick={onSubmit} style={{ float: 'right' }}>작성완료</Button>
        </div>
      </Form>
    </div>
  );
};

export default WriteMyRouteForm;

그리고 Tagbox 컴포넌트의 코드

import React, { useCallback, useState, useImperativeHandle } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import useInput from '../hooks/useInput';

const TagBox = ({ cref, getTags }) => {
  const [tagList, setTagList] = useState([]);
  const [input, onChangeInput, setInput] = useInput();

  useImperativeHandle(cref, () => ({
    send() {
      getTags(tagList);
    },
  }));

  const TagOneDiv = styled.div`
    font-size: 15px;
    background-color: #B4EEB4;
    color: white;
    border-radius: 20px;
    margin: 0 2px;
    padding: 3px;
  `;

  const TagBoxStyle = {
    backgroundColor: '#F8F8F8',
    fontSize: '15px',
    border: 'none',
    outline: 'none',
    margin: '0 0 3px 0',
    padding: '4px 11px',
  };

  const onRemove = useCallback((tag) => {
    setTagList(tagList.filter((t) => t !== tag));
  }, [tagList]);

  const TagOne = useCallback(({ tag }) => (
    <TagOneDiv onClick={() => onRemove(tag)}>{tag}</TagOneDiv>
  ));

  const insertTag = useCallback((tag) => {
    if (!tag) return;
    if (tagList.includes(tag)) return;
    setTagList([...tagList, tag]);
  }, [tagList]);

  const tagOut = useCallback((e) => {
    if (e.key === 'Enter') {
      insertTag(input.trim());
      setInput('');
    }
  }, [input, insertTag]);

  return (
    <div style={{ display: 'flex' }}>
      <div>
        <div style={{ display: 'flex', flexWrap: 'wrap' }}>
          {tagList.map((tag) => (
            <div>
              <TagOne key={tag} tag={tag} />
            </div>
          ))}
          <input placeholder="태그를 입력하세요" value={input} onChange={onChangeInput} onKeyPress={tagOut} style={TagBoxStyle} />
        </div>
      </div>

    </div>
  );
};

export default TagBox;

useRef를 이용해서 자식 컴포넌트에 고리를 걸고(?) 상태를 가져와 사용할 수 있게 되었다.

내일도 강사님 또 오셨으면 좋겠다. 너무 재밌다.

profile
Done is better than perfect🔥

0개의 댓글