웹/react

나도 만들 수 있다 todolist (외전)

allblack 2020. 5. 18. 04:55
반응형

잡담

아... 원래 이거 쓰고 있었는데 한번 날아 가서... 다시 작성합니다. ㅠㅠ

 

저번까지 했던 파일:

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

 

반응형