λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
JavaScript/React

[React] Hooks 정리

by soyλ―Έλ‹ˆ 2021. 10. 26.

 

 

 

πŸ’‘ useState

// useState ν•¨μˆ˜ μ‚¬μš©ν•˜κΈ°
import { useState } from "react";

const Counter = () => {
  const [value, setValue] = useState(0); 
  return (
    <div>
      <p>ν˜„μž¬ μΉ΄μš΄ν„° 값은 {value} 이닀.</p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};
export default Counter;

 

 

  • useState λŠ” ν•˜λ‚˜μ˜ μƒνƒœ κ°’λ§Œ 관리할 수 μžˆμœΌλ‚˜ μ»΄ν¬λ„ŒνŠΈμ—μ„œ 관리해야 ν•  μƒνƒœκ°€ μ—¬λŸ¬ 개라면 useState λ₯Ό μ—¬λŸ¬ 번 μ‚¬μš©ν•˜λ©΄ λœλ‹€.
  • useState(0) μ΄λž€ μ΄ˆκΈ°κ°’μ„ 0 으둜 ν•˜κ² λ‹€λŠ” μ˜λ―Έμ΄λ‹€.
  • const [value, setValue] μ—μ„œ 첫 번째 μ›μ†ŒμΈ value λŠ” μƒνƒœ κ°’, 두 번째 μ›μ†ŒμΈ setValue λŠ” μƒνƒœλ₯Ό μ„€μ •ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€.
  • 이 ν•¨μˆ˜μ— νŒŒλΌλ―Έν„°λ₯Ό λ„£μ–΄μ„œ ν˜ΈμΆœν•˜λ©΄(setValue(value+1) κ³Ό 같이) 전달받은 νŒŒλΌλ―Έν„°λ‘œ 값이 λ°”λ€Œκ³  μ»΄ν¬λ„ŒνŠΈκ°€ μ •μƒμ μœΌλ‘œ λ¦¬λ Œλ”λ§ν•œλ‹€.

 

 

μ›Ή μ‹€ν–‰ ν™”λ©΄

 

 

 

useState μ—¬λŸ¬ 번 μ‚¬μš©ν•˜κΈ°

 

import { useState } from "react";

const Info = () => {
  const [name, setName] = useState("");
  const [nickname, setNickname] = useState("");

  const onChangeName = (e) => {
    setName(e.target.value);
  };

  const onChangeNickName = (e) => {
    setNickname(e.target.value);
  };

  return (
    <div>
      <input value={name} onChange={onChangeName} />
      <input value={nickname} onChange={onChangeNickName} />
      <div>이름 : {name}</div>
      <div>λ‹‰λ„€μž„ : {nickname}</div>
    </div>
  );
};

export default Info;

 

 

 

μ›Ή μ‹€ν–‰ ν™”λ©΄

 

 

 

 

πŸ’‘ useEffect

λ¦¬μ•‘νŠΈ μ»΄ν¬λ„ŒνŠΈκ°€ λ Œλ”λ§ 될 λ•Œλ§ˆλ‹€ νŠΉμ • μž‘μ—…μ„ μˆ˜ν–‰ν•˜λ„λ‘ μ„€μ •ν•  수 μžˆλŠ” Hook 이닀. useEffect λŠ” 기본적으둜 λ Œλ”λ§λ˜κ³  λ‚œ μ§ν›„λ§ˆλ‹€ μ‹€ν–‰λ˜λ©°, 두 번째 νŒŒλΌλ―Έν„° 배열에 무엇을 λ„£λŠ”μ§€μ— 따라 μ‹€ν–‰λ˜λŠ” 쑰건이 달라진닀. 클래슀 ν˜• μ»΄ν¬λ„ŒνŠΈμΈ componentDidMount 와 componentDidUpdate λ₯Ό ν•©μΉœ ν˜•νƒœλ‘œ 보아도 λ¬΄λ°©ν•˜λ‹€.

 

import { useState, useEffect } from "react";

const Info = () => {
  const [name, setName] = useState("");
  const [nickname, setNickname] = useState("");

  useEffect(() => {
    console.log("λ Œλ”λ§μ΄ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.");
    console.log({
      nickname,
    });
  }, [nickname]);

  const onChangeName = (e) => {
    setName(e.target.value);
  };

  const onChangeNickName = (e) => {
    setNickname(e.target.value);
  };

  return (
    <div>
      <input value={name} onChange={onChangeName} />
      <input value={nickname} onChange={onChangeNickName} />
      <div>이름 : {name}</div>
      <div>λ‹‰λ„€μž„ : {nickname}</div>
    </div>
  );
};

export default Info;

 

useEffect λ₯Ό νŠΉμ • 값이 μ—…λ°μ΄νŠΈ 될 λ•Œλ§Œ ν˜ΈμΆœν•˜κ³  μ‹ΆμœΌλ©΄, useEffect 의 두 번째 νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬λ˜λŠ” λ°°μ—΄ [ ] μ•ˆμ— κ²€μ‚¬ν•˜κ³  싢은 값을 λ„£μ–΄ μ£Όλ©΄ λœλ‹€.

 

  useEffect(() => {
    console.log("λ Œλ”λ§μ΄ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.");
    console.log({
      nickname,
    });
  }, [nickname]);

 

 

κ²€μ‚¬ν•˜κ³ μž ν•˜λŠ” 값을 nickname 으둜 μ„€μ •ν–ˆλ”λ‹ˆ nickname 이 λ°”λ€” λ•Œλ§Œ useEffect 의 ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•˜λŠ” κ²ƒμœΌλ‘œ λ°”λ€Œμ—ˆλ‹€.

 

 

 

 

μ›Ή μ‹€ν–‰ ν™”λ©΄

 

 

 

 

 

 

useEffect  뒷정리 ν•˜κΈ°

μ»΄ν¬λ„ŒνŠΈκ°€ μ–Έλ§ˆμš΄νŠΈλ˜κΈ° μ „μ΄λ‚˜ μ—…λ°μ΄νŠΈ 되기 직전에 μ–΄λ– ν•œ μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ³  μ‹ΆμœΌλ©΄ useEffect μ—μ„œ 뒷정리(clean up) ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

 

// App.js
import React, { useState } from "react";
import Info from "./Info";
const App = () => {
  const [visible, setVisible] = useState(false);
  return (
    <div>
      <button
        onClick={() => {
          setVisible(!visible);
          console.log(!visible);
        }}
      >
        {visible ? "숨기기" : "보이기"}
      </button>
      <hr />
      {visible && <Info />}
    </div>
  );
};
export default App;

 

// Info.js
import { useState, useEffect } from "react";
const Info = () => {
  const [name, setName] = useState("");
  const [nickname, setNickname] = useState("");
  useEffect(() => {
    console.log("effect");
    return () => { // cleanup ν•¨μˆ˜ λ°˜ν™˜
      console.log("unmount");
    };
  }, []);
  const onChangeName = (e) => {
    setName(e.target.value);
  };
  const onChangeNickName = (e) => {
    setNickname(e.target.value);
  };
  return (
    <div>
      <input value={name} onChange={onChangeName} />
      <input value={nickname} onChange={onChangeNickName} />
      <div>이름 : {name}</div>
      <div>λ‹‰λ„€μž„ : {nickname}</div>
    </div>
  );
};
export default Info;

 

λ Œλ”λ§ 될 λ•Œλ§ˆλ‹€ 뒷정리 ν•¨μˆ˜κ°€ 계속 λ‚˜νƒ€λ‚˜λŠ” 것을 확인할 수 μžˆλ‹€. 뒷정리 ν•¨μˆ˜κ°€ 호좜될 λ•ŒλŠ” μ—…λ°μ΄νŠΈ 되기 μ§μ „μ˜ 값을 보여쀀닀. μ–Έλ§ˆμš΄νŠΈ(μ»΄ν¬λ„ŒνŠΈκ°€ DOM μ—μ„œ μ œκ±°λ˜λŠ” 것, μ—¬κΈ°μ„œλŠ” 숨기기 λ²„νŠΌ λˆ„λ₯Ό λ•Œλ₯Ό μ˜λ―Έν•œλ‹€.) 될 λ•Œλ§Œ 뒷정리 ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κ³  μ‹ΆμœΌλ©΄ useEffect ν•¨μˆ˜μ˜ 두 번째 νŒŒλΌλ―Έν„°μ— λΉ„μ–΄ μžˆλŠ” 배열을 λ„£μœΌλ©΄ λœλ‹€.

 

 

 

 

μ›Ή μ‹€ν–‰ ν™”λ©΄

 

 

 

 

 

 

 

 

 

πŸ’‘ useReducer

useReducer λŠ” useState 보닀 더 λ‹€μ–‘ν•œ μ»΄ν¬λ„ŒνŠΈ 상황에 따라 λ‹€μ–‘ν•œ μƒνƒœλ₯Ό λ‹€λ₯Έ κ°’μœΌλ‘œ μ—…λ°μ΄νŠΈ ν•΄μ£Όκ³  싢을 λ•Œ μ‚¬μš©ν•˜λŠ” Hook 이닀. Reducer λŠ” ν˜„μž¬ μƒνƒœ(state), 그리고 μ—…λ°μ΄νŠΈλ₯Ό μœ„ν•΄ ν•„μš”ν•œ 정보λ₯Ό 담은 action 값을 전달받아 μƒˆλ‘œμš΄ μƒνƒœλ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€. useReducer μ—μ„œ action 은 κ·Έ μ–΄λ–€ 값도 μ‚¬μš© κ°€λŠ₯ν•˜λ©°, reducer ν•¨μˆ˜μ—μ„œ μƒˆλ‘œμš΄ μƒνƒœλ₯Ό λ§Œλ“€ λ•ŒλŠ” λ°˜λ“œμ‹œ λΆˆλ³€μ„±μ„ μ§€μΌœ μ£Όμ–΄μ•Ό ν•œλ‹€.

 

 

// useReducer μ‚¬μš©ν•˜κΈ°

import { useReducer } from "react";
function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return { value: state.value + 1 };
    case "DECREMENT":
      return { value: state.value - 1 };
    default:
      return state;
  }
}

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, { value: 0 });

  return (
    <div>
      <p>
        ν˜„μž¬ μΉ΄μš΄ν„° 값은 <b>{state.value}</b>μž…λ‹ˆλ‹€.
      </p>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button>
    </div>
  );
};
export default Counter;

 

