1. Props

  • props : properties(속성)
  • 단어 뜻 그대로 컴포넌트의 속성값입니다.
  • props는 부모 컴포넌트로부터 전달 받은 데이터를 지니고 있는 객체입니다.
  • props를 통해 부모 컴포넌트로부터 자식 컴포넌트에게 state의 상태값, event handler를 넘겨줄 수 있습니다.

 

html에서 input태그를 쓸 떄 type="password"이런식으로 여러 속성을 주면 같은 태그인데 다르게 동작시킬 수 있었다.

그런 것처럼 컴포넌트도 이렇게 type이렇게 속성을 받을 수 있는데 input태그는 정해진 속성만 받을 수 있는 반면에 

컴포넌트는 우리가 만드는 것이니까 무슨 속성을 받아서 어떻게 쓸 지 정할 수 있다.  

 

그래서 속성들을 받아서 어딘가에 저장을 해야한다.  그래서 속성들을 저장해 놓는걸 객체 형태로 저장해 놓는다.

그래서 컴포넌트들은 props라는 객체를 기본적으로 내장하고 있다.

 

<디렉토리 구조>

 

<Child.js>

import React from "react";

class child extends React.Component {
  render(){
    return <h1>안녕하세요</h1>;
  }
}

export default Child;

 

 

<Parent.js>

 

import React from "react";
import Child from "./Child"

class child extends React.Component {
  render(){
    return <Child />
  }
}

export default Parent;

 

 

 

이런식으로하면 자식을 계속 재활용할 수 없다 무조건 안녕하세요라는 말밖에 못한다.

 

이럴 때 props를 쓴다. 

 

모든 컴포넌트는 props라는 객체를 내장하고 있다.

console.log로 확인해보자

 

<Child.js>

import React from "react";

class child extends React.Component {
  render(){
    console.log("Props>>>>>",this.props) //추가
    return <h1>안녕하세요</h1>;
  }
}

export default Child;

 

props는 컴포넌트가 가지고 있는거니까 앞에 this를 써준다. 

이 this가 엄밀히 따지고 들면 오브젝트로 props를 호출하는 대상 이런건데  

지금은 그냥 만들어진 컴포넌트를 가르키는 것이다 라고 생각하면 된다.

 

명확히하면 클래스는 아니고 클래스는 그 자체로는 아무것도 못만들잖아요.

그래서 클래스로 오브젝트를 만들잖아요 그 만들어진 오브젝트들이 다 각각의 this가 되는것이다.

나는 나의 props를 보고 싶은거니까 this(나).props로 보는 것

 

 

 

지금은 부모로부터 아무런 속성도 주지않았으니까 아무것도 안들어가 있다.

 

그러면 props를 자식한테 주입시켜보자.

 

Parent.js

import React from "react";
import Child from "./Child"

class child extends React.Component {
  render(){
    return <Child message="22기 여러분 안녕하세요" />
  }
}

export default Parent;

 

message(속성이름) "22기 여러분 안녕하세요"(속성값)

 

 

<Child.js>

import React from "react";

class child extends React.Component {
  render(){
    return <h1>{this.props.message}</h1>;
  }
}

export default Child;

 

this.props.message자체는 string이지만 객체의 값이다. 그러니 자바스크립트니까 jsx에서 써주기 위해 {}안에 넣어준다.

 

 

이렇게 하면 근본적인 큰 틀의 형태는 갖고 안의 내용들만 props를 통해서 제어할 수 있게 되는 것이다.

 

props로 넘길 수 있는건 어떤 값이던 제한이 없다. 

자바스크립트 데이터타입 스트링 넘버 어레이 오브객체 불린 다넘길 수 있다. 심지어 함수도 가능하다.

함수로 넘기는 경우를 보자

 

Parent.js

import React from "react";
import Child from "./Child"

class child extends React.Component {
  render(){
    return <Child message={22} introduce={() => alert("안녕하세요")} />
  }
}

//숫자와 함수를 props에 줬다. 문자는 {}안에 쓰지않고 그냥 ""로 주면 된다.

export default Parent;

 

함수는 자바스크립트니까 {}안에 작성한다.

 

Child.js에서 console.log(this.props)를 해보니 둘 다 잘 넘어왔다

 

Child.js에서 넘어온 introduce함수를 사용해보자

 

Child.js

import React from "react";

class child extends React.Component {
  render(){
    return <h1 onClick={this.props.introduce}>{this.props.message}</h1>;
  }
}

export default Child;

 

 

h1태그에 onClick이벤트를 걸었다.

 

 

22를 클릭하면 경고창이 나온다.

 

 

 

 

 

1. State

  • state : 상태
  • 단어 뜻 그대로 컴포넌트 내부에서 가지고 있는 컴포넌트의 상태값입니다.
  • state는 화면에 보여줄 컴포넌트의 UI 정보(상태)를 지니고 있는 객체입니다.
  • state는 컴포넌트 내에서 정의하고 사용하며 얼마든지 데이터(객체)가 변경될 수 있습니다.

state도 props와 마찬가지로 객체이다.

 

props는 주입받은 속성들을 가지고 있는 객체이다. 부모에서 전달해 주입해주는 것 

state는 상태를 가지고 있는 객체이다. 내가 가지고 있는 내 상태. 변할 수 있다! 변하지 않는 것들은 상태로 관리할 필요가 없다.

 

컴포넌트가 상태를 가져야하는 근본적인 이유는 우리가 컴포넌트 ui를 그릴 때 사용하기 위해서, ui를 그리는데 필요한 

상태들만 state에 담아놓고 관리하는 것이다.

 

그러니까 state는 컴포넌트가 가지고 있는 것이고 컴포넌트 내부에서 정의하고 사용하는 것이다.

 

 

 

 

 

Child.js

import React from "react";

class child extends React.Component {
  constructor(){
    super()
    this.state = {};
  }
  
  render(){
    return <h1 onClick={this.props.introduce}>{this.props.message}</h1>;
  }
}

export default Child;

state는 자기가 가지고 있는 거니까 내가 만들어 줘야 한다.

constructor라는 함수가 있는데 class면 내장되있는 함수다. 

class가 생성될 때 하는건데 잘모르겠이면 그냥 이 형태를 외워버리면 된다. (나중에 공부하자)

 

