나도 만들 수 있다 todolist (외전)
잡담
아... 원래 이거 쓰고 있었는데 한번 날아 가서... 다시 작성합니다. ㅠㅠ
저번까지 했던 파일:
https://github.com/famous0811/Todos
famous0811/Todos
블로그 자료. Contribute to famous0811/Todos development by creating an account on GitHub.
github.com
폴더 분할
이렇게 만들 예정입니다. 참고로 재가 다만든게 아니구여 밑에 있는 출처에서
많이 참고(?) 했습니다.
install
yarn add @reduxjs/toolkit//새로운 redux관리 프로그램
yarn add react-redux//connect사용하기위해서
이 두가지를 추가로 install 해주세요!!
Store and reducer
store폴더에 store.js파일과
reducer폴더를 만든다음
reducer폴더 안에
dayreducer.js
filltereducer.js
todosreducer.js
를 추가해주세요!
그리고 다음과 같이 작성해 주세요 설명은 옆에 달아 놨습니다.!
store.js
import {combineReducers,configureStore} from '@reduxjs/toolkit';
//reducer를 모아주는 함수,store를만들어주는 함수(미들웨어도 추가가)
import Todosreducer from './reducer/todosreducer.js';
//todolist를 관리하는 리듀서
import Filterruder from './reducer/filterreducer.js';
//footer를 관리하는 리듀서
import DaysReducer from './reducer/dayreducer.js';
//calender를 관리하는 리듀서
const rootreudcer=combineReducers({//3개의 리듀서를 합침
Todosreducer,
Filterruder,
DaysReducer
});
const store=configureStore({
reducer:rootreudcer//합친 리듀서 연결
})
export default store;//외부 인스톨이 가능하게 해줌
DaysReducer.js
import {createSlice} from '@reduxjs/toolkit';//action 과 reducer를 하나로 연결해줍니다.
const date=new Date();
const daySlice = createSlice({
name: 'day',//action type구분용?
initialState:{//지금 날짜 세팅(초기값)
month:date.getMonth()+1,
day:date.getDate()
},
reducers:{//리듀서 만들기
SetDays:{
reducer(state, action){
return action.payload;//action.payload로 초기화
},
prepare(month, day){
//setdays라는 리듀서를 호출하면 여기서 payload를 정리한다음에 reducer호출
return { payload: {month,day} }
}
}
}
});
export const {SetDays}=daySlice.actions;//외부에서 action함수 호출가능하게
export default daySlice.reducer;//리듀서 호출용
Filterruder.js
import {createSlice} from '@reduxjs/toolkit';
export const filter={//어떤걸 보이게 할지 설정
SHOW_ALL: 'SHOW_ALL',//다 출력
SHOW_COMPLETED: 'SHOW_COMPLETED',//clear한항목 출력
SHOW_ACTIVE: 'SHOW_ACTIVE',//notclear항목 출력
ALL_CLAER:'ALL_CLAER',//전채제거
}
const filterSlice =createSlice({
name:"filterview",
initialState:filter.SHOW_ALL,//초기값을 all로 설정
reducers:{
setVisibilityFilter(state, action) {
return action.payload
}
}
});
export const { setVisibilityFilter } = filterSlice.actions
export default filterSlice.reducer
Todosreducer.js
import {createSlice} from '@reduxjs/toolkit';
let nextTodoId=0;//아이디 값
const todoSlices = createSlice({
name: 'todos',
initialState:[],
reducers:{
addTodo:{//일정추가
reducer(state, action) {
const { id, text,month, day} = action.payload
state.push({ id, text,month, day, completed: false})
//id,내용,추가한 날짜,완료여부
},
prepare(text,days) {
return { payload: { text, id: nextTodoId++,...days} }
}
},
clearTodo(state,action) {//일정 완료 설정
const todo = state.find(todo => todo.id === action.payload)
if (todo) {
todo.completed = !todo.completed//완료or완료해제
}
},
deleteTodo(state,action) {//일정 제거
return state.filter(item=>item.id!==action.payload);
},
Alldelete(state, action){//모든 일정 제거
state.splice(0,state.length);
}
}
});
const {actions,reducer}=todoSlices;
export const {addTodo,clearTodo,deleteTodo,Alldelete}=actions;
export default reducer;
index.js(수정)
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {Provider} from 'react-redux';
/*원래 react는 상위 컴퍼넌트가 하위 컴퍼넌트에 props를 전달 해줘야 사용할수 있는데
만약 상위 컴퍼넌트에 props로 스토어를 보내면 하위컴퍼넌트들에게도
직접 전달 해줘야 하니까 불편해서
그런 작업을 생략 시켜줌 갸뀰
*/
import store from './store/store.js';//스토어 연결
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>//연결
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
containers and components
components폴더를 3개만 남기고 정리해 주세요!
containers폴더를 만든다음에
다음 파일들을 추가해 주세요
※app는 componets안에 넣는 것이 아닙니다.
보기 쉬우라고 이렇게 사진을 편집했습니다.;;
app.jsx
import React from 'react';
import {createGlobalStyle} from 'styled-components';
import ControlList from './components/ControlList';
const GolobalStyle=createGlobalStyle`
*{
margin: 0;
font-size:14px;
}
`;
function App() {//원래 있던걸 다 controlList에 넣었습니다.
return (
<div>
<GolobalStyle/>
<ControlList></ControlList>
</div>
);
}
export default App;
controlList.jsx
import React from 'react';
import Fotter from '../containers/Fotter';
import Getlist from '../containers/Getlist';
import TodoList from '../containers/TodosList';
import Clalendar from '../containers/Calendar';
//경로도 다 수정되었습니다.
function ControlList(props) {//원래 controlList보다 훨씬 깔끔하게 되었습니다.
return (
<div>
<h1>Todos</h1>
<Clalendar></Clalendar>
<Getlist></Getlist>
<TodoList></TodoList>
<Fotter></Fotter>
</div>
);
}
export default ControlList;
Calender.jsx(containers)
import React from 'react';
import Calendarcomponents from '../components/Clalendar';
import {connect} from 'react-redux';
import {SetDays} from '../store/reducer/dayreducer';//위에서 만들었던 reducer호출
function Calendar({SetDays}) {//이거 필수
function Updays(time){
SetDays(time.getMonth()+1,time.getDate());//값을 reducer에 전달
}
return (
<div>
<Calendarcomponents Updays={Updays}></Calendarcomponents>//calender컴포넌트에 전달
</div>
);
}
const mapDispatch={SetDays};//이렇게 안하면 호출이 안됨ㅎㅎ
export default connect(null,mapDispatch)(Calendar);//리덕스 작업한 함수를 반환
Calender.jsx
import React,{useState} from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';
function Clalendar({Updays}) {//이거랑
const [Dated,setdate] =useState(new Date());
const onchange = date=>setdate(date);
return (
<Calendar
className={"calender"}
onChange={onchange}
onClickDay={Updays}//이거만 수정됨
value={Dated}/>
);
}
export default Clalendar;
Getlist.jsx
import React,{useState} from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../store/reducer/todosreducer';
const mapDispatch={addTodo}
function Getlist({days,addTodo}) {
const [todoText, setTodoText] = useState('')
const onChange = e => setTodoText(e.target.value)
function addlist(e) {
e.preventDefault()
if (!todoText.trim()) {
return
}
addTodo(todoText,days);
setTodoText('');
}
return (
<div>
<form onSubmit={addlist}>
<input value={todoText} onChange={onChange} />
<button type="submit">Add Todo</button>
</form>
</div>
);
}
const mapStateToProps =(state) =>{
return{
days:state.DaysReducer//현재 어떤 날자로 설정되어있는지 가져오기
}
}
export default connect(mapStateToProps,mapDispatch)(Getlist);
TodosList.jsx
import React from 'react';
import {connect} from 'react-redux';
import {createSelector} from '@reduxjs/toolkit';
import {clearTodo,Alldelete,deleteTodo} from '../store/reducer/todosreducer';
import {filter,setVisibilityFilter} from '../store/reducer/filterreducer';
import List from '../components/List';
const selectTodos = state => state.Todosreducer;
const selectFilter = state => state.Filterruder;
const selectdays=state =>state.DaysReducer;
const SetViewTodoset=createSelector(//todos를 전달한 후에 원하는 값만 추출
[selectTodos,selectFilter,selectdays],//현재 store값 가져오기
(todos,nowfilter,days)=>{
switch(nowfilter){//현재 필터 분석
case filter.SHOW_ALL:
return todos.filter(t =>days.month===t.month && days.day===t.day)
case filter.SHOW_COMPLETED:
return todos.filter(t => t.completed && days.month===t.month && days.day===t.day)
case filter.SHOW_ACTIVE:
return todos.filter(t => !t.completed&& days.month===t.month && days.day===t.day)
case filter.ALL_CLAER:
//all_clear는 다 지워야 하기 때문에 일단 지워진척한뒤 실제로 지웁니다.
return [];
default:
throw new Error('Unknown filter: '+nowfilter);
}
}
)
function TodosList({todos,nowfilter,clearTodo,Alldelete,deleteTodo,setVisibilityFilter}) {
if(nowfilter===filter.ALL_CLAER)//여기서 삭제 합니다 ㅎㅎ
{
Alldelete([]);
setVisibilityFilter(filter.SHOW_ALL);
//이렇게 하는 이유는 ALL_CLEAR상태이면 추가해도 계속 삭제 할테니까 그걸 없에기 위해서 했습니다.
}
return (
<ul id="Lists">
{todos.map(todo=>(
<List key={todo.id} {...todo} onclick={()=>clearTodo(todo.id)} test={()=>deleteTodo(todo.id)}/>
))}
</ul>
);
}
const mapStateToProps=(state)=>{
return{
nowfilter:state.Filterruder,
todos:SetViewTodoset(state),
}
}
const mapDispatch={clearTodo,Alldelete,deleteTodo,setVisibilityFilter};
export default connect(mapStateToProps,mapDispatch)(TodosList);
list.jsx
import React from 'react';
import styled from 'styled-components';
const Contents =styled.li`
text-decoration:${(props)=>props.completed ? 'line-through' : 'none'};
margin-right:10px;
`;
const Listcontent =styled.div`
width:100px;
display: flex;
justify-content:space-around;
flex-direction: row;
`;
//여기만 보기가 너무 힘들어서 디자인을 살짝 추가 했습니다.ㅎㅎ
function List({onclick,test,completed,text,month, day}) {
return (
<Listcontent>
<Contents onClick={onclick} completed={completed}>
{text}
</Contents>
<div>
{month}/{day}
</div>
<div onClick={test} style={{color: 'red'}}>X</div>
</Listcontent>
);
}
export default List;
Fotter.jsx
import React from 'react';
import {connect} from 'react-redux';
import {filter,setVisibilityFilter} from '../store/reducer/filterreducer';
import {createSelector} from '@reduxjs/toolkit';
import styled from 'styled-components';
const Leight =styled.div`
display: ${(props) => props.leight ? "block" : "none"};
`;
const Contents=styled.div`
border: ${(props) =>props.nowfilter ? '1px solid pink' : "none"};
&:hover{
border:solid 1px pink;
}
`;
const mapDispatch ={setVisibilityFilter};
function Fotter({setVisibilityFilter,nowfilter,leight}) {
function Download(){
var lists=document.getElementById("Lists");
//todolist.jsx에 있는 ul테그입니다.
var text="";
if(!lists.childElementCount)
return;//추가 된것이 없으면 하지 않음
for(var j=0;j<lists.childElementCount;j++){//li태그 수많큼 반복함
var value=lists.childNodes[j];
text+=value.childNodes[0].textContent+" "+value.childNodes[1].textContent+",\n";
/*이렇게 작업 안하고 push하면 되지 않나라고 생각 하셨다면
좋은 생각은 아닙니다.. push로하면 {contents},{...}이런식으로 저장하기 때문에
text로 출력하면 이쉼표도 출력이 됩니다.
*/
}
var element = document.createElement('a');
element.setAttribute('href','data:text/plain;charset=utf-8,'+encodeURIComponent(text));
element.download="Todos.txt";
element.click();
}
return (
<div>
<Leight leight={leight}>{leight} items left</Leight>
<Contents nowfilter={nowfilter===filter.SHOW_ALL} onClick={()=>{setVisibilityFilter(filter.SHOW_ALL)}}>all</Contents>
<Contents nowfilter={nowfilter===filter.SHOW_COMPLETED} onClick={()=>{setVisibilityFilter(filter.SHOW_COMPLETED)}}>clear</Contents>
<Contents nowfilter={nowfilter===filter.SHOW_ACTIVE} onClick={()=>{setVisibilityFilter(filter.SHOW_ACTIVE)}}>active</Contents>
<Contents onClick={()=>{setVisibilityFilter(filter.ALL_CLAER)}}>clear completed</Contents>
<Contents onClick={Download}>Download</Contents>
</div>
);
}
const selectTodos = state => state.Todosreducer;
const selectFilter = state => state.Filterruder;
const selectdays=state =>state.DaysReducer;
const selectsize=createSelector(
/*이건 길이값을 어떻게 하면 가져올 수 있을까
하다가 끝네 이방법말고는 생각이 않나서 이렇게 만들었습니다
이건 다른 방법있으면 저도좀 알려주세요 ㅎㅎ
필터는 todolist에서 한것과 같습니다 다만 필터를 적용한뒤에 todos에 길이 만 가져옵니다.
*/
[selectTodos,selectFilter,selectdays],
(todos,nowfilter,days)=>{
switch(nowfilter){
case filter.SHOW_ALL:
return todos.filter(t =>days.month===t.month && days.day===t.day).length;
case filter.SHOW_COMPLETED:
return todos.filter(t => t.completed && days.month===t.month && days.day===t.day).length;
case filter.SHOW_ACTIVE:
return todos.filter(t => !t.completed&& days.month===t.month && days.day===t.day).length;
default:
return 0;
}
}
)
const commdispatch=function(state,nowstate){
return{
nowfilter:state.Filterruder,
leight:selectsize(state)
}
}
export default connect(commdispatch,mapDispatch)(Fotter);
결과:
혹시 똑같이 했는 데 안된다... 면 혹시라도 추가나 변경을 잘못하지 않았다 확인해 주시고 그래도 안된다면....
죄송합니다 ㅠㅠ
아레에 완성본이 있습니다. 이걸보고 해주세요.!! ^^;;;
https://github.com/famous0811/TodosRedux
famous0811/TodosRedux
Contribute to famous0811/TodosRedux development by creating an account on GitHub.
github.com
출쳐:
https://redux-toolkit.js.org/tutorials/intermediate-tutorial#creating-and-using-the-filters-slice
Redux Toolkit
# Intermediate Tutorial: Redux Toolkit in Action
redux-toolkit.js.org