useReducer 의 첫 번째 νŒŒλΌλ―Έν„°μ—λŠ” reducer ν•¨μˆ˜λ₯Ό λ„£κ³ , 두 번째 νŒŒλΌλ―Έν„°μ—λŠ” ν•΄λ‹Ή reducer 의 κΈ°λ³Έκ°’ {value: 0} 을 λ„£μ–΄μ€€λ‹€. 이 Hook 을 μ‚¬μš©ν•˜λ©΄ state κ°’κ³Ό dispatch ν•¨μˆ˜λ₯Ό λ°›μ•„ μ˜¨λ‹€. μ—¬κΈ°μ„œ state λŠ” ν˜„μž¬ 가리킀고 μžˆλŠ” μƒνƒœκ³ , dispatch λŠ” μ•‘μ…˜μ„ λ°œμƒμ‹œν‚€λŠ” ν•¨μˆ˜μ΄λ‹€. dispatch(action) κ³Ό 같은 ν˜•νƒœλ‘œ, ν•¨μˆ˜ μ•ˆμ— νŒŒλΌλ―Έν„°λ‘œ action 값을 λ„£μ–΄μ£Όλ©΄ reducer ν•¨μˆ˜κ°€ ν˜ΈμΆœλ˜λŠ” ꡬ쑰이닀. 

 

 

 