state는 컴포넌트가 가지고 있는 것이니까 this(나).state로 적는다. 그리고 객체니까 = {}로 만든다. 

 

class child extends React.Component {
  constructor(props){
    super(props)
    this.state = {};
  }

state에서 props값을 사용하고 싶으면 인자로 props를 넣어주면 된다.

 

 

console.log(this.state)로 콘솔을 찍어보자

지금은 state를 만들기만하고 아무것도 안넣었기 떄문에 빈객체로 나온다.

 

 

<Child.js>

import React from "react";

class child extends React.Component {
  constructor(){
    super()
    this.state = {
      titleColor:"red",
    };
  }
  
  render(){
    return <h1 onClick={this.props.introduce}>{this.props.message}</h1>;
  }
}

export default Child;

this.state안에 상태를 만들어 둘 수가 있다.

위처럼 객체 프로퍼티 만들듯이 넣으면 된다.

 

 

 

import React from "react";

class child extends React.Component {
  constructor(){
    super()
    this.state = {
      titleColor:"red",
    };
  }
  
  render(){
    return <h1 style={{color:this.state.titleColor}} onClick={this.props.introduce}>{this.props.message}</h1>;
  }
}

export default Child;

 이제 이것을 통해서 ui를 만들어야 한다, 랜더 안에서 ui를 정의한다.

인라인 스타일은 객체를 넘겨야 한다. 

 

*인라인 스타일이 속성 적용순위가 제일 높다.

 

 

근데 이걸 바꿔야 의미가 있다. 

계속 red일거면은 애초에 상태로 관리할 필요가 없다.

 

그냥 color: "red"로 집어넣으면 되지 그래서 이제 state를 바꾸는걸 해볼것이다.

 

import React from "react";

class child extends React.Component {
  constructor(){
    super()
    this.state = {
      titleColor:"red",
    };
  }
  
  changeTitleColor = () => {
    this.state.titleColor = "blue";
  }
  
  
  
  render(){
    return 
      <>
      <h1 
        style={{color:this.state.titleColor}} 
        onClick={this.props.introduce}
        >
          {this.props.message}
          </h1>;
          <button onClick={this.changeTitleColor}>타이틀 색깔</button>
      </>
  }
}

export default Child;

 

함수는 this가 바뀔 수도 있으니 애로우 펑션으로 해준다.

이렇게 했을 때 버튼을 누르면 22의 색이 파란색으로 변해야할거같은데 변하지 않는다.

 

왜냐하면 this.state.titleColor를 하면 바뀌기는 하는데 ui는 업데이트가 되지 않기 때문이다.

 

리액트는 ui를 자동으로 업데이트를 해준다 근데 얘가 매번 지가 알아서 업데이트를 할 순 없다.

업데이트가 필요해 라는 시그널을 줘야지 리액트가 알아서 ui를 업데이트 해준다.

 

그래서 ui를 업데이트해야한다는 시그널을 줄 떄는 리액트에서 제공한 함수를 써서 해줘야한다.

ui가 업데이트 되는 경우는 사실상 스테이트가 변경되었을 때 업데이트가 되면 된다.

 

이 함수는 React.Component에 내장된거기 떄문에 따로 선언하고 그냥사용할 수 있다.

 

  changeTitleColor = () => {
    this.setState({
      titleColor: "blue",
    })
  }

setSatae의 인자는 객체가 들어간다. 내가 바꾸고 싶은 객체를 넣으면 된다.

 

이제 버튼을 눌렀을 때 스테이트가 바뀌면서 리액트가 ui를 업데이트해야하는구나  인식을 하고 업데이트를 해주는 것이다.

이제 이런식으로 ui를 계속 변경하면 된다.

 

여기서 중요한거는 지금 버튼이랑 h1이 서로 연관이 있나?

서로한테는 관심이 없다. 원래 바닐라 자바스크립트라면 버튼에서 h1을 가져와서 컬러를 바꾸고 그런식으로 동작을 했다.

그런데 이제는 그렇게 절차적으로 흘러가지 않는 것이다.

모든 것은 다 스테이트만 바라보고 있는 것이다.

그래서 서로에게는 관심없다 모든건 다 스테이트 기준으로 내가 뭘하는지이다.

 

<h1>은 버튼이 있는지 없는지도 모른다. 그냥 내 컬러는 state.titleColor구나라고 밖에 모르는 것이다.

<button>은 <h1>이 있는지 없는지도 모른다. 그냥 나는 스테이트를 바꾸면 되는구나 인것이다.

각자가 따로 있는데 알아서 각자 스테이트를 통해서 자기 ui를 반영하는 그런 형태이다.

 

이제 우리는 선언하는 것이다. 보여줘야 할 ui는 이렇게 생겼어하고 끝인 것이다.

그리고 보여줘야할 ui를 결정하는 것은 이제 스테이트를 통해서 만들어 놓는거다.

우린 어떻게 보일지 선언만 해두면 알아서 업데이트 하는 완전한 패러다임의 변화다.

 

이게 바닐라 js와 리액트의 가장 큰 차이점이다.

 

*인라인 스타일링은 구리다. 쓰면 안된다. 우선 순위가 제일 높게 먹는다. 그래서 유지보수하기가 굉장히 힘들다.

만약에 내가 css에서 컬러 바꿔야지 하고 yellow로 넣었는데 안바뀌면 무슨일이지? 하고 들어가보면 인라인 스타일이

들어가 있는거다. 그럼 우선순위가 css파일의 속성을 다 무시하고 인라인 스타일이 제일 먼저니까 유지보수하기 힘들다.

 

스타일에 관련된 것은 모두 css파일에 있을거라고 생각한다. 근데  css파일을 쭉 보고 있는데 갑자기 어떤 스타일이

인라인으로 들어가서 js에도 들어가야 한다. 그러면 내가 스타일을 수정할 때 css도 보고 js도 봐야한다. 

 

이런 문제점 때문에 모든건 다 css파일로 스타일을 건드려야한다.

 

<Child.css>

.title {
  color: red;
  }
  
.title-blue{
  color: blue;
}

 

<Child.js>

import React from "react";
import "./Child.css"  //css파일 import 함

class child extends React.Component {
  constructor(){
    super()
    this.state = {
      titleColor:"red",
    };
  }
  
  changeTitleColor = () => {
    this.state.titleColor = "blue";
  }
  
  
  
