원지의 개발
article thumbnail
728x90

Component 반복

  • 목록 요소들을 반복처리 할 때는 map 함수 이용
  • 반드시 key props를 전달해야 함
    전달하지 않으면? 1.함수형에서는 error  2.클래스형에서는 안 나옴

map의 사용

  • callback의 return에 실린 값(연산 결과를 처리해서)으로 새로운 배열을 만들 때 사용
map ( item => item * 10 (조건) )
           ↘ func 매개변수 1개

map ( (item, index) => item * 10 (조건) )
           ↘ func 매개변수 2개

2023.01.12 - [클라이언트/React] - [React] ES6 문법(let, const), spread operator, Destructuring assignment, for of문, Backtick, Arrow Function(forEach, filter, map)

map으로 태그를 생성할 때 사용

  • newData를 화면에 뿌려줄건데 반복할 데이터를 state로 관리
  • list(=데이터).map으로 item을 매개변수로 받고, li 태그를 생성
    여기서 item은 data에 있던 { }객체 하나하나
  • li태그에 {item.topic}을 넣어서 화면에 나타내줌
  • input 태그를 관리해줄 state 만들고, 체인지 이벤트가 발생하면 setInputData로 사용자가 입력한 값 저장
  • 버튼을 클릭하면 기존의 데이터와 똑같은 객체를 만들어주는데
    id: 마지막 요소의 id + 1, topic: 사용자가 입력한 데이터

설명의 밑줄 부분

 

리액트에서 반복요소에는 key값 필수

key = { 고유한 값 or 없으면  index라도 } 꼭 넣어줘야 함

- 배열을 렌더링 시킬 때 빠르게 변화를 감지하기 위해서 사용
- 태그에 던지는 props의 개념임
- key는 화면에 나타나지는 않지만 꼭 사용해야 함
- key값을 주지 않으면 props에러 = warning 발생!

 

실습

App.js

import { Fragment } from "react"
import IterationComponent from "./component/IterationComponent";
import IterationComponent2 from "./component/IterationComponent2";


const App = () => {

    /* 
        p.180
        컴포넌트 반복하기
        map(콜백(item, index, arr), thisArg);
    */

    return(
        <Fragment>
            <IterationComponent/>
            <hr/>
            <IterationComponent2/>
        </Fragment>
    )
}

export default App;

IterationComponent.js

const IterationComponent = () => {
    
    //1. 반복처리
    const arr = [1,2,3,4,5];
    
    // const newArr = arr.map(function (item, index, arr) {
    //     return item * 10;
    // })
    /* 
        const newArr = arr.map((item, index, arr) => item * 10); //위와 같은 표현
        console.log(newArr); //[10, 20, 30, 40, 50] 나옴
    */


    //2. 반복처리 (태그) 
    //li를 차곡차곡 쌓는데 item 값을 넣음 = item 값을 화면에 뿌려줌
    //리액트에서 반복 처리시에 key를 태그에 작성합니다. (key는 고유한 값입니다)
    //key는 화면에서 렌더링할 때 변화를 감지하기 위해 참조하는 값입니다.
    //key는 화면에 나타나지는 않지만 꼭 사용해야 함. 태그에 던지는 props의 개념임
    const newArr = arr.map( (item, index) => <li key={index}>{item}</li> ); //li를 차곡차곡 쌓는데 item 값을 넣음
    console.log(newArr); //이걸 화면에 뿌려줌

    return(
        <>
            <ul>
                {newArr}
            </ul>
        </>
    )
}

export default IterationComponent;

IterationComponent2.js - 값 넣기

import { useState } from "react";

const IterationComponent2 = () => {

    //1. 반복할 데이터 (state로 관리)
    const arr = [ //배열 안에 객체 3개 생성
        {id: 1, topic: 'hello'},
        {id: 2, topic: 'bye'},
        {id: 3, topic: 'see you'}
    ];

    const [list, setList] = useState(arr); //useState로 arr 가져와서 list, setList

    //2. map 함수를 이용해서 li태그로 생성
    const newList = list.map( item => 
                        //console.log(item); //찍어보면 arr의 값이 하나씩 나옴
                        <li key={item.id}>{item.topic}</li> 
                    ); //한줄로 적어주기

    //3. input 데이터 관리
    const [data, setData] = useState(''); //input데이터를 관리할 state
    const handleChange = (e) => {
        setData(e.target.value);
    }

    //4. 추가하기 버튼 클릭시 input의 값을 list의 마지막에 추가
    const handleClick = (e) => {
        let obj = {id: list[list.length-1].id+1, topic: data}; /* 객체를 만들고 마지막에 추가, list[list.length-1] 리스트의 마지막 요소-1해야 마지막 데이터 나옴-주석 다시 */ 
        // list.push(obj); 이렇게 하면 안됨, list데이터를 직접적으로 손댄거와 같음
        let newArr = list.concat(obj); //concat은 배열요소를 합쳐줌, 원본리스트를 수정하지는 x, 기본 list에 obj가 합쳐진 새로운 리스트 반환
        setList(newArr); //state 변경
        setData(''); //input값 초기화
    }


    return (
        <>
            <h3>목록 추가하기</h3>
            <input type="text" onChange={handleChange} value={data}/> {/* state로 관리될 data 찍어주기 */}
            <button type="button" onClick={handleClick}>추가하기</button>{/* 추가하면 state에 넣어주고 바뀌는걸 감지해서 화면을 다시 그려줌 */}
            <ul>
                {newList} {/* 반복해서 처리한 컴포넌트를 newList 변수에 저장해서 화면에 뿌려줌 */}
            </ul>
        </>
    )

}

export default IterationComponent2;

IterationComponent2.js - 삭제 기능

     화살표 함수는 익명함수이기 때문에 호이스팅이 안됨
     ▶ 위에서 함수가 정의되어야 하고, 아래에서 호출이 되야함

function xx(a) {
	return () => { console.log(a) };
}

setInterval (xx(1), 1000);
  • setInterval로 1초에 한번씩 xx() 함수를 호출하는데 매개변수로 1을 넣음 ▶ 1초에 한번씩 콘솔에 1찍힘
    이런식으로 사용하면 됨

 

