[LG CNS AM CAMP 1기] 프론트엔드 6 | React

letthem·2025년 1월 2일
0

LG CNS AM CAMP 1기

목록 보기
6/16
post-thumbnail

이벤트 핸들링

상태변수가 언제 바뀌느냐 !

컴포넌트 내부에서 이벤트에 의한 값의 변화

클래스형 컴포넌트에서 이벤트 처리

EventPractice 컴포넌트에 입력창을 추가하고, 입력값을 콘솔에 출력하도록 수정

<input type="text" name="message" placeholder="입력해 보세요."
  onChange={e => console.log(e.target.value)} />

e를 출력하면

taget > value 가 내가 입력한 값이다.

그래서 e.target.value 로 많이 쓴다.

state 변수를 추가하고, 입력 내용을 해당 변수에 설정

import { Component } from "react";

class EventPractice extends Component {
  state = {
    message: ""
  }
  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input type="text" name="message" placeholder="입력해 보세요."
          onChange={e => {
            console.log(e.target.value);
            this.setState({ message: e.target.value })
          }}/>
        <h2>입력창 내용: {this.state.message}</h2>
      </div>
    )
  }
}

export default EventPractice;

버튼을 추가하고, 해당 버튼을 클릭하면 입력값과 입력 내용을 제거하도록 수정

import { Component } from "react";

class EventPractice extends Component {
  state = {
    message: ""
  }
  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input type="text" name="message" placeholder="입력해 보세요."
          value={this.state.message} // 해당 라인이 없으면 삭제 버튼을 클릭해도 입력창 내용은 지워지지 않는다
          onChange={e => {
            console.log(e.target.value);
            this.setState({ message: e.target.value })
          }}/>
        <h2>입력창 내용: {this.state.message}</h2>
        <button onClick={() => this.setState({ message: "" })}>삭제</button>
      </div>
    )
  }
}

export default EventPractice;

value 에 this.state.message 를 넣어줘서 상태를 함께해줘야 삭제 버튼을 누른 것이 입력창에도 반영이 된다.

이벤트 핸들러 함수를 미리 정의해서 전달하는 방식으로 변경

이벤트 핸들러 함수 ⬇️

e => {
  console.log(e.target.value);
  this.setState({ message: e.target.value })
}}
() => this.setState({ message: "" })

이렇게 함수를 외부에 정의해서 jsx 구문에서는 간단하게 나타낼 수 있다.⭐️ ⬇️

import { Component } from "react";

class EventPractice extends Component {
  state = {
    message: ""
  }

  handlerChange = e => {
    console.log(e.target.value);
    this.setState({ message: e.target.value });
  };

  handlerClick = () => {
    this.setState({ message: "" });
  };

  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input type="text" name="message" placeholder="입력해 보세요."
          value={this.state.message}
          onChange={this.handlerChange}/>
        <h2>입력창 내용: {this.state.message}</h2>
        <button onClick={this.handlerClick}>삭제</button>
      </div>
    )
  }
}

export default EventPractice;

화살표 함수로 사용하면 this 바인딩을 안 해줘도 돼서 화살표 함수로 사용해주면 된다.


input 창이 여러 개인 경우

import { Component } from "react";

class EventPractice extends Component {
  state = {
    message: "",
    username: ""
  }

  handlerChangeMessage = e => {
    console.log(e.target.value);
    this.setState({ message: e.target.value });
  };

  handlerChangeUsername = e => {
    console.log(e.target.value);
    this.setState({ username: e.target.value });
  };

  handlerClick = () => {
    this.setState({ message: "", username: "" });
  };

  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input type="text" name="message" placeholder="입력해 보세요."
          value={this.state.message}
          onChange={this.handlerChangeMessage}/>
        <input type="text" name="username" placeholder="입력해 보세요."
          value={this.state.username}
          onChange={this.handlerChangeUsername}/>
        <h2>message: {this.state.message}</h2>
        <h2>username: {this.state.username}</h2>
        <button onClick={this.handlerClick}>삭제</button>
      </div>
    )
  }
}

export default EventPractice;

이벤트 핸들러를 하나로 통합 => 계산된 속성명을 이용해서 이벤트가 발생한 입력창의 이름(name)에 해당하는 상태변수의 값을 변경