  render(){
    return 
      <>
        <h1  // 인라인 스타일을 지우고 className을 줬다.
          className={this.state.titleColor === "red" ? "title" : "title-blue"} 
          onClick={this.props.introduce}
          >
            {this.props.message}
        </h1>;
            <button onClick={this.changeTitleColor}>타이틀 색깔</button>
      </>
  }
}

export default Child;

 

jsx에서 if문 못쓴다. 정확히 말하면  if문은 쓸려면 쓸 수는 있는데 그렇게 까지 쓸 이유가 없다. 

그 로직이 굉장히 힘들고 너무 길어서 복잡해진다.

 

jsx밖에서는 쓸 수 있다. 

 

대신에 삼항연산자를 쓰면 된다. 

const a = boolean ? 'true' : 'false' ;

console.log(a);

조건이 참이면 true가 a에 할당이 되고 거짓이면 false가 할당이 된다.

이 삼항연산자는 바로 값을 뱉어낸다.

 

if문은 값을 뱉어내지는 않는다. 뭐할 때 무슨 동작을 해라인 것이다.

 

삼항연산자는 자바스크립트니까 {}를 써준다.

 

 

 

이제 다시 돌아와서 props와 state를 같이 써보자

 

props는 무슨 값이든 넘길 수 있다. 그러면 props를 통해서 Parent가 가지고 있는 state를 자식한테 넘길 수도 있다.

 

그래서 부모에서 가진 state를 통해서 자식의 ui를 변경시킬 수도 있는 것이다.

 

<Parent.js>

import React from "react";
import Child from "./Child"

class Parent extends React.Component {
  constructor(){
    super();
    this.state = {
      titleColor: "red",
    }
  }
  render(){
    return (
      <>
        <Child titleColor={this.state.title.Color}/>
      </>
      );
  }
}

//숫자와 함수를 props에 줬다. 문자는 {}안에 쓰지않고 그냥 ""로 주면 된다.

export default Parent;

 

<Child.js>

import React from "react";
import "./Child.css" 

class child extends React.Component {

  render(){
    return (
      <h1 
        className={this.props.titleColor === "red" ? "title" : "title-blue"} 
        >
          타이틀
      </h1>;
    )
  }
}

export default Child;

 

이렇게 함으로써 

부모에서 Props를 보내서 자식의 ui를 변경시킬 수 있게 되는 것이다.

 

그런데 Child에서 state를 만들어서 하면 되지 왜 이렇게 할까?

리액트가 설계자체가 무조건 위에서 아래로 단반향으로 흐름이 간다. 그래서 자식에서 부모로 뭘 전달할 수 없다.

child에서 내 state는 이거야 하고 Parent로 전달할 수 없다.

 

왜 이렇게 쓸까? 유지보수 때문이다. 각자 자식에서 부모로 넘기고 형제에서 형제로 넘기고 이렇게 되면은 

근본으로 어디서 왔는지 프로그램이 커질수록 추정이 안된다. 그래서 진리의 원천(source of truth)이라고 리액트에서 말하는데

모든 진리의 원천은 한가지다. 그래서 부모에서 딱 하나 가지고 있는거다. 그러면 다 부모를 다라보고 있으니까 각자 가지고 있을 때보다

덜헷갈린다.

유지보수하고 파악하기 편한거다.

 

 

Child에서 title과 button을 컴포넌트로 분리해보자.

 

Button.js

import React from "react";

class Button extends React.Component {
  render(){
    return <button>버튼 색깔 바꾸기</button>      
  }
}

export default Button;

 

 

 

Title.js

import React from "react";

class Title extends React.Component {
  render(){
    return <h1>나는 제목</h1> 
  }
}

export default Title;

 

 

Child.js

 

import React from "react";
import Button from "./Button"
import Title from "./Title"
import "./Child.css";

class child extends React.Component {

  render(){
    return (
      <>
        <Title />
        <Button />
      </>
    )
  }
}

export default Child;

 

버튼을 누르면 타이틀의 색이 바뀌는 기능을 다시 넣어보자.

 

색을 바꾸는 거니까 타이틀에 state가 있으면 될까?

 

 

 

 

 

 

 

 

Title.js

import React from "react";

class Title extends React.Component {
  constructor() {
    super();
    this.state = {
      titleColor: "red",
    };
  }
  
  render(){
    return <h1 className={this.state.titleColor = "red" ? "title" : "title-blue"}>
           나는 제목
           </h1> 
  }
}

export default Title;

 

Button.js

import React from "react";

class Button extends React.Component {
  
  render(){
    return 
      <button>버튼 색깔 바꾸기</button>      
  }
}

export default Button;

 

지금은 안하고 있지만 각 컴포넌트 별로 자기 css파일은 자기가 가지고 있어야 한다.

지금 css를 child.css에 넣었다. 컴포넌트가 재사용되는 ui이다. css도 ui의 일부다.

그래서 각 컴포넌트마다 자기 관련된 css들은 자기가 관리하고 있어야 한다.

 

부모에서 자식에  있는 클래스네임 써가지고 바꾸고 하면 안된다.

 

그러니 child.css를 title.css로 바꿔주자.

 

 

<child.css -> Title.css>

.title {
  color: red;
  }
  
.title-blue{
  color: blue;
}

 

 

이제 Button컴포넌트 <button>태그에서 똑같이 onClik이벤트로 state를 변경해주면 되는데 어떻게 해야하나?

 

어떻게하면 타이틀에 있는 스테이트를 버튼에서 바꿀 수 있을까?

 

동등한 계층이라서 억지로 자식을 넣을 수도 없다.

 

단방향이라서 형제끼리는 서로의 스테이트를 바꾸지 못한다.

 

그래서 스테이트를 부모로 올려버리는 것이다!

 

단방향이니 얘네 형제들끼리는 소통이 불가능하지만 부모에서 관리하고 있으면 부모에서 전달해줄 수 있다.

 

그러니 Child라는 부모 컴포넌트로 올려준다.

 

 

<Child.js>

import React from "react";
import Button from "./Button"
import Title from "./Title"
import "./Child.css";

class child extends React.Component {
  constructor(){
    super();
    this.state = {
      titleColor: "red",
    }
  }

  changeTitleColor = () => {
    this.setState({
      titleColor: "blue",
    });
  }

