[Chapter 4] To-Do 앱 최적화 하기 - 3

서희찬·2022년 3월 4일
0

The Origin : React

목록 보기
8/17
post-thumbnail

Tailwind CSS

부트스트랩같은 익스텐션이다..!

그 후

 npm install -D tailwindcss postcss autoprefixer

를 통해서 설치해준다.

npx tailwindcss init

를 하면 tailwind.cofig파일이 자동으로 생긴다.


그 후 경로를 넣어준다
그리고 app.css에 @를 해주고..
제대로 적용됐는지 확인해보면 된다.
헬로월드가 정상적으로 뜬다
굿굿

Tailwind CSS로 Todo 앱

스타일링 해주기

우선 스타일을 다 날려준다.
그 후 div에 className TwaillandCSS를 주면서 적용해나간다

App.js

// 함수형 
import React, {useState} from "react";  // 리액트 라이브러리에서 컴포넌트 들고오기
import "./App.css";
import Form from "./components/Form";
import List from "./components/List";

export default function App(){ // 컴포넌트를 사용할 수 있게 extends

  // state = { //객체로 state 생성 
  //   todoData : [], //배열안에 객체넣기 
  //   value:"",
  // };
  //state바꿔주기 
  const [todoData,setTodoData] = useState([]);
  const [value,setValue] = useState("");
  //this.todoData -> todoData로 바꿔주기 

const handleSumbit = (e) =>{
  //form아ㄴ에 input전송시 페이지 리로드 막자 
  e.preventDefault();
  //새로운 할 일 데이터 
  let newTodo = {
    id : Date.now(), //유니크한 값 
    title: value,
    completed : false, 
  }
  setTodoData(prev=>[...prev,newTodo]);
  setValue(""); 
  // ... : 전개연산자 
  // 이미 있는거에 새로운거 더해주기
  //입력란 안에 있던 글시 지워주기 설명 안하노 ㅋㅋ 
}
// 컨테이너를 감싸고
    return( // 반환한다 
      <div className="flex items-center justify-center w-screen h-screen bg-blue-100">
        {/* div박스하나를 만든다. 투두블락 */}
        <div className="w-full p-6 m-4 bg-white rounded shadow lg:w-3/4 lg:max-w-lg"> 
        {/* 반응형 클래스주기 */}
          {/* 그리고 제목박스도  */}
          <div className="flex justify-between mb-3">
            My To do List 
          </div>
          <List todoData={todoData} setTodoData={setTodoData}/>
          <Form handleSumbit={handleSumbit} value={value} setValue={setValue}/>
          {/* 밑에서부터 할일 목록을 나열한다 */}
          {/* 반복형으로 나열 */}
        </div>
      </div> 
    )
}





// 클래스형 
// import React, {Component} from "react";  // 리액트 라이브러리에서 컴포넌트 들고오기
// import "./App.css";

// export default class App extends Component{ // 컴포넌트를 사용할 수 있게 extends

//   btnStyle = {
//     color : "#fff",
//     border : "none",
//     padding : "5px 9px",
//     borderRadius : "50%",
//     cursor : "pointer",
//     float : "right",
//   }

// //style 
// getStyle = (completed) =>{
//   return{
//     padding : "10px",
//     borderBottom:"1px #ccc dotted",
//     textDecoration : completed ? "line-through" : "none",
//   }
// }

// //할일 목록 삭제 함수 
// hanndleClick=(id)=>{
//   //filter method를 사용해서 
//   //id가 같은거를 필터링 해버리자 
//   let newTodoData = this.state.todoData.filter(data=> data.id != id);
//   console.log('newTodoData',newTodoData);
//   //list의 id가 와서 데이터의 아이디가 아닌것만 트루를 반환해서 살린다 
//   this.setState({todoData:newTodoData}); 
// }

// handleChange =(e)=>{

//   this.setState({value : e.target.value});

// }
// handleSumbit = (e) =>{
//   //form아ㄴ에 input전송시 페이지 리로드 막자 
//   e.preventDefault();
//   //새로운 할 일 데이터 
//   let newTodo = {
//     id : Date.now(), //유니크한 값 
//     title: this.state.value,
//     completed : false, 
//   }
//   //원래 있던 할 일에 새로운 일을 더하자 
//   this.setState({todoData:[...this.state.todoData,newTodo],value:""}); 
//   // ... : 전개연산자 
//   // 이미 있는거에 새로운거 더해주기 