❓ 입력창이 10개 있으면 10개의 메서드가 만들어져야 할텐데,, 줄일 수 없을까?

💡 input 의 name 과 setState 안에 들어있는 키 값이 같다!
계산된 속성명을 사용해보자!!

handlerChange = (e) => {
  // 계산된 속성명 (어떤 변수가 가지고 있는 값을 객체의 키 이름으로 사용하겠다)
  this.setState({ [e.target.name]: e.target.value });
};
import { Component } from "react";

class EventPractice extends Component {
  state = {
    message: "",
    username: ""
  }

  // handlerChangeMessage = e => {
  //   console.log(e.target.value);
  //   this.setState({ message: e.target.value });
  // };

  // handlerChangeUsername = e => {
  //   console.log(e.target.value);
  //   this.setState({ username: e.target.value });
  // };

  handlerChange = (e) => {
    // 계산된 속성명 (어떤 변수가 가지고 있는 값을 객체의 키 이름으로 사용하겠다)
    this.setState({ [e.target.name]: e.target.value });
  };

  handlerClick = () => {
    this.setState({ message: "", username: "" });
  };

  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input type="text" name="message" placeholder="입력해 보세요."
          value={this.state.message}
          onChange={this.handlerChange}/>
        <input type="text" name="username" placeholder="입력해 보세요."
          value={this.state.username}
          onChange={this.handlerChange}/>
        <h2>message: {this.state.message}</h2>
        <h2>username: {this.state.username}</h2>
        <button onClick={this.handlerClick}>삭제</button>
      </div>
    )
  }
}

export default EventPractice;

하나의 요소(태그)에 여러 이벤트를 추가 => 메시지 입력창에 엔터 키를 눌렀을 때 삭제 버튼을 클릭한 것과 동일한 형태로 처리하도록 수정

handlerKeyUp = (e) => {
  console.log(e)
  if (e.key === "Enter") {
    this.handlerClick();
  }
}

e.key 를 보면 된다 !!

import { Component } from "react";

class EventPractice extends Component {
  state = {
    message: "",
    username: ""
  }

  handlerChange = (e) => {
    // 계산된 속성명 (어떤 변수가 가지고 있는 값을 객체의 키 이름으로 사용하겠다)
    this.setState({ [e.target.name]: e.target.value });
  };

  handlerClick = () => {
    this.setState({ message: "", username: "" });
  };

  handlerKeyUp = (e) => {
    console.log(e)
    if (e.key === "Enter") {
      this.handlerClick();
    }
  }
  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input type="text" name="message" placeholder="입력해 보세요."
          value={this.state.message}
          onChange={this.handlerChange}
          onKeyUp={this.handlerKeyUp}
          />
        <input type="text" name="username" placeholder="입력해 보세요."
          value={this.state.username}
          onChange={this.handlerChange}
          onKeyUp={handlerKeyUp}
          />
        <h2>message: {this.state.message}</h2>
        <h2>username: {this.state.username}</h2>
        <button onClick={this.handlerClick}>삭제</button>
      </div>
    )
  }
}

export default EventPractice;

함수형 컴포넌트에서 이벤트 처리 ⭐️⭐️⭐️

지금까지 구현했던 내용을 함수형 컴포넌트로 변경해보자 !!

import {useState} from "react";

function EventPractice() {
  const [message, setMessage] = useState("");
  const [username, setUsername] = useState("");

  const handlerChangeMessage = e => {
    setMessage(e.target.value)
  };

  const handlerChangeUsername = e => {
    setUsername(e.target.value)
  };


  const handlerClick = () => {
    setMessage("");
    setUsername("");
  };

  const handlerKeyUp = e => {
    if (e.key === "Enter") {
      handlerClick();
    }
  };
  
  return (
    <div>
      <h1>이벤트 연습</h1>
      <input type="text" name="message" placeholder="입력해 보세요."
        value={message}
        onChange={handlerChangeMessage}
        onKeyUp={handlerKeyUp}
        />
      <input type="text" name="username" placeholder="입력해 보세요."
        value={username}
        onChange={handlerChangeUsername}
        onKeyUp={handlerKeyUp}
        />
      <h2>message: {message}</h2>
      <h2>username: {username}</h2>
      <button onClick={handlerClick}>삭제</button>
    </div>
  )
  
}

export default EventPractice;

