3. 컴포넌트, jsx ~ 4. 첫 컴포넌트 만들기
jsx = javascript xml
내부 css style 은 객체로 작성해야 함 => <h1 style={{color: 'red'}} 그리고 숫자는 그냥 기입해도 되지만 문자는 '' 안에 문자열로 'red'
return 밖에 변수 지정해주고 내부에 {변수이름} 입력해주면 해당 값이 출력되어 나타남
하지만 객체는 안됨
만약 객체로 지정해주고 {변수이름.키} 이렇게 입력해주면 해당 값이 출력되어 나타남
컴포넌트 리턴문 안에는 꼭 하나의 태그만 가능한데 <div> </div> 가 아닌 <> </> 빈 태그도 가능
rsc 도 가능하지만 내보냄과 동시에 만들어 줄 수 있음
export default function hello ( ) { return ( ) }
5. css 작성법
index.css 는 전체 프로젝트에 영향을 미치는 스타일로 되어 있고 app.css 는 웹 컴포넌트에 한정된 내용들이 있음
만약 app.css 에 .box 라는 네임에 스타일을 지정하고 컴포넌트.css 에 .box 라는 네임에 스타일을 지정하면 전페이지에 영향을 미치게 됨
module.css
보통은 컴포넌트 이름으로 파일 이름을 지정 컴포넌트이름.module.css
import 'css 파일 경로'; 이렇게 해주는 게 아니라
import styles from 'css 파일 경로'; 이렇게 해줌
그러면 styles 라는 객체가 생성된 것이고
css 를 넣고 싶은 해당 컴포넌트 안의 요소에 가서 classname = {styles.box} 해주면 적용 가능
그리고 app.js 도 따로 module.css 를 만들고 import 해서 classname = {styles.box} 해주면 같은 이름을 적었더라도 서로 중복될 걱정이 없음
6. 리액트에서 이벤트 처리하기
두가지 방법이 있음
미리 함수를 만들어 놓고 전달해주기
- 리턴문 밖에 function showname () { console.log('mike'); }
- 리턴문 안에 <button onclick={ showname }> </button>
=> console 에 mike 라는 게 클릭할 때마다 뜸
onclick 내부에 직접 함수를 작성해주기
- 리턴문 안에 onclick = { ( ) => { console.log(30); } }
=> console 에 30 이 클릭할 때마다 뜸
이 두가지 방법은 상황에 따라 맞게 사용하면 되고
장점은 매개변수를 전달하기 편하다 라는 것
!&! 만약 매개변수를 전달해주고 싶다면
- 리턴문 밖에 function showage(age) { console.log(age); }
- 리턴문 안에 onclick = { ( ) => { console.log(10); } }
=> 10 을 매개변수로 받아 console 에 10 이 뜸
미리 함수를 만들어 놓고 전달하는 방법으로 input 태그 이벤트 처리로 값을 받아오기
- 리턴문 밖에 showtext(e) { console.log(e.target.value); }
- 리턴문 안에 <input type='text' onchange={showtext} />
=> showtext 는 (e) 이벤트 객체를 받고, target 은 input 태그, value 는 input 의 밸류로 작성한 값이 된다. 따라서 텍스트박스 안에 작성한 값이 onchange 될 때마다 콘솔에 그대로 찍힘
!&! onclick 내부에 직접 함수를 작성해주는 방법으로 하고 싶다면
- 리턴문 안에 <input type='text' onchange={ (e) => { console.log(e.target.value) } } />
=> 동일하게 적용 가능
!&! showtext 함수를 살리고 싶다면
- 리턴문 밖에 showtext(txt) { console.log(txt); }
- 리턴문 밖에 <input type='text' onchange={ e => { const txt=e.target.value; showtext(txt); } } />
=> txt 는 e.target.value 라고 지정, showtext 를 호출하는 방법으로 동일하게 적용 가능
7. usestate
state 는 컴포넌트가 가지고 있는 속성값으로 속성값이 변하면 리액트는 자동으로 ui 를 업데이트 시켜준다. 화면을 다시 그려주는 작업은 신경 쓰지 않아도 되어 편리함.
js 에서 적용 시켜준 것처럼 한다면
- 리턴문 밖에 function changename() { name = name === 'mike' ? 'jane' : 'mike'; console.log(name); }
- 리턴문 안에
<h2>{name}</h2>
<button onclick={changename}> change </button>
=> 해주면 이름은 바뀌지만 h2 에 업데이트가 되지 않음
그래서 js로 추가해준다면
- 리턴문 밖에 console.log(name); 다음에 document.getelementbyid('name').innerText = name;
- 리턴문 안에 <h2 id='name'>{name}</h2>
=> id 가 name 인 안의 텍스트를 name 으로 바꿔주고 이렇게 업데이트 작업이 작동됨
맨 상단에서 지정해준 let name = 'mike'; 는 단순히 변수일 뿐 컴포넌트가 관리하고 있는 상대값이 아님 그래서 바뀌어도 리액트는 그걸 인지하지 못하고 ui 를 업데이트 해주지 않음
그래서 usestate 를 씀
- const [name, setname] = usestate('mike');
- function changename() { const newname = name === 'mike' ? 'jane' : 'mike'; setname(newname);
!OR!
- function changename() { setname( name === 'mike' ? 'jane' : 'mike'); }
!OR!
- <button onclick={ () => { setname( name === 'mike' ? 'jane' : 'mike'); } }> change </button>
이 컴포넌트를 여러 개 만들더라도 각각 관리됨
8. props
props 값을 컴포넌트에서 억지로 변경하려고 하면 에러가 남
불러들인 컴포넌트에 <Hello name={30} /> 넣어주고 해당 컴포넌트 파일에 가서 function Hello (props) { } 해주고 리턴문 안에 넣어주고 싶은 곳에 {props.name} 하면 해당 값이 어플에 출력됨
또는 구조분해할당으로 function Hello ( {name} ) { } 하고 리턴문 안에 넣어주고 싶은 곳에 {name} 하면 해당 값이 출력됨
그리고 한 컴포넌트가 가지고 있는 state 를 props 로 넘길 수도 있음 하위 컴포넌트에 가서 값을 넣어주고 바로 위에 있는 것처럼 넣어주고 다시 상위로 돌아와 하위 컴포넌트를 불러준 뒤 그 곳에 <Hello name={30} /> 이런 식으로 값을 전달해주면 그 상위 컴포넌트에서는 state 지만 하위 컴포넌트 입장에서는 props 가 되는 것, 이렇게 하면 하위 컴포넌트에도 state 값이 적용되어 해당되는 값들도 같이 바뀜
따라서 일일이 값을 바꿔주지 않고 간단하게 props 를 적용해 바꿔줄 수 있음
9. 더미 데이터 구현, map 반복문
더미 데이터를 가져오고 그 더미 데이터만큼 컨텐츠를 만들어주고 싶다면 map 반복문 사용
map 반복문
- map 은 배열을 받아서 또 다른 배열을 반환해주는 것, 이때 반환되는 배열의 요소는 jsx(중괄호) 로 작성해주면 됨
- return ( <ul classname = 'list_day'> { dummy.days.map( day => ( <li> day {day.day} </li> ) ) } </ul> )
- 이때 키가 필요하다는 에러가 뜸 키는 반복되는 요소에 고유한 값을 넣어줘야 함
- <li key = { day.id }> day {day.day} </li>
- 이제 각 day 에 해당하는 데이터들을 표의 형태로 하위 컴포넌트로 만들기
- 특정 날짜를 클릭해서 들어갔을 때 단어들이 나오게
- return ( <table> <tbody> { dummy.words.map(word => { <tr key={word.id}> <td> { word.eng } </td> <td> { word.eng } </td> </tr> ) ) } </tbody> </table> )
- 이때 문제 1 : 모든 단어가 다 나와버림
- 그래서 여기서 filter 를 써서 day 가 1일 때로 고정 : const day = 1; const wordlist = dummy.words.filter( word => (word.day === day ) ) console.log(wordlist)
=> dummy 데이터 안의 word 들을 filter 로 걸러서 단어의 날짜가 day(1) 일 때만 출력
- 그리고 { wordlist.words.map(word => ~ 으로 수정해주면 day 1 의 단어들만 출력됨
- 여기서 const day = 1; 에 숫자를 2로 바꿔주면 day 2 단어가 출력되고 3이면 day 3 이 출력됨
- 문제 2 : 원래 특정 날짜를 클릭해서 들어갔을 때 각 day 의 단어들이 나오게 해야함 그건 라우팅으로
9-1. 번외 : react 에서 key 가 중요한 이유? 그냥 index 를 쓰면 안되는 이유?
- key 값에 index 를 넣어주면 당장은 에러가 뜨지 않음, 순서가 고정되어 있는 경우 큰 문제가 되지 않음, 순서가 일정하지 않을 때는 각 요소의 고유한 값으로 키를 사용해야 순서를 배정받을 수 있음
- 왜 이런 것일까? 그 이유는 react 는 형태가 바뀌면 이전 상태의 트리와 이후 상태의 트리를 비교함, 이때 키가 같다면 동일한 요소로 판단하고 업데이트를 해줌
- 반면 index 를 넣어주면 변경될 때 단순히 앞에 하나를 추가한 것 뿐이라도 모든 요소가 같이 변경됨, 왜냐 키도 함께 달라지기 때문에 react 는 단순히 앞에 하나 추가하고 뒤로 밀면된다는 걸 모름, 키와 값이 모두 다르니까 모두 바뀌었다고 생각하는 것, 그래서 제일 위에 추가했는데도 불구하고 요소에서는 가장 아래쪽에 추가됨,
- 고유한 값인 id 로 넣어주면 요소에서 가장 위쪽에 추가되고 나머지는 변화가 없는 것을 볼 수 있음, 키를 통해서 이전에 요소와 변경된 이후의 요소가 동일한 요소라는 걸 알고 있기 때문에 나머지 요소들은 변화가 없다는 걸 react 에 알려주는 것
- 이렇게하면 react 트리 업데이트 시 효율성이 상당히 증가하게 됨
- 정리해보면 배열의 순서나 내용이 변경되지 않는다면 아무 key 나 사용해도 됨(다르기만 하다면. 동일한 키는 에러. 따라서 index 를 사용해도 무방.) 하지만 순서나 내용이 변화되는 상황이라면 요소를 특정할 수 있는 고유한 key를 사용함 그렇지 않으면 매번 랜더가 발생하고 효율이 크게 떨어짐
10. 라우터 구현 react-router-dom
npm install react-router-dom 으로 먼저 라우터 설치
app.js 로 와서 import { browserrouter, route, switch } from 'react-router-dom';
return 바로 다음에 <browserrouter> 로 감싸주고 <header /> 는 모든 페이지에 나와야 하니 그 다음 부분을 <switch> 로 감싸 줌
그러면 <switch> 내부는 url 에 따라 각각 다른 페이지들을 보여줄 것
<switch> 의 외부는 모든 페이지들에 공통으로 노출
만약 <footer /> 가 필요하면 <switch> 밖에 작성해주면 됨
그 다음 <switch> 안에 <route> 를 사용해줄 것임
<route path='/'> path 는 주소를 적어주면 되는데 / 는 첫화면을 의미
첫화면은 <route path='/'> <daylist /> </route> 를 보여주고
다음은 <route path='/day'> <day /> </route> 를 보여줄 것임
근데 / 로 들어가도 daylist 가 보이고 /day 로 들어가도 <daylist /> 만 보임
<switch> 로 route 들을 감싸놓으면 일치하는 첫번째 결과를 보여줌
그래서 /day 로 들어가도 / 가 포함되어 있기 때문에 <daylist /> 을 보여줌
그래서 이런 경우는 <route exact path='/'> 라고 해줘야 함
그러면 정확하게 주소에 / 가 일치할 때만 <daylist /> 이 부분이 랜더링 됨
다시 /day 로 하면 day 에 해당하는 컨텐츠만 나옴
링크 연결해주기 :
html 에서는 a 태그로 링크를 연결해주지만 react 에서는 link 태그를 이용
<link to='/day'> day {day.day} </link>
import { link } from 'react-router-dom'; 해줘야 함
그리고 요소 검사해보면 a 태그로 랜더링된 걸 확인해볼 수 있음
클릭도 해보면 /day 로 잘 이동함
day 1 을 누르면 1 에 해당하는 단어가 나오도록 :
먼저 daylist 컴포넌트로 가서 <link to={ `/day/${day.day}` }> day {day.day} </link> 로 변경해서 day 1 버튼을 누르면 주소에 /day/1 로 뜨도록 설정
그런데 동일한 날짜의 단어만 나오고 있음
url 에 있는 값을 day 로 사용하려면?
이렇게 다이내믹한 url 을 처리할 때는 : 으로 처리하면 됨
app.js 로 가서 <route path='/day/:day'> 로 수정해주면 됨
day 1 로 들어왔을 때 :day 라는 변수로 1이라는 값을 얻을 수 있음
만약 :id 면 id 의 값으로 1이라는 값을 얻을 수 있는 것임
day 컴포넌트로 가서 이렇게 url 에 포함된 값을 얻을 때는 useparams 훅을 사용
import { useparams } from 'react-router-dom'; 먼저 해주고
const a = useparams(); console.log(a); 를 해보면 console 에 day : '2' 라는 값이 나오는 걸 볼 수 있음 경로를 /day/80 으로 바꿔보면 console 에 day : '80' 이라고 나옴 문자열이나 이상한 값을 넣어도 그대로 출력이 됨
그래서 다시 const day = 3; 으로 정해놨던 변수는 지우고 const day = useparams().day; 라고 작성하거나 const { day } = useparams(); 라고 해주면 특정날짜로 연결되어 이동하게 됨
하지만 단어들이 나오지 않음
방금 console 에도 뜨듯이 들어오는 값은 숫자가 아닌 문자, 하지만 json 의 day 에는 숫자가 들어오고 있음
따라서 문자와 숫자를 비교하고 있으니 아무것도 들어오지 않는 것
그래서 word => (word.day === number(day) ) 로 수정해주면 문자 값이 들어와도 숫자로
변경시켜주고 있음
여기서 일치하는 라우터가 없는 주소를 치면 / 로 정해진 것 외에는 아무것도 나오지 않음
주소를 잘못 친 경우 잘못된 경로라고 표시해주고 싶으면
다른 컴포넌트 파일을 만들어주고 그 안에 잘못된 경로임을 알려주는 문구과 홈으로 돌아가는 링크도 추가해줌
app.js 로 돌아가 route 들 맨 마지막에 path 를 적지 않고 <route> <위에서 만든 컴포넌트 파일 /> </route> 해주면
앞에 있는 route 들이 모두 만족하지 않으면 <위에서 만든 컴포넌트 파일 /> 이 페이지가 보이게 됨
만약 위에 적어놓으면 모든 페이지가 다 <위에서 만든 컴포넌트 파일 /> 이쪽으로 가니까 주의 해야함
11. json-server, rest api
영어 뜻 보기, 삭제 버튼, 체크박스
day 컴포넌트로 가서 체크박스 하나 만들고 <td> <input type='checkbox' /> </td>
하단에 뜻 보기, 삭제 버튼 만들고 <td> <button> 뜻 보기 </button> <button> 삭제 </button> </td>
state 때 말했듯이 컴포넌트 별로 state 를 가지고 있는데 버튼 눌렀을 때 뜻이 나타났다 없어졌다 하는 건 한 단어에만 해당하는 것이기 때문에 따로 컴포넌트를 제작해주는 것이 좋음
word 컴포넌트를 따로 만들고 단어를 의미하는 부분을 때서 리턴문 안에 넣기
그리고 원래 있었던 자리에는 word 컴포넌트를 불러서 반복
word, key 값 잊지 말기, word 컴포넌트에는 키값 지워주기
버튼을 구현하기 위해서는 state 가 필요함
const [isshow, setisshow] = uststate(flase);
<td> {isshow && word.kor} </td>
=> 초기값을 flase 로 설정해 isshow 일 때만 뜻이 보이도록
function toggleshow( ) { setisshow(!isshow); }
<button onclick={toggleshow}> 뜻 { isshow ? '숨기기' : '보기'} ~
=> 버튼을 클릭하면 뜻이 보이도록 isshow 가 true 일 때 버튼이 눌리고 보이도록
=> 그리고 isshow 가 true 면 숨기기, isshow 가 false 면 보기 로 누를 때마다 바뀌도록
체크 하면 외운 것으로 dim 처리:
dummy 데이터에 isdone : false 로 되어 있는데
isdone false 면 아직 못 외운거니까 체크를 해제, true 면 외웠다는 뜻으로 체킹되도록
- <input> 안에 checked = {word.isdone} 해주고
- 미리 맞는 css 준비해준 뒤 <tr> 에 classname = {word.isdone ? 'off' : ''}
=> isdone 이 ture 면 외웠으니까 off style 적용, 아니면 빈 문자열
근데 이렇게 하면 한번 체킹되고 다시 클릭하면 바뀌지 않음
요소 검사의 에러를 보면 어떤 액션을 취해도 checked 가 항상 word.isdone 즉, false 로 고정이 되어 있기 때문에 읽기전용이랑 다를 바가 없다고 뜸
그래서 onchange 로 반응형으로 적용
- const [isdone, setisdone] = usestate(word.isdone);
- function toggledone() { setisdone( !isdone ); } 로직은 똑같고 반대값으로 바꿔줌
- <input> 안에 onchange={toggledone} 을 넣어줌
- state 를 사용할 때 isdone 으로 했으니, word 의 isdone 은 바꿔준 적이 없으니 tr classname 에 word.isdone 이 아닌 isdone 으로 변경, checked 에도 isdone 으로 변경
=> usestate 를 이용해 변화가 일어날 때마다 dim 과 checked 와 onchange 에 isdone 이 true 로 변화가 있을 때 input 이 계속 변화하도록 적용
사용자 액션에 따라서 데이터를 읽고 쓰고 업데이트하고 삭제하는 방법 :
DB 를 구축하고 api 를 만들어야 됨 json 서버를 이용해서 rest full api? 를 만들기
api 만드는 게 제일 귀찮음 백엔드 찾아보고 db 설치하려면 힘드니까 json 서버를 씀
json 서버는 빠르고 쉽게 rest api 를 구축해줌 공공 목적이나 프로토타입, 작은 프로젝트에
쓸 수 있음
terminal 에서 npm install -g json-server 설치
json-server --watch ./src/db/data.json --port 3001
=> 중간의 경로는 json 파일의 경로, --port 는 해당 파일 주소 localhost:3001 의 숫자
=> 터미널에 나온 주소로 들어가면 해당 주소에 들어가 있는 json 파일 데이터들이 나와 있음
rest api 란?
url 주소와 메서드로 crud 를 요청하는 것
Create : post 메서드
Read : get 메서드
Update : put 메서드
Delete : delete 메서드
- 터미널에 나온 주소로 들어가서 /2 를 주소 끝에 붙여주고 그 데이터들 중 해당하는 데이터가 나옴 -> get 메서드로 읽은 것
- delete 메서드로 호출하면 2번이 지워짐
- 다른 내용들을 작성해서 put 으로 호출하면 수정됨
- 다시 돌아가서 이 주소로 post 를 날리면 새로운 단어가 하나 생성됨
- 물론 적절한 내용을 담아 보내줘야 함 각각 맞는 경로들을 담아서 보내주면 새로운 단어들을 생성할 수 있음
- 만약 words 다음에 물음표를 하고 day=1 을 해주면 해당하는 날의 단어들만 모두 갖고 올 수 있음
- url 와 메서드만 보더라도 어떤 요청인지 유추 가능한 장점이 있음
- 각 메서드를 이용해 만들고 가져오고 수정하고 지워볼 것임, 물론 그 모든 것들은 json 파일에 다 저장될 것임
12. useeffect, fetch 로 api 호출
api 를 가져왔으니 더미로 작업했던 부분들을 하나씩 바꿔줌
- const [days, setdays] = usestate([])
=> 처음에는 빈배열로 만들고 api 에서 list 로 가져와서 바꿔주는 방식으로 하겠음 그럼 데이터가 바뀌면 자동으로 랜더링 될 것임
- return ( <ul classname = 'list_day'> { dummy.days.map( day => ( <li> day {day.day} </li> ) ) } </ul> )
=> dummy.days.map 부분에 dummy 를 없앰
api 를 호출하기 전 useeffect 를 알아볼 것임
- 어떤 상태값이 바뀌었을 때 동작하는 함수를 작성할 수 있음
- useeffect( ( ) => { console.log('change'); } )
- 첫번째 매개변수로 함수를 넣음, 이 함수가 호출된 타이밍은 랜더링 결과가 실제 돔에 반영된 직후임 그러니까 div 등 요소가 그려지고 나서 호출됨, 그리고 컴포넌트가 사라지기 직전에도 마지막으로 호출됨
클릭할 때마다 state 가 변경시키기:
- const [count, setcount] = usestate(0);
=> count 라는 state 값을 만들고 초기값은 0 으로 부여
- onclick 함수를 만들어서 count 가 1씩 증가되도록
=> function onclick () { setcount(count + 1 }, <button onclick={onclick}>{count}</button>
이렇게 useeffect 는 상태값이 변경되어서 상태값이 다시 랜더링된 다음에 호출됨
랜더링이 끝나고 어떤 작업을 해주고 싶다면, api 호출 작업, useeffect 첫번째 함수로 전달해주면 됨
그런데 이렇게만 해주면 매번 변경이 이러날 때마다 불필요하게 함수가 호출될 수 있음
원하지 않는 상황에서도 호출될 수 있다는 뜻
다른 state 를 작성했을 때 console.log('change'); 도 콘솔에 나타나게 됨
만약 무거운 작업을 하는 경우라면 문제가 됨 불필요한 실행을 하게 되는 것이기 때문에
그래서 이럴 때는 두번째 매개변수로 배열을 전달함
useeffect( ( ) => { console.log('change'); }, [ count ] )
=> count 가 변경되었을 때만 실행됨 , 이것을 의존성 배열 이라고 함
api 의 경우 랜더링되고 최초에 한번만 호출해주면 됨
그럴 때는 빈배열을 입력하면 됨
api 비동기 통신을 위해서 fetch 를 이용할 것임
useeffect( ( ) => { fetch(~localhost:3001/days) }, [ ] )
=> fetch(api 경로) 이렇게 하면 promise 가 반환됨
fetch(api 경로) .then ( res => { return res.json() } )
=> 여기서 api 경로가 json 데이터가 아니라면 .json 이렇게 메서드 사용해줌 이렇게 하면 json 으로 변환되고 promise 를 반환
fetch(api 경로) .then ( res => { return res.json() } ) .then ( data => { setdays( data ); } )
=> json 으로 변환한 것을 setdays 에 data 라는 매개변수로 넣어 전달
이제 dummy 로 사용되던 데이터들을 다 교체해줄 것임
day 컴포넌트로 가서
useeffect( ( ) => { fetch( `~localhost:3001/words?day=${day}` ) .then ( res => { return res.json() } ) .then ( data => { setdays( data ); } ) }, [ ] )
위에서 useparams 훅으로 주소창에 문자열이 들어옴, <route path='/day/:day'> 로 해줬기 때문에 따라서 ${day} 해주면 day 에 해당하는 1,2,3 이 ${day} 여기에도 들어옴
그런데 이렇게 하면 [ ] 이쪽에 day 가 의존성 배열이 없다 라는 에러가 뜸
그 이유는 주소를 ${day} 같이 백틱으로 특정값을 사용하면 [ ] 입력하라는 뜻
의존성 배열이 비어있기 때문에 day 가 변경되어도 새로운 정보를 가져올 수 없게 됨
그래서 [day] 해주면 day 값이 최신 값으로 보장 받을 수 있게 됨
그런데 daylist 와 day 컴포넌트에서 쓴 useeffect 로 가져온 데이터가 주소 빼고는 똑같이 작성됨
그래서 이렇게 동일한 로직은 사용자가 직접 훅을 만들어서 사용할 수 있음 custom 훅이라고 함
13. custom hooks
- 다른 폴더를 하나 만들고, usefetch 라고 원하는 이름의 컴포넌트를 만들어준 뒤
- const [ words, setwords ] = usestate([]);
useeffect( ( ) => { fetch( `~localhost:3001/words?day=${day}` ) .then ( res => { return res.json() } ) .then ( data => { setdays( data ); } ) }, [ ] )
- day 컴포넌트의 위 코드를 그대로 가져와 usefetch () { } 의 중괄호 안에 넣어줌
- 훅들 import 해주고
- day 와 daylist 에 주소만 다르니까 usefetch ( url ) { } 이라고 매개변수를 지정해준 뒤
- fetch( `~localhost:3001/words?day=${day}` ) -> fetch(url) 해줌
=> 주소를 받아서 fetch 를 해준 것
- 의존성 배열에도 달라지는 주소값을 업데이트 해줄거니까 url 넣어주고
- const [ words, setwords ] = usestate([]); 이 부분을 다른 컴포넌트에서도 사용할 수 있게 data, setdata 로 변경 / setdays( data ) 도 setdata 로 같이 변경
- 결국 우리가 필요한 건 data 니까 return data; 를 해줌
이렇게 하면 나만의 커스텀 훅이 완성!
위에 것 설명 : data 라는 상태값이 있고 url 즉, api 를 넘겨받아서 fetch 하고 응답하는 데이터를 setdata 해주는 것 그리고 마지막으로 그 최종 data 를 넘겨줌
그러면 daylist 컴포넌트에 가서
usefetch( '~localhost:3001/days' ) 해주면 이 컴포넌트가 랜더링되고
usefetch 컴포넌트에 해줬던 useeffect 가 실행이 되는 것
그리고 usefetch ( url ) 은 '~localhost:3001/days' 이 값을 넘겨주는 것
거기에 const days = usefetch( '~localhost:3001/days' ); 해주면 days 가 daylists 에서 days.map 으로 반복해서 넣어주고 있었기 때문에 각 데이터가 알아서 들어감
따라서 api data 로 받아오는 부분은 const days = usefetch( '~localhost:3001/days' ); 이 한줄로 처리할 수 있음
day 컴포넌트도 마찬가지로
const words = usefetch( `~localhost:3001/words?day=${day}` );
=> 주소를 전달해주기만 하면 됨
이렇게 비슷한 작업들을 하나의 커스텀 훅으로 만들어서 여기저기에 쓸 수 있음
반복x 코드도 간결해짐
수정해야 될 경우, 만들어뒀던 usefetch 컴포넌트로 가서 수정만 해주면 됨
14. put(수정), delete(삭제)
put 메서드로 update 해주기:
- 다 외웠다는 뜻으로 체크박스를 체크해주면 그 단어를 dim 처리 하도록 업데이트 해줘야 함
- 지금은 체크하고 새로고침해주면 다시 원 상태로 돌아옴
- word 컴포넌트에 toggledone 함수에 setisdone(!isdone) 해준 것을 지우고 그 안에 작성해줄 것임
- 사용법은 get 할 때와 비슷
- fetch( `~localhost:3001/words/${word.id}` ) => 단어의 id 로 넣어줌
- fetch( `~localhost:3001/words/${word.id}` ), { } -> 그리고 객체 안에 옵션을 넣어줄 것임
- { method : 'PUT', headers : { 'Content-Type' : 'application/json' }, }
=> Content-Type(https://jw910911.tistory.com/117) 은 보내는 리소스의 타입을 의미함 api 연동시에 보내는 자원을 명시하기 위해 보통 사용함 평범한 문자열부터 html, 이미지 등 여러가지가 있음 우리는 json 형태로 보낼 것이기 때문에 json 이라고 한 것임
- headers 다음에, body 를 입력해줄 것임 단순히 정보를 가져오는 get 과는 달리, put 은 수정해줘야 하기 때문에 수정을 위한 정보들을 실어서 보내줘야 함 그것을 body 에 입력
- body : { ...word, isdone : !isdone } => ...word 이전 데이터에 isdone 을 바꿔서 입력해주면 됨
- 이걸 json 의 문자열로 변환하기 위해서 body : JSON.stringify( ) 로 감싸줌
- .then( res => { if(res.ok) { setisdone(!isdone) } => then 하고 응답을 받아서 만약 응답이 ok 면 스테이트를 바꿔주도록
이렇게 하면 반영이 됨
단어 삭제 버튼을 누르면 삭제를 물어보고 확인을 누르면 삭제하는 것:
- word 컴포넌트에서 del 이라는 함수를 만들고 삭제 버튼에 onclick 으로 넣어줌
- 함수 안에 if( window.confirm( '삭제하시겠습니까? ) ) { fetch ( `~localhost:3001/words/${word.id}` ), { method : 'DELETE' }
=> 만약 창에 '삭제~' 가 뜨면 url 로 주소를 넘겨주고, 메서드 옵션은 delete 로
- 이렇게만 하면 삭제가 바로 안되고 새로고침을 해야 삭제됨 삭제된 이후에 이 단어리스트를 다시 그려주지 않았기 때문임
- 그래서 삭제 요청을 하고 ok 응답을 받으면 컴포넌트를 다시 랜더링 해주도록 설정
- 이때 null 을 리턴해주면 아무것도 표현하지 않음
- const [word, setword] = usestate(w); 해주고 이름이 겹치면 안되니까 function Word( {word: w} ) 로 수정 / word: w -> 새로운 이름을 할당해준 것 prop 로 넘어온 word 를 w 라는 변수명으로 사용하겠다 라는 뜻
- .then( res => { if ( res.ok ) { setword ( { id: 0 } ); } } ); => 삭제를 하면 word 의 id 를 0 으로 바꿔줌
- 그리고 함수 밖에 if ( word.id === 0 ) { return null; } => word 의 아이디가 0 이면 리턴으로 null 해서 아무것도 표현하지 않게 함
- 새로고침해도 안 보이고 json 파일에도 확인해보면 완전히 삭제된 것을 볼 수 있음
이제 다음에는 post 로 단어와 day 추가해줄 것
15. post(생성), usehistory
단어 추가를 위해 createword 컴포넌트 생성,
app.js 에 route 로 컴포넌트 연결해주고,
header 에도 단어 추가 버튼에 link to 로 route 주소 연결,
단어 등록 폼을 하나 만듦 - label eng input computer, label kor input 컴퓨터, day 추가 select 와 option
day 추가 하는 부분 에는 하드 코딩으로 넣었는데 실제 daylist 를 받아서 넣어주면 됨
저번에 만들어 둔 데이터 custom 훅이 있으니까 그걸 활용
const days = useFetch( '~localhost:3001/days' );
데이터 불러주고 map 을 이용해 넣어주면 됨
{ days.map ( day => { <option key = {day.id} value={day.day}> {day.day} </option> } ) }
그러면 1,2,3 셀렉트 박스가 주르륵 나옴
그리고 저장 버튼을 누르면 새로고침이 되는데 이건 form 태그로 감싸져 있어서 그럼
function onSubmit (e) { e.preventDefault( ); } 함수를 만들어주고
<form onSubmit={onSubmit}> 이라고 수정해주면 버튼을 눌러도 새로고침 되지 않음
preventDefault 로 기본 기능을 막아준 것
저장 버튼을 눌렀을 때 단어와 뜻에 대한 정보를 찍어보기 :
input 창에 적힌 값들을 얻기 위해 useRef 라는 훅을 사용
useRef 라는 훅은 돔에 접근하게 해줌 예를 들어 스크롤 위치를 확인하거나 포커스를 줄 때 사용할 수 있음
const engRef = useRef (null);
const korRef = useRef (null);
const dayRef = useRef (null);
=> 각 초기값이 null 인 세개의 ref 객체를 만듦
각 input, select 에 ref 를 연결 -> ref={ engRef } / ref={ korRef } / ref={ dayRef }
이렇게 연결해주면 돔 요소가 생성된 후 접근할 수 있음
이 저장버튼을 클릭하는 숫자는 당연히 랜더링 결과가 돔에 반영된 이후 임
그래서 console.log( engRef.current.value ); console.log( korRef.current.value ); console.log( dayRef.current.value ); 이렇게 찍어주고 아무 거나 적은 뒤 저장을 누르고 콘솔을 보면 그 값이 제대로 나오는 걸 볼 수 있음
이렇게 current 속성을 이용하면 해당 요소에 접근할 수 있고 value 는 input 에 입력한 값을 얻을 수 있음
이제 post 를 이용해서 단어 생성 :
필요한 정보는 day, eng(영어), kor(뜻), isdone 임
day, eng, kor 는 이 form 으로 입력을 받고 isdone 은 초기값 false 로 고정해서 저장
word 컴포넌트에서 put 했던 코드를 가져와서 사용할 것
fetch 에 들어가는 주소는 words/ 까지만 해주고,
메서드는 POST,
body 는 중괄호 안을 day, eng, kor, isdone : false 로 수정
이 값들은 current.value 했던 값들을 찍어주면 됨
day : dayRef.current.value, eng : engRef.current.value, kor : korRef.current.value
그리고 if ( res.ok ) 쪽으로 가서 생성되면 alert('생성이 완료되었습니다') 을 하나 띄워줄 것임
이제 생성이 될 때마다 직접 가보는 방법 말고 생성이 완료되면 day 컴포넌트로 보내주도록 :
console.log( engRef.current.value ); console.log( korRef.current.value ); console.log( dayRef.current.value ); -> 이 부분은 지워주고
useHistory 를 사용할 것임 useHistory 는 react-router 에서 지원하는 기능
history 객체는 페이지를 이동한 내역을 정보로 가지고 있는 객체
alert 밑에 history.push( `/day/${ dayRef.current.value }` )
useHistory 로 생성한 history 에 주소를 push 해주면 해당 페이지로 이동함
link to 처럼 a 태그를 사용하지 않고 페이지를 전환시킬 때 유용하게 사용할 수 있음
그니까 history.push 와 이벤트를 이용하여 저장 버튼을 클릭하면 저장할 때 선택한 day 의 페이지로 이동하는 주소의 path로 이동
날짜를 생성하는 작업 :
createday 라는 컴포넌트를 만들고 link to 로 연결해주기
현재 일수 : 10일 -> 현재 몇 일까지 있는지 표시해줄 것임
day 추가 라는 button 도 생성
const days = useFetch( '~localhost:3001/days' ); 불러오고
현재 일수 : { days.length }일 -> 현재 일수를 데이터의 인덱스 길이만큼으로 표시하도록 수정
버튼을 누르면 day 가 추가되도록 수정할 것임
단어 추가와 똑같은 로직이니까 가져오면 됨 fetch 해줬던 것 가져오기
day 추가 버튼에는 onclick 하면 {addDay} 호출 추가
함수 addDay 에 가져온 fetch 넣어주고
fetch url 에 words 가 아닌 days 로 하고 필요한 정보는 day 하나 이고 id 는 자동으로 만들어 주기
body 에는 day 하나만 있으면 됨
현재 3일까지 있으니까 4일을 추가하면 되고 4일까지 있으면 5일 추가하면 됨
결론적으로 현재 날짜에 +1 을 해주면 됨
=> day : days. length + 1
완료가 되면 첫번째 페이지로 가도록 history.push(`/`) 로 수정, const history = useHistory(); 추가
16. 마치며
단어 페이지나, day 선택이 네트워크 연결이 느려서 안 뜨면 로딩이 뜨도록 설정
단어 추가에 저장 버튼을 눌렀는데 네트워크 연결이 느려서 창이 안 뜨고 저장버튼을 계속 누를 수 있게 되면 state 와 로딩 함수를 추가해서 단어 추가 과정이 안 뜨도록
여기에다가 day를 잘못 만들 수도 있으니 삭제하는 기능도 필요해보이고, day 1 단어에서 다른 day 단어 페이지로 가려면 header 의 로고를 클릭해야 하는데 슬라이더 처럼 양 쪽에 화살표를 설치해서 다른 페이지로 갈 수 있게 하는 기능도 필요해보임 -> 과제.. 할 수 있을까..?
출처 : 코딩앙마 React JS 유튜브 강의