//2. map 함수를 이용해서 li태그로 생성
    const newList = list.map( item => 
                        //console.log(item); //찍어보면 arr의 값이 하나씩 나옴
                        <li key={item.id} onDoubleClick={ () => { //호이스팅을 할 수 없으니 실행시킬 익명함수 안에서 return으로 handleRemove !!!호출!!! (전달 아님)
                            return handleRemove(item.id); //이렇게 실행시키면 함수의 매개변수를 줄 수 있게 됨
                        } }>
                            {item.topic}
                        </li> 
                    ); //한줄로 적어주기
                    

//5. 삭제기능
    //화살표 함수는 익명함수기 때문에 호이스팅 불가
    //이벤트 안에서 함수를 호출로 연결하는 방법
    //onClick={ () => 함수() }
    const handleRemove = (a) => {
        console.log(a); //키 = id 나옴
        //id가 필요함 - id 가져와서 지워야하기 때문
        
        //filter - 콜백의 리턴이 true인 값을 가지고 새로운 배열생성
        // const ex = [1,2,3,4,5].filter = ( (item) => item != 3);
        const newList = list.filter( item => item.id !== a );
        setList(newList);
    }

위쪽에 함수 정의하지 않고, 아래에서 삭제 기능 만드는 방법

  • 만들어진 li함수를 더블 클릭하면 삭제
  • 더블 클릭 할 때 만들어진 익명함수 안에서 return으로 handleRemove !!!호출!!! (전달 아님)
  • 이렇게 하면 handleRemove() 실행시킬 함수에 매개변수를 줄 수 있고
  • 매개변수를 유일한 값이 id로 가져와서 filter로 조건 걸기
    filter - 콜백의 리턴이 true인 값을 가지고 새로운 배열 생성
    list.filter( item => item.id !== a); 리스트를 가져와서 filter로 객체의 id가 더블클릭한 id가 아닌 것만 다시 새로운 배열(newList)에 넣어줌
  • 그 새로운 배열(newList)을 setList로 list에 저장

 

더보기

전체 코드

import { useState } from "react";


/* 
    //5. 삭제기능
    //화살표 함수는 익명함수 대체하는 거여서 당겨쓰기인 호이스팅이 되지 않음
    //그래서 위에서 함수가 정의되어 있고, 아래 (2)번에서 호출이 되야함
    const handleRemove = () => { //list를 더블클릭 했을 떄
        console.log(1); }
*/

const IterationComponent2 = () => {

    //1. 반복할 데이터 (state로 관리)
    const arr = [ //배열 안에 객체 3개 생성
        {id: 1, topic: 'hello'},
        {id: 2, topic: 'bye'},
        {id: 3, topic: 'see you'}
    ];

    const [list, setList] = useState(arr); //useState로 arr 가져와서 list, setList

    //2. map 함수를 이용해서 li태그로 생성
    const newList = list.map( item => 
                        //console.log(item); //찍어보면 arr의 값이 하나씩 나옴
                        <li key={item.id} onDoubleClick={ () => { //호이스팅을 할 수 없으니 실행시킬 익명함수 안에서 return으로 handleRemove !!!호출!!! (전달 아님)
                            return handleRemove(item.id); //이렇게 실행시키면 함수의 매개변수를 줄 수 있게 됨
                        } }>
                            {item.topic}
                        </li> 
                    ); //한줄로 적어주기

    //3. input 데이터 관리
    const [data, setData] = useState(''); //input데이터를 관리할 state
    const handleChange = (e) => {
        setData(e.target.value);
    }

    //4. 추가하기 버튼 클릭시 input의 값을 list의 마지막에 추가
    const handleClick = (e) => {
        let obj = {id: list[list.length-1].id+1, topic: data}; /* 객체를 만들고 마지막에 추가, list[list.length-1] 리스트의 마지막 요소-1해야 마지막 데이터 나옴-주석 다시 */ 
        // list.push(obj); 이렇게 하면 안됨, list데이터를 직접적으로 손댄거와 같음
        let newArr = list.concat(obj); //concat은 배열요소를 합쳐줌, 원본리스트를 수정하지는 x, 기본 list에 obj가 합쳐진 새로운 리스트 반환
        setList(newArr); //state 변경
        setData(''); //input값 초기화
    }

    //5. 삭제기능
    //화살표 함수는 익명함수기 때문에 호이스팅 불가
    //이벤트 안에서 함수를 호출로 연결하는 방법
    //onClick={ () => 함수() }
    const handleRemove = (a) => {
        console.log(a); //키 = id 나옴
        //id가 필요함 - id 가져와서 지워야하기 때문
        
        //filter - 콜백의 리턴이 true인 값을 가지고 새로운 배열생성
        // const ex = [1,2,3,4,5].filter = ( (item) => item != 3);
        const newList = list.filter( item => item.id !== a );
        setList(newList);
    }    

    return (
        <>
            <h3>목록 추가하기</h3>
            <input type="text" onChange={handleChange} value={data}/> {/* state로 관리될 data 찍어주기 */}
            <button type="button" onClick={handleClick}>추가하기</button>{/* 추가하면 state에 넣어주고 바뀌는걸 감지해서 화면을 다시 그려줌 */}
            <ul>
                {newList} {/* 반복해서 처리한 컴포넌트를 newList 변수에 저장해서 화면에 뿌려줌 */}
            </ul>
        </>
    )

}

export default IterationComponent2;

 

IterationComponentQ.js - 내 풀이

/* 
    이미지를 가져오는 방법
    1. 외부 서버에서 경로를 참조 받음 (가장 일반적인 방법)

    2. src 폴더 밑에 img파일 참고 가능 (선호x)
       import img1 from '../img/img4.png';

    3. public 폴더 밑에 넣는 경우 이미지를 바로 참조 가능 (강사님 추천) - 간단
*/

import { useState } from "react";