더 간략해졌다 !

하나의 이벤트 핸들러로 통합

import {useState} from "react";

function EventPractice() {
  const [message, setMessage] = useState("");
  const [username, setUsername] = useState("");

  /*
  const handlerChangeMessage = e => {
    setMessage(e.target.value)
  };

  const handlerChangeUsername = e => {
    setUsername(e.target.value)
  };
  */

  const handlerChange = e => {
    if (e.target.name === "message") {
      setMessage(e.target.value);
    } else if (e.target.name === "username") {
      setUsername(e.target.value);
    }
  }

  const handlerClick = () => {
    setMessage("");
    setUsername("");
  };

  const handlerKeyUp = e => {
    if (e.key === "Enter") {
      handlerClick();
    }
  };
  
  return (
    <div>
      <h1>이벤트 연습</h1>
      <input type="text" name="message" placeholder="입력해 보세요."
        value={message}
        onChange={handlerChange}
        onKeyUp={handlerKeyUp}
        />
      <input type="text" name="username" placeholder="입력해 보세요."
        value={username}
        onChange={handlerChange}
        onKeyUp={handlerKeyUp}
        />
      <h2>message: {message}</h2>
      <h2>username: {username}</h2>
      <button onClick={handlerClick}>삭제</button>
    </div>
  )
  
}

export default EventPractice;

이렇게 통합은 되었는데 찜찜하다,, 입력창이 많아지면 else if 가 너무 늘어날 것 같다.

입력창의 이름을 관리하는 상태 변수를 추가 ⭐️⭐️⭐️⭐️🩷🩷🩷🩷

와 이거 진짜 유용한듯 ㅜㅜ 최고다

/* 이거 대신
const [message, setMessage] = useState("");
const [username, setUsername] = useState("");
*/

const [form, setForm] = useState({ message: "", username: "" });

// 객체 비구조화
const { message, username } = form;

이렇게 바꿔버리기 !!!! 객체 비구조화를 했기 때문에 value 값도 안 건들여도 된다.

const handlerChange = e => {
  /* 이거 대신
  if (e.target.name === "message") {
    setMessage(e.target.value);
  } else if (e.target.name === "username") {
    setUsername(e.target.value);
  }
  */
  
  // 전개 연산자를 이용해서 form 객체의 사본을 만들고, 그 사본에 변경을 반영
  const newForm = { ...form, [e.target.name]: e.target.value };
  // setter 함수를 이용해서 사본을 저장
  setForm(newForm);
}

나머지는 그대로 !!
확장성이 아주 좋아진 코드가 되었다.


email 추가까지 한 전체 코드 ⬇️

import {useState} from "react";

function EventPractice() {
  /*
  const [message, setMessage] = useState("");
  const [username, setUsername] = useState("");
  */

  const [form, setForm] = useState({ message: "", username: "", email: "" });

  // 객체 비구조화
  const { message, username, email } = form;

const handlerChange = e => {
  /*
  if (e.target.name === "message") {
    setMessage(e.target.value);
  } else if (e.target.name === "username") {
    setUsername(e.target.value);
  }
  */
  const newForm = { ...form, [e.target.name]: e.target.value };
  setForm(newForm);
}

  const handlerClick = () => {
    /*
    setMessage("");
    setUsername("");
    */
    // 전개 연산자를 이용해서 form 객체의 사본을 만들고, 그 사본에 변경을 반영
    const newForm = { message: "", username: "", email: "" };
    // setter 함수를 이용해서 사본을 저장
    setForm(newForm);
  };

  const handlerKeyUp = e => {
    if (e.key === "Enter") {
      handlerClick();
    }
  };
  
  return (
    <div>
      <h1>이벤트 연습</h1>
      <input type="text" name="message" placeholder="입력해 보세요."
        value={message}
        onChange={handlerChange}
        onKeyUp={handlerKeyUp}
        />
      <input type="text" name="username" placeholder="입력해 보세요."
        value={username}
        onChange={handlerChange}
        onKeyUp={handlerKeyUp}
        />
      <input type="text" name="email" placeholder="입력해 보세요."
        value={email}
        onChange={handlerChange}
        onKeyUp={handlerKeyUp}
        />
      <h2>message: {message}</h2>
      <h2>username: {username}</h2>
      <h2>email: {email}</h2>
      
      <button onClick={handlerClick}>삭제</button>
    </div>
  )
  
}

