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

letthem·2024년 12월 31일
0

LG CNS AM CAMP 1기

목록 보기
5/16
post-thumbnail

클래스형 컴포넌트

MyComponentClass.js

import { Component } from "react";

class MyComponentClass extends Component {
  render() {
    return (
      <>
        <h1>이름은 홍길동입니다.</h1>
        <h2>나이는 23살입니다.</h2>
      </>
    )
  }
}

export default MyComponentClass;

App.js

import { Component } from "react";
import MyComponentClass from "./MyComponentClass";

class App extends Component{
  render() {
    return (
      <>
        <MyComponentClass />
      </>
    );
  }
}

export default App;

함수형 컴포넌트

MyComponentFunction.js

function MyComponentFunction() {
  return (
    <>
      <h1>이름은 홍길동입니다.</h1>
      <h2>나이는 23살입니다.</h2>
    </>
  )
}

export default MyComponentFunction;

App.js

import { Component } from "react";
import MyComponentClass from "./MyComponentClass";
import MyComponentFunction from "./MyComponentFunction";

class App extends Component{
  render() {
    return (
      <>
        <h1>클래스형 컴포넌트</h1>
        <MyComponentClass />
        <hr />
        <h1>함수형 컴포넌트</h1>
        <MyComponentFunction />
      </>
    );
  }
}

export default App;

props

  • properties의 줄임말
  • 컴포넌트의 속성을 설정할 때 사용
  • props 값은 해당 컴포넌트를 사용하는 부모 컴포넌트에서 설정
  • 컴포넌트 자신은 해당 props를 읽기 전용으로 사용만 가능

컴포넌트는 일종의 객첸데, 그 객체에 내가 전달해주는 고유한 속성값이라고 생각하면 된다!

이름을 전달해서 출력하도록 수정

App.js <= 부모 컴포넌트

import { Component } from "react";
import MyComponentClass from "./MyComponentClass";
import MyComponentFunction from "./MyComponentFunction";

class App extends Component{
  render() {
    return (
      <>
        <h1>클래스형 컴포넌트</h1>
        <MyComponentClass name="홍길동"/>
        <hr />
        <h1>함수형 컴포넌트</h1>
        <MyComponentFunction name="고길동"/>
      </>
    );
  }
}

export default App;

MyComponentClass 컴포넌트에서 props 값을 활용하도록 수정

  • 부모(App.js)가 props 넘겨주기만 하면 MyComponentClass에 자동으로 넘어온다.
  • this.props.부모가넘겨준변수이름 <- props 이름이 정해져있어서 그대로 써야한다.
import { Component } from "react";

class MyComponentClass extends Component {
  render() {
    console.log(this);
    console.log(this.props, typeof this.props);

    return (
      <>
        <h1>이름은 {this.props.name}입니다.</h1>
        <h2>나이는 23살입니다.</h2>
      </>
    )
  }
}

export default MyComponentClass;

MyComponentFunction 컴포넌트에서 props 값을 활용하도록 수정

  • 매개변수로 정의하고 사용한다.
  • 매개변수이기 때문에 props 말고 다른 이름을 써도 되지만 관례적으로 props로 쓰자.
function MyComponentFunction(props) {
  console.log(props);
  return (
    <>
      <h1>이름은 {props.name}입니다.</h1>
      <h2>나이는 23살입니다.</h2>
    </>
  )
}

export default MyComponentFunction;

여러 값을 자식 컴포넌트로 전달하도록 수정

App.js

  • 문자열의 경우 중괄호 생략 가능하다.
import { Component } from "react";
import MyComponentClass from "./MyComponentClass";
import MyComponentFunction from "./MyComponentFunction";

class App extends Component{
  render() {
    return (
      <>
        <h1>클래스형 컴포넌트</h1>
        <MyComponentClass name="홍길동" age={23} nickname={"길동"}/>
        <hr />
        <h1>함수형 컴포넌트</h1>
        <MyComponentFunction name="고길동" age={40} nickname="길동"/>
      </>
    );
  }
}

export default App;

MyComponentClass 컴포넌트에서 props 변수를 받아서 출력하도록 수정

import { Component } from "react";

class MyComponentClass extends Component {
  render() {
    console.log(this);
    console.log(this.props, typeof this.props);

    return (
      <>
        <h1>이름은 {this.props.name}입니다.</h1>
        <h2>나이는 {this.props.age}살입니다.</h2>
        <h2>별명은 {this.props.nickname}입니다.</h2>
      </>
    )
  }
}

export default MyComponentClass;