  render(){
    return (
      <>
        <Title  titleColor={this.state.titleColor}/> //부모의 state를 전달
        <Button changeTitleColor={this.changeTitleColor}/>
      </>
    )
  }
}

export default Child;

 

<Title.js>

import React from "react";

class Title extends React.Component { 
  render(){
    return <h1 className={this.props.titleColor = "red" ? "title" : "title-blue"}>
           나는 제목
           </h1> 
  }
}

export default Title;

constructor를 삭제하고

this.state.titleColor를 this.props.titleColor로 바꿔주었다.

 

그다음 버튼은 타이틀의 스테이트는 못 건드리겠지만 부모인 Child에서 바꾸는걸 button으로 전달해주면 된다.

 

부모에서 스테이트를 바꾸는 함수를 만들고 그 함수를 버튼에게 props로 넘겨주면 된다.

버튼의 props로 넘겼다고 this가 버튼을 가리키게 되는건 아니다 왜냐하면 

changeTitleColor는 Child컴포넌트에 만들어서 넘긴거니까 this가 가르키는건 Child다. 그러니 child의 state를 바꿔준다.

 

<Button.js>

import React from "react";

class Button extends React.Component {
  
  render(){
    return 
      <button onClick={this.props.changeTitleColor}버튼 색깔 바꾸기</button>      
  }
}

export default Button;

이제 버튼을 누르면 색이 바뀐다.

 

내용을 정리하자면 서로 다른 형제들끼리 소통을 할 수 있게 부모로 스테이트를 끌어올려버리는 것이다.

 

 


모든걸 state로 쓰는 경우가 있는데 state는 변하는 상태만 state로 관리하는 것이다.

 

애초에 state를 관리를 하는게 ui를 변화시키기 위함이다. 그런데 안변하는걸 state로 가지고 있을 필요가 없다.

 

+html은 함수를 쓸 수 없다. 반면 jsx는 자바스크립트니까 함수를 써서 jsx를 만들 수 있다. ex)Array.map()

맵함수를 이용해서 반복되는 ui를 구성할 수 있다.

 

 

'React > Today I learned' 카테고리의 다른 글

리액트 매우 쉬운 별점기능 구현  (0) 2021.07.11
fetch함수를 이용한 로그인&회원가입  (0) 2021.07.02
Sass  (0) 2021.06.30
Router  (0) 2021.06.29
[React] 비동기, 동기  (0) 2021.06.27

 

 

 

Sass: Syntactically Awesome Style Sheets

 

css전처리기는? css전에 무언갈 처리해주는 도구.

 

왜 필요한가? 브라우저가 읽을 수 있는 문서는 html뿐이다. 브라우저가 읽을 수 있는 스타일 시트는 css뿐이다.

근데 css는 쓰다보면 불편하다 그 불편함을 해소하기 위해서 만들어진게 css전처리기다. 

 

하는 일은 애네들이 제공하는건 css가 제공하지 않는 편리한 문법들을 통해서  css를 작성할 수 있게 해주는 것이다.

 

sass파일로 스타일시트를 만드는 것이다. 

 

sass는 전처리기다. 브라우저가 읽을 수 있는건 css밖에 없고 그래서 최종적으로 사용자한테 나가기전에 실제 서버가 뜨기전에 애네들이 그전에 처리를 해주는 것이다. sass를 css로 바꾸는 처리를 (css전처리기)

 

1. 설치

npm install node-sass@4.14.1 --save
  • 설치 시 node-modules 폴더에 node-sass 폴더가 생성됩니다. (package source code)
  • --save : package.json에 설치된 패키지의 이름과 버전 정보를 업데이트
// package.json
{
  "name": "westagram-react",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "node-sass": "^4.14.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.3"
  }
}

 

2. .css 파일의 확장자 .scss로 바꾸기

 