export default EventPractice;

컴포넌트 반복

정렬되지 않은 목록을 출력하는 컴포넌트를 생성

IterationSample.js 파일을 생성하고 App.js에 추가

const IterationSample = () => {
  return (
    <ul>
        <li></li>
        <li>여름</li>
        <li>가을</li>
        <li>겨울</li>
    </ul>
  );
};

export default IterationSample;

IterationSample 컴포넌트를 항목을 데이터로 선언하고 map 함수를 이용해서 출력하도록 개선 ⭐️⭐️⭐️⭐️

const IterationSample = () => {
  const words = ["봄", "여름", "가을", "겨울"];
  const wordList =words.map(word => <li>{word}</li>);

  return <ul>{wordList}</ul>
};

export default IterationSample;

🚨 key 값이 없다ㅜ

배열의 index를 이용해서 key props를 설정

const IterationSample = () => {
  const words = ["봄", "여름", "가을", "겨울"];
  const wordList =words.map((word, index) => <li key={index}>{word}</li>);

  return <ul>{wordList}</ul>
};

export default IterationSample;

데이터 자체에 식별자(id)를 추가

보통 데이터는 객체의 배열 넘어오고 id값이 있으니 이를 활용해보자 !

const IterationSample = () => {
  const words = [
      { id: 1, word: "봄" }, 
      { id: 2, word: "여름" },
      { id: 3, word: "가을" },
      { id: 4, word: "겨울" },
    ];
  const wordList =words.map(item => <li key={item.id}>{item.word}</li>);

  return <ul>{wordList}</ul>
};

export default IterationSample;

리스트 데이터의 항목을 동적으로 추가할 수 있도록 수정

목록 데이터를 관리하는 변수를 상태변수로 변경하고, 항목을 입력하는 입력창과 버튼을 추가

import { useState } from "react";

const IterationSample = () => {
  // 목록 데이터를 관리하는 상태변수
  const [words, setWords] = useState([
    { id: 1, word: "봄" }, 
    { id: 2, word: "여름" },
    { id: 3, word: "가을" },
    { id: 4, word: "겨울" },
  ]);
  const wordList =words.map(item => <li key={item.id}>{item.word}</li>);

  // 입력창 내용을 관리하는 상태변수
  const [inputText, setInputText] = useState("");
  
  return (
    <>
      <input type="text" value={inputText} />
      <button>추가</button>
      <ul>{wordList}</ul>
    </>
  );
};

export default IterationSample;

상태변수에 객체의 배열을 담아 초기화해주었다 !

입력창에 내용을 입력했을 때 상태변수의 값을 변경하는 이벤트 핸들러를 추가

// 이벤트 핸들러
const handleChange = e => setInputText(e.target.value);

...

<input type="text" value={inputText} onChange={handleChange}/>

추가 버튼을 클릭했을 때 동작을 처리할 이벤트 핸들러를 추가

사본 만드는 스프레드 연산자를 사용해도 되고

const newWords = [ ...words, { id: 10, word: inputText } ];

concat 함수는 붙여서 새 배열을 반환하므로 이것도 마찬가지로 활용해도 된다

const newWords = words.concat({ id: 100, word: inputText });

// id를 관리하는 상태변수
const [nextId, setNextId] = useState(5);

const handleAddItem = () => {
  // const newWords = [ ...words, { id: nextId, word: inputText } ]; // 이렇게 해도 되고
  const newWords = words.concat({ id: nextId, word: inputText });
  setWords(newWords);
  setNextId(nextId + 1);
  setInputText("");
};

...

<button onClick={handleAddItem}>추가</button>

전체 코드 ⬇️

import { useState } from "react";

