React

React Part 1 - Step 2

hminor 2023. 6. 25. 17:45
반응형

만약 input태그에 뭔가 입력시 코드를 실행하고 싶다면

onChange/ onInput 을 작성하면 된다.

그래서 이벤트핸들러(onClick, onChange, onInput …) 종류는 굉장히 많다.

여기서 input태그 내부의 입력값을 가져오고 싶을 경우 함수의 파라미터값을 가져와서 사용

// onchange가 감지할 때마다 생성되는 e
<input onchange={(e)=> { console.log(e) }}/>

// onchange가 감지할 때마다 생성되는 이벤트가 발생한 html태그
<input onchange={(e)=> { console.log(e.tartget) }}/>

// onchange가 감지할 때마다 생성되는 이벤트가 발생한 html태그에 입력한 값
<input onchange={(e)=> { console.log(e.target.value) }}/>

그리고 현재 강의대로 작성했다면 아래의 코드가 작성이 되어있을텐데

그렇다면 span 태그에 있는 따봉을 눌러도 모달창이 뜨게된다.

이유는 클릭 이벤트는 상위 html로 퍼지게 되는데 이걸 이벤트 버블링이라고 부른다.

{
  글제목.map((article,i)=> {
    return (
      <div>
        <h1 onClick={()=> {
          let title = article
          선택글제목변경(title)
          setModal(true)
        }}>{article} <span style={{cursor: 'pointer'}} onClick={()=> {
          let thumb = [...따봉]
          thumb[i] = 따봉[i] +1
          따봉변경(thumb)
        }}>👍</span>{따봉[i]}</h1>
        <div>{글제목2[i]}</div>
      </div>
    )
  })
}

그래서 이벤트 버블링 현상을 막고 싶다면 하위태그에서 클릭 이벤트시 stopPropagation을 넣어주기.

{
  글제목.map((article,i)=> {
    return (
      <div>
        <h1 onClick={()=> {
          let title = article
          선택글제목변경(title)
          setModal(true)
        }}>{article} <span style={{cursor: 'pointer'}} onClick={(e)=> {
          e.stopPropagation()
					let thumb = [...따봉]
          thumb[i] = 따봉[i] +1
          따봉변경(thumb)
        }}>👍</span>{따봉[i]}</h1>
        <div>{글제목2[i]}</div>
      </div>
    )
  })
}

그런데 console.loe()로 확인을 해보게 되면 늦게 처리된다.

이유는 state변경함수는 늦게처리되는데 이것을 비동기처리라고 한다.

그래서 빠르게 처리될 수 있는 console.log()부터 처리한 뒤 state변경 함수를 처리하게 되서어 늦게 변경된다.

숙제.

  1. 버튼을 누르면 글 하나 추가되는 기능 만들기
    1. 우선 내가한 방식은 강사님이 말씀해주신것처럼 기존에 정의해둔 글제목 state를 변수에 담아줌과 동시에 가장 앞에 e.target.value를 추가하여 담아주고 state 변경하기 위한 함수로 글제목함수에 해당 변수를 넣어주어 해결
      1. 추가> 강사님이 하는 방법은
        1. title.unshift(입력값) 로 unshift() 메서드를 통해 추가
    <input onChange={(e)=> {
      입력값변경(e.target.value)
      
    }}/>
    <button onClick={()=> {
      let title = [입력값,...글제목]
      글제목함수(title)
    }}>저장</button>
    
  2. 글마다 삭제버튼 & 삭제 하기
    1. 고민을 조금했는데 그냥 filter() 메서드를 사용하여 현재 map() 메서드로 돌아서 얻게되는 article 값이 있기 때문에 해당 값과 같지 않은 것들을 변수에 담아주도록 하고 글제목함수를 통해 state변경해주기
      1. 추가> 강사님이 하는 방법은
        1. title.splice(i, 1)로 splice() 메서드로 삭제했음
    {
      글제목.map((article,i)=> {
        return (
          <div>
            <h1 onClick={()=> {
              let title = article
              선택글제목변경(title)
              setModal(true)
            }}>{article} <span style={{cursor: 'pointer'}} onClick={(e)=> {
              e.stopPropagation()
              let thumb = [...따봉]
              thumb[i] = 따봉[i] +1
              따봉변경(thumb)
            }}>👍</span>{따봉[i]}</h1>
            <div>{글제목2[i]} <button onClick={(e)=>{
              let title = [...글제목].filter((t) => {
                return t != article
              })  
              글제목함수(title)
            }}>삭제</button></div>
          </div>
        )
      })
    }
    

