국비교육(22-23)

45일차(2)/React(8) : 모듈화된 CSS 적용하기 / form 요소 활용

서리/Seori 2022. 12. 11. 00:56

45일차(2)/React(8) : 모듈화된 CSS 적용하기 / form 요소 활용

 

App05.js - CSS 적용

//App05.js

import { Component } from "react";
//css/custom.css로딩하기
import "./css/custom.css";

/*
    [ 모듈화된 css를 사용하는 방법 ]
    1.css 파일의 이름에 .module.를 추가한다
    2.from과 함께 import 해서 사용한다.
*/
import styles from "./css/custom.module.css";

class App extends Component{
    state={
        obj:{
            color:"blue",
            border: "1px solid red",
            backgroundColor:"yellow"
        },
        isYellow:false
    }
    render(){
        const classes="box bg-yellow";
        return(
            <div className="container">
                <h1>css 적용 예제</h1>
                <button onClick={()=>{
                    this.setState({
                        obj:{
                            ...this.state.obj,
                            backgroundColor:"greenyellow"
                        }
                    });
                }}>배경색 바꾸기</button>
                <p style={this.state.obj}>p1입니다.</p>
                <div className="box bg-yellow">box</div>
                {/* 
                    jsx에서 { 중괄호 내부 }
                    중괄호 내부는 javascript에서 사용하는 데이터가 참조되어야 한다.
                    ex) number, string, object, array, function
                    number=>10,20
                    string=> "abcd", 'abcd', `abcd`
                    object=> { key:value }
                    array=> [value2,value2]
                    function=> ()=>{}
                    -  데이터를 즉석에서 만들면서 참조할수도 있고, 이미 만들어진 데이터를 참조할 수도 있다.
                */}
                <div className={"box bg-pink"}>box2</div>
                <div className={classes}>box3</div>
                <div className={`box bg-pink`}>box4</div>
                <div className={`box ${ true ? "bg-yellow" : "" }`}>box5</div>
                <div className={`box ${ false ? "bg-yellow" : "" }`}>box6</div>
                <div className={`box ${ true && "bg-yellow" }`}>box7</div>
                <div className={`box ${ this.state.isYellow ? "bg-yellow" : "" }`} onClick={()=>{
                    this.setState({
                        isYellow:!this.state.isYellow
                    })
                }}>box8</div>
                <p className={styles["text-red"]}>p요소입니다.</p>
                <p className={styles["text-bold"]}>p요소입니다.</p>
                <p className={`${styles["text-red"]} ${styles["text-bold"]}`}>p요소입니다.</p>
            </div>
        )
    }
    handleCss = (ref) => {
        this.pRef="green"
        this.setState()
    }
}

export default App;

 

/css/custom.module.css 파일생성

/* custom.module.css */

.text-red{
    color:red;
}

.text-blue{
    color:blue;
}

.text-bold{
    font-weight: bold;    
}

.text-big{
    font-size: 2rem;
}

 

- 이 파일은 import해도 css가 적용되지 않는다.

- custom.module.css 로 작성하면 글로벌 영역이 아니라 local 영역에서만 적용된다. 그렇게 약속된 것..

 

 

- 리액트에서는 다양한 component 를 사용한다.

- 그렇기 때문에 각각의 component 에만 적용되는 css를 가지고 있어야 할 필요가 있다.

 

- 어떤 css는 특정 component 에서만 필요할 수 있다.

 이런 css는 파일에 module. 을 붙여서 만든다.

 

- 이렇게 하면 단순히 클래스명을 적는 것만으로는 적용이 안 된다.

- 이것을 적용하려면 from 을 사용해서 import하면 된다.

 

import styles from "./css/custom.module.css";
<p className={styles.text-red}>p요소입니다.</p>

- styles 부분의 명칭은 자유롭게 지정 가능

    ↓

<p className={styles [text-red] }>p요소입니다.

- 그런데 text-red 를 js에서는 빼기로 생각한다. 이를 방지하기 위해서는 해당 내용을 [ ] 로 감싸주면 된다.

 

 

- javascript object 에서 방의 내용을 참조할 때 위와 같이도 쓸 수 있다. 이런 문법으로 참조하는 것이 필요할 때가 있다.

- 보통은 . 점을 찍어서 참조하지만 그렇게 참조할 수가 없는 경우, 그럴 때 대괄호로 ["xxx"] 으로 사용하면 참조 가능하다.

 

[ 모듈화된 css를 사용하는 방법 ]   
1.css 파일의 이름에 .module.를 추가한다   
2.from과 함께 import 해서 사용한다.

 

