kakao로그인은 솔직히 어렵지않다고 생각했다 왜냐면 백엔드 없이 리액트로도 충분히 혼자해봤었기 때문이다. 하지만 그런 간단한 로그인 절차만 거치는 과정이 아닌 카카오에서 제공해주는 사용자의 정보들을 넘겨받고 넘겨받는 과정이 상당히 복잡해서 많이 놀랐다(이거만 삽질을 3일...)

우선 카카오 로그인을 구현할때 백엔드 개발자분들이 그냥 버튼만 클릭하면 백에서 redirect url을 설정하여서 로그인이 되면 사용자의 정보들을 다 알아서 받아오고 redirect url로 넘겨 그 정보들을 프론트에서 표기하는 방식으로 하려고했다.

근데 여기서의 실수는 백엔드의 local에서는 프론트의 local로 redirect를 할수없다는 점이였다. (물론 이것도 나중에 와서 안 사실인데 만약 프론트와 백엔드가 배포를 한 상태라면 백에서 redicert를 프론트쪽으로 쏴줄수있었겠다는 생각이 들었다...)

그래서 이 난감한 상황을 어떻게 할지 생각을하다가 카카오 공문을 보면서 다시금 구현해보기로 했다. 

이렇게 그냥 하기로했는데 문제는 카카오에서는 사용자의 전화번호를 전달해주지않는다.

우리의 서비스 특성상 사용자의 전화번호를 꼭 필요하기에 저렇게 일반적인 방법이 아닌 다른방법으로 바꿔서 소셜로그인을 진행하자고 생각했다.

소셜로그인을 진행하되, 수집항목을 추가하자 였다.

프론트에서 REST API를 통하여 사용자의 로그인을 유도하면 사용자는 카카오 로그인을 진행하게 되며, 이때 사용자는 우리가 설정한 redirect url로 넘어가게된다 이때의 인가코드를 프론트에서 처리하여 백으로 보내준다.

그후 백에서 인가코드를 받으면 응답으로 전화번호와 비밀번호를 준다. 만약 가입한 사용자가 처음 이용자라면 다른 redirect url로 넘어가 사용자에게 전화번호를 수집한다 그 후 프론트에서 로그인에 필요한 전화번호와 비밀번호를 백으로 보내어 로그인 처리를 하게 만들었다.

다만 여기에서의 에러는 또 발생한다 

아까의 mixed content에러에서는 원래 백엔드 배포주소인 http와 통신테스트를 위해 로컬로 통신을 하면 같은 http니까 응답이 제대로 왔었다 하지만 카카오에서는 http는 막아놔서 https로밖에 전송을 하지 못한다. 그래서 백엔드가 배포한 주소로 인가코드를 로컬이던 배포된주소로던 뭐로 보내서 응답을 받아내야하는데 계속 못받았다.

이러한 삽질끝에 그냥 백엔드 배포 주소를 https로 바꿔서 진행하였다.

 

소셜 로그인이 아닌 일반 로그인 기능을 구현하는데 있어서

리액트와 스프링 부트와의 협업으로 진행하였다 회원가입 절차는 api 명세서에 나와있는대로 진행하였다

api명세서에 

이러한 기능들이 있어서 axios를 사용하여서 통신을 진행하였다.

다만 통신에서 문제가 있었다면

백엔드와 프론트엔드 사이에서의 로컬에서는 통신이 잘되었지만 배포된 주소로는 잘안되었다는 점이다.

이러한 이유는 크롬에서 https와 http간의 통신을 막아놨다는 이유이다.

만약 내가 로컬로 요청을 보내어 응답을 받기위해 백엔드 배포주소(http)로 요청을 보내면 응답이 제대로 오지만 프론트 엔드 배포주소를 https로 하고 백엔드주소로 보내면 응답이 오지않는다는 소리이다.

이건 https를 적용후 api 요청을 http로 보내면 발생하는 브라우저 보안 에러이다.

https는 http의 TCP/IP통신에 SSL를 한층 더 위에 얹은 것이다.

 

이것에 대한 해결방법을 강구하던 도중 API요청을 HTTP에서 HTTPS로 바꿔서 요청을 보내기인데 이건 말도안되는 소리라고 생각했다 배포주소가 있는데 거기서 바꿔서 요청을 보낸다한들 안되기때문이다.