여기서 추가로 더 시도 해본것으로

  1. 작성 후 해당 input 태그의 value를 빈칸으로 초기화 하기
    1. DOM을 이용해서 id에 해당하는 input 태그를 지정후 해당 태그를 ‘’으로 변경
    else {
      let title = [입력값,...글제목]
      let inp = document.getElementById('input')
      inp.value = ''
      글제목함수(title)
    }
    
  2. 저장 버튼 클릭이 아니어도 Enter 버튼을 누르게 되면 저장이 될 수 있도록 하기
    1. 우선 button 태그에서 많은 시도를 해봤는데 결국에는 input 태그로 해결을 했고
    2. input태그에서도 type을 submit으로 해서 onSubmit으로도 해보고 했지만 잘 안되었다.
    3. 그래서 결국 해결한 방법은 type을 없애고 onKeyDown의 event를 가져와서 해당 event의 key가 Enter의 경우 실행되도록 하여 해결
    <input id="input" onChange={(e)=> {
            입력값변경(e.target.value)
            console.log(입력값);
          }} onKeyDown={(e)=> {
            if (e.key === 'Enter') {
              console.log(e.key);
              if (입력값 === ''){
                alert('경고')
              }
              else {
                let title = [입력값,...글제목]
                let inp = document.getElementById('input')
                inp.value = ''
                글제목함수(title)
              }
            }
          }} />
    
  3. 그리고 입력칸이 빈칸일 경우 저장되지 않도록 하기
    1. 이것은 간단했다 그냥 현재 입력값이라고 하는 state의 값이 ‘’ 이렇게 비워져있다면 alert 처리를 해주는 것으로 해결
    if (입력값 === ''){
      alert('경고')
    }
    

 

클래스 문법으로 컴포넌트를 만드는 방법

우선 class는 변수, 함수 보관함으로 생각하면 된다.

  • 리엑트에서는 우선적으로 채워줘야하는 것이 아래와 같다 (그냥 template처럼 생각하면 된다.)
    • constructor
    • super
    • render
  • 그래서 아래와 같이 작성하게 되면 Modal2를 작성하는 것만으로 긴 HTML을 작성할 수 있게 된다.
  • class Modal2 extends React.Component { constructor() { super() } render() { return ( <div>안녕</div> ) } }
  • 해당 클래스는 App 의 최상단 태그 안에 작성해야 작성이 된다.
  • 그래서 현재 사용하는 Component와 Class를 통해 만든 Component 작성을 비교하면 아래와 같다
// 현재
function Modal(props) {
  return (
    <div className="modal" style={{backgroundColor:props.color}}>
      <h2 id="title">{props.선택글제목}</h2>
      <p>날짜</p>
      <p>상세내용</p>
    </div>
  );
}

// class를 통해 만든 Component
class Modal2 extends React.Component {
  constructor() {
    super()
  }
  render() {
    return (
      <div>안녕</div>
    )
  }
}

  • class를 통해 만든 컴포넌트에서의 state만드는 방법
class Modal2 extends React.Component {
  constructor() {
    super();
    this.state= {
      name: 'kim',
      age: 20
    }
  }
  render() {
    return (
      <div>안녕</div>
    )
  }
}
  • state적용 꼭 가장 최상단 태그 안에 작성을 해야 한다.
class Modal2 extends React.Component {
  constructor() {
    super();
    this.state= {
      name: 'kim',
      age: 20
    }
  }
  render() {
    return (
      <div>
        안녕 {this.state.name}

      </div>
      
    )
  }
}
  • 기존의 state 값을 변경하고자 할 경우 아래와 같이 this.setState({변수: 변경값}) 이렇게 변경해주면 된다.
