Skip to content

Latest commit

 

History

History
582 lines (449 loc) · 15.6 KB

File metadata and controls

582 lines (449 loc) · 15.6 KB

2주차 React 스터디 정리

제목
4장 이벤트 핸들링
5장 ref.DOM 에 이름달기

4장: 이벤트 핸들링

HTML vs React (onClick)

우선 이 코드를 먼저 보고 갑시다!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Title</title>
  </head>
  <body>
    <button onclick="alert('알러트입니다')">Click</button>
  </body>
</html>

기존의 HTML 에서 DOM 요소에 접근을 할 때는 위의 코드와 같이 " " 사이에 자바스크립트 코드를 넣거나
혹은 같은 맥락의 addEventListener 를 통해 이벤트 핸들링을 하곤 했습니다.
리액트 에서의 이벤트핸들링은 비슷하면서도 살짝 다른데요,, 밑의 코드를 보겠습니다!

위의 HTML 코드와 다른 점을 느끼셨나요?
크게 두 가지 다른 점을 느낄 수 있습니다.

import React, { useState } from "react";

const Greeting = () => {
  const [message, setMessage] = useState("");
  const onClickHi = () => {
    setMessage("하이염");
  };
  const onClickBye = () => {
    setMessage("빠이염");
  };
  return (
    <div>
      <button onClick={onClickHi}>Hi</button>
      <button onClick={onClickBye}>Bye</button>
      <h1>{message}</h1>
    </div>
  );
};

export default Greeting;
  1. camelCase
    저번주에서도 다뤘던 내용이지만 리액트에서는 이벤트 이름, 자바스크립트에 이미 존재하는 단어들을 카멜 케이스로 작성합니다.

  2. { }
    기존 HTML 에서는 " " 에 자바스크립트 코드를 넣었지만 리액트 에서는 함수 형태의 객체를 전달합니다.

💢 주의사항 💢

리액트 에서는 DOM 요소에만 이벤트를 설정할 수 있습니다.

<ExComponent onClick={doSomething} />

예를 들어 위와 같이 컴포넌트에 onClick 을 설정한다면 이는 이벤트가 아닌 그냥 onClick 이라는 이름의 props 를 전달하게 되는 것입니다.


간단한 onChange 이벤트 예제

import React from "react";

const WriteMessage = () => {
  const handleChange = (event) => {
    console.log(event.target.value);
  };
  return (
    <input
      type="text"
      name="message"
      placeholder="Write Something ... 📝"
      onChange={handleChange}
    />
  );
};

export default WriteMessage;

이런 식으로 input 값의 value 를 콘솔에 찍어볼 수 있습니다.
onChange 의 파라미터인 event 객체는 리액트Systhetic Event 로 우리가 기존에 알고 있던 네이티브 이벤트의 객체로 이루어져 있어 비슷하게 사용하면 됩니다.
하지만 Systhetic Event 가 네이티브 이벤트와 다른 점은 Event Polling 인데요, 이벤트가 끝나고 나면 이벤트가 초기화되므로 정보를 참조할 수 없습니다.
Event Polling 의 해결방법과 Synthetic Event 에 대한 설명은 이 곳에서 추가로 확인할 수 있습니다.

stateinput 값 담기

위의 코드에 state 를 이용하여 value 를 업데이트 시키고 이를 화면에 찍어보겠습니다.
state 값 을 지난주에 배운 세터함수 를 활용하여 변경시킬 수 있습니다.
세터함수 에 위에서 콘솔에 찍어봤던 event.target.value 를 넣어주면 되겠죠?
이후 inputvaluestate 값 을 전달해주고 아무 태그나 이용하여 state 값 을 찍어봅시다.

import React, { useState } from "react";

const WriteMessage = () => {
  const [message, setMessage] = useState("");
  const handleChange = (event) => {
    setMessage(event.target.value);
  };
  return (
    <>
      <input
        type="text"
        name="message"
        placeholder="Write Something ... 📝"
        value={message}
        onChange={handleChange}
      />
      <h1>내가 쓴 메시지: {message}</h1>
    </>
  );
};

export default WriteMessage;

리셋 시키기

버튼을 누르면 inputvalue 를 리셋시켜 보겠습니다.
세터함수 를 이용하여 빈 문자열을 넣어주면 됩니다.
반복되는 코드가 중복되는 부분은 생략하였습니다.

// 콜백함수 handleReset
const handleReset = () => {
  setMessage("");
};

// 버튼을 클릭하면 handleReset 실행
<button onClick={handleReset}>초기화</button>;

여러 개의 input 다루기

input 이 하나가 아니라 여러 개라면 어떻게 해야할까요?

const [writer, setWriter] = useState("");
const [message, setMessage] = useState("");

와 같이 input 의 개수만큼 useState 를 활용해도 되지만 input 의 개수가 많아진다면
위에서 사용한 event 객체를 활용하는 방법이 더 효율적일 것입니다.