this.props 너무 반복된다.

객체 비구조화를 이용해서 코드를 단순화

import { Component } from "react";

class MyComponentClass extends Component {
  render() {
    const { name, age, nickname } = this.props;
    return (
      <>
        <h1>이름은 {name}입니다.</h1>
        <h2>나이는 {age}살입니다.</h2>
        <h2>별명은 {nickname}입니다.</h2>
      </>
    )
  }
}

export default MyComponentClass;

MyComponentFunction 컴포넌트에서 props 변수를 받아서 출력하도록 수정

function MyComponentFunction(props) {
  console.log(props);
  return (
    <>
      <h1>이름은 {props.name}입니다.</h1>
      <h2>나이는 {props.age}살입니다.</h2>
      <h2>나이는 {props.nickname}살입니다.</h2>
    </>
  )
}

export default MyComponentFunction;

객체 비구조화를 이용해서 코드를 단순화

function MyComponentFunction(props) {
  const {name, age, nickname} = props;
  return (
    <>
      <h1>이름은 {name}입니다.</h1>
      <h2>나이는 {age}살입니다.</h2>
      <h2>나이는 {nickname}살입니다.</h2>
    </>
  )
}

export default MyComponentFunction;

매개변수를 객체 비구조화하도록 정의

  • 매개변수로 받을 때부터 비구조화해서 받아버리기 !
function MyComponentFunction({name, age, nickname}) {

  return (
    <>
      <h1>이름은 {name}입니다.</h1>
      <h2>나이는 {age}살입니다.</h2>
      <h2>나이는 {nickname}살입니다.</h2>
    </>
  )
}

export default MyComponentFunction;

wow 이렇게 쓰는 이유를 이제 정확히 이해했다 -!

props 값이 누락된 경우

App.js 파일에 props 값을 전달하지 않도록 수정

import { Component } from "react";
import MyComponentClass from "./MyComponentClass";
import MyComponentFunction from "./MyComponentFunction";

class App extends Component{
  render() {
    return (
      <>
        <h1>클래스형 컴포넌트</h1>
        <MyComponentClass />
        <hr />
        <h1>함수형 컴포넌트</h1>
        <MyComponentFunction />
      </>
    );
  }
}

export default App;

내용이 출력되지 않는다.

defaultProps를 설정해 props 변수가 누락된 경우 사용할 기본값을 설정

MyComponentClass.js

import { Component } from "react";

class MyComponentClass extends Component {
  render() {
    const {name, age, nickname} = this.props;
    return (
      <>
        <h1>이름은 {name}입니다.</h1>
        <h2>나이는 {age}살입니다.</h2>
        <h2>별명은 {nickname}입니다.</h2>
      </>
    )
  }
}

MyComponentClass.defaultProps = {
  name: "아무개",
  age: 0,
  nickname: "없음"
};

export default MyComponentClass;

함수형 컴포넌트의 경우 함수의 디폴트 파라미터로 대체
cf
{name = "아무개", age = 0, nickname = "없음"} ← 이런식으로 !

function MyComponentFunction({name = "아무개", age = 0, nickname = "없음"}) {

  return (
    <>
      <h1>이름은 {name}입니다.</h1>
      <h2>나이는 {age}살입니다.</h2>
      <h2>나이는 {nickname}살입니다.</h2>
    </>
  )
}

/*
MyComponentFunction.defaultProps = {
  name: "아무개",
  age: 0,
  nickname: "없음"
};
*/

export default MyComponentFunction;

props.children

App.js 파일을 자식 컴포넌트가 내용(contents)을 포함하도록 수정

import { Component } from "react";
import MyComponentClass from "./MyComponentClass";
import MyComponentFunction from "./MyComponentFunction";

class App extends Component{
  render() {
    return (
      <>
        <h1>클래스형 컴포넌트</h1>
        <MyComponentClass>
          <i>어떤 내용</i>
        </MyComponentClass>
        <hr />
        <h1>함수형 컴포넌트</h1>
        <MyComponentFunction>
          <i>어떤 내용</i>
        </MyComponentFunction>
      </>
    );
  }
}

export default App;

자식 컴포넌트의 내용(<i> 태그의 내용이 출력되지 않는 것을 확인)

MyComponentClass 컴포넌트에 내용(contents)을 출력하는 코드를 추가

import { Component } from "react";

class MyComponentClass extends Component {
  render() {
    const {name, age, nickname} = this.props;
    return (
      <>
        <h1>이름은 {name}입니다.</h1>
        <h2>나이는 {age}살입니다.</h2>
        <h2>별명은 {nickname}입니다.</h2>
        <div>{this.props.children}</div>
      </>
    )
  }
}