//   //입력란 안에 있던 글시 지워주기 설명 안하노 ㅋㅋ 
// }


// state = { //객체로 state 생성 
//   todoData : [ //배열안에 객체넣기 
//     {
//       id:"1",
//       title:"공부하기",
//       completed: false
//     },
//     {
//       id:"2",
//       title:"청소하기",
//       completed: false 
//     }
//   ],
//   value:""
// }


// handleCompleteChange = (id) =>{
//   let newTodoData = this.state.todoData.map(data=>{
//     if(data.id === id){
//       data.completed = !data.completed; 
//     }
//     return data; 
//   })
//   this.setState({todoData:newTodoData});
// }

//   render(){ // 변환한다 
//     return( // 반환한다 
//       // 컨테이너를 감싸고
//       <div className="container">
//         {/* div박스하나를 만든다. 투두블락 */}
//         <div className="todoBlock">
//           {/* 그리고 제목박스도  */}
//           <div className="title">
//             My To do List 
//           </div>
//           {/* 밑에서부터 할일 목록을 나열한다 */}
//           {/* 반복형으로 나열 */}
//           {this.state.todoData.map(data=>(
//             // this는 클래스를 가리키고 클래스 안에 todoData라는 리스트객체를 가지고 와서 그 안에 데이터를 꺼내는데 map함수를 써서 꺼낸다
//             // map은 객체별 요소를 data라는 변수로 정해주고 data객체 안에 id,completed,title을 가져온다
//             // style같은경우도 겹치는 경우가 많으니 this를 사용해서 클래스 내에 만들어둔 스타일을 가지고 와서 사용한다
//             // react에서는 반복되는 값들을 가지고올때 유니크한 값와 같은 key값을 줘야한다 
//             <div style={this.getStyle(data.completed)} key={data.id}>
//               <input type="checkbox" defaultChecked={data.completed} onChange={()=>this.handleCompleteChange(data.id)}></input>
//               {data.title}
//               <button style={this.btnStyle} onClick={()=>this.hanndleClick(data.id)}>X</button>
//             </div>
//           ))}
//           <form style={{ display : 'flex'}} onSubmit={this.handleSumbit}>
//             <input 
//               type="text" 
//               name="value" 
//               style={{flex:'10', padding:'5px'}} 
//               placeholder="해야할 일 을 입력해주세요" 
//               value={this.setState.value}
//               onChange={this.handleChange}
//               />
//             <input
//               type="submit"
//               value="입력"
//               className="btn"
//               style={{flex:'1'}}
//             />
//           </form>


//         </div>
//       </div>
      
//     )
//   }
// }

List.js

// rfc 엔터하면 함수형 컴포넌트 만들기 가능 
import React from 'react'

export default function List({todoData, setTodoData}) {


    const handleCompleteChange = (id) =>{
        let newTodoData = todoData.map(data=>{
        if(data.id === id){
        data.completed = !data.completed; 
        }
        return data; 
    });
    setTodoData(newTodoData);
    this.setState({todoData:newTodoData});
    };


    //할일 목록 삭제 함수 
    const hanndleClick=(id)=>{
        //filter method를 사용해서 
        //id가 같은거를 필터링 해버리자 
        let newTodoData = todoData.filter(data=> data.id !== id);
        console.log('newTodoData',newTodoData);
        //list의 id가 와서 데이터의 아이디가 아닌것만 트루를 반환해서 살린다 
        setTodoData(newTodoData);
    };

  return (
    <div>
        {todoData.map(data=>(
        // this는 클래스를 가리키고 클래스 안에 todoData라는 리스트객체를 가지고 와서 그 안에 데이터를 꺼내는데 map함수를 써서 꺼낸다
        // map은 객체별 요소를 data라는 변수로 정해주고 data객체 안에 id,completed,title을 가져온다
        // style같은경우도 겹치는 경우가 많으니 this를 사용해서 클래스 내에 만들어둔 스타일을 가지고 와서 사용한다
        // react에서는 반복되는 값들을 가지고올때 유니크한 값와 같은 key값을 줘야한다 
        <div key={data.id} >
            <div className="flex items-center justify-between w-full px-4 py-2 my-2 text-gray-600 bg-gray-100 border rounded ">
                <div className="items-center" >
                    
                    <input type="checkbox" defaultChecked={data.completed} onChange={()=>handleCompleteChange(data.id)}></input>
                    <span className={data.completed ? "line-through" :undefined}>{data.title}</span>
                </div>
                <div className="items-center">
                    <button className="px-4 py-2 float-right" onClick={()=>hanndleClick(data.id)}>X</button>
                </div>
            </div>
        </div>
        ))}
    </div>
  )
}