// Info.js
import { useReducer } from "react";
function reducer(state, action) {
  return {
    ...state,  // useReducer λ‘œλΆ€ν„° 전달받은 state κ°’ (name:'', nickname:'') 볡사
    [action.name]: action.value, // action.name 은 e.target.name (input 의 name) 이며 action.value λŠ” e.target.value (input 의 value) 인 것이닀.
  };
}

const Info = () => {
  const [state, dispatch] = useReducer(reducer, {
    name: "",  // reducer 에 ...state κ°’μœΌλ‘œ μ „λ‹¬ν•˜κΈ°
    nickname: "",
  });
  const { name, nickname } = state; // state κ°€ 가지고 μžˆλŠ” name, nickname 값을 name, nickname ν•„λ“œμ— λ„£μ–΄μ€€λ‹€.

  const onChange = (e) => {
    dispatch(e.target); // action 은 e.target
    console.log(e.target);
  };

  return (
    <div>
      <input name="name" value={name} onChange={onChange} />
      <input name="nickname" value={nickname} onChange={onChange} />
      <div>이름 : {name}</div>
      <div>λ‹‰λ„€μž„ : {nickname}</div>
    </div>
  );
};

export default Info;

 

 

dispatch(e.target) 으둜 μ—¬κΈ°μ„œ dispatch κ°€ μ „λ‹¬ν•˜λŠ” action 은 e.target 이닀. 즉, action.name 은 e.target.name 이고 이것은 input μš”μ†Œμ˜ name 속성을 λ§ν•œλ‹€. action.value λŠ” e.target.value 이고 이것은 input μš”μ†Œμ˜ value 속성을 λ§ν•œλ‹€. λ”°λΌμ„œ μœ„μ˜ input μš”μ†Œμ— name="name" 및 name="nickname" 을 λͺ…μ‹œν•΄μ£Όμ§€ μ•ŠμœΌλ©΄ input 에 κ°’ μž…λ ₯이 λΆˆκ°€ν•˜κ³ , 싀행이 μ œλŒ€λ‘œ λ˜μ§€ μ•ŠλŠ”λ‹€.

 

 

 

 

 