MyComponentClass.defaultProps = {
  name: "아무개",
  age: 0,
  nickname: "없음"
};

export default MyComponentClass;

MyComponentFunction 컴포넌트에 내용을 출력하는 코드를 추가

function MyComponentFunction({name = "아무개", age = 0, nickname = "없음", children}) {

  return (
    <>
      <h1>이름은 {name}입니다.</h1>
      <h2>나이는 {age}살입니다.</h2>
      <h2>나이는 {nickname}살입니다.</h2>
      <div>{children}</div>
    </>
  )
}

export default MyComponentFunction;

children은 이름을 정확히 지켜줘야 한다.

객체 배열 형태의 데이터를 map 함수를 이용해서 출력 ⭐️⭐️⭐️⭐️⭐️

  • 재사용 가능한 컴포넌트 적극 활용!
import { Component } from "react";
import MyComponentClass from "./MyComponentClass";
import MyComponentFunction from "./MyComponentFunction";

const datas = [
  {name: "홍길동", age: 23, nickname: "호부호형을 원하는 자"},
  {name: "고길동", age: 43, nickname: "둘리가 싫은 자"},
  {name: "신길동", age: 50, nickname: "신길동 매운 짬뽕"}
];

class App extends Component{
  render() {
    return (
      <>
        <h1>클래스형 컴포넌트</h1>
        {
          datas.map(data => 
            <MyComponentClass name={data.name} age={data.age} nickname={data.nickname}>
              <i>어떤 내용</i>
            </MyComponentClass>
          )
        }
        <hr />
        <h1>함수형 컴포넌트</h1>
        {
          datas.map(data => 
            <MyComponentFunction>
              <i>어떤 내용</i>
            </MyComponentFunction>
          )
        }
      </>
    );
  }
}

export default App;

[실습] 신호등 모양을 출력하는 컴포넌트 작성

  • App.js 파일에 일정한 크기의 Lamp를 포함한 TrafficLight 컴포넌트를 포함
  • TrafficLight 컴포넌트는 빨강, 초록, 노랑 속성을 가지는 같은 크기의 Lamp 컴포넌트 세 개를 포함
  • Lamp 컴포넌트는 색상과 크기를 부모 컴포넌트(TrafficLight)로부터 전달 받아서 해당 색상과 크기의 원을 출력
  • 100px의 붉은 동그라미를 출력 => <div style={{ width: 100, height: 100, borderRadius: 50, backgroundColor: 'red' }} />

내 실습 코드 ⬇️
App.js

import TrafficLight from "./TrafficLight";

function App() {
  return (
    <>
      <TrafficLight/>
    </>
  )
}

export default App;

TrafficLight.js

import Lamp from "./Lamp";

const colors = [ {color: "red"}, {color: "yellow"}, {color: "green"}];

function TrafficLight() {

  return (
    <>
      {
        colors.map(data => <Lamp bgColor={data.color}/>)
      }
    </>
  )
}

export default TrafficLight;

Lamp.js

function Lamp({ bgColor }) {

  return (
    <>
      <div style={{ width: 100, height: 100, borderRadius: 50, backgroundColor: `${bgColor}` }} />
    </>
  )
}

export default Lamp;

App.js에서 신호등의 크기와 색깔을 전달해서 출력하도록 수정

App.js

import TrafficLight from "./TrafficLight";

function App() {
  const tlSize = 100;
  const tlColors = ["red", "blue", "green", "yellow"];

  return (
    <>
      <TrafficLight size={tlSize} colors={tlColors} />
    </>
  );
}

export default App;

TrafficLight.js

function Lamp({ size, color }) {
  return (
    <div style={{ width: size, height: size, borderRadius: size / 2, backgroundColor: color }} />
  );
}

export default function TrafficLight({ size, colors }) {
  return (
    <>
       {
         colors.map(color => <Lamp size={size} color={color} />)
       }
    </>
  );
}

[실습] 댓글 컴포넌트 제작

댓글을 작성한 작성자의 이미지, 작성자의 이름, 댓글 내용을 출력하는 컴포넌트를 제작

컴포넌트 구조 정의

src 폴더 아래에 comment 폴더를 생성하고, Comment.js 파일을 추가

function Comment () {
  return (
    <>
      <h1>댓글 컴포넌트</h1>
    </>
  )
}

export default Comment;

comment 폴더에 CommentList.js 파일을 추가

import Comment from "./Comment";

