Project๐Ÿ› ๏ธโš™๏ธ

[React/TypeScript] Input์˜ event.target.value์„ string์ด ์•„๋‹Œ ํŠน์ • ํƒ€์ž…์œผ๋กœ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ์„๊นŒ?

Gaeun Lee 2024. 5. 16. 05:12

์•ˆ๋…•ํ•˜์„ธ์š”. ์บก์Šคํ†ค ํ”„๋กœ์ ํŠธ ์ค‘ ๊ฒฝํ—˜ํ•œ Trouble Shooting์„ ๊ณต์œ ํ•ด๋ณด๊ณ ์ž ๊ธ€์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

 

๋‹ค๋“ค ๊ตฌ๊ธ€ ํผ, ๋„ค์ด๋ฒ„ ํผ๊ณผ ๊ฐ™์€ ์„œ๋น„์Šค๋ฅผ ๋งŽ์ด ์ด์šฉํ•ด๋ณด์…จ์„๊นŒ์š”?

ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ์บก์Šคํ†ค ํ”„๋กœ์ ํŠธ์—์„œ ํ•™์ƒ๋“ค์—๊ฒŒ ํ–‰์‚ฌ ์ฐธ์—ฌ ์‹ ์ฒญ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•œ ํผ (์„ค๋ฌธ) ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด, ํผ ์ƒ์„ฑ ํŽ˜์ด์ง€๋ฅผ ์ง์ ‘ ๋งŒ๋“ค๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ์š”.

ํผ ์ƒ์„ฑ ํŽ˜์ด์ง€์—์„œ๋Š” ๋งŽ์€ ์ด๋ฒคํŠธ๋“ค(์ถ”๊ฐ€, ์‚ญ์ œ, ์ˆ˜์ • ๋“ฑ)์ด ์ด๋ฃจ์–ด์ง€๋ฏ€๋กœ, ์ด์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ Input ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์ค˜์•ผ ํ•ด์„œ ์‹ ๊ฒฝ ์“ธ ๋ถ€๋ถ„์ด ๋งŽ์€ ํŽ˜์ด์ง€๋ผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊นŒ์ง€ ์ด์šฉํ•œ๋‹ค๋ฉด Type Guard๊นŒ์ง€ ์‹ ๊ฒฝ์จ์ค˜์•ผ ํ•˜๋Š”๋ฐ์š”๐Ÿฅฒ

์บก์Šคํ†ค ํ”„๋กœ์ ํŠธ ํผ ์ƒ์„ฑ ํŽ˜์ด์ง€

 

 

 

 

ํผ ์ƒ์„ฑ ํŽ˜์ด์ง€์—์„œ๋Š” useState๋กœ ๊ด€๋ฆฌ๋˜๋Š” questionList๋ฅผ ๋งตํ•‘ํ•˜์—ฌ ์งˆ๋ฌธ ๋ฐ•์Šค ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

const [questionList, setQuestionList] = useState<Question[]>([]);

์งˆ๋ฌธ ๋ฐ•์Šค ์ปดํฌ๋„ŒํŠธ

questionList๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” Question์˜ ํƒ€์ž…์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

type Question = {
  type: QuestionType;
  title: string;
  description: string;
};
type QuestionType = 'SUBJECTIVE' | 'MULTIPLE'; // ์ฃผ๊ด€์‹, ๊ฐ๊ด€์‹

 

์งˆ๋ฌธ ๋ฐ•์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์‹œ๋ฉด ์งˆ๋ฌธ ์œ ํ˜•์„ ์„ ํƒํ•˜๋Š” ์ฃผ๊ด€์‹/๊ฐ๊ด€์‹ ๋ฒ„ํŠผ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ผ๋””์˜ค input์„ ํด๋ฆญ์‹œ onChangeEvent ํ•ธ๋“ค๋Ÿฌ๋Š” questionList[ํ•ด๋‹น ์ธ๋ฑ์Šค]์˜ type property๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

                  <input
                    className="hidden"
                    name={`question-type-${index}`}
                    type="radio"
                    value={questionList[index].type}
                    onChange={(e) => {
                      setQuestionList((prev) => {
                        return [
                          ...prev.slice(0, index),
                          { ...prev[index], type: e.target.value },
                          ...prev.slice(index + 1),
                        ];
                      });
                    }}
                    checked={questionList[index].tag === type}
                  />
                  {icon}
                </label>
Type '{ type: string; title: string; description: string; }' is not assignable to type 'Question'. Types of property 'type' are incompatible. Type 'string' is not assignable to type 'QuestionType'.

๊ทธ๋Ÿฌ๋‚˜ onChange ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ํƒ€์ž… ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

 