Sass 파일의 확장자는 .scss 입니다. .css 확장자로 되어 있는 파일을 전부 .scss 확장자로 바꿔주세요. (자바스크립트 파일의 import 구문도 수정해주세요.

 

3. Nesting 기능 적용하기

Sass의 가장 기본적인 기능으로 Nesting 이라는 것이 있습니다. JSX 최상위 요소의 className을 컴포넌트 이름과 동일하게 설정해주고, .scss 파일에서도 최상위 요소 안 쪽에서 하위 요소의 스타일 속성을 정의할 수 있도록 해주세요.

 

<Login.js>

import React from "react";
import "./Login.scss";

class Login extends React.Component {
  render() {
    return <h1 className="title">Login Page</h1>
  }
}

export default Login;

 

<Login.scss>

.title{
  color: red;
}

 

 

 

스타일링이 깨지는 이유. 

 

<Main.js>

import React from "react";

class Main extends React.Component {
  render() {
    return <h1 className="title">Hello Main</h1>
  }
}

export default Main;

Main.js는 따로 scss파일을 import하지 않았다.

 

 

 

그런데 Main경로를 들어가면 스타일이 적용되어 있다.

개발자 도구를 확인해보자.

 

Login.scss에서 준 스타일이 메인에도 적용되어있다.

왜이럴까?

 

<Routes.js>

import React from "react";
import { BrowserRouter as Router, Switch, Route } form "react-router-dom";
import Login from "./pages/Login/Login"
import Main from "./pages/Main/Main"
import Nav from "./components/Nav"
 
class Routes extends React.Component {
  render(){
    return(
      <Router>
        <Nav /> // 추가
        <Switch>
          <Route exact path="/" component={Login}/>
          <Route exact path="/main" component={Main}/>
        </Switch>
      </Router>
    )
  }
}
 
export default Routes;

그 이유는 Routes.js에서 다 합쳐지기 때문이다.

 

기존에 mpa(멀티플페이지)에서는 html이 따로 있었으니까 각기 css파일을 가지고 있었다.

그런데 spa는 html이 하나고 거기에 들어가는건 Routes컴포넌트고 Routes.js에서 모든 컴포넌트를 가져온다. 

그러면 Login.js에 딸려있는 Login.scss도 같이 가져오게 되는 것이다.

그러면 전체 페이지에 모든 컴포넌트에 붙여놓은 scss파일들이 다 한 번에 적용되고 있다.

그러니까 클래스네임이 겹치거나 하면은 스타일링이 다깨지는 문제가 발생하는 것이다.

 

이런 문제를 해결하기 위해서 네스팅을 해야한다.

 

<Login.js>

import React from "react";
import "./Login.scss";

class Login extends React.Component {
  render() {
    return(
      <div className="Login">
      <h1 className="title">Login Page</h1>
      </div>
    ) 
  }
}

export default Login;

네스팅을 할 때 제일 편리한거는 최상위 부모태그에서 클래스 네임을 부모컨퍼넌트와 똑같이 주는 것이다.

 

<Login.sass>

.Login .title{
  color: red;
}

이런식으로 네스팅을 해주면 되지만 이렇게 일일히 모두 네스팅을 해주는건 너무 힘들다.

그래서 sass의 기능을 이용할거다.

 

.Login{
 .tile{
   color:red;
 }
}

sass의 기능을 이용하면 이런식으로 네스팅할 수 있다.

 

 

 

터미널에 다음과 같이 치면 Login.css파일이 생기는데 Login.sass와 비교하면 

 

css파일로 변환했을 때 전처리기가 동작해서 해줘서 변환된걸 확인할 수 있다.

 

 

4. 변수 활용, & 연산자, mixin 등 기본 기능 적용하기

Sass 에는 Nesting 외에도 여러가지 기능이 있습니다. 공식 문서를 통해서 다른 기능들도 살펴보고 필요에 따라서 적절하게 활용해주세요.

 

변수

컬러들은 변수화시켜서 사용한다.

<Login.scss>

$button-color: blue /*$를 붙여 변수를 선언한다.*/

.Login{
 .tile{
   color:red;
 }
 
 .LoginBtn {
   color: $button-color;
 }
}

여러페이지들의 버튼이 이 변수를 쓰고 있으면 버튼 색을 바꾸고 싶을 때 이 $button-color의 값만 바꾸면 된다.

 

이런 변수들은 Login에서만 쓰지 않고 전체페이지에서 사용하니까 따로 파일을 만들어서 관리해줘야 한다.

variables.scss같은 파일명으로 관리해주자.

 

<variables.scss>

$button-color: blue 

 

 <Login.scss>

 

@import "../../styles/variables.scss"; /*분리한 variables를 import한 것 ;(세미콜론) 꼭 붙이자*/

.Login{
 .tile{
   color:red;
 }
 
 .LoginBtn {
   color: $button-color;
 }
}

 

 

common에서 변수까지 한 번에 관리해도 되는데 다른 scss파일들에서 변수를 쓰기 위해 common을 import를 하면 

매 scss파일마다 *{box-sizing: border-box;}가 붙어서 개발자도구에서 보면 중복돼서 여러개가 들어갈 수 있다.

 

그렇게 되면 보기가 불편하기 때문에 변수들만 따로 빼서 관리한다.

 

 

그러면 variable도 index.js에 넣어서 관리하면 안되나?

안된다! 최종css로 변환된게 라우터에 들어가는 것이기 떄문에 전에 처리할 때는 다 필요하기 때문에 variable이나 mixin은 

각각 import를 해줘야 한다!

 

Mixin

자주반복되는것들을 공통으로 묶어놓고 그것을 꺼내쓸수있다.

 

@import "../../styles/variables.scss";

@mixin flexCenter {
  display: flex;
  justify-content: center;
  align-items: center;
}

.Login{
 @include flexCenter;
 .tile{
   color:red;
 }
 
 .LoginBtn {
   color: $button-color;
 }
}

 

 

mixin한 flexCenter가 클래스login에 잘 적용됐다.

 

 

.Login{
 .title{
   color:red;
   &:hover {   /* &는 부모인 title을 가리킨다. */
     color: blue;
   }
 }
 
 .LoginBtn {
   color: $button-color;
 }
}

또한 &는 부모를 가리킨다.

 

이거말고도 기능이 많다. mixin을 함수처럼 사용할 수도 있다. flex자체를 함수형으로 만들어서 justify랑 align을 그때그때 인자로 주게 설정할 수도 있다.

나중에 공식문서를 확인해보자.

'React > Today I learned' 카테고리의 다른 글

fetch함수를 이용한 로그인&회원가입  (0) 2021.07.02
state,props,event  (0) 2021.07.01
Router  (0) 2021.06.29
[React] 비동기, 동기  (0) 2021.06.27
[React] why react?, JSX, Node.js, CRA  (0) 2021.06.27

 

 

 

 

 

1. SPA

(1) SPA (Single Page Application) - 페이지가 한 개인 애플리케이션(html파일이 한 개)

(2) Westagram-frontend : login.html, main.html - 페이지 수만큼 html 파일이 존재(MPA)

(3) 리액트 프로젝트에서 .html 파일의 개수는? 1개 >>> SPA(Single Page Application)

(4) 한 개의 웹페이지(html) 안에서 여러 개의 페이지를 보여주는 방법은? >>> Routing

 

 

2. Routing

(1) 라우팅(Routing)이란 다른 경로(url 주소)에 따라 다른 View(화면)를 보여주는 것 입니다.(웹에서 경로라는건 url을 뜻한다)

(2) 리액트 자체에는 이러한 기능이 내장되어있지 않습니다.

(3) 리액트가 Framework 가 아닌 Library 로 분류되는 이유입니다.

(4) 그렇기 때문에 서드파티 라이브러리(Third-party Library)인 React-router를 사용합니다.

(React-router는 리액트의 라우팅 기능을 위해 가장 많이 사용되는 라이브러리이다.)

 

 

 

 

3. React Router

Create React App(CRA)에 특별히 routing을 위한 로직이 들어있지 않기 때문에, 가장 인기 있는 routing solution인 react-router 를 추가해서 routing을 구현하도록 하겠습니다.

 

 

3-1. react-router 설치

npm install react-router-dom --save 

 

리액트 라우터 돔을 설치

 

--save는 패키지.json의 디펜던시에 추가 패키지 정보를 저장합니다.

 

 

 

3-2. Routes 컴포넌트 구현하기

 

<예시>

import React from 'react'; //Node_modules안의 react에서 React를 가져온다.
import {
  BrowserRouter as Router, //BrowserRouter를 Router라고 부르겠다.
  Switch,
  Route,
} from 'react-router-dom'; //리액트라우터돔에서 BrowserRouter, switch,route를 가져온다.

import Login from './pages/Login/Login';
import Signup from './pages/Signup/Signup';
import Main from './pages/Main/Main';

class Routes extends React.Component {
  render() {
    return (
      <Router>
        <Nav />
        <Switch>
          <Route exact path="/" component={Login} />
          <Route exact path="/signup" component={Signup}/>
          <Route exact path="/main" component={Main} />
        </Switch>
        <Footer />
      </Router>
    )
  }
}

export default Routes;

 

 

태그랑 비슷하게 생겼는데 대문자로 시작하는건 컴포넌트다.

ex) <Nav />

 