const IterationSample = () => {
  // 목록 데이터를 관리하는 상태변수
  const [words, setWords] = useState([
    { id: 1, word: "봄" }, 
    { id: 2, word: "여름" },
    { id: 3, word: "가을" },
    { id: 4, word: "겨울" },
  ]);
  const wordList =words.map(item => <li key={item.id}>{item.word}</li>);

  // 입력창 내용을 관리하는 상태변수
  const [inputText, setInputText] = useState("");
  
  // id를 관리하는 상태변수
  const [nextId, setNextId] = useState(5);

  // 이벤트 핸들러
  const handleChange = e => setInputText(e.target.value);

  const handleAddItem = () => {
    // const newWords = [ ...words, { id: nextId, word: inputText } ]; // 이렇게 해도 되고
    const newWords = words.concat({ id: nextId, word: inputText });
    setWords(newWords);
    setNextId(nextId + 1);
    setInputText("");
  };

  return (
    <>
      <input type="text" value={inputText} onChange={handleChange}/>
      <button onClick={handleAddItem}>추가</button>
      <ul>{wordList}</ul>
    </>
  );
};

export default IterationSample;

리스트 항목을 더블클릭하면 선택한 항목을 삭제하도록 기능을 추가

const wordList =words.map(item => <li key={item.id} onDoubleClick={() => handleDoubleClick(item.id)}>{item.word}</li>);

// 삭제 - filter                      
const handleDoubleClick = id => {
  const newWords = words.filter(item => item.id !== id);
  setWords(newWords);
}

여기서 filter 를 사용해서 해당 item의 id값이 아닌 것만 필터링 해주면 삭제 기능이 된다.

onDoubleClick={() => handleDoubleClick(item.id)}
이 함수 실행에 필요한 이벤트 객체가 아닌 다른 매개변수가 필요하다면
handleDoubleClick만 써주는 것이 아니라
() => handleDoubleClick(item.id) 이렇게 또다른 익명 함수를 정의해줘야 한다.


최종 코드 ⬇️

import { useState } from "react";

const IterationSample = () => {
  // 목록 데이터를 관리하는 상태변수
  const [words, setWords] = useState([
    { id: 1, word: "봄" }, 
    { id: 2, word: "여름" },
    { id: 3, word: "가을" },
    { id: 4, word: "겨울" },
  ]);
  const wordList =words.map(item => <li key={item.id} onDoubleClick={() => handleDoubleClick(item.id)}>{item.word}</li>);

  // 입력창 내용을 관리하는 상태변수
  const [inputText, setInputText] = useState("");
  
  // id를 관리하는 상태변수
  const [nextId, setNextId] = useState(5);

  // 이벤트 핸들러
  const handleChange = e => setInputText(e.target.value);

  // 추가 - concat
  const handleAddItem = () => {
    // const newWords = [ ...words, { id: nextId, word: inputText } ]; // 이렇게 해도 되고
    const newWords = words.concat({ id: nextId, word: inputText });
    setWords(newWords);
    setNextId(nextId + 1);
    setInputText("");
  };

// 삭제 - filter
const handleDoubleClick = id => {
  const newWords = words.filter(item => item.id !== id);
  setWords(newWords);
}

  return (
    <>
      <input type="text" value={inputText} onChange={handleChange}/>
      <button onClick={handleAddItem}>추가</button>
      <ul>{wordList}</ul>
    </>
  );
};

export default IterationSample;


onDoubleClick={handleDoubleClick}
이처럼 여기엔 함수의 이름이 와야하는데

onDoubleClick={handleDoubleClick(item.id)}
매개변수를 넣어 함수를 실행하고 있으니까 오류가 난다.

onDoubleClick={() => handleDoubleClick(item.id)}
그래서 매개변수를 넣어주려면 함수를 여기서 다시 정의해줘야 한다.

State 내리기 / 끌어올리기 ⭐️

State 내리기

부모 컴포넌트의 상태 변수를 자식 컴포넌트의 props로 전달
부모 컴포넌트의 상태 변수가 변경되면 부모 컴포넌트, 자식 컴포넌트 모두 다시 렌더링된다.

이걸 해보자 ! ⬇️

<Parent>
	<input type="number" />		⇐ 값이 바뀌면,
	<ChildA />				⇐ props로 전달된 값에 2를 곱한 값이 출력
	<ChildB />				⇐ props로 전달된 값에 3을 곱한 값이 출력 
</Parent>

App.js

import { useState } from "react";

function ChildB({value}) {
  return (
    <p>{value * 3}</p>
  )
}

function ChildA({count}) {
  return (
    <p>{count * 2}</p>
  )
}

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <input type="number" value={count} onChange={e => setCount(e.target.value)}/>
      <ChildA count={count}/>
      <ChildB value={count}/>
    </>
  )
}