- 컴포넌트별로 자기만의 css를 사용하고 싶을 경우 파일명에 module을 넣어서 만들면 된다.

- css 파일명을 작성할 때 .module을 작성하고, 필요한 요소에서는 점을 찍어서 선택자를 써야한다. ex)

 

<p className={`${styles["text-red"]} ${styles["text-bold"]}`}>p요소입니다.</p>

여러개의 style을 적용하고 싶은 경우. `` 과 ${} 으로 묶어준다.

 


 

App05_ex1.js

- 모듈화된 css를 적용하는 다른 방법

//App05_ex1.js

import "./css/custom.css"
//모듈화된 css
import styles from "./css/custom.module.css";

import { Component } from "react";
//여기서 import한 cn은 function으로 생각하고 사용하면 된다.
import cn from "classnames";
import binder from "classnames/bind";
//classnames binder를 이용해서 모듈화된 css를 바인딩해서 그 참조값을 cx에 대입
const cx=binder.bind(styles);

class App extends Component{

    state={
        btnColor:'btn-primary',
        isChecked: false
    }

    render(){
        return(
            <div className="container">
                <h3>모듈화된 css 사용의 불편함 해결하기</h3>
                <div className="box bg-yellow">box</div>
                <div className={'box bg-yellow'}>box2</div>
                {/* cn(적용할 클래스를 문자열로 나열) */}
                <div className={cn('box', 'bg-yellow')}>box3</div>
                {/* cn({}) object를 전달해서 true, false를 활용해서 클래스를 적용할지 말지를 결정 */}
                <div className={cn({box:true, 'bg-yellow':true})}>box4</div>
                <div className={cn('box', {'bg-yellow':true})}>box5</div>

                <h2>동적으로 class 를 적용하는 예제</h2>
                <select name="color" onChange={this.onColorChange} 
                        value={this.state.btnColor}>
                    <option value="btn-primary">primary</option>
                    <option value="btn-info">info</option>
                    <option value="btn-success">success</option>
                </select>
                <input type="checkbox" checked={this.state.isChecked}
                    onChange={this.onCheckChange}/>
                <br/>
                <button className={cn('btn', this.state.btnColor,{'btn-lg':this.state.isChecked})}>색상이 바뀌는 버튼</button>

                <h2>모듈화된 css를 좀더 편하게 사용하기</h2>
                <p className={styles["text-red"]}>p1입니다.</p>
                <p className={cx("text-red")}>p2입니다.</p>
                <p className={cx("text-blue", "text-bold", "text-big")}>p3입니다.</p>
                <p className={cx("text-blue", {"text-bold":true})}>p4입니다.</p>
            </div>
        )
    }
    onCheckChange=(e)=>{
        //이벤트가 일어난 요소의 checked 값 (true or false) 를 얻어와서 
        const isChecked=e.target.checked;
        //상태값에 반영한다. 
        this.setState({isChecked:isChecked});
    }

    //select 요소가 change 되었을때 호출되는 함수 
    onColorChange=(e)=>{
        //이벤트가 일어난 요소의 value 값을 읽어온다. 
        const selectedColor=e.target.value;
        //상태값을 바꿔줘서 UI 가 업데이트 되도록한다. 
        this.setState({btnColor:selectedColor});
    }
}

export default App;

 

*외부 노드 페이지 사이트 : https://npmjs.com/

 - classnames를 검색 : https://www.npmjs.com/package/classnames

 

- className을 조건부로 조인해주는 유틸리티

- 클래스를 import해서 사용하게 해준다.

 

- 실사용 예!

- 클래스를 편리하게 제어하고 사용하게 해주는 기능

 

- 명령 프롬프트에서 npm install 로 설치해본다!

- 설치되면 json 파일에 바로 classnames가 추가된 것을 볼 수 있다.

 

- 설치되면 이제 classnames를 import해서 사용할 수 있다.

 

<div className="box bg-yellow">box</div>
<div className={'box bg-yellow'}>box2</div>
<div className={cn('box', 'bg-yellow')}>box3</div>
<div className={cn({box:true, 'bg-yellow':true})}>box4</div>
<div className={cn('box', {'bg-yellow':true})}>box5</div>

box1: 클래스명을 문자열로 나열

box2: 클래스명을 javascript 영역인 {} 안에 넣어서 적용

box3: cn(적용할 클래스를 문자열로 나열)
box4: cn({}) object를 전달해서 true, false를 활용해서 클래스를 적용할지 말지를 결정

box5 : object와 문자열의 조합

 