μ›Ή μ‹€ν–‰ ν™”λ©΄

 

 

 

 

 

 

πŸ’‘ useMemo

useMemo λ₯Ό μ‚¬μš©ν•˜λ©΄ ν•¨μˆ˜ μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€μ—μ„œ λ°œμƒν•˜λŠ” 연산을 μ΅œμ ν™”ν•  수 μžˆλ‹€.

 

// Average.js
import { useState, useMemo } from "react";
const getAverage = (numbers) => {
  console.log("평균값 계산 쀑..");
  console.log(numbers);
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b);
  return sum / numbers.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState("");

  const onChange = (e) => {
    setNumber(e.target.value);
  };

  const onInsert = () => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber("");
  };

  // νŠΉμ • 값이 λ°”λ€Œμ—ˆμ„ λ•Œλ§Œ 연산을 μ‹€ν–‰ν•˜κ³  μ›ν•˜λŠ” 값이 λ°”λ€Œμ§€ μ•Šμ•˜λ‹€λ©΄ 이전에 μ—°μ‚°ν–ˆλ˜ κ²°κ³Όλ₯Ό λ‹€μ‹œ μ‚¬μš©ν•˜λŠ” 방식
  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={number} onChange={onChange} />
      <button onClick={onInsert}>등둝</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      {/* list λŠ” value 만 λ‹΄κΈ΄ 리슀트둜 됨. */}
      <div>
        <b>평균값: </b> {avg}
      </div>
    </div>
  );
};