function CommentList() {
  return ( 
    <>
      <Comment />
      <Comment />
      <Comment />
    </>
  )
}
export default CommentList;

App.js 파일에 CommentList 컴포넌트를 추가

import CommentList from "./comment/CommentList";

function App() {
  return (
    <>
      <CommentList />
    </>
  )
}

export default App;

컴포넌트 구현

Comment 컴포넌트에 작성한 사람의 이미지, 이름, 댓글 내용을 보여주는 코드를 추가

사람 이미지 ⇒ https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png

function Comment () {
  return (
    <div>
      {/* 작성한 사람의 이미지 */}
      <div>
        <img src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png" alt="profile" />
      </div>
      {/* 작성한 사람의 이름과 댓글 내용 */}
      <div>
        <span>작성자 이름</span>
        <span>댓글 내용</span>
      </div>
    </div>
  )
}

export default Comment;

Comment 컴포넌트에 스타일을 추가

function Comment () {
  const styles = {
    wrapper: {
      display: "flex",
      flexDirection: "row",
      border: "1px solid gray",
      borderRadius: 16,
      padding: 8,
      margin: 8
    },
    image: {
      width: 50,
      height: 50,
      borderRadius: 25
    },
    contentContainer: {
      marginLeft: 10,
      display: "flex",
      flexDirection: "column",
    },
    nameText: {
      color: "black",
      fontSize: 16,
      fontWeight: "bold",
      marginBottom: 5
    },
    commentText: {
      color: "black",
      fontSize: 16
    }
  };
  return (
    <div style={styles.wrapper}>
      {/* 작성한 사람의 이미지 */}
      <div>
        <img style={styles.image} src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png" alt="profile" />
      </div>
      {/* 작성한 사람의 이름과 댓글 내용 */}
      <div style={styles.contentContainer}>
        <span style={styles.nameText}>작성자 이름</span>
        <span style={styles.commentText}>댓글 내용</span>
      </div>
    </div>
  )
}

export default Comment;

Comment 컴포넌트에 props 변수로 전달된 값을 출력하도록 변경

function Comment (props) {
  const styles = {
    wrapper: {
      display: "flex",
      flexDirection: "row",
      border: "1px solid gray",
      borderRadius: 16,
      padding: 8,
      margin: 8
    },
    image: {
      width: 50,
      height: 50,
      borderRadius: 25
    },
    contentContainer: {
      marginLeft: 10,
      display: "flex",
      flexDirection: "column",
    },
    nameText: {
      color: "black",
      fontSize: 16,
      fontWeight: "bold",
      marginBottom: 5
    },
    commentText: {
      color: "black",
      fontSize: 16
    }
  };
  return (
    <div style={styles.wrapper}>
      {/* 작성한 사람의 이미지 */}
      <div>
        <img style={styles.image} src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png" alt="profile" />
      </div>
      {/* 작성한 사람의 이름과 댓글 내용 */}
      <div style={styles.contentContainer}>
        <span style={styles.nameText}>{props.name}</span>
        <span style={styles.commentText}>{props.comment}</span>
      </div>
    </div>
  )
}

export default Comment;

CommentList 컴포넌트에 props 값을 설정해서 Comment 컴포넌트를 호출하도록 수정

import Comment from "./Comment";

function CommentList() {
  return ( 
    <>
      <Comment name="홍길동" comment="동쪽에 살아요." />
      <Comment name="홍길남" comment="남쪽에 살아요." />
      <Comment name="고길동" comment="둘리가 싫어요." />
    </>
  )
}
export default CommentList;

댓글 데이터를 이용하도록 수정

CommentList.js 파일에 댓글 데이터를 추가하고 map함수 이용해서 Comment가 출력되도록 수정

import Comment from "./Comment";

const comments = [
  { name: "홍길동", comment: "동쪽에 살아요" },
  { name: "홍길남", comment: "남쪽에 살아요" },
  { name: "고길동", comment: "둘리가 싫어요" },
]
function CommentList() {
  return ( 
    <>
    { // 익명 함수 표현식
      comments.map(function (data) {
        return <Comment name={data.name} comment={data.comment} />;
      })
    }
    { // 화살표 함수
      comments.map(data => {
        return <Comment name={data.name} comment={data.comment} />;
      })
    }
    { // 화살표 함수 축약
      comments.map(data => <Comment name={data.name} comment={data.comment} />)
    }
    </>
  )
}
export default CommentList;

key가 누락되었을 때 경고

import Comment from "./Comment";