- 클릭시 이벤트가 일어나는 button 요소 추가하기

 

- 모듈화된 css의 경우 import 해서 사용하는 것이 불편하다. 개선방안은?

 

 

- classnames의 binder 를 활용한다.

- 모듈화된 css를 binder 안에 넣어서 변수(cx)에 담아준다.

 

<p className={styles["text-red"]}>p1입니다.</p>
<p className={cx("text-red")}>p2입니다.</p>
<p className={cx("text-blue", "text-bold", "text-big")}>p3입니다.</p>
<p className={cx("text-blue", {"text-bold":true})}>p4입니다.</p>

p1: styles["클래스명"] 으로 사용. 기존 모듈화된 CSS를 사용하는 방식

p2: cx("클래스명") 으로 cx 상수에 바로 문자열 넣기

p3: cx("클래스명1", "클래스명2", "클래스명3")  여러개의 클래스명 적용 가능

p4: cx("클래스명1", {클래스명:T/F}) 로 cx안에 object로 값 전달

 

 

- classnames 는 import된 이름으로 사용하면 된다.(cn) classnames를 설치한 다음에 import해서 사용

- classnames 의 binder를 사용해서 styles를 넣어준다.

- 그러면 마치 cn으로 일반 클래스를 사용하듯이 cx를 사용할 수 있다.

- 여러개 나열하기에는 cx가 편하다.

 

 


 

App06.js

//App06.js

import { Component } from "react";

class App extends Component{
    state={
        msg:""
    }
    render(){
        return(
            <div className="container">
                <h1>form 요소를 사용해 보기</h1>
                {/* form 안에 있는 submit 버튼을 누르면 submit 이벤트가 발생한다. */}
                <form onSubmit={this.handleSubmit}>
                    <label htmlFor="msg">메세지 입력</label>
                    <input type="text" id="msg" onChange={(e)=>{
                        this.setState({
                            msg:e.target.value
                        })
                    }} value={this.state.msg} />
                    <button type="submit">전송</button>
                </form>
                <p>현재 입력한 내용: {this.state.msg}</p>
            </div>
        );
    }
    //form 에 submit 이벤트가 발생하면 호출되는 함수
    handleSubmit = (e) => {
        e.preventDefault(); //폼 제출 막기
        alert(this.state.msg+" 를 전송합니다.");
    }
}

export default App;

 

- 보통 이런 형태로 input id와 label for 를 일치시키지만,

 React에서는 이 경우 에러가 발생할 수 있다.

 

- 이 class 안쪽은 자바스크립트 영역으로 작성하는 것이기 때문에, for도 예약어이다.

- 그래서 for과 id를 짝을 지을 때 htmlFor 로 작성한다.

 (class → className 으로 바꾸는 이유와 같다.)

 

onSubmit() : 폼에 submit이라는 이벤트가 일어날 때 발생시킬 이벤트를 넣어주면 된다.

 

handleSubmit = (e) => {
    e.preventDefault(); //폼 제출 막기
}

.preventDefault()

- 폼 제출(submit)을 막을 때 사용!

- 전송시 페이지가 새로고침되는 것을 막으려면 폼 제출을 막고 ajax로 전달하는 경우가 많다.

 

- input에 onChange 함수를 넣고 (input 요소에 뭔가 값이 입력될 때 실행되는 함수)

  {} 안에는 입력받은 값이 바로 setState 되도록 작성해주면 된다

 

 

- 입력받은 내용을 상태값(state)으로 관리하고 있다가 필요한 시점에 전송할 수 있도록 하면 된다.

 

 


 

App06_ex1.js

//App06_ex1.js

import { Component } from "react";