또하나는 프론트엔드에서의 INDEX파일에 meta태그를 추가하는 방법인데 

<meta 
  http-equiv="Content-Security-Policy"
  content="upgrade-insecure-requests"
/>

이러한 방식으로 해보고 요청을 보내보면 axios에서 통신하려는 url이 강제로 변형이되어버려 잘못된 주소로 요청이 가는것을 확인했다 이유는 좀 더 알아봐야한다...

그렇게 삽질을 계속 3일간 하던도중 백엔드에서 그냥 배포주소를 http에서 https로 바꾸는 것으로 결정을 했다.

그런데 이러한 과정에서도 도메인을 따로 사는 과정도있었고 복잡했다...

그 후 통신을 했는데 잘되었다. https -> https

처음엔 요청할때의 바디가 잘못이되었다하고 계속 코드를 수정하며 바꿔가면서 요청을 보냈다 무수히 많은 에러를 보던도중 계속해서 mixed content에러가 뜨길래 이것에 대해서 봐보니 처음부터 잘못되었다는 사실에 놀랐다..하하...

그래도 끝까지 할수있었던건 백엔드 개발자들의 꾸준한 노력이였다..그걸보면서 동기부여를 삼았다. 

그덕에 놓지않고 계속해서 할수있었다. 또하나의 이유는 콘솔에 정확히 찍혔던 requestBody이다. 바디가 정확하게 보내진다는걸 보여주는 단하나의 희망줄이였다...

 

 

Ajax란 자바스크립트를 사용하여  브라우저가 서버에게 비동기 방식으로 데이터를 요청하고, 서버가 응답한 데이터를 수신하여 웹페이지를 동적으로 갱신하는 프로그래밍 방식임.

ajax는 브라우저에서 제공하는 web api인 XMLHttpRequest 객체를 기반으로 동작한다. XMLHttpRequest는 http비동기 통신을 위한 메서드와 프로퍼티를 제공함.

예전에는 html태그로 시작해서 html태그로 끝나는 완전한 html을 서버로부터 전송받아 웹페이지 전체를 처음부터 다시 렌더링하는 방식으로 동작하였다. 따라서 화면이 전환되면 서버로부터 새로운 html을 전송받아 웹페이지 전체를 처음부터 다시 렌더링하였다.

근데 이러한 방식에는 문제점이 있다.

1. 이전  웹페이지와 차이가 없어서 변경할 필요가 없는 부분까지 포함된 완전한 html을 서버로부터 매번 다시 전송받기 때문에 불필요한 데이터 통신 발생

2. 변경할 필요없는 부분까지 처음부터 다시 렌더링되는데 이로인하여서 화면 전환이 일어나면 화면이 순간적으로 깜빡이는 현상이 발생

3. 클라이언트와 서버와의 통신이 동기 방식으로 동작하기 때문에 서버로부터 응답이 있을때까지는 다음 처리는 블로킹됨.

 

근데 이러한 문제점 속에서 ajax의 등장으로 인하여 서버로부터 웹페이지의 변경에 필요한 데이터만 비동기 방식으로 전송받아서 웹페이지를 변경할 필요가 없는 부분은 다시 렌더링하지 않고, 변경할 필요가 있는 부분만 한정적으로 렌더링 하는 방식으로 바뀜 -> 퍼포먼스가 자연스러워짐.

장점으로는 

1. 변경할 부분을 갱신하는데 필요한 데이터만 서버로부터 전송받기 때문에 불필요한 데이터 통신 발생x

2. 변경할 필요가 없는 부분은 다시 렌더링하지 않는다. 따라서 화면이 순간적으로 깜빡이지않음.

3. 클라이언트와 서버와의 통신이 비동기 방식으로 동작하기 때문에 서버에게 요청을 보낸 이후 블로킹 발생x

 

JSON은 클라이언트와 서버간의 HTTP통신을 위한 텍스트 데이터 포멧임. 자바스크립트에 종속되지않은 언어 독립형 데이터 포멧으로 대부분의 프로그래밍 언어에서 사용가능.