const comments = [
  { name: "홍길동", comment: "동쪽에 살아요" },
  { name: "홍길남", comment: "남쪽에 살아요" },
  { name: "고길동", comment: "둘리가 싫어요" },
]
function CommentList() {
  return ( 
    <>
    { // 익명 함수 표현식
      comments.map(function (data, index) {
        return <Comment key={index} name={data.name} comment={data.comment} />;
      })
    }
    { // 화살표 함수
      comments.map((data, index) => {
        return <Comment key={index} name={data.name} comment={data.comment} />;
      })
    }
    { // 화살표 함수 축약
      comments.map((data, index) => <Comment key={index} name={data.name} comment={data.comment} />)
    }
    </>
  )
}
export default CommentList;

이렇게 map 함수의 두 번째 인자인 index 를 key 값으로 넣어주면 된다.

[실습] 글쓴이별 이미지를 다르게 출력하도록 수정해 보세요.

import Comment from "./Comment";

const comments = [
  { name: "홍길동", comment: "동쪽에 살아요", picture: "https://png.pngtree.com/png-clipart/20190705/original/pngtree-vector-business-men-icon-png-image_4186858.jpg"},
  { name: "홍길남", comment: "남쪽에 살아요", picture: "https://png.pngtree.com/png-clipart/20190630/original/pngtree-vector-avatar-icon-png-image_4162757.jpg" },
  { name: "고길동", comment: "둘리가 싫어요", picture: "https://png.pngtree.com/png-clipart/20190520/original/pngtree-male-worker-icon-graphic-png-image_3668949.jpg" },
]
function CommentList() {
  return ( 
    <>
    { // 익명 함수 표현식
      comments.map(function (data, index) {
        return <Comment key={index} comment={data} />;
      })
    }
    { // 화살표 함수
      comments.map((data, index) => {
        return <Comment key={index} comment={data} />;
      })
    }
    { // 화살표 함수 축약
      comments.map((data, index) => <Comment key={index} comment={data}/>)
    }
    </>
  )
}
export default CommentList;
function Comment ({comment: c}) {
  const {name, comment, picture} = c;

  const styles = {
    wrapper: {
      display: "flex",
      flexDirection: "row",
      border: "1px solid gray",
      borderRadius: 16,
      padding: 8,
      margin: 8
    },
    image: {
      width: 50,
      height: 50,
      borderRadius: 25
    },
    contentContainer: {
      marginLeft: 10,
      display: "flex",
      flexDirection: "column",
    },
    nameText: {
      color: "black",
      fontSize: 16,
      fontWeight: "bold",
      marginBottom: 5
    },
    commentText: {
      color: "black",
      fontSize: 16
    }
  };
  return (
    <div style={styles.wrapper}>
      {/* 작성한 사람의 이미지 */}
      <div>
        <img style={styles.image} src={picture} alt="profile" />
      </div>
      {/* 작성한 사람의 이름과 댓글 내용 */}
      <div style={styles.contentContainer}>
        <span style={styles.nameText}>{name}</span>
        <span style={styles.commentText}>{comment}</span>
      </div>
    </div>
  )
}

export default Comment;

또는 props를 이렇게 받을 수도 있다