function App() {
  return (
    <>
      <Parent />
    </>
  );
}

export default App;

function ChildA(count) { ... } 이렇게 하면 NaN 이 떴었는데 객체 비구조화를 안 해서 그렇다ㅜ

count가 객체이기 때문에 중괄호를 안 넣으면 사용할 때 count.count 이런식으로 사용해야 한다.

function ChildA({count}) { ... } 이렇게 중괄호를 넣어서 사용해주자

State 끌어 올리기

자식 컴포넌트의 변경 사항을 부모 컴포넌트로 전달하는 방법
부모 컴포넌트에서 정의한 함수를 자식 컴포넌트의 props 변수로 전달, 해당 함수를 자식 컴포넌트에서 이벤트 핸들러로 지정

이걸 해보자 ! ⬇️

<Parent>
  <h1>counter</h1>
  <Child>
    <button>하나 증가</button>
  </Child>
<Parent>
import { useState } from "react";

function Child({ increment }) {
  return <button onClick={increment}>하나증가</button>;
}

function Parent() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  return (
    <>
      <h1>{count}</h1>
      <Child increment={increment}/>
    </>
  )
}

function App() {
  return (
    <Parent />
  );
}

export default App;

[실습] Counter

내 실습 코드 ⬇️

import { useState } from "react";

function Child({ increment, decrement }) {
  return (
    <>
      <button onClick={increment}> +1 </button>
      <button onClick={decrement}> -1 </button>
    </>
  )
}

function Parent() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const resetCount = () => setCount(0);

  return (
    <>
      <div>{count}</div>
      <button onClick={resetCount}>Reset</button>
      <Child increment={increment} decrement={decrement}/>
    </>
  )
}

function App() {
  return (
    <Parent />
  );
}

export default App;

강사님 코드 ⬇️

import { useState } from "react";

function Child({ addCount, subCount }) {
  return (
    <>
      <button onClick={addCount}> +1 </button>
      <button onClick={subCount}> -1 </button>
    </>
  )
}

function Parent() {
  const [count, setCount] = useState(0);
  const addCount = () => setCount(count + 1);
  const subCount = () => setCount(count - 1);
  const resetCount = () => setCount(0);
  

  return (
    <>
      <div>{count}</div>
      <button onClick={resetCount}>Reset</button>
      <Child addCount={addCount} subCount={subCount}/>
    </>
  )
}

function App() {
  return (
    <Parent />
  );
}

export default App;

[실습] Counter 2

import { useState } from "react";

function Controller({ plusOne, minusOne }) {
  return (
    <>
      <button onClick={plusOne}> +1 </button>
      <button onClick={minusOne}> -1 </button>
    </>
  )
}

function Display({ count }) {
  return (
    <h1>{count}</h1>
  )
}

function Parent() {
  const [count, setCount] = useState(0);
  const reset = () => setCount(0);
  const plusOne = () => setCount(count + 1);
  const minusOne = () => setCount(count - 1);
  
  return (
    <>
      <button onClick={reset}>Reset</button>
      <Controller plusOne={plusOne} minusOne={minusOne}/>
      <Display count={count}/>
    </>
  )
}

function App() {
  return (
    <Parent />
  );
}

export default App;


[실습] 화씨, 섭씨 변환 및 물 끓음 표시 🥵

화씨 = (섭씨 9 / 5) + 32;
섭씨 = (화씨 - 32)
5 / 9;

물의 끓음 여부를 알려주는 컴포넌트

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <div>물이 끓습니다.</div>
  } else {
    return <div>물이 끓지 않습니다.</div>
  }
}

function App() {
  return (
    <>
      <BoilingVerdict celsius={100} />
      <BoilingVerdict celsius={99} />
    </>
  );
}

export default App;

섭씨 온도를 입력 받아서 해당 온도에서 물 끓음 여부를 출력하도록 수정

import { useState } from "react";

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <div>물이 끓습니다.</div>
  } else {
    return <div>물이 끓지 않습니다.</div>
  }
}

function Calculator() {
  const [temperature, setTemperature] = useState(0);
  const handleChage = e => setTemperature(e.target.value);
  return (
    <>
      <fieldset>
        <legend>섭씨 온도를 입력하세요.</legend>
        <input value={temperature} onChange={handleChage} />
        <BoilingVerdict celsius={temperature}/>
      </fieldset>
    </>
  )
}