e.target.value์˜ ํƒ€์ž…์ด string์ด๋ฏ€๋กœ QuestionType๊ณผ ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค๋Š” ์˜ค๋ฅ˜์ธ๋ฐ์š”.

์ปค์„œ๋ฅผ ๋Œ€์„œ ํ™•์ธํ•ด๋ณด๋‹ˆ e.target.value๊ฐ€ string์œผ๋กœ ์ง€์ •๋˜์–ด ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

๊ทธ๋ ‡๋‹ค๋ฉด value์˜ ๋ถ€๋ชจ์ธ Change Event์˜ ํƒ€์ž…์„ ์ง์ ‘ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

 

๋„ค! input์˜ ๊ธฐ๋ณธ ์†์„ฑ์„ ์ƒ์†ํ•˜๊ณ  Change Event๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜๋Š” input ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง์ ‘ ๋งŒ๋“ ๋‹ค๋ฉด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

 

์šฐ์„  Radio Input ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์ „์— ์ปดํฌ๋„ŒํŠธ props์˜ ํƒ€์ž…์„ ์ •์˜ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

props ํƒ€์ž…์ธ QuestionTypeButton์— Input ๊ธฐ๋ณธ ์†์„ฑ์„ ์ƒ์†๋ฐ›๊ฒŒ ํ•˜์˜€๊ณ , onChange ์†์„ฑ์€ event์— ์ง์ ‘ QuestionTypeButtonChangeEvent ํƒ€์ž…์„ ๋ช…์‹œํ•˜์—ฌ ์˜ค๋ฒ„๋ผ์ด๋”ฉ๋˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

type QuestionTypeButton = HTMLAttributes<HTMLInputElement> & {
  ...์ƒ๋žต
  value: QuestionType;
  onChange: (e: QuestionTypeButtonChangeEvent) => void;
};

 

event์˜ ํƒ€์ž…์ธ QuestionTypeButtonChangeEvent๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด HTMLInputElement ChangeEvent์˜ ํƒ€์ž…์„ ์ƒ์†๋ฐ›์œผ๋ฉฐ, target.value์˜ ํƒ€์ž…๋งŒ QuestionType์œผ๋กœ ์ง€์ •ํ•˜์—ฌ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด event.target.value๋Š” string์ด ์•„๋‹Œ QuestionType์œผ๋กœ ๋ช…์‹œ๋ฉ๋‹ˆ๋‹ค!

type QuestionTypeButtonChangeEvent = ChangeEvent<HTMLInputElement> & {
  target: { value: QuestionType };
};

 

๊ทธ๋ ‡๊ฒŒ ๋งŒ๋“  QuestionTypeButton ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

export type QuestionTypeButtonChangeEvent = ChangeEvent<HTMLInputElement> & {
  target: { value: QuestionType };
};

type QuestionTypeButton = HTMLAttributes<HTMLInputElement> & {
  ...์ƒ๋žต
  value: QuestionType;
  onChange: (e: QuestionTypeButtonChangeEvent) => void;
};

export const QuestionTypeButton = ({
  ...์ƒ๋žต
  value,
  onChange,
  ...props
}: QuestionTypeButton) => {
  return (
    <label>
      <input
		...์ƒ๋žต
        value={value}
        onChange={onChange}
        {...props}
      />
      {icon} <span>{typeValue[value]}</span>
    </label>
  );
};

 

๋งŒ๋“  QuestionTypeButton์„ ์‚ฌ์šฉํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์‹œ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ๋‹ค์‹œ ๊ฒช๊ฒŒ ๋˜๋Š”๋ฐ์š”.

<QuestionTypeButton
  ...์ƒ๋žต
  index={index}
  value={type}
  onChange={(e) => {
    setQuestionList((prev) => {
      return [
        ...prev.slice(0, index),
        { ...prev[index], type: e.target.value },
        ...prev.slice(index + 1),
      ];
    });
  }}
/>
))}

 

event์˜ ํƒ€์ž…์„ ํ™•์ธํ•ด๋ณด๋‹ˆ FormEvent์™€ ๊ฐ™์ด ์ง€์ •๋˜์–ด์žˆ๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฏ€๋กœ event์˜ ํƒ€์ž…์„ ์•ž์—์„œ ์ •์˜ํ•œ QuestionTypeButtonChangeEvent๋กœ ๋ช…์‹œํ•œ๋‹ค๋ฉด ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

onChange={(e: QuestionTypeButtonChangeEvent) => { .... }

 

๊ทธ๋ ‡๊ฒŒ ๋˜๋ฉด ์งˆ๋ฌธ ์œ ํ˜• ์„ ํƒ ๋ฒ„ํŠผ์„ ํด๋ฆญ์‹œ questionList์˜ ์ƒํƒœ ์•Œ๋งž๊ฒŒ ์—…๋ฐ์ดํŠธ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!