function Comment (props) {
  const {comment: c} = props;
  const {name, comment, picture} = c;
...

[실습] 홍길동만 출력되도록 수정

import Comment from "./Comment";

const comments = [
  { name: "홍길동", comment: "동쪽에 살아요", picture: "https://png.pngtree.com/png-clipart/20190705/original/pngtree-vector-business-men-icon-png-image_4186858.jpg"},
  { name: "홍길남", comment: "남쪽에 살아요", picture: "https://png.pngtree.com/png-clipart/20190630/original/pngtree-vector-avatar-icon-png-image_4162757.jpg" },
  { name: "고길동", comment: "둘리가 싫어요", picture: "https://png.pngtree.com/png-clipart/20190520/original/pngtree-male-worker-icon-graphic-png-image_3668949.jpg" },
]
function CommentList() {
  return ( 
    <>
      <h1>모두 출력</h1>
      {
        comments.map((data, index) => <Comment key={index} name={data.name} comment={data.comment} picture={data.picture}/>)
      }
      <h1>홍길동만 출력</h1>
      { // filter 메서드를 이용하는 경우
        comments
          .filter(data => data.name === "홍길동")
          .map((data, index) => <Comment key={index} name={data.name} comment={data.comment} picture={data.picture}/>)
      }
      { // && 연산자를 이용한 조건부 렌더링
        comments 
          .map((data, index) => data.name === "홍길동" && <Comment key={index} name={data.name} comment={data.comment} picture={data.picture}/>)
      }
      { // 삼항연산자를 이용한 조건부 렌더링
        comments 
          .map((data, index) => data.name === "홍길동" ? <Comment key={index} name={data.name} comment={data.comment} picture={data.picture}/> : null)
      }
    </>
  )
}
export default CommentList;

[실습] 댓글 정보와 사용자 정보가 분리되어 있는 경우

댓글 작성자 이름과 동일한 이름을 가진 사용자 정보를 반환하는 함수를 만들어서 사용

import Comment from "./Comment";

const users = [
  { name: "홍길동", picture: "https://png.pngtree.com/png-clipart/20190705/original/pngtree-vector-business-men-icon-png-image_4186858.jpg"},
  { name: "홍길남", picture: "https://png.pngtree.com/png-clipart/20190630/original/pngtree-vector-avatar-icon-png-image_4162757.jpg" },
  { name: "고길동", picture: "https://png.pngtree.com/png-clipart/20190520/original/pngtree-male-worker-icon-graphic-png-image_3668949.jpg" },
];

const comments = [
  { name: "홍길동", comment: "동쪽에 살아요" },
  { name: "홍길남", comment: "남쪽에 살아요" },
  { name: "고길동", comment: "둘리가 싫어요" },
];

function getUserPicture(name) {
  return users.filter(user => user.name === name)[0].picture;
}

function CommentList() {
  return ( 
    <>
      <h1>모두 출력</h1>
      {
        comments.map((data, index) => <Comment key={index} name={data.name} comment={data.comment} picture={getUserPicture(data.name)} />)
      }
    </>
  )
}
export default CommentList;

이렇게도 가능 ! ⬇️

{
        comments.map((data, index) => <Comment key={index} name={data.name} comment={data.comment} picture={users.filter(user => user.name === data.name)[0].picture} />)
      }

filter 라서 배열로 반환해주기 때문에 결과값이 하나라도 [0] 이런식으로 첫 번째 요소를 가져와줘야 한다.

find 함수로 대체 ⇒ 처음으로 일치하는 배열 요소를 반환하는 함수 ⇒ 배열 인덱스를 사용할 필요가 없음

{
        comments.map((data, index) => <Comment key={index} name={data.name} comment={data.comment} picture={users.find(user => user.name === data.name).picture} />)
      }

댓글 정보와 사용자 정보를 결합한 새로운 데이터를 정의해서 활용 ⭐️

import Comment from "./Comment";

const users = [
  { name: "홍길동", picture: "https://png.pngtree.com/png-clipart/20190705/original/pngtree-vector-business-men-icon-png-image_4186858.jpg"},
  { name: "홍길남", picture: "https://png.pngtree.com/png-clipart/20190630/original/pngtree-vector-avatar-icon-png-image_4162757.jpg" },
  { name: "고길동", picture: "https://png.pngtree.com/png-clipart/20190520/original/pngtree-male-worker-icon-graphic-png-image_3668949.jpg" },
];

const comments = [
  { name: "홍길동", comment: "동쪽에 살아요" },
  { name: "홍길남", comment: "남쪽에 살아요" },
  { name: "고길동", comment: "둘리가 싫어요" },
];

const commentsWithPicture = comments.map(c => ({...c, picture: users.filter(u => u.name === c.name)[0].picture}));

function CommentList() {
  return ( 
    <>
      <h1>모두 출력</h1>
      {
        commentsWithPicture.map((data, index) => <Comment key={index} name={data.name} comment={data.comment} picture={data.picture} />)
      }
    </>
  )
}
export default CommentList;

find 사용해서 이렇게도 가능 !

const commentsWithPicture = comments.map(c => ({...c, picture: users.find(u => u.name === c.name).picture}));

[실습] 사용자 정보에 주민번호가 전달되었을 때, 남, 여 구분을 이름 뒤에 출력하도록 수정해 보세요. 예) 홍길동 (남)

import Comment from "./Comment";

const users = [
  { name: "홍길동", regno: "701010-1457934", picture: "https://png.pngtree.com/png-clipart/20190705/original/pngtree-vector-business-men-icon-png-image_4186858.jpg"},
  { name: "홍길남", regno: "201010-3457934", picture: "https://png.pngtree.com/png-clipart/20190630/original/pngtree-vector-avatar-icon-png-image_4162757.jpg" },
  { name: "고길동", regno: "211010-4157934", picture: "https://png.pngtree.com/png-clipart/20190520/original/pngtree-male-worker-icon-graphic-png-image_3668949.jpg" },
];