JSON은 자바스크립트의 객체 리터럴과 유사하게 키와 값으로 구성된 순수한 텍스트임.

{
	"name": "Lee",
    "age": 20,
    "alive": true,
    "hobby": ["traveling", "tennis"]
}

JSON의 키는 반드시 큰따옴표로 묶어야함. 값은 객체 리터럴과 같은 표기법을 그대로 사용할 수 있음. 하지만 문자열은 반드시 큰따옴표로 묶어야함.

 

JSON.stringify 메서드는 객체를 JSON포멧의 문자열로 변환한다. 클라이언트가 서버로 객체를 전송하려면 객체를 문자열화 해야하는데 이를 직렬화라함.

const obj = {
	name: "Lee",
    age: 20,
    alive: true,
    hobby: ['traveling', 'tennis']
};

//객체를 json포멧의 문자열로 변환하기
const json = JSON.stringify(obj);
console.log(typeof json, json);
//콘솔에 찍으면 결과는
/*
string {
	"name": "Lee",
    "age": 20,
    "alive": true,
    "hobby": [
    	"traveling",
        "tennis"
        ]
}
*/

//replacer 함수. 값의 타입이 Number이면 필터링되어 반환되지않는다.
function filter(key, value) {
	//undefined는 반환하지 않는다는 의미
    return typeof value === 'number' ? undefined : value;
 }
 
 //JSON.stringify 메서드에 두번째 인수로 replacer함수를 전달
 const strFilteredObject = JSON.stringify(obj, filter, 2);
 console.log(typeof strFilteredObject, strFilteredObject);
 /*
 string {
 	"name": "Lee",
    "alive": true,
    "hobby" : [
    	"traveling",
        "tennis"
      ]
  }
  */

 

JSON.stringify 메서드는 객체뿐만 아니라 배열도 JSON포맷의 문자열로 변환한다.

 

const todos = [
	{id: 1, content: 'HTML', completed; false},
    {id: 2, content: 'CSS', completed; true},
    {id: 3, content: 'JavaScript', completed; false}
 ];
 
 //배열을 json 포맷의 문자열로 변환한다.
 const json = JSON.stringify(todos, null, 2);
 console.log(typeof json, json);
 /*
 string [
 	{
    	"id": 1,
        "content": "HTML",
        "completed": false
    },
    {
    	"id": 2,
        "content": "CSS",
        "completed": true
    },
    {
    	"id": 3,
        "content": "JavaScript",
        "completed": false
    }
  ]
 */

 

JSON.parse메서드는 JSON 포맷의 문자열을 객체로 변환한다. 서버로부터 클라이언트에게 전송된 JSON 데이터는 문자열이다. 이 문자열을 객체로서 사용하려면 JSON포맷의 문자열을 객체화해야하는데 이를 역직렬화라고 한다.

const obj = {
	name: 'Lee',
    age: 20,
    alive: true,
    hoddy: ['traveling', 'tennis']
};

//객체를 JSON 포맷의 문자열로 변환함.
const json = JSON.stringify(obj);

//JSON 포맷의 문자열을 객체로 변환함
const parsed = JSON.parse(json);
console.log(typeof parsed, parsed);
//object {name: "Lee", age: 20, alive: true, hobby: ["traveling", "tennis"]}

 배열이 JSON 포맷의 문자열로 변환되어있는 경우 JSON.parse는 문자열을 배열 객체로 변환한다.

배열의 요소가 객체인 경우 배열의 요소까지 객체로 변환한다.

const todos = [
	{id: 1, content: 'HTML', completed; false},
    {id: 2, content: 'CSS', completed; true},
    {id: 3, content: 'JavaScript', completed; false}
 ];
 
 //배열을 JSON 포맷의 문자열로 변환한다.
 
 const json = JSON.stringify(todos);
 
 //JSON 포맷의 문자열을 배열로 변환한다. 배열의 요소까지 객체로 변환된다.
 
 const parsed = JSON.parse(json);
 console.log(typeof parsed, parsed);
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>counter</title>
</head>
<body>
    <div id="counter">0</div>
    <button id="increase">+</button>
    <button id="decrease">-</button>
    <script>
        const $counter = document.getElementById('counter-x');
        const $increase = document.getElementById('increase');
        const $decrease = document.getElementById('decrease');

        let num = 0;
        const render = function () {
            $counter.innerHTML = num;
        };

        $increase.onclick = function () {
            num ++;
            console.log('increase 버튼 클릭', num);
            render();
        };

        $decrease.onclick = function () {
            num --;
            console.log('decrease 버튼 클릭', num);
            render();
        };
    </script>