class Modal2 extends React.Component {
  constructor() {
    super();
    this.state= {
      name: 'kim',
      age: 20
    }
  }
  render() {
    return (
      <div>
        안녕 {this.state.name}
        <button onClick={()=> {
          this.setState({name:'hongmin'})
        }}>버튼</button>
      </div>
      
    )
  }
}
  • 그리고 props를 하고자 할 경우엔
class Modal2 extends React.Component {
  constructor(props) {
    super(props);
    this.state= {
      name: 'kim',
      age: 20
    }
  }
  render() {
    return (
      <div>
        안녕 {this.props}
        <button onClick={()=> {
          this.setState({name:'hongmin'})
        }}>버튼</button>
      </div>
      
    )
  }
}

font 적용

로컬에 다운로드한 폰트를 적용하는 방법

  • react에 assets 폴더를 만든다
  • font라는 폴더를 만들어 정리를 한번 더 해준다
// css 파일을 생성

@font-face {
	font-family: '해당 폰트를 불러올 이름 작성(웬만하면 원래 이름 적어주기)'
	src: url('현재 파일이 있는 경로')
}

// 적용하는 방법
font-family: '해당 폰트 이름'

 


새로운 리엑트 프로젝트 생성

기존의 blog 폴더가 있는 곳에서의 상위 폴더에서

shift + 마우스 우클릭 을 하게 되면 여기서 powerShell 창 열기가 나오게 되고 클릭하면 된다.

이후 PowerShell에서 npx create-react-app {프로젝트 이름} 을 작성하게 되면 프로젝트가 생성된다.

React에서 Bootstrap 사용하기

우선 구글에 react bootstrap을 입력하면 상단에 나오는 홈페이지에서 cdn같은 걸 복사 후 터미널에서 실행 시키고

페이지 하단에 있는 CSS 부분에서 App.js 에 넣을 import 도 함께 넣어주자.

이후 만약 버튼을 하나 넣으려고 할때

아래와 같은 코드만 넣어준다고 해서 되는 것이 아니라 component이기 때문에
 react bootstrap 홈페이지에서 importing Components부분을 참고하여 import또한 같이 해줘야한다.
<Button variant="primary">Primary</Button>{' '}

/여기서 문제점/

리엑트에서 부트스트랩을 사용하려 할 경우 !important 때문인지 해당 태그 안에서 style을 먹이지 않으면 css가 먹지 않는 것 같다.

css파일 내에서 적용을 하려해도 안되는게 무슨 이유일까…?

background-image 속성

url() 주소를 넣어주어 해당 경로를 입력해주고

background-position 속성을 통해 해당 배경 사진의 위치를 변경할 수 있다.

그리고 css 파일 상에서는 위의 방법과 같이 작성해주면 되지만

App.js 즉 html에서 src폴더의 이미지를 넣을 땐

상단에 import {작명} from {이미지 경로} 와 같이 경로를 받아온후

style={{ backgroundImage:’url( ‘+작명+’ )’ }} 과 같이 해줘야한다.

주의할점

꼭 backgroundImage: ‘ url ( ’ + 작명 + ‘ ) ‘ 이렇게 해줘야한다는것

다음으로 하단에 3개의 사진을 넣어주기

궁금했던 부분: Q. 왜 그냥 bootstrap을 사용하지 않고 react bootstrap을 사용할까?

A. 조금 더 용량 절약이 가능하기 때문

html에서 img 파일을 불러올 때 위의 방식대로 하게 되면

너무 번거롭고 불편하기에

public 파일에 넣고 사용을 하기도 한다.

그래서 public 폴더에 있는 이미지를 사용할 경우

그냥 / 하나면 하고 바로 파일 명을 작성하면 사용이 가능하다

단 위의 방법을 사용할 경우 주의할 점

메인 페이지가 아닌 다른 경로에서 사용할 경우 조금 번거로워진다.

ex) <img src={process.env.PUBLIC_URL + ‘/img/logo,png’} />


DB가 아닌 그냥 현재 React에서 데이터를 넣은 후 값을 가져오고자 할 때