const comments = [
  { name: "홍길동", comment: "동쪽에 살아요" },
  { name: "홍길남", comment: "남쪽에 살아요" },
  { name: "고길동", comment: "둘리가 싫어요" },
];

function getUserGender (name) {
  const regno = users.find(u => u.name === name).regno;
  return [1, 3].includes(Number(regno[7])) ? "남" : "여";
}

const commentsWithPicture = comments.map(c => 
  ({
    ...c, 
    picture: users.find(u => u.name === c.name).picture,
    name: `${c.name} (${getUserGender(c.name)})`
  }));

function CommentList() {
  return ( 
    <>
      <h1>모두 출력</h1>
      {
        commentsWithPicture.map((data, index) => <Comment key={index} name={data.name} comment={data.comment} picture={data.picture} />)
      }
    </>
  )
}
export default CommentList;


state 변수

컴포넌트 내부에서 읽고 업데이트할 수 있는 값
state 변수 값이 변경되면 리렌더링이 발생 <= 클래스형 컴포넌트는 setState() 메서드를, 함수형 컴포넌트는 useState 훅 함수가 반환하는 세터 함수를 이용해서 값을 변경
화면에 즉시 반영되는 값만 쓰자!

클래스형 컴포넌트에서 state 변수를 사용

import { Component } from "react";
import Counter from "./Counter";

class App extends Component {
  render() {
    return (
      <>
        <Counter />
      </>
    );
  }
}

export default App;
import { Component } from "react"

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0
    };
  }
  render() {
    return (
      <>
        <h1>{this.state.number}</h1>
        <button onClick={() => {
            console.log(this.state.number);
            this.state.number = this.state.number + 1;
          }}>
          하나 증가
        </button>
      </>
    );
  }
}

export default Counter;

하나 증가를 누르면 숫자가 올라가야하는데 반영이 안 된다.
리렌더링이 안 된다,,,
버튼을 클릭해도 변경 숫자가 화면에 표시되지 않는다 😭

setState() 메서드를 이용해서 상태 변경해보자

<h1>{this.state.number}</h1>
<button onClick={() => {
    console.log(this.state.number);
    // this.state.number = this.state.number + 1;
    this.setState({ number: this.state.number + 1 });
  }}>
  하나 증가
</button>

state 변수가 많은 경우

비구조화 할당!

import { Component } from "react"

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
      fixedNumber: 10
    };
  }
  render() {
    const { number, fixedNumber } = this.state;
    return (
      <>
        <h1>{number}</h1>
        <button onClick={() => {this.setState({ number: number + 1 })}}>
          하나 증가
        </button>
        <h1>{fixedNumber}</h1>
      </>
    );
  }
}

export default Counter;

constructor 메서드를 사용하지 않고 state 변수를 초기화

import { Component } from "react"

class Counter extends Component {
  state = {
    number: 0,
    fixedNumber: 10
  };
  render() {
    const { number, fixedNumber } = this.state;
    return (
      <>
        <h1>{number}</h1>
        <button onClick={() => {this.setState({ number: number + 1 })}}>
          하나 증가
        </button>
        <h1>{fixedNumber}</h1>
      </>
    );
  }
}

export default Counter;

주로 이렇게 많이 쓴다.

setState() 메서드를 연속해서 호출하는 경우

updator 함수 형태로 넣어줘야한다.

import { Component } from "react"

class Counter extends Component {
  state = {
    number: 0,
    fixedNumber: 10
  };
  render() {
    const { number, fixedNumber } = this.state;
    return (
      <>
        <h1>{number}</h1>
        <button onClick={() => {
          this.setState(prevState => ({ number: prevState.number + 1}));
          this.setState(prevState => ({ number: prevState.number + 1}));
          this.setState(prevState => ({ number: prevState.number + 1}));
          this.setState(prevState => ({ number: prevState.number + 1}));
          this.setState(prevState => ({ number: prevState.number + 1}));
        }}>
          다섯 증가
        </button>
        <h1>{fixedNumber}</h1>
      </>
    );
  }
}

export default Counter;

상태 변수 업데이트 후 특정 작업을 실행할 때 -> setState 메서드의 두 번째 파라미터에 콜백 함수를 등록

업데이트 되고나서 호출되는 함수 !!!

<button onClick={() => {
  this.setState({ number: number + 1 }, () => {
  console.log('setState가 호출되었습니다.', this.state, number);
  });
}}>
  다섯 증가
</button>

함수형 컴포넌트에서 useState 사용하기 ⭐️😍