</body>
</html>

이러한 단순한 숫자가 더해지거나 빼지는 코드가 있다 이 코드를 돌리면 +버튼이나 -버튼을 누르면 에러가 발생하는데 이제 이 에러를 분석하자.

개발자도구의 console을 열어보면 

Uncaught TypeError: Cannot set properties of null (setting 'innerHTML')
    at render (java.html:19:32)
    at $increase.onclick (java.html:25:13)

보면 에러가 발생하기는 했으나 html파일에 포함된 자바스크립트가 일부 실행된것은 확인했다.

에러 정보에보면 오른쪽에 에러발생위치를 나타내는 링크를 클릭해보면 자바스크립트 코드를 디버깅할수있는 source패널로 이동한다.

java.html:19

 

에러가 발생한 위치는

const render = function () {
            $counter.innerHTML = num;
        };

그리고 에러에 보면 Uncaught TypeError: Cannot set properties of null (setting 'innerHTML')라는 에러 정보가 표시되는데 이때 $counter변수의 값이 null인지 확인해보고 null이면 그 이유를 알아내서 에러가 발생한 원인을 제거해보자.

발생한 코드의 라인번호 클릭 후 다시 -버튼을 눌러보면 디버깅 모드로 들어가는데 이때 $counter에 커서를 두면 이것의 변수가 나오는데 이 값은 null 이였다. 

원인은 

const $counter = document.getElementById('counter-x');

이 부분에서 $counter의 변수에 값을 할당할때 html요소의 아이디를 counter-x라고 잘못 지정한것이다.

이 부분을 counter로 다시 수정하면 제대로 동작한다.

 

아까 까지 배웠던 합성에 대비되는 개념으로 상속이 있음. 컴퓨터 프로그래밍에서의 상속은 객체지향 프로그래밍에서 나온 개념인데 이건 부모 클래스를 상속받아서 새로운 자식 클래스를 만든다는 개념으로 자식 클래스는 부모 클래스가 가진 변수나 함수 등의 속성을 모두 갖게됨.

 

리액트에서는 다른 컴포넌트로부터 상속받아서 새로운 컴포넌트를 만드는 것을 고려해 볼 수 있음!

리액트 개발한 메타는 수천개의 리액트 컴포넌트를 사용한 경험을 바탕으로 추천할 만한 상속 기반의 컴포넌트를 찾아보려했으나 그러지 못함! -> 상속을 사용하는것보다 합성을 사용해서 개발하는게 더 좋은 방법임!

 

복잡한 컴포넌트 쪼개서 여러개의 컴포넌트 만들고, 만든 컴포넌트들 조합하여 새로운 컴포넌트를 만드는게 best

 

그렇다면 이제 합성방법을 사용해서 직접 한번 컴포넌트들을 만들어보자

 

이 컴포넌트는 하위 컴포넌트를 감싸서 카드형태로 보여주는 컴포넌트임

containment와 specialization 합성방법을 모두 사용하여 구현

children을 사용한 부분이 containment, title과 background를 사용한 부분이 specialization

이 범용적으로 재사용이 가능한 컴포넌트를 사용하여 프로필카드 컴포넌트를 만들어볼거임.

이렇게 간단하게 만들기 가능!

이 프로필카드 컴포넌트는 카드 컴포넌트를 이용하여 title과 backgroundColor을 설정하고 children에 간단한 소개글을 작성함으로써 사용자의 프로필을 나타내는 컴포넌트 완성됨.

 

간단하게 정리해보자면

리액트는 복잡한 컴포넌트를 쪼개서 여러개의 재사용이 가능한 컴포넌트로 만드는 것이 중요하고 이 컴포넌트들을 잘 조합하여서 새로운 컴포넌트를 만들면됌.