class App extends Component{
    state={
        id:'',
        pwd:'',
        isSave:false  //체크박스의 체크 여부 
    }
    //input 요소에 변화가 생겼을때 호출되는 함수 
    handleChange=(e)=>{
        //이벤트가 일어난 폼요소의 name 속성의 value 얻어오기  
        const name=e.target.name; //"id" or "pwd" or "isSave"
        // value 를 미리 얻어내기 (체크박스인 경우에는 체크 여부를 얻어낸다 )
        const value=e.target.type === 'checkbox' ? e.target.checked : e.target.value;

        //ECMA6 문법을 활용하면 아래와 같이 된다. 
        this.setState({
            [name]:value
        });
    }
    //폼에 submit 이벤트가 발행했을때 호출되는 함수 
    handleSubmit=(e)=>{
        e.preventDefault();

    }
    render() {
        return (
            <div className="container">
                <h1>React form 테스트</h1>
                <form onSubmit={this.handleSubmit}>
                    <div className="form-group">
                        <label htmlFor="id">아이디</label>
                        <input type="text" value={this.state.id} onChange={this.handleChange} className="form-control" name="id" id="id" />
                    </div>
                    <div className="form-group">
                        <label htmlFor="pwd">비밀번호</label>
                        <input type="text" value={this.state.pwd} onChange={this.handleChange} className="form-control" name="pwd" id="pwd" />
                    </div>
                    <div className="form-group">
                        <label>
                            <input onChange={this.handleChange} checked={this.state.isSave} name="isSave" type="checkbox"/> 아이디 저장
                        </label>
                    </div>
                    <button className="btn btn-primary" type="submit">로그인</button>
                </form>
                <p> 아이디 : <strong>{this.state.id}</strong></p>
                <p> 비밀번호 : <strong>{this.state.pwd}</strong></p>
                <p> 아이디 저장할지 여부 : <strong>{this.state.isSave.toString()}</strong></p>
            </div>
        );
    }
}

export default App;

 

{num:1, name:"kim"}

- 때로는 object의 방의 이름이 변수 안에 들어 있을 수도 있다.

 

let a ="num"

let b="name"

{ a:1 , b: "kim" }

- a, b라는 변수를 가지고 object를 만들고자 것인데,

  저렇게 만들면 a,b가 변수가 아니라 그냥 a,b라는 방의 이름이 되어버린다.

 

 

{ [a] : 1, [b] : "kim" }

- 위와 같이 변수 안에 있는 값을 참조해서 방을 만들고 싶은 경우 이렇게 표기할 수 있다.

 

 

- setState에서 위의 예제를 이용하고 있다.

 

 

- 현재 input 3개가 changeEvent가 일어나면 동일한 함수를 호출하게 되어있다.(handleChange)

  어떻게 이를 구분하는가?

 → name=" " 의 값이 다르므로 name 값으로 구분해서 사용!

 

e.target.name / e.target.type

- 각각 이벤트가 일어난 타겟(e.target) 의 name 속성의 밸류, 타입 속성의 밸류를 읽어올수있다.

- type이 체크박스인 경우: 체크박스의 체크여부를 읽어오겠다

  type이 체크박스가 아닌 경우: 입력한 값을 넣어주겠다 라는 뜻!

 

[name]:value 는 각각의 input에서 아래 3가지 값을 읽어올 수 있다.

id: banana(입력한 값)

pwd: 1234

isSave: true

 

- 상태값이 바로바로 관리되어 아래 p요소에 출력되는 것을 볼 수 있다!

 


 

App06_ex2.js

//App06_ex2.js

import { Component } from "react";

class App extends Component{
    //폼에 submit 이벤트가 발행했을때 호출되는 함수 
    handleSubmit=(e)=>{
        e.preventDefault();
        //폼에 입력한 값을 읽어온다
        const id=this.id.value;
        const pwd=this.pwd.value;
        const isSave=this.isSave.checked;
        const info=`아이디:${id} 비밀번호:${pwd} 저장여부:${isSave}`;
        this.info.innerText=info;
    }
    render() {
        return (
            <div className="container">
                <h1>React form 테스트</h1>
                <form onSubmit={this.handleSubmit}>
                    <div className="form-group">
                        <label htmlFor="id">아이디</label>
                        <input ref={(ref)=>{this.id=ref;}} type="text"  className="form-control" name="id" id="id" />
                    </div>
                    <div className="form-group">
                        <label htmlFor="pwd">비밀번호</label>
                        <input ref={(ref)=>{this.pwd=ref;}} type="text"className="form-control" name="pwd" id="pwd" />
                    </div>
                    <div className="form-group">
                        <label>
                            <input ref={(ref)=>{this.isSave=ref;}} name="isSave" type="checkbox"/> 아이디 저장
                        </label>
                    </div>
                    <button className="btn btn-primary" type="submit">로그인</button>
                </form>
                <p ref={(ref)=>{this.info=ref;}}></p>
            </div>
        );
    }
}

export default App;

 

- input 요소에 입력받은 값을 읽어오는 다른 방법! 참조값 ref 를 사용했다.

- state에서 관리하던 값을 info 에 넣어서 ref로 출력한 것.

 

 

- 각각의 참조값을 필드를 만들면서 저장했다.

- this.id / this.pwd / this.isSave 라는 필드를 생성하면서 ref를 저장한 것!

 

- 필드에 저장한 값은 handleSubmit 함수가 실행될 때 읽어온다.