const IterationComponentQ = () => {

    //1. 반복할 데이터 (state로 관리)
    const arr = [
        {src : '/img/img1.png', title : '아이폰10', price: 1000}, //이거 한줄이 item
        {src : '/img/img2.png', title : '아이폰11', price: 2000},
        {src : '/img/img3.png', title : '아이폰12', price: 3000},
        {src : '/img/img4.png', title : '아이폰13', price: 4000},
    ]
    
    const [list, setList] = useState(arr); //useState로 arr 가져와서 list, setList

    //2. map 함수를 이용해서 div 태그 생성
    const newList = list.map ( (item, index)=>
                                    <div key={index} onClick={ () => { //실행시킬 익명함수 안에서 return으로 handleClick 호출
                                        return handleClick(item.src);
                                    }}>
                                        <img src={item.src}  alt={item.title} width="100"/>
                                        <p>제품: {item.title}</p>
                                        <p>가격: {item.price}</p>
                                    </div> );

    //3. 사진 클릭시 사진을 크게 보여주기
    const [data, setData] = useState('');
    const handleClick = (e) => {
        const img = <img src={e} alt="제목" width="250"/> //이렇게 변수로 해 주면 alt 넣어도 안나옴!
        setData(img);
    }


    return (
        <>
            <h3>이미지 데이터를 반복하기</h3>
            {data}

            {/* 
                <img src="https://raw.githubusercontent.com/yopy0817/data_example/master/img/img1.png" alt="제목" width="100"/>
                <img src={img1} alt="제목" width="100" />
                <img src="/img/img1.png" alt="제목" width="100" />
            */}

            <div style={{display: "flex", justifyContent: "space-around"}}>
                {/* 반복할 요소의 모형 */}
                {/*
                    <div>
                        <img src="/img/img1.png" alt="제목" width="100"/>
                        <p>제목</p>
                        <p>가격</p>
                    </div>
                */}
                    {newList}
            </div>
        </>
    )
}

export default IterationComponentQ;
  •  

IterationComponentQ.js - 강사님 풀이

/* 
    이미지를 가져오는 방법
    1. 외부 서버에서 경로를 참조 받음 (가장 일반적인 방법)

    2. src 폴더 밑에 img파일 참고 가능 (선호x)
       import img1 from '../img/img4.png';

    3. public 폴더 밑에 넣는 경우 이미지를 바로 참조 가능 (강사님 추천) - 간단
*/

import { useState } from "react";

 const IterationComponentQ = () => {

    
     const arr = [
         {src : '/img/img1.png', title : '아이폰10', price: 1000}, //이거 한줄이 item
         {src : '/img/img2.png', title : '아이폰11', price: 2000},
         {src : '/img/img3.png', title : '아이폰12', price: 3000},
         {src : '/img/img4.png', title : '아이폰13', price: 4000},
     ]
    
     //1. state로 arr을 관리
     const [data, setData] = useState(arr);

     //2. 반복처리
     const newArr = data.map ( item =>
                                     <div key={item.src}>
                                         <img src={item.src}  alt={item.title} width="100" onClick={()=>handleContent(item.src)}/>
                                         <p>제품: {item.title}</p>
                                         <p>가격: {item.price}</p>
                                     </div> );

     //3. 클릭시에 화면에 그려질 내용을 state로 관리
     const [content, setContent] = useState({src:'/img/img1.png'});

     const handleContent = (a) => {
         setContent({src: a});
     }

     return (
         <>
             <h3>이미지 데이터를 반복하기</h3>

             <div>
                 <img src={content.src} width="200"/> {/* content를 객체로 만들어서 거기에 src를 변경해주는거라서 사용할때 content.src라고 해야함 */}
             </div>

             <div style={{display: "flex", justifyContent: "space-around"}}>
                     {newArr}
             </div>
         </>
     )
 }


export default IterationComponentQ;
  •  

IterationComponentQ2.js - 내 풀이

import { useState } from "react";


const IterationComponentQ2 = () => {
    //1 - select는 컴포넌트 반복으로 option태그를 생성 (state필요x)
    const select = ['HTML', 'Javascript', 'CSS', 'Java', 'Oracle', 'Mysql'];
    
    //2 - data는 state로 관리하고 화면에 li태그로 반복함 (고정값)
    const data = [
        {id: 1, type: 'Java', teacher: '이순신'},
        {id: 2, type: 'Java', teacher: '홍길자'},
        {id: 3, type: 'Javascript', teacher: '홍길동'},
        {id: 4, type: 'Oracle', teacher: '이순신'},
        {id: 5, type: 'Mysql', teacher: '이순신'},
        {id: 6, type: 'CSS', teacher: '박찬호'},
        {id: 7, type: 'HTML', teacher: 'coding404'},
    ];

    // onChange={ () => { 이벤트를 어디에다 걸어줘야 하는가 - select 태그에 바로 걸어주면 됨
    //     return handleChange(item.id);
    // } }

    const option = select.map ( item =>
                <option key={item}>{item}</option> //key에 객체가 아니니까 자기자신을 넣어줌
        );

    //3 - 셀렉트 박스가 체인지되면, 이벤트객체를 활용해서 선택된 값만 필터링해서 보여주면 됨 (변화되는 값)
    const [list, setList] = useState(data);
    const newList = list.map ( item => 
                <li key={item.id}> {item.type} - {item.teacher} </li>
    );
    
    //handleChange 함수
    const handleChange = (e) => {
        console.log(e.target.value); //e.target = select, e.target.value = option 값
        const newArr = data.filter(item => item.type === e.target.value); //필터를 걸어주는데 list로 걸면 list가 바뀌면서 덮어져서 사라지고, data로 걸면 기존 데이터에서만 필터가 걸림
        setList(newArr);
    };

    //4 - 숙제: 검색 기능 만들기 - 대소문자 가리지 않고, 일부만 검색해도 나옴

    return (
        <>
            <h3>컴포넌트 반복 실습</h3>
            <h3>검색기능 - 대소문자를 구분하지 않고, teacher, type에 포함된 데이터만 검색</h3>
                
                Search:
                <input type="text"/>
                <button type="button">검색</button>
                <br/>
                
                과목찾기:
                <select onChange={handleChange}>
                    {option}
                </select>
                <ul>
                    {newList}
                </ul>
        </>
    )

}

export default IterationComponentQ2
  •  

IterationComponentQ2.js - 강사님 풀이

import { useState } from "react";