Say.js

import { useState } from "react";

function Say () {
  const [message, setMessage] = useState('');
  const [color, setColor] = useState('black');

  return (
    <>
      <h1 style={{ color }}>{message}</h1> {/* 단축 속성명 color: color -> color */}
      <button onClick={() => setMessage("입장합니다.")}>입장</button>
      <button onClick={() => setMessage("퇴장합니다.")}>퇴장</button>
      <button onClick={() => setColor("red")}>빨간색</button>
      <button onClick={() => setColor("blue")}>파란색</button>
      <button onClick={() => setColor("yellow")}>노란색</button>
    </>
  )
}
export default Say;

상태변수만 바꾸면 돼서 편하게 사용이 가능하다 !!!
onClick 콜백함수 사용 이유

state 사용 시 주의사항

  1. state 값을 변경할 때 setState (클래스형 컴포넌트) 또는 useState 훅 함수가 반환한 setter 함수 (함수형 컴포넌트) 를 이용해야 한다.
    → 그렇지 않으면 변경된 값이 화면에 반영될 수 없다. (forceUpdate() 함수를 이용해서 강제로 반영할 수 있으나, 절대 권장하지 않는다. - 리액트의 생명주기를 깨기 때문)

  2. 배열 또는 객체를 업데이트해야 하는 경우, 주소를 공유하기 때문에 그 사본을 만들고 그 사본의 값을 업데이트 후 setState 또는 setter 함수를 통해서 반영해야 한다.

객체의 사본을 만드는 방법 => 전개 연산자 사용

const obj = { a: 1, b: 2, c: 3 };
const newObj = { ...obj, b: 20 };

배열의 사본을 만드는 방법 => 전개 연산자 또는 배열 내장 함수를 활용

const arr = [1, 2, 3, 4];
const newArr1 = [...arr]; // [1, 2, 3, 4]
const newArr2 = arr.concat(100); // [1, 2, 3, 4, 100]
const newArr3 = arr.filter(i => i % 2 === 0); // [2, 4]
const newArr4 = arr.map(i => i * i); // [1, 4, 9, 16]

🚨 주의

const newArr5 = arr.push(100); // 기존 arr 배열에 100을 추가하고, 배열 길이를 반환

concat
push


이벤트 핸들링

리액트에서 이벤트 사용 시 주의사항

1. 이벤트 이름은 카멜 표현법을 사용

onClick, onKeyUp

2. 이벤트 핸들러로 자바스크립트 코드를 사용할 수 없고, 함수 형태로 전달

<button onclick="javascript: alear('clicked')" /> // (X)
<button onClick={ () => {...} } /> // 이벤트 핸들러 함수를 직접 정의
// 외부에 정의한 함수 이름을 전달. 일반적으로 이벤트 핸들러 함수 이름은 on 또는 handler 접두어를 사용
<button onClick={ onClick } /> 
<button onClick={ handlerClick } />

3. 이벤트는 DOM 요소에만 설정이 가능

DOM 요소 : HTML 태그들
ex) <div>

<div onMouseOver={ () => {...} } /> ... </div> // DOM 요소(=태그)에 이벤트 설정이 가능
<MyComponent onMouseOver={ () => {...} } /> ... </MyComponent> // 컴포넌트에 이벤트를 설정할 수 없다. 여기서 onMouseOver는 props로 동작해서 props 변수로 함수가 전달된다.

리액트에서 처리할 수 있는 이벤트 종류

cf_1
cf_2


[실습] useState 활용

증가 버튼을 클릭하면 OOOO 영역에 "현재 카운드는 OOO입니다."를 출력하도록, App, Todo, Title 컴포넌트를 작성해 보세요.

<App>
	<Todo>
		<Title title="OOOO">
			<p>OOOO</p>
        </Title>
        <button>증가</button>
	</Todo>
</App>

내 실습 코드
App.js

import { Todo } from "./Todo";

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

export default App;

Todo.js

import { useState } from "react"
import { Title } from "./Title"

export const Todo = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <Title title={count} />
      <button onClick={() => setCount(count + 1)}>증가</button>
    </>
  )
}

Title.js

export const Title = ({title}) => {
  return (
    <p>현재 카운트는 {title}입니다.</p>
  )
}

강사님 코드

import { useState } from "react";

function Title({title}) {
  return (
    <p>현재 카운트는 {title}입니다.</p>
  );
}

function Todo() {
  const [count, setCount] = useState();
  return (
    <>
      <Title title={count}></Title>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </>
  );
}

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

export default App;

0개의 댓글