App.js 내부에서 하기에는 너무 코드가 길어지니까 다른 파일에서 불러오고자 할 경우!

  1. 먼저 src폴더 내부에 ex) data.js 파일을 생성 후
  2. 해당 파일에서 데이터를 export하고
    • 만약 변수 a를 export 하려고 한다면 let a = 10 export default a
  3. App.js에서 import하면 끝
    •  App.js 파일에서 아래와 같이 코드를 작성하면 끝 import 작명 from './data.js'

여기서 만약에 여러 개를 export 시키려 한다면

// data/js

let a = 10
let b = 100

// 아래와 같이 중괄호로 감싸줘야한다.
export {a,b}

여기서 import를 하는 App.js에서는 아래와 같이 작성해야한다.

// App.js

import {a,b} from './data.js'

단 여러개를 export 했을 경우 import 할 경우 작명을 하나만 export 했을 때처럼 자유롭게 할 수 없고 export 했을 때의 변수 그대로 사용해야 한다.

그리고 함수,모달 또한 export 가능하다!!

과제

상품 목록 컴포넌트화

import {Navbar,Nav} from 'react-bootstrap';

let nav = [
  <Navbar id='navbar' variant="dark">
    <div className="grid subfont">
      <div className=''></div>
      <Nav style={{color:'black',justifyContent: 'center',alignItems: 'center' }} href="#home"><span class="material-symbols-outlined" style={{marginRight:'3px'}}>menu</span>메뉴</Nav>
      <div></div>
      <Nav style={{color:'black',justifyContent: 'center',alignItems: 'center' }}  href="#features"><span class="material-symbols-outlined">search</span>검색</Nav>
      <div></div>
      <Nav className='mainfont' style={{color:'black',justifyContent: 'center',alignItems: 'center', fontSize:'2rem', letterSpacing:'5px'}}  href="#home" >LOUIS VUITTON</Nav>
      <div></div>
      <Nav style={{color:'black',justifyContent: 'center',alignItems: 'center' }}  href="#home" >위시리스트</Nav>
      <div></div>
      <Nav style={{color:'black',justifyContent: 'center',alignItems: 'center' }}  href="#pricing">My lv</Nav>
      <div></div>
      <Nav style={{color:'black',justifyContent: 'center',alignItems: 'center' }}  href="#pricing"><span class="material-symbols-outlined">shopping_cart_checkout</span></Nav>
    </div>
  </Navbar>
]

 

상품명 데이터 바인딩도 잘 해오기

let data = [
  {
    id : 0,
    title : "White and Black",
    content : "Born in France",
    price : 120000
  },

  {
    id : 1,
    title : "Red Knit",
    content : "Born in Seoul",
    price : 110000
  },

  {
    id : 2,
    title : "Grey Yordan",
    content : "Born in the States",
    price : 130000
  }
]

let items = [
  <div style={{marginRight:'4px'}}>
    <img className='items' src="https://images.pexels.com/photos/4602025/pexels-photo-4602025.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" alt="" />
    <p style={{marginTop:'7px'}}>{data[0].title}</p>
    <p>{data[0].content}</p>
  </div>,
  <div style={{marginRight:'4px', marginLeft:'2px'}}>
    <img className='items' src="https://images.pexels.com/photos/4276653/pexels-photo-4276653.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" alt="" />
    <p style={{marginTop:'7px'}}>{data[1].title}</p>
    <p>{data[1].content}</p>
  </div>,
  <div style={{marginLeft:'4px'}}>
    <img className='items' src="https://images.pexels.com/photos/4061385/pexels-photo-4061385.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" alt="" />
    <p style={{marginTop:'7px'}}>{data[2].title}</p>
    <p>{data[2].content}</p>
  </div>
]

 

반복적인 부분은 map 반복문 써보기

  1. array helper method의 경우엔 map을 예시로 들어본다면
  2. array.map() 이렇게 그냥 배열 형태인 곳 어디에든 사용하면 되는거 같다.
<div className='contain'>
  <div></div>
  {item.map((item) => {
    return item
  })}
  <div></div>
</div>

위와 같이 data를 export 하고 import 하여 App.js에서 사용하다 보니 엄청 깔끔 해졌다.

import './App.css';
import React ,{useState} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import {nav, items} from './data.js'