소문자로하면 그냥 태그로 인식한다.

 

 

 

 

 

 

직접  구현하기

 

 

<Routes.js>

import React from "react";
import { BrowserRouter as Router, Switch, Route } form "react-router-dom";

import Login from "./pages/Login/Login"
import Main from "./pages/Main/Main"

class Routes extends React.Component {
  render(){
    return(
      <Router>
        <Switch>
          <Route path="/" component={Login}/>
          <Route path="/main" component={Main}/>
        </Switch>
      </Router>
    )
  }
}

export default Routes;

 

import할 때 라이브러리 -> 컴포넌트 -> 기타나머지 파일(css) 순서로 해주는게 좋다.

 

path="/"는 뒤에 추가적인 주소를 안붙였을 때 나오는 주소이다.

component={}는 해당경로에 들어갔을 때 나오는 컴포넌트를 넣으면 된다.

 

 

 

리액트는 그냥 가져왔는데 (import React from "react";)

리액트라우터돔은 약간 객체처럼 {}로 가져왔다.

 

export는 두가지 종류가 있다.

named export vs default export

 

named export는 익스포트할 때 

export { Routes } ; 

이런 형식이다. 이렇게 하면 임포트할 때도 중괄호를 쳐서 가져와야한다. 

import { Routes } from "./Routes"

 

default export는 중괄호를 칠필요는 없다.

export default Routes;

 

named export는 여러개를 내보내야할 때 쓴다.

default export는 하나만 가능.

 

컴포넌트를 익스포트할 땐 보통 디폴트익스폴트하는게 편해서 디폴트익스포트를 쓴다.

 

 

 

 

<index.js>

import React from "react";
import ReactDOM form "react-dom";
import Routes from "./Routes"

import "./styles/common.css"

ReactDOM.render(<Routes />,document.getElementById("root"));

 

 

 

<Login.js>

import React from "react";

class Login extends React.Component {
  render(){
    return <h1>Hello Login</h1>
  }
}

export default Login;

<Main.js>

import React from "react";

class Main extends React.Component {
  render(){
    return <h1>Hello Main</h1>
  }
}

export default Main;

 

 

<localhost:3000>

 

 

 

main으로 옮기기 위해서 localhost:3000/main을 쳐도 안들어가진다. 

왜냐하면 Routes.js의 Route에 exact를 안 붙였기 때문이다.

import React from "react";
import { BrowserRouter as Router, Switch, Route } form "react-router-dom";

import Login from "./pages/Login/Login"
import Main from "./pages/Main/Main"

class Routes extends React.Component {
  render(){
    return(
      <Router>
        <Switch>
          <Route exact path="/" component={Login}/>
          <Route exact path="/main" component={Main}/>
        </Switch>
      </Router>
    )
  }
}

export default Routes;

위의 path에 /가 있고 아래에도 /main으로 /가 들어간다.

이렇게 됏을 때 exact를 안넣으면 어 /main 제일 앞에 /가 있네? 하고 무조건 login으로 이동해버린다.

 

이렇게 되면 /main으로 이동할 수 없으니 exact라는 속성을 넣어준다.

 

 

<localhost:3000/main>

exact를 입력하니 정상적으로 이동한다.

 

 

이제 버튼을 눌러 경로를 바꾸게 해보자. route를 이동하는 방법은 

1.<link>컴포넌트를 사용하는 방법

2.withRouterHOC로 구현하는 방법이 있다.

 

1번

<Login.js>

import React from "react";
import {Link} from "react-router-dom"

class Login extends React.Component {
  render(){
    return <Link to="/main">Go To Main Page</Link>
  }
}

export default Login;

 

링크의 to속성에 경로를 적어준다.

 

react-router-dom 에서 제공하는 <Link> 컴포넌트는 DOM에서 <a> 로 변환(Compile) 됩니다.

 

그런데 이럴꺼면 a태그 쓰지 굳이 link컴포넌트를 임포트해와서 쓸까?

 

a태그로 이동을 하게되면은 서버에다가 다시 파일을 요청한다. 그러면 html파일을 다시 받아오는 거다.

그렇기떄문에 전체파일을 한 번 다시 다 받아온다고 보면 된다.

 

그런데 화면에서 특정부분만 바꿔주면 될 수도 있다. ex)Nav나 footer같은 경우는 항상고정되고 내부안의 내용만 바뀌는 경우 굳이 nav와 footer까지 굳이 다시 서버에 요청을 해서 다 받아오고 html을 다시 받아오기 위한 요청을 보내고 할 필요가 없다.

 

link컴포넌트를 쓰면은 결과적으로 a로 컴파일은 되는데 여기서 컴파일된 a태그는 새롭게 파일을 받아오는 요청을 보내지 않는다.

단순히 경로만 바꿔준다.

 

그래서 불필요한 요청 필요없는 부분까지 다시 랜더링하지않고 효율적으로 바뀌는 부분만 바뀔 수 있게 해주는 기능이 있다.

 

<a> vs <link>

<a> 외부사이트로 이동하는 경우

<link> 프로젝트 내에서 페이지 전환하는 경우

 

 

 

2번 withRouterHOC

 

 

Nav.js

import React from 'react'

class Nav extends React.Component {
  render() {
    return {
      <nav>
        <button>LoginPage</button>
        <button>MainPage</button>
      </nav>
    }
  }
}

export default Nav;

 

 

 

<Routes.js>

import React from "react";
import { BrowserRouter as Router, Switch, Route } form "react-router-dom";
import Login from "./pages/Login/Login"
import Main from "./pages/Main/Main"
import Nav from "./components/Nav"

class Routes extends React.Component {
  render(){
    return(
      <Router>
        <Nav /> // 추가
        <Switch>
          <Route exact path="/" component={Login}/>
          <Route exact path="/main" component={Main}/>
        </Switch>
      </Router>
    )
  }
}

export default Routes;

 