function App() {
  return (
    <>
      <Calculator />
    </>
  );
}

export default App;


온도를 입력받는 컴포넌트를 생성

import { useState } from "react";

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <div>물이 끓습니다.</div>
  } else {
    return <div>물이 끓지 않습니다.</div>
  }
}

const scaleName = {
  c: "섭씨",
  f: "화씨"
};

function Temperature({ scale }) {
  const [temperature, setTemperature] = useState(0);
  const handleChage = e => setTemperature(e.target.value);
  
  return (
    <fieldset>
      <legend>온도를 입력하세요. (단위: {scaleName[scale]} </legend>
      <input value={temperature} onChange={handleChage} />
    </fieldset>
  )
}
function Calculator() {
  return (
    <>
      <Temperature scale="c" />
      <Temperature scale="f" />
      {/*
      <BoilingVerdict celsius={temperature}/>
      */}
    </>
  )
}

function App() {
  return (
    <>
      <Calculator />
    </>
  );
}

export default App;

온도를 변환하는 함수를 정의

import { useState } from "react";

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <div>물이 끓습니다.</div>
  } else {
    return <div>물이 끓지 않습니다.</div>
  }
}

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature); // 실수로 바꾸기
  if (Number.isNaN(input)) { // 숫자가 아니면
    return "";
  }

  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000; // 반올림
  return rounded.toString();
}

const scaleName = {
  c: "섭씨",
  f: "화씨"
};

function Temperature({ scale }) {
  const [temperature, setTemperature] = useState(0);
  const handleChage = e => setTemperature(e.target.value);
  
  return (
    <fieldset>
      <legend>온도를 입력하세요. (단위: {scaleName[scale]} </legend>
      <input value={temperature} onChange={handleChage} />
    </fieldset>
  )
}

function Calculator() {
  return (
    <>
      <Temperature scale="c" />
      <Temperature scale="f" />
      {/*
      <BoilingVerdict celsius={temperature}/>
      */}
    </>
  )
}

function App() {
  return (
    <>
      <Calculator />
    </>
  );
}

export default App;

온도 단위와 값을 관리할 상태 변수를 정의하고, 해당 값을 변경하는 핸들러 함수를 만들어서 props 변수로 전달

import { useState } from "react";

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <div>물이 끓습니다.</div>
  } else {
    return <div>물이 끓지 않습니다.</div>
  }
}

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature); // 실수로 바꾸기
  if (Number.isNaN(input)) { // 숫자가 아니면
    return "";
  }

  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000; // 반올림
  return rounded.toString();
}

const scaleName = {
  c: "섭씨",
  f: "화씨"
};

function Temperature({ scale }) {
  const [temperature, setTemperature] = useState(0);
  const handleChage = e => setTemperature(e.target.value);
  
  return (
    <fieldset>
      <legend>온도를 입력하세요. (단위: {scaleName[scale]}) </legend>
      <input value={temperature} onChange={handleChage} />
    </fieldset>
  )
}

function Calculator() {
  const [temperature, setTemperature] = useState("");
  const [scale, setScale] = useState("c");

  // 화씨 온도가 입력된 경우 호출될 함수
  const changeFahrenheit = e => {
    setTemperature(e);
    setScale("f");
  }

  // 섭씨 온도가 입력된 경우 호출될 함수
  const changeCelsius = e => {
    setTemperature(e);
    setScale("c");
  }

  return (
    <>
      <Temperature scale="c" changeTemperature={changeCelsius} />
      <Temperature scale="f" changeTemperature={changeFahrenheit} />
      {/*
      <BoilingVerdict celsius={temperature}/>
      */}
    </>
  )
}

function App() {
  return (
    <>
      <Calculator />
    </>
  );
}

export default App;

온도가 바뀌는 경우, 부모 컴포넌트가 전달한 함수를 호출하도록 수정하고, 화씨와 섭씨 온도를 계산해서 자신 컴포넌트로 전달

최종 코드

import { useState } from "react";

function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <div>물이 끓습니다.</div>
  } else {
    return <div>물이 끓지 않습니다.</div>
  }
}

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