Form.js

import React from 'react'

export default function Form({handleSumbit,value,setValue}) {

    const handleChange =(e)=>{

        // this.setState({value : e.target.value});
        setValue(e.target.value); 
    };
    return (
        <form  onSubmit={handleSumbit} className="flex pt-2">
            <input 
                type="text" 
                name="value" 
                className="w-full px-3 py-2 mr-4 text-gray-500 rounded shadow"
                placeholder="해야할 일 을 입력해주세요" 
                value={value}
                onChange={handleChange}
                />
            <input
                type="submit"
                value="입력"
                className="p-2  text-blue-400 border-2 border-blue-300 rounded hover:text-white hover:bg-blue-200"
            />
        </form>
  )
}


그러면 이렇게 짜라란~

Drag and Drop 기능

react-build-dnd로 쉽게 구현가능

npm install react-beautiful-dnd --save 로 설치 
<DragDropContext /> - Wraps the part of your application you want to have drag and drop enabled for 
<Droppable /> - An area that can be dropped into. Cotains <Draggable .?s 
<Draggavle /> - What can be dragged around 

우선, List.js에 이렇게 코드를 작성해준다

List.js

        <DragDropContext>
            <Droppable>    
                {todoData.map(data=>(
                    <Draggable>
                        <div key={data.id} >
                            <div className="flex items-center justify-between w-full px-4 py-2 my-2 text-gray-600 bg-gray-100 border rounded ">
                                <div className="items-center" >
                                    <input type="checkbox" defaultChecked={data.completed} onChange={()=>handleCompleteChange(data.id)}></input>
                                    <span className={data.completed ? "line-through" :undefined}>{data.title}</span>
                                </div>
                                <div className="items-center">
                                    <button className="px-4 py-2 float-right" onClick={()=>hanndleClick(data.id)}>X</button>
                                </div>
                            </div>
                        </div>
                    </Draggable>
                ))}
            </Droppable>
        </DragDropContext>

이렇게 넣어주고