state 값 을 단일 값이 아닌 객체로 선언해봅시다.

const [inputs, setInputs] = useState({
  writer: "",
  message: "",
});

이렇게 되면 inputs 에 writermessage 라는 key 를 가진 객체가 생성됩니다.

writermessage 를 자유롭게 쓰기 위해선 Destructing (비구조화 할당) 이 유용하겠죠?

const { writer, message } = inputs;

이제 컴포넌트 내에서 자유롭게 writermessage 를 사용할 수 있습니다.

메시지 작성자인 writer field 를 하나 추가하고 리셋 함수인 handleReset 까지 수정해보겠습니다.

// handleReset()
const handleReset = () => {
  setInputs({
    writer: "",
    message: "",
  });
};

// writer input field
<input
  type="text"
  name="writer"
  placeholder="What's your name ... 👩"
  value={writer}
  onChange={handleChange}
/>;

이제 handleChange 함수만 수정해주면 되는데요, 선행되어야 하는 자바스크립트 지식이 있습니다.

  1. Spread
    지난주 발표내용에도 있었고 예빈님이 추가설명까지 해주셨기 때문에 자세한 내용은 생략합니다.
    MDN 혹은 벨로퍼트님 자료를 참고해주세요!

  2. Object
    객체 안에서 key 를 [ ] 로 감싸면 그 안에 넣은 레퍼런스가 가리키는 실제 값이 key 값으로 사용됩니다.
    ~~이게 뭐라고 엄청 헷갈렸는데 쉽게 말하자면 변수(상수) 값을 객체의 key 로 쓰고 싶다~ 하면 쓰시면 되는겁니다!~~

    const newKey = "name";
    const object = { [newKey]: "kevin" };
    // object = {name: 'kevin'}

다 아신다는 가정하에 handleChange 함수를 수정해보겠습니다.
event.targetnamevalue 를 둘 다 사용해야하기에 destructing 을 이용하여 간단하게 지정해주었습니다.
이제 세터함수 를 통해 state 값 을 변경해줘야합니다. 여기서 앞의 두 자바스크립트 지식을 써야 하는데요,
우리는 객체를 업데이트 시켜야 하기 때문에 spread operator 를 사용하여야 하며
사본을 만들고 난 후 그 사본의 상태를 세터함수 를 통해 변경하게 되는 것입니다.

const handleChange = (event) => {
  const { name, value } = event.target;
  setInputs({
    ...inputs,
    [name]: value,
  });
};

inputs[name] = value 가 안되는 이유는 리액트의 불변성 때문인데요,
기존 상태를 이런 식으로 직접 수정하게 되면 값이 바뀌어도 리렌더링이 되지 않습니다.
또한 추후에 컴포넌트 성능 최적화를 위해서는 이 불변성을 지켜줘야 합니다.
이 부분에 대해 더 알고 싶으시다면 벨로퍼트님 블로그를 참고해주세요!

전체 코드 (클릭하면 볼 수 있음)
import React, { useState } from "react";

const WriteMessage = () => {
  const [inputs, setInputs] = useState({
    writer: "",
    message: "",
  });

  // Destructing
  const { writer, message } = inputs;

  const handleChange = (event) => {
    const { name, value } = event.target;
    setInputs({
      ...inputs,
      [name]: value,
    });
  };

  const handleReset = () => {
    setInputs({
      writer: "",
      message: "",
    });
  };

  return (
    <>
      <input
        type="text"
        name="writer"
        placeholder="What's your name ... 👩"
        value={writer}
        onChange={handleChange}
      />
      <input
        type="text"
        name="message"
        placeholder="Write Something ... 📝"
        value={message}
        onChange={handleChange}
      />
      <button onClick={handleReset}>초기화</button>
      <h1>작성자:{writer}</h1>
      <h1>내용: {message}</h1>
    </>
  );
};

export default WriteMessage;

5장: ref.DOM 에 이름 달기

ref 는 언제 사용?

순수 자바스크립트 를 사용 할 때 특정 DOM 을 선택해야 하는 상황에서는
getElementById, querySelector 와 같은 DOM Selector 함수 를 사용해서 DOM 을 선택합니다.

리액트 에서도 가끔씩 DOM직접 선택해야 하는 상황이 있을 수 있습니다. 예를 들면

  • 특정 input 에 포커스 주기
  • 스크롤 박스 조작하기
  • Canvas 요소에 그림 그리기 등

이럴 때 리액트에서는 ref 라는 것을 사용합니다.


ref 사용방법

ref 를 사용하는 방법은 총 3가지 입니다.
원하는 방법을 골라서 사용하면 되지만 최근에는 함수형 컴포넌트와 함께 hooks 를 많이 쓰는 추세입니다.

  • 함수형 컴포넌트: useRef