// 화씨, 섭씨를 변경하기 전 입력값 검증과 변경 후 일정한 형식으로 반올림하는 로직을 공통 적용
function tryConvert(temperature, convert) {
  const input = parseFloat(temperature); // 실수로 바꾸기
  if (Number.isNaN(input)) { // 숫자가 아니면
    return "";
  }

  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000; // 반올림
  return rounded.toString();
}

const scaleName = {
  c: "섭씨",
  f: "화씨"
};


function Temperature({ scale, changeTemperature, temperature }) {
  // 변경된 내용을 부모 컴포넌트로 전달하므로 상태변수를 유지할 필요가 없다.
  // const [temperature, setTemperature] = useState(0);
  // const handleChage = e => setTemperature(e.target.value);
  const handleChange = e => changeTemperature(e.target.value);

  return (
    <fieldset>
      <legend>온도를 입력하세요. (단위: {scaleName[scale]}) </legend>
      <input value={temperature} onChange={handleChange} />
    </fieldset>
  )
}

function Calculator() {
  const [temperature, setTemperature] = useState("");
  const [scale, setScale] = useState("c");

  // 화씨 온도가 입력된 경우 호출될 함수
  const changeFahrenheit = e => {
    setTemperature(e);
    setScale("f");
  }

  // 섭씨 온도가 입력된 경우 호출될 함수
  const changeCelsius = e => {
    setTemperature(e);
    setScale("c");
  }

  const celsius = scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
  const fahrenheit = scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;


  return (
    <>
      <Temperature scale="c" changeTemperature={changeCelsius} temperature={celsius} />
      <Temperature scale="f" changeTemperature={changeFahrenheit} temperature={fahrenheit} />
      <BoilingVerdict celsius={celsius}/>
    </>
  )
}

function App() {
  return (
    <>
      <Calculator />
    </>
  );
}

export default App;


💡

  • scale: 어떤 입력창인지 관리하는 역할. (화씨 입력창인지 섭씨 입력창인지)
  • 공통적인 화씨, 섭씨 반올림 로직을 공통으로 적용하기 위해 따로 tryConverter라는 함수를 분리했다!

form

사용자가 입력한 내용을 server side로 보내는 역할

여러 타입의 input 들이 있을 수 있다.

<form action="내용을 전달할 서버 주소" method="내용을 전달할 방법" enctype="인코딩 방식">
  <input type="..." />
  <textarea></textarea>
  <button></button>
</form>

전송 버튼을 클릭하면 브라우저에서 서버로 요청이 발생해서 상태변수가 초기화되는 문제가 발생

import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>카운트 증가</button>
      <form>
        <button type="submit">전송</button>
      </form>
    </>
  )
}

export default App;

form 태그 안에 있는 내용들을 서버에 요청이 발생하는데
리액트 기반의 SPA 에서는 그런 현상이 일어나면 안된다.

form 태그를 사용할 때 서버로 요청이 전달(발생)되지 않도록 하는 것이 필요

e.preventDefault() 를 사용하자 !

import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  const handleSubmit = e => {
    console.log("form 태그의 기본 동작이 처리되지 않도록 설정")
    e.preventDefault();
    console.log("서버로 요청이 전달되지 않도록 처리");
    console.log("자바스크립트 코드를 이용해서 서버로 값을 전달하고, 전달받은 값을 사용하는 기능을 구현");
    
  };

  return (
    <>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>카운트 증가</button>
      <form onSubmit={handleSubmit}>
        <button type="submit">전송</button>
      </form>
    </>
  )
}

export default App;

‼️

link, form, a 태그는 서버쪽으로 요청이 간다. 그래서 router를 이용해서 페이지 이동하자 !

💡

프로젝트를 하다가 키가 안 먹을 때 구글링해서 e.preventDefault(); 이걸 넣어서 해결한 적이 많은데 이런 이유인 줄 처음 알았다 ! wow

ref

  • DOM 요소나 React 컴포넌트에 대한 참조를 생성하고 관리하는 데 사용되는 특별한 속성
  • render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법을 제공
  • 사용 사례
    - 포커스, 텍스트 선택 영역, 혹은 미디어의 재생을 관리할 때
    • 애니메이션을 직접 실행할 때 (외부에 버튼 등을 두고 그걸로 조작하고 싶을 때)
    • 서드 파티 DOM 라이브러리를 React와 같이 사용할 때

0개의 댓글