export default Average;

 

  const avg = useMemo(() => getAverage(list), [list]);

 

useMemo λ₯Ό μ‚¬μš©ν•˜λ©΄ λ Œλ”λ§ ν•˜λŠ” κ³Όμ •μ—μ„œ νŠΉμ • 값이 λ°”λ€Œμ—ˆμ„ λ•Œλ§Œ 연산을 μ‹€ν–‰ν•˜κ³  μ›ν•˜λŠ” 값이 λ°”λ€Œμ§€ μ•Šμ•˜λ‹€λ©΄ 이전에 μ—°μ‚°ν–ˆλ˜ κ²°κ³Όλ₯Ό λ‹€μ‹œ μ‚¬μš©ν•˜λŠ” 방식이닀. μœ„μ˜ μ½”λ“œμ—μ„œλŠ” list 의 값이 λ°”λ€Œμ—ˆμ„ λ•Œλ§Œ 연산을 μ‹€ν–‰ν•˜λ„λ‘ 두 번째 νŒŒλΌλ―Έν„°λ₯Ό list 둜 두어 λ Œλ”λ§ ν•  λ•Œλ§ˆλ‹€ κ²€μ‚¬ν•˜λ„λ‘ ν–ˆλ‹€.

 

 

<ul>
   {list.map((value, index) => (
   <li key={index}>{value}</li>
    ))}
</ul>

 

list.map 으둜 인해 value 와 index 에 따라 <li> νƒœκ·Έλ‘œ 묢여지고 list 둜 λ¬Άμ—¬μ Έ λ‚˜μ˜¨λ‹€.

 

 

 

 

 

πŸ’‘ useCallback

// useCallback
import { useState, useMemo, useCallback } from "react";
const getAverage = (numbers) => {
  console.log("평균값 계산 쀑..");
  console.log(numbers);
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b);
  return sum / numbers.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState("");

  const onChange = useCallback((e) => {
    setNumber(e.target.value);
  }, []);              // μ»΄ν¬λ„ŒνŠΈκ°€ 처음 λ Œλ”λ§λ  λ•Œλ§Œ ν•¨μˆ˜ 생성

  const onInsert = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber("");
  }, [number, list]);  // number ν˜Ήμ€ list κ°€ λ°”λ€Œμ—ˆμ„ λ•Œλ§Œ ν•¨μˆ˜ 생성

  // νŠΉμ • 값이 λ°”λ€Œμ—ˆμ„ λ•Œλ§Œ 연산을 μ‹€ν–‰ν•˜κ³  μ›ν•˜λŠ” 값이 λ°”λ€Œμ§€ μ•Šμ•˜λ‹€λ©΄ 이전에 μ—°μ‚°ν–ˆλ˜ κ²°κ³Όλ₯Ό λ‹€μ‹œ μ‚¬μš©ν•˜λŠ” 방식
  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={number} onChange={onChange} />
      <button onClick={onInsert}>등둝</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      {/* list λŠ” value 만 λ‹΄κΈ΄ 리슀트둜 됨. */}
      <div>
        <b>평균값: </b> {avg}
      </div>
    </div>
  );
};

export default Average;

 