//강사님 답
const IterationComponentQ2 = () => {
    //1 - select는 컴포넌트 반복으로 option태그를 생성 (state필요x, 고정값)
    const select = ['HTML', 'Javascript', 'CSS', 'Java', 'Oracle', 'Mysql'];
    const newSelect = select.map( (item, index) => <option key={index}>{item}</option> );
    
    //2 - data는 state로 관리하고 화면에 li태그로 반복함 (변화되는 값)
    const data = [
        {id: 1, type: 'Java', teacher: '이순신'},
        {id: 2, type: 'Java', teacher: '홍길자'},
        {id: 3, type: 'Javascript', teacher: '홍길동'},
        {id: 4, type: 'Oracle', teacher: '이순신'},
        {id: 5, type: 'Mysql', teacher: '이순신'},
        {id: 6, type: 'CSS', teacher: '박찬호'},
        {id: 7, type: 'HTML', teacher: 'coding404'},
    ];

    const [list, setList] = useState(data);
    const newList = list.map ( item => <li key={item.id}>{item.type} - {item.teacher}</li> );

    //3 - 셀렉트 박스가 체인지되면, 이벤트객체를 활용해서 선택된 값만 필터링해서 보여주면 됨
    const handleChange = (e) => {
        console.log(e.target.value); //선택한 과목 나옴
        let newList = data.filter( item => item.type === e.target.value ); //지역변수로 담아줌(굳이)
        console.log(list); //콘솔로 찍어서 왜 사라지는지 확인
        //첫번째는 데이터가 다 나오는데 두번째부터는 이전 state가 change되었기 떄문에 안나옴 ▶ 원본인 data에서 필터를 걸어줘야 함
        setList(newList);
    }

    return (
        <>
            <h3>컴포넌트 반복 실습</h3>
                과목찾기 :
                <select onChange={handleChange}>
                    {newSelect}
                </select>
                <ul>
                    {newList}
                </ul>
        </>
    )

}

export default IterationComponentQ2
  •  

IterationComponentQ2.js - 검색 기능

toLowerCase() 사용

    // 강사님 답
    const [search, setSearch] = useState('');
    const handleSarch = (e) => {
        setSearch(e.target.value);
    }

    const handleSarch = () => {
        //search 사용자의 검색값
        //data 객체에서의 조회
    let newList = data.filter( obj => obj.type.toLowerCase().includes(search) ||
									  obj.teacher.includes(search) )
	setList(newList);
    }
  •  

toLocalLowerCase() 사용

//4 - 숙제: 검색 기능 만들기 - 대소문자 가리지 않고, 일부만 검색해도 나옴
    //input 값을 관리하기 위한 state
    const [search, setSearch] = useState(data);
    const handleSarch = (e) => {
        console.log(e.target.value); //사용자가 입력한 값
        setSearch(e.target.value);
    }
    
    const handleClick = () => {
        const newSearch = data.filter( item => item.type.toLocaleLowerCase().toLocaleUpperCase().includes(search) || item.teacher.includes(search) );
        // console.log(newSearch);
        setList(newSearch);
    }
  •  

이미지 가져오기

1. 외부 서버에서 경로를 참조 받음 (가장 일반적인 방법)
2. src 폴더 밑에 img파일 참고 가능 (선호x) import img1 from '../img/img4.png';
3. public 폴더 밑에 넣는 경우 이미지를 바로 참조 가능 (강사님 추천) - 간단


hook

  • 함수형태의 컴포넌트에서 사용되는 몇가지 기술을 Hook (useState, userEffect 등)
  • 리액트 훅은 함수형 컴포넌트가 클래스형 컴포넌트의 기능을 사용할 수 있도록 해주는 기능

규칙

  1. 최상위에서만 Hook을 호출 - 내부함수로 들어가면 안됨
    1. 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 안됨
    2. [결과] 이 규칙을 따르면 컴포넌트가 렌더링될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장됨
  2. 리액트 함수 컴포넌트에서만 Hook을 호출해야 함

 

1. useState

2023.01.16 - [클라이언트/React] - [React] state, 이벤트 핸들링

컴포넌트에서 상태값을 제어하는 가장 기본이 되는 hook
 

2. useEffect

컴포넌트의 life cycle 다룸

★ useEffect는 여러개여도 됨 ★

리액트 컴포넌트가 mount, mount이후, state변경될 때, unmount때 마다 특정작업을 수행

 

  • componentDidMount, componenetDidUpdate, componentWillUnmount는 클래스형 컴포넌트에서 제공해주는 건데 함수형에서 대체해주는 게 useEffect임

 

  1. 클래스형 componentDidMount() 대체

  • 렌더링 된 이후에 사용되기 때문에 가장 많이 쓰임

<함수형 hook>

1st - 렌더링 될 때마다 실행

useEffect( () => {
	console.log(`렌더링완료`);
});

2nd - 처음 한 번만 실행

  • mount 이후 업데이트 될 때는 실행되지 않으려면, 두번째 매개변수 배열를 줌
    = mount 이후에 딱 한번만 실행됨
useEffect( () => {
	console.log(`처음만 실행됩니다`);
}, []);​

 

  2. 클래스형 componentDidUpdate() 대체

  • state가 체인지되고난 이후에 처리

<함수형 hook>

  //특정값이 업데이트 될 때만 실행해주려면 두번째 매개변수에 값을 state값을 지정합니다
    useEffect( () => {
        console.log(`name이 업데이트 시 실행됩니다`)
    }, [name]);
    
    //여러개 써주고 싶을 떄는 [name, 다른 state값, 다른 state값] 이런식으로 써줌

 

  3. 클래스형 componentWillUnMount() 대체

  • 컴포넌트가 화면에서 사라지기 직전에 호출
  • state는 직전 값이 나옴 - 여기서는 {name}

<함수형 훅>

//useEffect    
    useEffect( () => {
        console.log(`name이 업데이트 시 실행됩니다`)
        
        //unmount이후 실행됩니다.
        return () => {
            console.log(`unmount에 실행됩니다.`);
        }
    }, [name]);

 

3. useRef()

  • 특정 이름에 태그달기
  • 무분별하게 사용하지X
  • 이벤트를 사용하다 보면 특정 태그에 접근해서 핸들링 하는 경우가 생김
  • arrow function에 event 매개변수를 이용해서, 자신 태그에는 접근할 수 있지만, 다른태그는 핸들링 하기가 어렵고, 이런경우 useRef() 훅을 이용해서 특정태그에 이름을 지정하고 핸들링 할 수 있음
    ex) 버튼을 누르면 input의 값을 가져와서 확인하고 검색할 때 (검색하기 숙제에서)

 //Ref의 사용
        console.log(inputTag); //{current: input} 나옴
        console.log(inputTag.current); //tag그 자체
        console.log(inputTag.current.value); //tag안의 value

 