import React, { useRef } from "react";

const RefExample = () => {
  const input = useRef();

  const handleFocus = () => {
    input.current.focus();
  };

  return (
    <div>
      <input ref={input} />
      <button onClick={handleFocus}>FOCUS!</button>
    </div>
  );
};

export default RefExample;
  • 클래스형 컴포넌트: React.createRef, 콜백함수

    • React.createRef
    import React, { Component } from "react";
    
    class RefExample extends Component {
      input = React.createRef();
    
      handleFocus = () => {
        this.input.current.focus();
      };
    
      render() {
        return (
          <div>
            <input ref={this.input} />
            <button onClick={this.handleFocus}>FOCUS!</button>
          </div>
        );
      }
    }
    
    export default RefExample;
    • 콜백함수
    import React, { Component } from "react";
    
    class RefExample extends Component {
      handleFocus = () => {
        this.input.focus();
      };
    
      render() {
        return (
          <div>
            <input ref={(ref) => (this.input = ref)} />
            <button onClick={this.handleFocus}>FOCUS!</button>
          </div>
        );
      }
    }
    
    export default RefExample;

컴포넌트에 ref 달기

컴포넌트에도 ref 를 달 수가 있는데요, 이렇게 하면 컴포넌트 내부의 메소드 및 멤버 변수에도 접근할 수 있습니다.

우선 스크롤 박스를 만들어보겠습니다.

import React, { useRef } from "react";

const RefExample = () => {
  const box = useRef();

  const style = {
    border: "1px solid black",
    height: "300px",
    width: "300px",
    overflow: "auto",
    position: "relative",
  };

  const innerStyle = {
    width: "100%",
    height: "650px",
    background: "linear-gradient(white,black)",
  };

  return (
    <div style={style} ref={box}>
      <div style={innerStyle} />
    </div>
  );
};

export default RefExample;

💢 설명 전 짚고 넘어가야할 것 💢
교재 예제는 모두 클래스형 컴포넌트에서 콜백함수로 ref 를 전달합니다.
이를 모두 함수형 컴포넌트로 바꾸는 과정에서 useRef 외에 forwardRef, useImperativeHandle 을 사용합니다.
Hook 에 대한 자세한 내용은 8장에서 다루게 됩니다. 여기서는 쓰인 Hook API 만 간단하게 설명하겠습니다.

// 자식 컴포넌트

import React, { forwardRef, useImperativeHandle, useRef } from "react";

const RefExample = forwardRef((props, ref) => {
  const box = useRef();

  const style = {
    border: "1px solid black",
    height: "300px",
    width: "300px",
    overflow: "auto",
    position: "relative",
  };

  const innerStyle = {
    width: "100%",
    height: "650px",
    background: "linear-gradient(white,black)",
  };

  useImperativeHandle(ref, () => ({
    scrollToBottom() {
      const { scrollHeight, clientHeight } = box.current;
      box.current.scrollTop = scrollHeight - clientHeight;
    },
  }));

  return (
    <div style={style} ref={box}>
      <div style={innerStyle} />
    </div>
  );
});

export default RefExample;
// 부모 컴포넌트

import { useRef } from "react";
import RefExample from "./components/RefExample";

function App() {
  const scrollBox = useRef();

  const handleDown = () => {
    scrollBox.current.scrollToBottom();
  };

  return (
    <>
      <RefExample ref={scrollBox} />
      <button onClick={handleDown}>맨 밑으로</button>
    </>
  );
}

export default App;

참고 블로그 1
참고 블로그 2

하지만 이 코드는 가독성이 떨어진 뿐더러 굳이 자식 컴포넌트에 scrollToBottom 콜백함수를 넣을 필요는 없으므로 아래 첨부 된 코드가 좀 더 효율적이라고 할 수 있습니다.
(알려주신 예빈님 감사합니다!)

// 자식
import React, { forwardRef } from "react";

const RefExample = forwardRef((props, ref) => {
  const style = {
    border: "1px solid black",
    height: "300px",
    width: "300px",
    overflow: "auto",
    position: "relative",
  };

  const innerStyle = {
    width: "100%",
    height: "650px",
    background: "linear-gradient(white,black)",
  };

  return (
    <div style={style} ref={ref}>
      <div style={innerStyle} />
    </div>
  );
});

export default RefExample;
// 부모
import { useRef } from "react";
import RefExample from "./RefExample";

function App() {
  const scrollBox = useRef();

  const handleDown = () => {
    scrollToBottom();
  };

  const scrollToBottom = () => {
    const { scrollHeight, clientHeight } = scrollBox.current;
    scrollBox.current.scrollTop = scrollHeight - clientHeight;
  };

  return (
    <>
      <RefExample ref={scrollBox} />
      <button onClick={handleDown}>맨 밑으로</button>
    </>
  );
}

export default App;