function App() {

  let [item, setItems] = useState(items)

  return (
    <div className="App">
      {/* navbar */}
      {nav}
      
      <div className='main-bg'>
        {/* 배경사진 */}
      </div>

      <div className='contain'>
        <div></div>
        {item.map((item) => {
          return item
        })}
        <div></div>
      </div>
    </div>
  );
}

export default App;

props 다시 한번 정리

// component에 data를 props 시키려고 할 때
<component data={data}> </compoenent>

강사님이 의도한 거??

-------------------------------------------------------------------------------------
// 강사님이 의도한 바는 조금 다른거 같다
// App.js 내에서 아래의 코드를 작성해서 컴포넌트를 만드는 것으로 의도 하셨다.

function Card() {
  return (
    <div style={{marginRight:'4px'}}>
      <img className='items' src="https://images.pexels.com/photos/4602025/pexels-photo-4602025.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" alt="" />
      <p style={{marginTop:'7px'}}>{data[0].title}</p>
      <p>{data[0].content}</p>
    </div>
  )
}

// 이후 현재 Card 라는 컴포넌트 내부엔 datas 라는 데이터가 없기 때문에 
// App컴포넌트에서 자식 컴포넌트인 Card 컴포넌트에게 props를 해줘야해서
// App컴포넌트 태그 안에 Card 컴포넌트 생성 후 props 해주기

<div>
  <Card data={data}></Card>
</div>

// 다만 여러개의 컴포넌트를 가져올 예정이기에 위의 코드로 3개를 만든다면 아래처럼 될텐데
<div>
  <Card data={data}></Card>
	<Card data={data}></Card>
	<Card data={data}></Card>
</div>


// 이렇게 되면 같은 data[0] 의 데이터만 조회하게 되기 때문에 
<div>
  <Card data={data[0]}></Card>
	<Card data={data[1]}></Card>
	<Card data={data[2]}></Card>
</div>

function Card() {
  return (
    <div style={{marginRight:'4px'}}>
      <img className='items' src="https://images.pexels.com/photos/4602025/pexels-photo-4602025.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" alt="" />
      <p style={{marginTop:'7px'}}>{data.title}</p>
      <p>{data.content}</p>
    </div>
  )
}

// 이렇게 작성해주면 각자 다른 data의 title과 content가 조회된다.
// 다만 이미지가 서로 다르게 되는데 이때 만약 서로 유사한 이미지 주소 혹은 파일명의 경우엔
// props로 해당 데이터에 맞는 숫자를 함께 props 해주고 데이터를 변경해주면 된다.
// 예를 들어 이미지 주소가 아래와 같을 경우
// https://codingapple1.github.io/shop/shoes1.jpg
// https://codingapple1.github.io/shop/shoes2.jpg
// https://codingapple1.github.io/shop/shoes3.jpg
// props 할때 함께 숫자도 넘겨주기

<div>
  <Card data={data[0]} i={1}></Card>
	<Card data={data[1]} i={2}></Card>
	<Card data={data[2]} i={3}></Card>
</div>

// 이후 주소 또한 함께 변경하자면
// 먼저 src속성을 자바스크립트 입력란으로 변경해주고 {} => 문자열로 변경 '' 이후
// props 할 부분과 문자열인 부분을 나누어 + + 처리 해주어 처리 (문자 중간 변수 넣기 문법)
function Card() {
  return (
    <div style={{marginRight:'4px'}}>
      <img className='items' 
			src={'https://codingapple1.github.io/shop/shoes' + props.i + '.jpg'} alt="" />
      <p style={{marginTop:'7px'}}>{data.title}</p>
      <p>{data.content}</p>
    </div>
  )
}

// 그리고 map()함수를 이용해서 컴포넌트를 보이도록 하는 방법
// 위와 같이 작성하게 되면 계속 변화하는 변수의 경우엔 
// 시시각각으로 변경을 해야하기에 손이 많이 간다 그래서 반복문을 통해 다 보여주도록 하기

// map()함수를 작성할 때는 {}중괄호 안에다가 작성하기
// 우선 사진의 경우엔 지금은 조금 맞지는 않지만 이렇게도 가능하다는 것!
{
  data.map((data, idx)=> {
    return (
      <Card data={data} i={4602025-idx}></Card>
    )
  })
}​