4. useReducer() - 부가적인 훅

  • useState의 사용을 외부에서 할 수 있게 해주는 훅
    (state의 변경을 외부에서 제어할 수 있음)
  • Redux에 반드시 들어가는 개념
const [현재값, 리듀서를 업데이트 할 수 있는 함수] = useReducer( 외부에서 사용할 리듀서함수, 리듀서의 초기값)

외부에서 사용할 리듀서 함수 예시

//action을 판단해서 state를 체인지

const myReducer = (state, action) => {
    //action은 객체
    // console.log(state);
    // console.log(action);
    if(action.type === 'increase') {
        state = {value: state.value +1};
    } else if(action.type === 'decrease') {
        state = {value: state.value -1};
    } else if(action.type === 'reset') {
        state = {value: 0};

    }

    return state; //변화된 값에 대한 결과를 return (반드시 걸어주기)

}

App.js & HookReducer.js - 내부에서 관리

  • useEffect로 리듀서를 한 번 실행시키고,  리듀서를 업데이트 할 수 있는 함수인 func() 호출
  • 매개변수로 type: 'increase'를 넣어주면 action으로 전달이 됨
  • 외부에서 사용할 리듀서 함수인 myReducer를 실행시키고 리턴으로 변화된 값에 대한 결과 = state를 줌
  • 마지막으로 콘솔에 찍어보면 바뀐 값이 나옴
  • 이렇게 내부에서 관리한 것을 그대로 외부로 옮겨서 import 하면 됨

HookReducerComponent.js & HookReducer2.js- 외부에서 관리

  • HookReducerComponent.js 파일을 만들어주고 myReducer를 복사 *예제는 nameReducer
  • export로 내보내주고, 사용할 HookReducer.js 파일에서는 import
  • 사용하는 방법은 따로 없음

hook 실습에서 사용할 app.js

import { useState } from "react";
import HookEffect from "./hook/HookEffect";
import HookRef from "./hook/HookRef";
import HookQ from "./hook/HookQ";
import HookQ2 from "./hook/HookQ2";
import HookReducer from "./hook/HookReducer";
import HookReducer2 from "./hook/HookReducer2";

const App = () => {

    /* 
        p.223
        1. 필수훅
            useState()
            컴포넌트에서 상태값을 제어하는 가장 기본이 되는 hook

            useEffect()
            컴포넌트의 라이프사이클(생명주기)를 다룹니다.
            mount, mount이후, state 변경될 때, unmount 이전에 특정 작업을 수행할 수 있습니다.

            useRef()
            태그의 이름 달기

        2. 부가적인 훅
        useReducer()
        useState의 사용을 외부에서 할 수 있게 해주는 훅 (state의 변경을 외부에서 제어할 수 있습니다.)

        const [현재값, 리듀서를 업데이트 할 수 있는 함수] = useReducer( 외부에서 사용할 리듀서함수, 리듀서의 초기값)
    */

    const [visible, setVisible] = useState(true);

    const handleClick = () => {
        console.log(visible);
        setVisible(!visible); //visible이 가진 값의 반대값 ▶ false, true 왔다갔다 해야하기 때문
    }

    return(
        <>
            {/* effect 훅 */}
            <button onClick={handleClick}>{visible ? "숨기기" : "보이기"}</button>
            <br/>
            { visible ? <HookEffect/> : null } {/* visible이 true이면 hookEffect컴포넌트가 보이게 */}
        
            {/* ref 훅 */}
            <hr/>
            <HookRef/>

            {/* 훅 실습 */}
            <hr/>
            <HookQ/>
            <hr/>
            <HookQ2/>

            {/* 리듀서 훅 */}
            <hr/>
            <HookReducer/>
            
            <hr/>
            <HookReducer2/>
        </>
    )
}

export default App;

1. HookEffect.js

 

App.js

import { useState } from "react";
import HookEffect from "./hook/HookEffect";
import HookRef from "./hook/HookRef";
import HookQ from "./hook/HookQ";
import HookQ2 from "./hook/HookQ2";
import HookReducer from "./hook/HookReducer";
import HookReducer2 from "./hook/HookReducer2";

const App = () => {

    const [visible, setVisible] = useState(true);

    const handleClick = () => {
        console.log(visible);
        setVisible(!visible); //visible이 가진 값의 반대값 ▶ false, true 왔다갔다 해야하기 때문
    }

    return(
        <>
            {/* effect 훅 */}
            <button onClick={handleClick}>{visible ? "숨기기" : "보이기"}</button>
            <br/>
            { visible ? <HookEffect/> : null } {/* visible이 true이면 hookEffect컴포넌트가 보이게 */}
        </>
    )
}

export default App;
  • App.js에서 버튼을 하나 만들고, 클릭 이벤트가 실행되면 컴포넌트가 숨기거나 보이게 하기
  • 상태를 관리해줄 state 생성하고, 초기값이 true를 넣어서 우선 항상 보이게 하기
  • 클릭 이벤트에서 false, true를 왔다 갔다하기 위해 setVisible로 visible이 아닌 값을 저장
  • 화면에서는 visible이 true이면 HookEffect 컴포넌트가 보이고, 아니면 null값 - 3항 연산자
    { visible ? <HookEffect/> : null }
import { useEffect, useState } from "react";


const HookEffect = () => {

    const [name, setName] = useState('');
    const [age, setAge] = useState('');

    const handleName = (e) => {
        setName(e.target.value);
    }

    const handleAge = (e) => {
        setAge(e.target.value);
    }

    useEffect( () => {
        console.log("name이 변경될 떄 render 됩니다");

        //컴포넌트가 unmount될 때 실행됩니다.
        return () => {
            console.log(`unmount가 됩니다`); //렌더링이 그려지면, 기존화면은 지워집니다.
            console.log(`update전 값: ${name}`); //state는 직전 값이 나옵니다.
        }
    }, [name])

    //★★★★★ useEffect는 여러개여도 됩니다 ★★★★★

    return(
        <>
            이름: <input type="text" onChange={handleName}/><br/>
            나이: <input type="number" onChange={handleAge}/><br/>

            이름: {name}, 나이: {age}
        </>
    )
}

export default HookEffect;
  • useEffect로 name이 체인지 되고 난 후 "name이 변경될 때 render 됩니다" 콘솔에 찍음 - 주두번째 매개변수로 [name]
  • return을 걸어줘서 지워주는데 렍더링이 그려지면서 기존화면은 지워지고
    state인 {name}은 직전 값이 나옴