useCallback 의 첫 번째 νŒŒλΌλ―Έν„°μ—λŠ” μƒμ„±ν•˜κ³  싢은 ν•¨μˆ˜λ₯Ό λ„£κ³ , 두 번째 νŒŒλΌλ―Έν„°μ—λŠ” 배열을 λ„£μœΌλ©΄ λœλ‹€. 이 λ°°μ—΄μ—λŠ” μ–΄λ–€ 값이 λ°”λ€Œμ—ˆμ„ λ•Œ ν•¨μˆ˜λ₯Ό μƒˆλ‘œ 생성해야 ν•˜λŠ”μ§€ λͺ…μ‹œν•΄μ•Ό ν•œλ‹€. onChange 처럼 λΉ„μ–΄ μžˆλŠ” 배열을 λ„£κ²Œ 되면 μ»΄ν¬λ„ŒνŠΈκ°€ λ Œλ”λ§ 될 λ•Œ λ§Œλ“€μ—ˆλ˜ ν•¨μˆ˜λ₯Ό κ³„μ†ν•΄μ„œ μž¬μ‚¬μš©ν•˜κ²Œ 되며 onInsert 처럼 λ°°μ—΄ μ•ˆμ— number 와 list λ₯Ό λ„£κ²Œ 되면 input λ‚΄μš©μ΄ λ°”λ€Œκ±°λ‚˜ μƒˆλ‘œμš΄ ν•­λͺ©μ΄ 좔가될 λ•Œ μƒˆλ‘œ λ§Œλ“€μ–΄μ§„ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜κ²Œ λœλ‹€.

 

 

 

 

 

πŸ’‘ useRef

ν•¨μˆ˜ μ»΄ν¬λ„ŒνŠΈμ—μ„œ ref λ₯Ό μ‰½κ²Œ μ‚¬μš©ν•  수 μžˆλ„λ‘ ν•΄ μ€€λ‹€.

 

// useRef

import { useState, useMemo, useCallback, useRef } from "react";
const getAverage = (numbers) => {
  console.log("평균값 계산 쀑..");
  console.log(numbers);
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b);
  return sum / numbers.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState("");
  const inputEl = useRef(null);

  const onChange = useCallback((e) => {
    setNumber(e.target.value);
  }, []); // μ»΄ν¬λ„ŒνŠΈκ°€ 처음 λ Œλ”λ§λ  λ•Œλ§Œ ν•¨μˆ˜ 생성

  const onInsert = useCallback(() => {
    const nextList = list.concat(parseInt(number));
    setList(nextList);
    setNumber("");
    inputEl.current.focus();
  }, [number, list]); // number ν˜Ήμ€ list κ°€ λ°”λ€Œμ—ˆμ„ λ•Œλ§Œ ν•¨μˆ˜ 생성

  // νŠΉμ • 값이 λ°”λ€Œμ—ˆμ„ λ•Œλ§Œ 연산을 μ‹€ν–‰ν•˜κ³  μ›ν•˜λŠ” 값이 λ°”λ€Œμ§€ μ•Šμ•˜λ‹€λ©΄ 이전에 μ—°μ‚°ν–ˆλ˜ κ²°κ³Όλ₯Ό λ‹€μ‹œ μ‚¬μš©ν•˜λŠ” 방식
  const avg = useMemo(() => getAverage(list), [list]);

  return (
    <div>
      <input value={number} onChange={onChange} ref={inputEl} />
      <button onClick={onInsert}>등둝</button>
      <ul>
        {list.map((value, index) => (
          <li key={index}>{value}</li>
        ))}
      </ul>
      {/* list λŠ” value 만 λ‹΄κΈ΄ 리슀트둜 됨. */}
      <div>
        <b>평균값: </b> {avg}
      </div>
    </div>
  );
};

export default Average;

 

inputEl 은 input μš”μ†Œλ₯Ό λ§ν•œλ‹€. inputEl.current 값이 μ‹€μ œ input μš”μ†Œλ₯Ό 가리킨닀. 둜컬 λ³€μˆ˜λŠ” λ Œλ”λ§κ³Ό 상관없이 λ°”λ€” 수 μžˆλŠ” 값을 μ˜λ―Έν•œλ‹€.

 

 

 

 

 

 

 

λ„μ„œ [λ¦¬μ•‘νŠΈλ₯Ό λ‹€λ£¨λŠ” 기술] 을 μ°Έκ³ ν•˜μ—¬ μž‘μ„±ν•œ κΈ€μž…λ‹ˆλ‹€.

λŒ“κΈ€