합성이란 여러개의 컴포넌트를 합쳐서 새로운 컴포넌트를 만드는것이고, 다양하고 복잡한 컴포넌트를 효율적으로 개발할 수 있음.

이러한 합성 기법에는 containment(하위 컴포넌트를 포함하는 형태의 합성방법, 리액트 컴포넌트의 props에 기본적으로 들어가있는 children속성 사용, 여러개의 children집합이 필요한 경우 별도로 props를 각각 정의해서 사용)

specialization(

Specialization는 전문화 특수화라는 뜻을 가지고있음 Specialization의 의미가 어렵다고 느껴질순있지만 예시를 보면서 이해해보자!

다이얼로그라는 것은 굉장히 범용적인 의미를 가지고있음

모든 종류의 다이얼로그를 다 포함하는 개념이라고 볼 수 있는데 웰컴 다이얼로그라는것은 누군가를 반기기위한 다이얼로그라고 볼수있음 범용적인게 아니라 좀 더 구체화된것임.

이처럼 범용적인 개념을 구별이 되게 구체화 하는것을 Specialization라고함

웰컴 다이얼로그는 다이얼로그의 특별한 케이스

기존의 객체지향 언어에서는 상속을 사용하여 Specialization을 구현함.

하지만 리액트에서는 합성을 사용하여서 Specialization을 구현함

function Dialog(props) {
	return (
    	<FancyBorder color="blue">
        	<h1 className="Dialog-title">
            	{props.title}
            </h1>
            <p className="Dialog-message">
            	{props.message}
            </p>
         </FancyBorder>
      );
  }
  
  function WelcomeDialog(props) {
  	return (
    	<Dialog
        	title="어서오세요"
            message="우리 사이트에 방문하신 것을 환영합니다"
         />
      );
  }

위에있는 Dialog라는 컴포넌트는 범용적인 의미를 가진 컴포넌트임 

그리고 이 컴포넌트를 사용하는 WelcomeDialog컴포넌트가 나옴 .

Dialog컴포넌트에서는 title과 message라는 두가지 props를 갖고있는데 각각 다이얼로그에 나오는 제목과 메세지를 의미함.

따라서 어떻게 props를 전달해주느냐에따라 메세지의 문구가 달라짐

이렇게 Specialization은 범용적으로 쓸수있는 컴포넌트를 만들어놓고 이를 특수화 시켜서 컴포넌트를 사용하는 합성방식임.

 

그렇다면 아까 배웠던 containment와 Specialization를 같이 사용하려면 어떻게 해야할까?

우선 containment를 위해서 props.children을 사용하고 Specialization을 위해 직접 정의한 props를 사용하면 됨!

 

function Dialog(props) {
	return (
    	<FancyBorder color="blue">
        	<h1 className="Dialog-title">
            	{props.title}
            </h1>
         	<p className="Dialog-message">
            	{props.message}
            </p>
            {props.children}
        </FancyBorder>
     );
  }
  
  function SignUpDialog(props) {
  	const [nickname, setNickname] = useState('');
    
    const handleChange = (event) => {
    	setNickname(event.target.value);
     }
    
    const handleSignU[ = () => {
    	alert(`어서오세요, ${nickname}님!`);
    }
    
    return (
    	<Dialog 
        	title="화성 탐사 프로그램"
            message="닉네임을 입력해주세요.">
            <input
            	value={nickname}
                onChange={handleChange} />
            <button onClick={handleSignUp}>
            	가입하기
            </button>
         </Dialog>
      );
  }

하위 컴포넌트가 다이얼로그 하단에 렌더링되고, 실제 dialog컴포넌트를 사용하는 signupdialog컴포넌트를 살펴보면 Specialization을 위한 props인 title, message에 값을 넣어주고 있으며 사용자로 부터 닉네임을 입력받고 가입하도록 유도하기 위해 <input>과 버튼 태그가 들어있습니다 이 두개의 태그는 모두 props.children으로 전달되어 다이얼로그에 표시됩니다. 이러한 형태로 containment와 Specialization을 동시에 사용할 수 있습니다!!!!

'REACT' 카테고리의 다른 글

Composition  (0) 2023.07.14
key & form  (0) 2023.07.14
Life cycle and hook  (0) 2023.07.14
Shared state  (0) 2023.07.13
React Foundation  (0) 2023.06.27

원래 composition은 구성이란 뜻을 갖고있지만 리액트에서는 합성을 의미한다.

즉 여러개의 컴포넌트를 합쳐서 새로운 컴포넌트를 만드는 것을 말한다.

 

containment는 하위 컴포넌트를 포함하는 형태의 합성방법.

보통 사이드바나 다이얼로그 같은 박스 형태의 컴포넌트는 자신의 하위 컴포넌트를 미리 알수없음.

예시로 동일한 사이드바  컴포넌트를 사용하는 두개의 쇼핑몰이 있다고 가정해보자면, 하나의 쇼핑몰에는 의류와 관련된 메뉴가 8개 들어있고 다른 쇼핑몰에서는 식료품과 관련된 메뉴가 10개가 존재함.

이렇게 어떤게 하위 컴포넌트로 올지 모르지?

그래서 이러한 경우에는 containment방법을 사용하여 합성을 사용하게됨.

이걸 사용하는 방법은 리액트 컴포넌트의 props에 기본적으로 들어있는 children속성을 사용하면됨.

 

function FancyBorder(props) {
	return (
    	<div className={'FancyBorder FancyBorder-' +props.color>
        	{props.children}
        </div>
      );
  }

이 컴포넌트를 사용하면 자신의 하위 컴포넌트를 모두 포함하여 예쁜 테두리로 감싸주는 컴포넌트가 됨

그러면 이걸 사용하는 예제를 한번 봐보자

function WelcomeDialog(props) {
	return (
    	<FancyBorder color="blue">
        	<h1 className="Dialog-title">
            	어서와 !
            </h1>
            <p className="Dialog-message">
            	우리 사이트에 방문한걸 환영합니다
            </p>
        </FancyBorder>
    );
 }

자 이 코드를 보면 fancyborder컴포넌트로 감싸진 부분에 h1 p라는 두개의 태그가 들어가있는데 이 두 태그는 모두 fancyborder컴포넌트에 children이라는 props로 전달이됨.

-> 그럼 결과적으로 파란색의 태두리로 감싸지는 결과가 나옴!

 

이 react에서는 props.children를 통해 하위 컴포넌트를 하나로 모아서 제공해줌

but 여러개의 children 집합이 필요한 경우는 어떻게 해야할까?

이런 경우에는 별도로 props를 정의해서 각각 원하는 컴포넌트를 넣어주면됌.

 

function SplitPane(props) {
	return (
    	<div className="SplitPane">
        	<div className="SplitPane-left>
            	{props.left}
            </div>
            <div className="SplitPane-right">
            	{props.right}
            </div>
        </div>
     );
 }
 
 function App(props) {
 	return (
    	<SplitPane
        	left={
            	<Contacts />
            }
            right={
            	<Chat />
           }
        />
     );
 }

우선 맨 위에있는 SplitPane 컴포넌트는 화면을 왼쪽과 오른쪽을 분할해서 보여주는 컴포넌트이다.

여기에서 left right라는 두개의 props를 정의하여 각각 다른 컴포넌트를 넣어주고 있음 이 left right를 props로 받게되고 왼오로 분리해서 렌더링함

이렇게 여러개의 children이 필요한 경우에는 별도의 props를 정의하여서 사용하면됌.

 

이렇게 props.children이나 직접 정의한 props를 이용하여 하위 컴포넌트를 포함하는 형태로 합성하는 방법을 containment라고함.

'REACT' 카테고리의 다른 글

Specialization  (0) 2023.07.18
key & form  (0) 2023.07.14
Life cycle and hook  (0) 2023.07.14
Shared state  (0) 2023.07.13
React Foundation  (0) 2023.06.27

리액트에서의 키는 리스트에서 아이템을 구분하기 위한 고유한 문자열!

리액트에서의 키의 값은 같은 리스트에 있는 엘리먼트 사이에서만 고유한 값이면 됨.

 

그럼 실제로 고유한 키값을 어떻게 만들어서 사용해야하는지 봐보자고

이건 키값으로 숫자의 값을 사용한 것임.

const numbers =[1,2,3,4,5];
const listItems=numbers.map((number) => 
	<li key={number.toString()}>
    	{number}
    </li>
);

 

다음은 키값으로 id를 사용하는 방식

const todoItems = todo.map((todo) => 
	<li key={todo.id}>
    	{todo.text}
    </li>
);

 

 

이번엔 키값으로 인덱스를 사용하는 방법

리액트에서는 키를 명시적으로 넣어주지않으면 기본적으로 이 인덱스 값을 키값으로 사용함.

const todoItems = todos.map((todo,index) => 
	<li key={index}>
    	{todo.text}
     </li>
 );

 

그럼 이걸 활용해서 출석부 컴포넌트를 만들어보자고

 

다음은 폼이란 무엇인가?

보통 회원가입하거나 로그인할때 텍스트를 입력하는 양식 많이 보이는데 텍스트 입력뿐 아니라 체크박스나 셀렉트등 사용자가 무언가 선택을 해야 하는것 모두를 폼이라고 생각하면됨.

정리하면 사용자로부터 입력을 받기 위해 사용하는것

 

제어 컴포넌트는 사용자가 입력한 값에 접근하고 제어할 수 있도록 해주는 컴포넌트입니다.

말한 그대로 누군가의 통제를 받는 컴포넌트인데 여기서 통제를 하는게 리액트임

정리하자면 제어 컴포넌트는 그 값이 리액트의 통제를 받는 입력 폼 엘리먼트를 의미함.

제어 컴포넌트는 모든 데이터를 state에서 관리함.

 

자 그러면 사용자의 이름을 입력받는 html 폼을 리액트의 제어 컴포넌트로 만들어보자

function NameForm(props) {
	const [value, setValue] = useState('');
    
    const handleChange = (event) => {
    	setValue(event.target.value);
     }
     
     const handleSubmit = (event) => {
     	alert('입력한 이름:' + value);
        event.preventDefalut();
      }
     
     return (
     	<form onSubmit={handleSubmit}>
        	<lable>
            	이름:
                <input type="text" value={value} onChange={handleChange} />
             </lable>
             <button type="submit">제출</button>
        </form>
     )
  }

이 코드에서 input태그의 value={value}부분을 볼 수 있는데 리액트 컴포넌트의 state에서 값을 가져다 넣어주는 것이다.

그래서 state의 값이 항상 input에 표시가된다.

또한 입력값이 변경되었을때 호출되는 onChange={handleChange}처럼 handleChange함수가 호출되도록 했는데 handleChange함수에서는 setValue()함수를 사용하여 새롭게 변경된 값을 value라는 state에 저장한다.

 

만약에 사용자가 입력한 모든 알파벳을 대문자로 변경시켜서 관리하고싶으면

const handleChange = (event) => {
	setValue(event.target.value.toUpperCase());
 }

이렇게 해주면되는데 handleChange함수로 들어오는 이벤트의 타겟값을 toUpperCase() 함수를 이용하여 모두 대문자로 변경한 후에 그 값을 state에 저장하는 것이다. 이처럼 제어 컴포넌트로 사용자의 입력을 제어할수있다.

 

다음은 여러줄에 걸쳐서 나올 정도로 긴 텍스트를 입력받기 위한 html태그인 textarea태그이다.

반면에 리액트에서는 textarea태그에 value라는 attribute를 사용하여 텍스트를 표시한다.

아래 코드를 보면서 이해해보자

function RequestForm(props) {
	const [value,setValue] = useState('요청사항을 입력하세요');
    
    const handleChange = (event) => {
    	setValue(event.target.value);
     }
    
    const handleSubmit = (event) => {
    	alert('입력한 요청사항:'+value);
        event.preventDefalut();
    }
    
    return (
    	<form onSubmit={handleSubmit}>
        	<lable>
            	요청사항:
                	<textarea value={value} onChange={handleChange} />
            </lable>
            <button type="submit">제출</button>
        </form>
     )
 }

이건 이름같은 적은 텍스트의 입력을 받는게아닌 요청사항처럼 많은 텍스트의 입력을 받는 컴포넌트인데 이건 아마 곧 시작하게될 플젝에서 쓸듯..?

뭐암튼 state로는 value가 있으며 이 값을 textarea라는 태그의 value라는 attribute에 넣어줌으로써 화면에 나타나게되는데 이때 useState에서 보이는것과같이 초깃값으로 요청사항을 입력하세요를 넣어줌! 

이래서 렌더링될때부터 textarea에 텍스트가 나타남.

 

select태그

드롭다운 목록을 보여주기 위한 html태그임.

이건 여러가지 옵션중에서 하나를 선택할수있는 기능을 제공함!

html에서는 아래와같이 사용함.

<select>
	<option value="apple">사과</option>
    <option value="banana">바나나</option>
    <option value="grape">포도</option>
    <option value="watermelon">수박</option>
</select>

 

그럼 리액트에서는 어떻게 쓰일까?

function FruitSelect(props) {
	const [value,setValue] = useState('grape');
    
    const handleChange = (event) => {
    	setValue(event.target.value);
    }
    
    const handleSubmit = (event) => {
    	alert('선택한 과일:'+value);
        event.preventDefalut();
    }
    
    return (
    	<form onSubmit={handleSubmit}>
        	<label>
            	과일을 선택하세요:
                <select value={value} onChange={handleChange}>
                	<option value="apple">사과</option>
                    <option value="banana">바나나</option>
                    <option value="grape">포도</option>
                    <option value="watermelon">수박</option>
                 </select>
             </label>
             <button type="submit">제출</button>
         </form>
      )
  }

FruitSelect라는 컴포넌트가 있고이 컴포넌트의 state로 grape라는 초기값을 가진 value가 하나 있다. 

이 값을 select 태그에 value로 넣어주고 있다. 

값이 변경된 경우에는 위와 마찬가지로 handleChange함수에서 setValue함수를 사용하여 값을 업데이트한다.

이 방식을 사용하게 되면 사용자가 옵션을 선택했을때 value라는 하나의 값만을 업데이트 하면 되기 때문에 더 편리하다.

만약 다중으로 선택이 되게 하려면 아래와같이 multiple속성값을 true로 해주고 value로 선택된 옵션의 값이 들어있는 배열을 넣어주면됨.

 

<select multiple={true} value={['B','C']}>

 

file input태그

말 그대로 디바이스의 저장 장치로부터 사용자가 하나 또는 여러개의 파일을 선택할 수 있게 해주는 html태그임.

서버로 파일을 업로드하거나 자바스크립트의 file api를 사용하여서 파일을 다룰때 사용함

<input type="file" />

이런식으로 사용함.

 

그렇다면 지금까지는 하나의 컴포넌트에서 하나의 입력만을 다뤘는데 만약 하나의 컴포넌트에서 여러개의 입력을 다루기 위해서는 어떻게 해야할까?

 

이런 경우에서는 여러개의 state를 선언하여서 각각의 입력에 대해 사용하면됌.

 

앞에처럼 제어 컴포넌트에 value prop을 정해진 값으로 넣으면 코드를 수정하지 않는한 입력값을 바꿀수없음.

하지만 만약 value prop은 넣되 자유롭게 입력할 수 있게만들고싶으면 값에 undefined or null을 넣어주면됌.

 

ReactDOM.render(<input value="hi" />, rootnode);

setTimeout(function() {
	ReactDOM.render(<input value={null} />, rootNode);
 }, 1000);

처음에는 input의 값이 hi로 정해져있어서 값을 바꿀 수 없는 입력 불가 상태였다가 timer에 의해 1초뒤에 value가 null인 input태그가 렌더링 되면서 입력 가능한 상태로 바뀜.

이 방법을 잘 활용하면 value prop을 넣으면서 동시에 사용자가 자유롭게 입력 할 수 있게 만들기 가능.

 

그럼 한번 사용자 정보를 입력받는 가입 양식 컴포넌트를 한번 만들어보자

가입 입력 양식을 제출 이런식으로 간단하게 짜줌.

'REACT' 카테고리의 다른 글

Specialization  (0) 2023.07.18
Composition  (0) 2023.07.14
Life cycle and hook  (0) 2023.07.14
Shared state  (0) 2023.07.13
React Foundation  (0) 2023.06.27

+ Recent posts