2. HookRef.js

import { useState, useRef } from "react";


const HookRef = () => {
    //사용자 입력값 data, 화면에 출력값 result
    const [form, setForm] = useState({data: '', result: ''});

    //input 태그에 값입력시 체인지 이벤트
    const handleChange = (e) => {
        setForm({...form, ["data"]: e.target.value});
    }
    console.log(form);

    //등록, 버튼 클릭시 등록 이벤트
    const handleClick = () => {
        setForm( {data: '', result: form.data} ); //data는 인풋에 적힌 것을 지우고, result에다가는 바꾼 데이터 값 넣음
    
        //Ref의 사용
        console.log(inputTag); //{current: input} 나옴
        console.log(inputTag.current); //tag그 자체
        console.log(inputTag.current.value); //tag안의 value

        inputTag.current.focus();
    }

    //특정 태그에 이름달기 uesRef()
    //반환된 이름을 사용할 태그에 ref 속성에 넣습니다.
    //1. useRef()를 사용하여 하나의 변수로 이름을 지정해주고 
    //2. 만든useRef태그를 태그에 ref={}안에 넣어주면 된다.
    const inputTag = useRef();
    console.log(inputTag);

    return (
        <>
            내용: <input type="text" onChange={handleChange} value={form.data} ref={inputTag}/>
            <button onClick={handleClick}>등록하기</button>
            <br/>
            결과: {form.result}
        </>
    )
}


export default HookRef;
  • input에 내용을 입력하고, 버튼을 누르면 입력값이 결과로 뜨게 하기
  • 사용자의 입력값 input과 데이터의 출력값 result를 한번에 관리할 state를 객체로 생성
  • input에서 입력값이 바뀔때마다 setForm으로 data를 바꿔줌
    ...form 객체 그대로 복사 후, data만 e.target.value 값 지정
  • 버튼 클릭시 data값이 result에 저장되도록 하기위해 state를 따로 만들지 않고,
    useRef 사용 ▶ 현재 이벤트가 발생한 태그는 버튼이고, 데이터에 접근할 태그는 input일 때 접근 가능하게 함
  • useRef를 생성하고 이름을 input 태그에 넣어줌 (ref = 이름)
  • setForm으로 data는 공백, result에는 form.data를 저장하고, useRef로 이름.current.focus() 사용하여 input태그에 포커싱 맞추기

3. HookQ.js

import { useEffect, useState, useRef } from "react";



const HookQ = () => {

    /* 
        1. 컴포넌트가 마운트 된 이후 한번만 id태그에 포커스를 줍니다.

        2. id, pw는 state로 관리합니다. (변화되는 값이므로)
           로그인 버튼 클릭시 공백이라면 공백인 태그에 포커스를 주세요.
           로그인 버튼 클릭시 공백이 아니라면 경고창으로 id와 pw를 출력해주세요
    */

    const [data, setData] = useState({id: '', pw: ''});

    const inputId = useRef(null);
    const inputPw = useRef(null);

    useEffect( () => {
        // console.log("id 포커스");
        inputId.current.focus();
    }, [] ) //[] 처음 한번만 실행

    //input 태그의 핸들링 - id 혹은 pw가 체인지 될때마다 setData로 data에 저장
    const handleChange = (e) => {
        setData({...data, [e.target.name]: e.target.value});
    }

    //버튼에서 이벤트 핸들링하는데 다른 태그의 데이터를 사용해야 할 때 useRef 사용
    const handleClick = () => {
        // console.log(inputId.current);
        // console.log(inputPw.current);

        if(inputId.current.value === '') {
            inputId.current.focus(); //id태그
        } else if(inputPw.current.value === '') {
            inputPw.current.focus(); //pw태그
        } else {
            alert(`아이디는 ${data.id}이고, 비밀번호는 ${data.pw}입니다.`);
            setData({["id"]: '', ['pw']: ''}); //input에 value 값 넣는것 빼먹지 말자.....
        }
    }

    return (
        <div>
            <h3>hook 실습</h3>
            <input type="text" name="id" placeholder="아이디" ref={inputId} onChange={handleChange} value={data.id}/>
            <input type="password" name="pw" placeholder="비밀번호" ref={inputPw} onChange={handleChange} value={data.pw}/>
            <button onClick={handleClick}>로그인</button>
        </div>
    )
}

export default HookQ;
  • 포커싱을 주기 위해 각각의 input에 ref 걸어줌
  • id와 pw을 한번에 관리하기 위해 객체로 state 생성
  • 처음 한번만 실행하기 위해 useEffect를 사용([ ])해 처음 한번만 실행하고 current.focus();
  • input 태그가 변경 될 때마다 data의 id, pw에 저장
    setData로 기존의 데이터 복제 ...data 하고 변하는 값들 저장
    e.target.name = input 태그의 name (id 혹은 pw), e.target.value = input에 사용자가 입력한 값
  • 버튼 클릭 했을 때 조건문 ▶ 여기서 다른 태그로 접근할 때 ref.current.value로 접근 가능
    (1) input의 아이디가 비었으면 id태그에 포커싱
    (2) input의 비밀번호가 비었으면 pw태그에 포커싱
    (3) 아이디, 비밀번호가 입력되었으면 알림창에 뜨게 하고, input 값 공백으로 바꾸기
  • input 태그 value 값 넣기 (value가 있어야 공백으로 바꿀 수 있음)

4. HookQ2.js

hookQ2 - 내 답

import { useRef, useState } from "react";



const HookQ2 = () => {

    /* 
        실습(할 일 목록 만들기)
        1. state는 { todo: '', list: [] }로 관리하세요 - 객체로 하나로 묶어서 관리해라
        2. 할 일 목록을 작성하고 클릭시, list에 input에 입력값을 추가하고 map을 통해서 화면을 그립니다.
        3. 등록된 이후에는 ref를 활용해서 input 태그에 포커싱을 줍니다.
    */

    const [data, setData] = useState({todo: '', list: []});

    
    const handleChange = (e) => {
        setData({...data, ['todo']:e.target.value});
        console.log(data.todo);
    }
    

    //map함수를 이용해서 li태그 생성 - map은 반복요소에서 사용(data.list.map)
    const newList = data.list.map( (item, index) => 
            <li key={index}>{item.todo}</li>
        );

    const inputTag = useRef(''); //ref

    const handleClick = () => {
        if(inputTag.current.value === '') {
            inputTag.current.focus();
            return;
        }

        let obj = {todo: data.todo, list:[data.todo]};
        let newArr = data.list.concat(obj);

        setData({...data, ['todo']: '', ['list']: newArr});

        inputTag.current.focus();
    }
    
    return (
        <div>
            <h3>ref로 할 일 목록 만들기</h3>
            <input type="text" name="todo" placeholder="할 일 목록" ref={inputTag} value={data.todo} onChange={handleChange}/>
            <button onClick={handleClick}>등록하기</button>

            <ul>
                {/* 화면을 반복처리... */}
                {newList}
            </ul>
        </div>
    )
}