<Nav />는 스위치 바깥이니까 무슨 경로든 계속 나오고 있다.

 

 

 

 

 

 

<예시>

import React from 'react';
import { withRouter } from 'react-router-dom';

class Login extends React.Component {

  goToMain = () => {
    this.props.history.push('/main');
  }

  // 실제 활용 예시
  // goToMain = () => {
  //   if(response.message === "valid user"){
  //     this.props.history.push('/main');
  //   } else {
  //     alert("너 우리 회원 아님. 가입 먼저 해주세요")
  //     this.props.history.push('/signup');
  //   }
  // }

  render() {
    return (
      <div>
        <button
          className="loginBtn"
          onClick={this.goToMain}
        >
          로그인
        </button>
      </div>
    )
  }
}

export default withRouter(Login);

 

임포트에서 withRouter라는걸 가져온다.

근데 얘는 어떤 얘냐면 HOC라는 애다.

(High Order Component) 쉽게말해서 컴포넌트를 감싸는 컴포넌트(한계층위의 컴포넌트)이다.

 

그냥 컴포넌트를 감싸서 컴포넌트를 내보내주는 또다른 한계층위의 컴포넌트 이런 정도로 이해하면 된다.

 

무슨 기능을 하냐면 

 

  goToMain = () => {
    this.props.history.push('/main');
  }

 

클래스를 쓰면 this를 많이 쓰는데 클래스안에서  매서드를 만들 때 this를 쓸때는 무조건 애로우평션으로 적어줘야한다.

왜냐면 그냥 function으로 쓰게되면은 this가 계속 시시각각 변할 수 있다.

하지만 애로우펑션으로 쓰면은 여기서 this는 계속 Login이라는 컴퍼넌트가 되는 것이다.

 

this는 Login컴퍼넌트다. 그러면 .으로 이 컴퍼넌트 안에 들어갓다. 그러면 이 login이라는 컴퍼넌트가 최종적으로  객체로 만들어진다는 것이다.

 

props안에 들어갓다. 이 컴포넌트 안에 있는 어떤 property(속성)인 것 같다. 그리고나서 props안에 또 . 하고 들어갓다.

그러면 프롭스도 객체다.

 

히스토리도 객체다.

 

푸쉬는 메소드(함수)

 

우리는 this안에 있는 props라는 객체에 들어가서 history라는 객체에 들어가서 push라는 매소드를 호출해서 뭔가 동작을 일으킬거다.

 

 

 

 

 

<Nav.js>

 

console.log(this.props) 

맨위 Object는 this.props가 객체라는 것 

 

props를 열어보면 아무것도 없다.

__proto__라는건 기본적으로 자바스크립트 객체에 들어가 있는 것이다

 

그 외 나머지 요소는하나도 없다.

 

 

<Nav.js>

console.log("Nav props", this.props) 

<Login.js>

console.log("Login Props", this.props)

Nav props는 아무것도 안들어와 있다.

 

반면 login props는 history, location, match 이런 값들이 들어와 있다. 

히스토리도 객체인데 그 안을 보면 push가 들어가 있는걸 볼 수 있다. f는 펑션이라는 뜻이다.

props안에 history라는 객체가 들어가 있고 그 안에 push라는 메소드가 있다. 

 

근데 왜 Nav에만 없고 Login에만 있을까?

Login은 Routes.js파일에서 Switch안에 Route안에 들어가 있다.

반면에 Nav는 그 바깥에 나와있다.

 

Route안에 들어가 있는 컴포넌트들은 props안에 history, location, match와 같은 것을 주입받는 것이다.

 

그래서 Nav도 props로 history,location,match같은걸 주입받기 위해서 쓰는게 withRouterHOC다. 

 

import React from 'react'
import {withRouter} from "react-router-dom";

class Nav extends React.Component {
  render() {
    comsole.log("Nav props", this.props);
    return {
      <nav>
        <button>LoginPage</button>
        <button>MainPage</button>
      </nav>
    }
  }
}

export default withRouter(Nav);

 

withRouterHOC를 적용한 후 다시 콘솔 로그를 찍으면 history,location,match가 들어와 있다.

 

라우트안에 안들어가 있는 애들중에서 history,location,match가 필요할 때는 withRouter로 감싸줘야하는 것이다.

 

 

<Nav.js>

import React from 'react'
import {withRouter} from "react-router-dom";

class Nav extends React.Component {
  goToMain = () => {
    this.props.history.push("/main");   //push의 인자로 경로를 넣어줬다.
  };
  
  goToLogin = () => {
    this.props.history.push("/");
  }
  
  render() {
    comsole.log("Nav props", this.props);
    return {
      <nav>
        <button Onclick = {this.goToLogin}>LoginPage</button>
        <button Onclick = {this.goToMain}>MainPage</button>
      </nav>
    }
  }
}

export default withRouter(Nav);

 

 

 

Go To LoginPage를 누르면 로그인 페이지로 이동하고 

Go To MainPage를 누르면 메인 페이지로 이동한다.

 

이걸로 index.html페이지라는 하나의 페이지 안에서 경로에 따라서 다른화면을 보여주게 되는 spa를 리액트라우터로 구현한 것이다.

 

링크대신 withRouterHOC를 사용하는 이유는 함수로 쓸 수 있기 떄문이다.

함수로 사용하면 조건문을 넣을 수 있다. 특정조건이면 호출을해서 이동시켜야 하는 경우 this.props.history.push를 이용해서 하는 것이다.

 

3-4-3. 두 가지 방법의 활용법

  1. <Link>
    • 클릭 시 바로 이동하는 로직 구현 시에 사용합니다.
    • ex. Nav Bar, Aside Menu, 아이템 리스트 페이지에서 아이템 클릭 시 > 상세 페이지로 이동
  2. withRouterHOC
    • 페이지 전환 시 추가로 처리해야 하는 로직이 있는 경우 withRouterHOC 방법으로 구현합니다.
    • ex. 로그인 버튼 클릭 시
      • Backend API로 데이터(User Info) 전송
      • User Data 인증 / 인가(이 유저가 우리 유저가 맞는지 확인하고 맞으면 허락해주는 것)
      • response message
      • Case 1 : 회원 가입되어 있는 사용자 > Main 페이지로 이동
      • Case 2 : 회원 가입이 되어 있지 않은 사용자 > Signup 페이지로 이동

 

 

 