return (
    <div>
        {/* // this는 클래스를 가리키고 클래스 안에 todoData라는 리스트객체를 가지고 와서 그 안에 데이터를 꺼내는데 map함수를 써서 꺼낸다
        // map은 객체별 요소를 data라는 변수로 정해주고 data객체 안에 id,completed,title을 가져온다
        // style같은경우도 겹치는 경우가 많으니 this를 사용해서 클래스 내에 만들어둔 스타일을 가지고 와서 사용한다
        // react에서는 반복되는 값들을 가지고올때 유니크한 값와 같은 key값을 줘야한다  */}
        <DragDropContext onDragEnd={handleEnd}>
            <Droppable droppableId="todo">
                {(provided)=> (
                    <div {...provided.droppableProps} ref={provided.innerRef}>
                        {todoData.map((data,index)=>(
                            <Draggable
                            key={data.id}
                            draableId={data.id.toString()}
                            index={index}
                            >
                                {(provided,snapshot)=>(
                                <div 
                                key={data.id} 
                                {...provided.draggableProps} 
                                ref={provided.innerRef} 
                                {...provided.dragHandleProps}
                                className={`${snapshot.isDragging ? "bg-gray-400":"bg-gray-100"} flex items-center justify-between w-full px-4 py-2 my-2 text-gray-600  border rounded`}
                                >
                                        <div className="items-center" >
                                            <input type="checkbox" defaultChecked={data.completed} onChange={()=>handleCompleteChange(data.id)}></input>
                                            <span className={data.completed ? "line-through" :undefined}>{data.title}</span>
                                        </div>
                                        <div className="items-center">
                                            <button className="px-4 py-2 float-right" onClick={()=>hanndleClick(data.id)}>X</button>
                                        </div>
                                    </div>
                                )}
                            </Draggable>
                        ))}
                        {provided.placeholder}
                    </div>
                )}
            </Droppable>
        </DragDropContext>
    </div>

이렇게 provided를 주면된다.
.그런데 이렇게 해도 드래그하고 위치가 바끼지않으니
onDragEnd에 handleEnd함수를 만들어주자.

const handleEnd = (result) =>{
    //result 매개변수에는 source 항목 및 대상 위치와 같은 드래그 이벤트에 대한 정보가 포함됩니다.
    console.log(result);

    if(!result.destination) return;

}

이렇게 해주고
실제로 todo를 변경해주기 위해서는 splice를 써야한다

splice()

splice 메서드는 배열의 기존 요소를 삭제 또는 교체하거나 새 요소를 추가하여 배열의 내용을 변경합니다.
그렇대..

추가

삭제

// rfc 엔터하면 함수형 컴포넌트 만들기 가능 
import React from 'react'
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';

export default function List({todoData, setTodoData}) {

const handleCompleteChange = (id) =>{
    let newTodoData = todoData.map(data=>{
    if(data.id === id){
    data.completed = !data.completed; 
    }
    return data; 
});
setTodoData(newTodoData);
this.setState({todoData:newTodoData});
};


//할일 목록 삭제 함수 
const hanndleClick=(id)=>{
    //filter method를 사용해서 
    //id가 같은거를 필터링 해버리자 
    let newTodoData = todoData.filter(data=> data.id !== id);
    console.log('newTodoData',newTodoData);
    //list의 id가 와서 데이터의 아이디가 아닌것만 트루를 반환해서 살린다 
    setTodoData(newTodoData);
};

const handleEnd = (result) =>{
//result 매개변수에는 source 항목 및 대상 위치와 같은 드래그 이벤트에 대한 정보가 포함됩니다.
console.log(result);

// 목적지가 없으면 함수 종료
if(!result.destination) return;

// 리액트 불변성 지키기 위한 새로운 tododata생성 
const newTodoData = todoData; 

// 1. 변경시키는 아이템을 배열에서 지움 
// 2. return 값으로 지워진 아이템을 잡음 
const [reorderItem] = newTodoData.splice(result.source.index, 1);

// 원하는 자리에 reoderedItem을 invert 한다
newTodoData.splice(result.destination.index, 0, reorderItem);
setTodoData(newTodoData);

};

return (

<div>
    {/* // this는 클래스를 가리키고 클래스 안에 todoData라는 리스트객체를 가지고 와서 그 안에 데이터를 꺼내는데 map함수를 써서 꺼낸다
    // map은 객체별 요소를 data라는 변수로 정해주고 data객체 안에 id,completed,title을 가져온다
    // style같은경우도 겹치는 경우가 많으니 this를 사용해서 클래스 내에 만들어둔 스타일을 가지고 와서 사용한다
    // react에서는 반복되는 값들을 가지고올때 유니크한 값와 같은 key값을 줘야한다  */}
    <DragDropContext onDragEnd={handleEnd}>
        <Droppable droppableId="todo">
            {(provided)=> (
                <div {...provided.droppableProps} ref={provided.innerRef}>
                    {todoData.map((data,index)=>(
                        <Draggable
                        key={data.id}
                        draableId={data.id.toString()}
                        index={index}
                        >
                            {(provided,snapshot)=>(
                            <div 
                            key={data.id} 
                            {...provided.draggableProps} 
                            ref={provided.innerRef} 
                            {...provided.dragHandleProps}
                            className={`${snapshot.isDragging ? "bg-gray-400":"bg-gray-100"} flex items-center justify-between w-full px-4 py-2 my-2 text-gray-600  border rounded`}
                            >
                                    <div className="items-center" >
                                        <input type="checkbox" defaultChecked={data.completed} onChange={()=>handleCompleteChange(data.id)}></input>
                                        <span className={data.completed ? "line-through" :undefined}>{data.title}</span>
                                    </div>
                                    <div className="items-center">
                                        <button className="px-4 py-2 float-right" onClick={()=>hanndleClick(data.id)}>X</button>
                                    </div>
                                </div>
                            )}
                        </Draggable>
                    ))}
                    {provided.placeholder}
                </div>
            )}
        </Droppable>
    </DragDropContext>
</div>

)
}

최종코드이다! 
profile
Carnegie Mellon University Robotics Institute | Research Associate | Developing For Our Lives, 세상에 기여하는 삶을 살고자 개발하고 있습니다

0개의 댓글

관련 채용 정보