export default HookQ2;
  • todo와 list를 하나로 관리할 useState 객체 생성
  • input의 value 값이 바뀔때마다 onChange로 data에 저장
    & input의 value 값은 data.todo
  • 버튼 클릭 시 mpa으로 list에 li태그 생성
    data.list.map 으로 value가 item.todo인 li태그 생성
  • 버튼에서 input 태그에 접근하려고 useRef 생성 후 input에 ref 이름 생성
  • 버튼 클릭시 이벤트는 객체 모형을 똑같이 만들어서 기존의 데이터 data.list에 concat으로 연결
    setData로 todo는 공백으로 list는 newArr 저장 후 input에 포커싱
  • 추가적인 if 조건문으로 input태그가 현재 공백이면 포커싱하면서 등록은 되지 않도록 return 걸기

hookQ2 - 강사님 답안

import { useRef, useState } from "react";

//강사님 답
const HookQ2 = () => {

    /* 
        실습(할 일 목록 만들기)
        1. state는 { todo: '', list: [] }로 관리하세요 - 객체로 하나로 묶어서 관리해라
        2. 할 일 목록을 작성하고 클릭시, list에 input에 입력값을 추가하고 map을 통해서 화면을 그립니다.
        3. 등록된 이후에는 ref를 활용해서 input 태그에 포커싱을 줍니다.
    */

    const inputRef = useRef(null); //input태그
    const [data, setData] = useState({todo: '', list: []});


    
    //인풋데이터 핸들링
    const handleChange = (e) => {
        setData({...data, ["todo"]: e.target.value});
    }
    console.log(data); //사용자가 입력한 값
    
    
    //추가하기
    const handleClick = () => {
        const newList = data.list.concat( data.todo ); //기존 list는 유지, 새롭게 합쳐진 list를 반환
        console.log(newList);
        setData({todo: '', list: newList}); //list키는 newList로 todo는 공백으로, 객체를 통쨰로 갈아끼움
        
        inputRef.current.focus(); //등록한 다음 포커싱 맞추기
    }
    
    
    //화면 그림 - 반복요소에는 반드시 key값이 존재해야 함
    const newArr = data.list.map((item, index)=> <li key={index}>{index+1}. {item}</li>)

    return (
        <div>
            <h3>ref로 할 일 목록 만들기</h3>
            <input type="text" name="todo" placeholder="할 일 목록" onChange={handleChange} value={data.todo} ref={inputRef}/>
            <button onClick={handleClick}>등록하기</button>

            <ul>
                화면을 반복처리
                {newArr}
            </ul>
        </div>
    )
}

export default HookQ2;
  • useRef, useState 생성 후 input데이터 핸들링
  • 버튼 클릭시 기존의 list뒤에 data.todo인 새로 적은 값 붙여서 새로운 newList 만들기
  • setData로 newList 넣고, data.todo는 공백으로 저장 후 input태그에 포커싱 맞추기
  • 화면에 그릴 반복 요소에는 반드시 key값이 존재함
    map으로 li태그를 만들면서 적은 값 추가

hookQ22 - concat

import { useRef, useState, useEffect } from "react";

const HookQ22 = () => {

    /*
    실습(할일목록 만들기)
    1. state는 {todo: '', list; []}로 관리하세요
    2. 할일 목록을 작성하고 클릭 시, list에 인풋에 입력값을 추가하고 map을 통해서 화면을 그립니다.
    3. 등록버튼 클릭 이후에는 ref를 활용해서 input태그에 포커싱을 줍니다.
    */


    const todoTag = useRef();

    const [todoData, setTodoData] = useState({ todo: '', list: [] }); // 맨 처음에 초기값 세팅
    let newList = todoData.list.map((item, index) => <li key={index}>{item}</li>); // 배열에다가 li태그 붙여서 반환하고, 이 데이터를 화면에 뿌릴 것
    
    const handleTodoInput = (e) => { 
        setTodoData({ ...todoData, todo: e.target.value }); // 인풋값을 todo에 저장해둠
    }
    
    const todoRegister = () => { // 클릭이벤트
        if (todoTag.current.value === "") { // 값을 입력하지 않으면 실행하지 않도록 함
            todoTag.current.focus(); // 다만, 포커스를 줘서 바로 입력할 수 있게 해둠
            return;
        }
        let newArr = todoData.list.concat(todoData.todo); // todo에 저장한 input값을 list에 담아 newArr로 반환받는다
        setTodoData({ ...todoData, todo: '', list: newArr }); // 원래 todoData를 복사해오고, todo를 공백으로 바꾼다. 이미 list에 담았기 때문이다. 그리고 list에는 반환받은 newArr를 담는다.
        // 그 후 17줄로 돌아감. IterationComponent2와 동일한 원리
        todoTag.current.focus(); 
    }

    return (
        <>
            <hr />
            <h3>ref로 할일목록 만들기</h3>
            <input type="text" name="todo" placeholder="할일목록" ref={todoTag} onChange={handleTodoInput} value={todoData.todo} />
            <button onClick={todoRegister}>등록하기</button>
            <ul>
                {newList}
            </ul>
        </>
    )
}

export default HookQ22;

hookQ23 - push

import { useState } from "react";