'React > Today I learned' 카테고리의 다른 글

state,props,event  (0) 2021.07.01
Sass  (0) 2021.06.30
[React] 비동기, 동기  (0) 2021.06.27
[React] why react?, JSX, Node.js, CRA  (0) 2021.06.27
[React] key값을 써야하는 이유  (0) 2021.06.26

 

 

인증에 필요한건 아이디, 이메일주소, 비밀번호등이 있다.

그중에서 비밀번호는 법규상의 강제(국가의 강제) 때문에 따로 관리해줘야 한다.

 

 

비밀번호를 관리하는 방법엔 두가지가 있다. 

 

첫번째데이터베이스에 넣을 때 부터 암호화를 하는 것이다. 

데이터베이스에 저장시 개인 정보를 해싱하여 복원할 수 없도록 한다. 

(해싱은 난독화 한다는 것)

 

암호화한 사람도 다시 복구할 수 없게 원상태가 뭔지 알 수 없게 해야한다.

왜냐하면 데이터를 만든 개발자가 해킹을 할 수 있기 때문이다.

 

그래서 암호를 친 사람 본인만 알 수 있도록 관리를 해야한다.

 

<아마존 옆의 자물쇠 모양>

 

두번째네트워크 상에서 전송을 할 때 암호화하는 방법 (https)

 

ssl인증서가 적용된 https사용하는 것.

 

인증서를 입혀서 통신을 하면 네트워크 통신자체가 암호화가 된다.

 

 

 

해쉬는 함수여서 하나의 인풋이 있으면 하나의 아웃풋이 있다.

 

해쉬의 특징은 나온 아웃풋을 가지고 인풋을 유추할 수 없다. 즉, 단반향이라는 것이다.(복호화를 할 수 없다)

사람들이 이 특징을 가지고 암호화를 만든 것이다.

 

 

 

해시는 언제 누가 똑같은 값을 넣으면 똑같은 값이 나온다.

 

그 결과 md5, sha-1와 같은 해시함수는 나온지 오래되고 너무 많이 쓰이다 보니 

나쁜 사람들이 슈퍼컴퓨터를 돌려서  노가다를 통해 아웃풋값에 해당하는 인풋값을 다 유추해놨다.

그걸 데이터화 시킨게 레인보우 테이블이다.

 

그래서 보안이 취약해져서 sha-256같은 해시를 쓴다.

 

이를 방지하기 위해서 saultingkey stretching이 등장했다.

 

 

 

Salting = 랜덤한 어떤 문자열을 붙여주는 것

Key Stretching = 솔트값이랑 합쳐서 해싱을 계속 늘려주는 것

 

결국 풀리지만 이렇게까지 하는 이유는 시간을 벌기 위해서다.

 

 

첫번째는 어떤 알고리즘을 썼는지

두번째는 키 스트레치를 몇 번 했는지

세번째는 솔트값

네번째는 해쉬를 거친 비밀번호

 

* 솔트값이나 키스트레치를 몇번 했는지 알려준다고? 라고 생각할 수 있지만 해쉬된 비밀번호를 모르면 아예 소용이 없다.

그래서 1,2,3,번이 노출된거에 대해 의문을 품지 않아도 괜찮다.

 

 

 

 

인가가 필요한 이유는? http의 특징 때문이다.

Stateless : 성질 단기기억 상실증/ 각각의 통신기록이 저장되지 않는다.

 

각각의 통신기록이 저장되지 않기 때문에 로그인을 했어도 이 사용자가 로그인 했다는 증거를 줘야한다.

 

그 증거가 토큰이다.

 

 

토큰은 너 지금 로그인 잘 됐는데 너가 이 다음에 어떤 요청을 해도 내가 너 로그인 했던거 모르니까

이거 들고와 하고 주는게 JWT(JSON Web Token)다.

 

JWT를 만드는건 백엔드다. 

백엔드가 프론트에게 준다(서버가 클라이언트에게 준다) 그런데 백엔드가 유저한테준다고 하면 이상하다.

왜냐하면 토큰은 프론트가 관리하기 때문에 유저는 토큰이 있는줄도 모르기 때문이다.

 

 

백엔드가 프론트에게 토큰을 주는 방법은?

 

토큰처럼 메타데이터 (데이터에 대한 데이터를 담고 있는 것)는 헤더에 담아서 주고받는다.

 

프론트는 나중에 백엔드에게 토큰 들고있다고 알려줄땐 헤더에 담아서 주면 되는 것이다.

 

 

 

헤더는 토큰에 대한 정의가 들어간다.

 

 

바디(페이로드)는 정말 필요한 데이터를 담아준다. 

1. 로그인한 사용자에 대한 정보 (유저아이디 1번인 김xx씨가 로그인한거다 라는 내용을 이 페이로드에 담는다.) 

2. 토큰은 만료시간

 

1번에서 유의할 점은 user-id와 같은 식별만 가능한 식별자를 넣는다는 것이다.  그렇게 하지 않고 이메일,계정,핸드폰 번호 같은 정보를 넣어놓으면 토큰을 탈취당할 시 매우 위험하기 때문이다.

 

 

 

 

 

헤더랑 내용은 암호화하지 않고 인코딩만 한다.

서명은 헤더와 내용을 담아가지고 다시 암호화해서 붙여준다.

또한 secret이란 값도 같이 들어가 있다.

 

secret는 토큰이 우리가 발급한게 맞는지 확인해주는 것이다.

(만약 왓챠에서 발급받은 토큰이 넷플릭스에서 통과되면 안되니까!)

 

 

서명에도 헤더랑 내용이 들어가니까 어찌보면 중복아닌가? 이렇게하는 이유는?

처음에 로그인에 성공하면 jwt를 백엔드에서 만들어서 프론트엔드에게 준다.

그리고 프론트는 그걸 들고다니다가 로그인이 필요한 기능을 할 때 헤더에 담아서

백엔드에게 준다. 백엔드는 그걸 받아서 다시 복호화한다. 열어서 이 토큰이 우리 유저께

맞는지 체크를 하는 것이다.  그 인증방식이 헤더랑 내용을 합친게 서명이랑 똑같으면

매칭 해주는 것이다. + 시크릿키까지 넣어서 체크

 

이러한 이유로 중복을 하는것이다.

 

 

 

 

 

 

 

 

+ Recent posts