const HookQ23 = () => {

    /* 
    실습(할일목록 만들기)
    1. state는 {todo: '', list: [] }로 관리하세요
    2. 할일 목록을 작성하고 클릭시, list에 인풋에 입력값을 추가하고 map을 통해서 화면을 그립니다
    3. 등록버튼 클릭 이후에는 ref를 활용해서 input태그에 포커싱을 줍니다
    */

    const [data, setData] = useState({todo: '', list: [] });

    //input데이터 todo에 저장
    const handleInput = (e)=> {
        setData({...data, ["todo"]: e.target.value})
    }
    
    //
    const handleClick = ()=>{           
        const newList = data.list; //list를 복사해서 newList에 넣어줌-> 복사를 해주는 이유는 state는 바로 수정이 불가능하기때문에 push를 바로 쓸 경우 state문법에 위배된다
        newList.push(data.todo);

        setData({todo: '', list:newList})
        // console.log(data);
        
    }

    //리스트 화면에 뿌리기
    const addList = data.list.map((item, index) =>
        <li key={index}>{item}</li>
    )

    return(
        <div>
            <h3>ref로 할일목록 만들기</h3>
            <input type="text" name="todo" placeholder="할일목록" onChange={handleInput} value={data.todo}/>
            <button onClick={handleClick}>등록하기</button>
            <ul>
                {addList}
            </ul>
        </div>
    )
}
export default HookQ23;
  • 클릭시 data.list를 새로운 newList에 복하해서 저장하고, data.todo를 하나씩 넣어줌
    복사해주는 이유? state는 바로 수정이 불가능하기 때문에 push를 바로 쓸 경우 state 문법에 위배되므로
  • 그리고 setData로 저장
  • 저장한 화면을 뿌릴때는 map을 이용하여 li태그 생성 후 뿌리기

5. HookReducer.js

//리듀서 선언 - 매개변수로 (현재의 state, 업데이트에 필요한 정보)

import { useEffect, useReducer } from "react";

import {myReducer} from './HookReducerComponent';

//action을 판단해서 state를 체인지
/* 
const myReducer = (state, action) => {
    //action은 객체
    // console.log(state);
    // console.log(action);
    if(action.type === 'increase') {
        state = {value: state.vlaue +1};
    } else if(action.type === 'decrease') {
        state = {value: state.vlaue -1};
    } else if(action.type === 'reset') {
        state = {value: 0};

    }

    return state; //변화된 값에 대한 결과를 return (반드시 걸어주기)
}
 */

const HookReducer = () => {

    const [state, func] = useReducer(myReducer, {value: 0}); //(외부에서 사용할 리듀서함수, 리듀서가 가져야할 초기값)

    /* 
    useEffect( () => {
        func({type: 'increase'}); //리듀서를 실행시키고, myReducer의 action으로 전달됩니다.

    }, []);

    console.log(state);
    */

    const up = () => {
        func({type: "increase"});
    }


    return (
        <>
            <button onClick={up}>증가</button>
            <button onClick={ () => func({type:"decrease"}) } >감소</button>
            <button onClick={ () => func({type:"reset"}) }>초기화</button>
            결과 {state.value} {/* state는 객체여서 state.value 해야함 */}
        </>
    )
}

export default HookReducer;
  • 관련 내용은 위쪽 Reducer에서 보면 됨
  • 외부에서 가져와서 사용할 때
    증가 버튼 이벤트 - up함수 실행
    감소 버튼 이벤트 - 익명함수 func() 실행하면서 매개변수로 decrease
    초기화 버튼 이벤트 - 익명함수 func() 실행하면서 매개변수로 reset

hookreducercomponent.js

import { useEffect, useReducer } from "react";

//1. myReducer
//action을 판단해서 state를 체인지
const myReducer = (state, action) => {
    //action은 객체
    // console.log(state);
    // console.log(action);
    if(action.type === 'increase') {
        state = {value: state.value +1};
    } else if(action.type === 'decrease') {
        state = {value: state.value -1};
    } else if(action.type === 'reset') {
        state = {value: 0};

    }

    return state; //변화된 값에 대한 결과를 return (반드시 걸어주기)

}

//기본 디폴트 모형
export {myReducer};

6. HookReducer2.js

import { useReducer } from "react";

import { nameReducer } from "./HookReducerComponent";

/* 
//리듀서
const nameReducer = (state, action) => {

    // console.log(action.name); //e.target.name
    
    // if(action.name == "name") {
    //     state = {...state, ["name"]:action.value};
    // } else if(action.name == "age") {
    //     state = {...state, ["age"]:action.value};
    // }
   
    state = {...state, [action.name]: action.value}; //위의 if조건문과 같음

    return state;
}
 */

const HookReducer2 = () => {
    //[state, 리듀서 제어함수] = useReducer(리듀서, 초기값);
    const [state, func] = useReducer(nameReducer, {name: '', age:''});

    const {name, age} = state; //스테이트 값 구조분해 할당

    return (
        <>
            이름: <input type="text" name="name" onChange={ (e) => func(e.target) }/>
            나이: <input type="text" name="age" onChange={ (e) => func(e.target) }/> {/* e.target 각각 구분 가능해서 똑같아도 됨 */}

            결과값: {name}<br/>
            결과값: {age}<br/>
        </>
    )
}

export default HookReducer2;
  • 리듀서 함수 생성
  • input 태그에는 체인지 이벤트가 발생할 때마다 리듀서를 업데이트 할 함수인 func() 넣어줌
    매개변수로 e.target을 넣어서 그 태그를 선택했음을 나타냄

 

  • 외부에서 state를 변경하고 state값을 구조분해 할당하기

hookreducercomponent.js

import { useEffect, useReducer } from "react";

//2. nameReducer
const nameReducer = (state, action) => {

     console.log(action.name); //e.target.name
    
     //if(action.name == "name") {
     //    state = {...state, ["name"]:action.value};
     //} else if(action.name == "age") {
     //    state = {...state, ["age"]:action.value};
     //}
   
    state = {...state, [action.name]: action.value}; //위의 if조건문과 같음

    return state;
}

//기본 디폴트 모형
export {nameReducer};
  • 외부에서 사용할 리듀서 함수인 nameReducer 실행시키고, action을 찍어보면 객체가 나옴

cosole.log(action)

  • 조건문으로 객체 이름이 name이면 state에 기존의 state값을 복사하고, name 키의 값을 action.value로 저장
    객체 이름이 age이면 state에 기존의 state값을 복사하고, age 키의 값을 action.value로 저장
  • 리턴값은 state

 


오늘 하루

더보기

기억에 남는 부분

 

어려운 부분

 

문제 해결 부분

 

728x90
profile

원지의 개발

@원지다

250x250