---
tags: React
---
# React - Redux Saga todo-app
**gitlab專案連結** : [[Redux saga todo-app]](http://192.168.1.136/robin98727/Redux-Saga-todo-app)
完整畫面[圖片點此](https://i.imgur.com/wPOyjuk.png)
## 1. 目錄架構
```
Redux-Saga-todo-app
|_____ src
|_____actions
| |_____index.js
|_____components
| |_____App.jsx
|_____containers
| |_____App.jsx
|_____lib
| |_____api.js
|_____reducers
| |_____clearapi.js
| |_____getapi.js
| |_____index.js
| |_____postapi.js
| |_____putapi.js
|_____sagas
| |_____index.js
```
## 2. 程式流程

## 3.功能實作
### 功能一(新增列表)
#### 程式碼(components/App.jsx)
對應function
```javascript=
//輸入文字
handleSubmit(e) {
e.preventDefault() //避免畫面刷新
const taskname = e.target.querySelector('#taskname').value
const done = false
this.props.fetchPostDataRequest(taskname,done) //發出寫入api請求
this.setState({value: ''}) //清空文字欄剛輸入的文字
}
```
render()
```javascript=
//輸入框
<form onSubmit={this.handleSubmit}>
<input type = "text" id = "taskname" value={this.state.value} onChange={this.textChange}/>
</form>
//顯示列表
<ul>
{ this.props.names.map((d, i) => {
return this.isShow(this.props.finishs[i]) && <li key={i}>
<input type="checkbox" onChange={(e) =>this.changeDone(e,d,i)} checked={this.props.finishs[i]}/>
<span>{d}</span>
</li> })}
</ul>
```
#### 程式碼(actions/index.js)
API post
```javascript=
//發出寫入API的請求
export const FETCH_POST_DATA_REQUEST = "FETCH_POST_DATA_REQUEST"
export const fetch_post_data_request = (name,finish) => action(FETCH_POST_DATA_REQUEST, { name,finish })
//成功寫入API
export const FETCH_POST_DATA_SUCCESS = "FETCH_POST_DATA_SUCCESS"
export const fetch_post_data_success = () => action(FETCH_POST_DATA_SUCCESS)
//寫入API失敗
export const FETCH_POST_DATA_FAILURE = "FETCH_POST_DATA_FAILURE"
export const fetch_post_data_failure = (error) => action(FETCH_POST_DATA_FAILURE, { error })
```
API get
```javascript=
//發出讀取API的請求
export const FETCH_GET_DATA_REQUEST = "FETCH_GET_DATA_REQUEST"
export const fetch_get_data_request = () => action(FETCH_GET_DATA_REQUEST)
//讀取API成功
export const FETCH_GET_DATA_SUCCESS = "FETCH_GET_DATA_SUCCESS"
export const fetch_get_data_success = (name,finish) => action(FETCH_GET_DATA_SUCCESS, { name,finish })
//讀取API失敗
export const FETCH_GET_DATA_FAILURE = "FETCH_GET_DATA_FAILURE"
export const fetch_get_data_failure = (error) => action(FETCH_GET_DATA_FAILURE, { error })
```
#### 程式碼(containers/App.jsx)
```javascript=
import {
fetch_post_data_request
} from '../actions'
//api data存在names、finishs的array中
function mapStateToProps(state) {
return {
names: state.getapi.names,
finishs: state.getapi.finishs,
}
}
function mapDispatchToProps(dispatch) {
return {
fetchPostDataRequest: (name, finish) => dispatch(fetch_post_data_request(name, finish))
};
}
```
#### 程式碼(reducers/postapi.js)
```javascript=
import {
FETCH_POST_DATA_REQUEST,
FETCH_POST_DATA_FAILURE,
FETCH_POST_DATA_SUCCESS
} from '../actions/index'
const InitialState = {
err: '',
postisloading: false //資料讀取是否讀取中
}
export default (state = InitialState, action) => {
switch(action.type) {
case FETCH_POST_DATA_REQUEST:
return {
...state,
postisloading: true
}
case FETCH_POST_DATA_SUCCESS:
return {
...state,
postisloading: false
}
case FETCH_POST_DATA_FAILURE:
return {
...state,
err: action.err,
postisloading: false
}
default:
return state
}
}
```
#### 程式碼(reducers/getapi.js)
```javascript=
import {
FETCH_GET_DATA_SUCCESS,
FETCH_GET_DATA_FAILURE,
FETCH_GET_DATA_REQUEST
} from '../actions/index'
const InitialState = {
names: [], //名稱
finishs: [], //是否打勾
err: '', //錯誤訊息
isloading: false //資料是否讀取中
}
export default (state = InitialState, action) => {
switch(action.type) {
case FETCH_GET_DATA_REQUEST:
return {
...state,
isloading: true
}
case FETCH_GET_DATA_SUCCESS:
return {
...state,
names: action.name,
finishs: action.finish,
isloading: false
}
case FETCH_GET_DATA_FAILURE:
return {
...state,
err: action.err,
isloading: false
}
default:
return state
}
}
```
#### 程式碼(sagas/index.js)
發出post的api
```javascript=
function* fetchPostData(action) {
//將值寫入api
const data = yield call(apiCallToFetchPost, action.name, false)
if (data) { //判斷是否有資料
//回傳成功
yield put(actionname.fetch_post_data_success())
} else {
//回傳失敗
yield put(actionname.fetch_post_data_failure(data.error))
}
}
//呼叫POST API,使用者輸入後會傳入name,狀態為未勾選
function apiCallToFetchPost(name) {
return (
fetch(api_url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(({
title: name,
completed: false //未勾選
}))
})
.then(res => {
if (res.ok) return res.json() //回傳輸入的資料
else return null
})
.catch((error) => {
console.error(error)
})
)
}
```
當觸發action時呼叫對應function
```javascript=
export default function* root() {
yield takeLatest([actionname.FETCH_POST_DATA_SUCCESS,
actionname.FETCH_GET_DATA_REQUEST,
actionname.FETCH_POST_DATA_REQUEST],
fetchGetData)
yield takeLatest(actionname.FETCH_POST_DATA_REQUEST, fetchPostData)
}
```
### 功能二(勾選資料)
#### 程式碼(components/App.jsx)
對應function
```javascript=
//改變所有checkbox的勾選狀態
SelectAll(done) {
this.props.fetchPutAllDataRequest(done)
}
//勾選全選的按鈕
handleSelectAll() {
const isAll = this.isSelectAll()
this.SelectAll(!isAll)
}
//計算是否全部的checkbox都被勾選
isSelectAll() {
if(!this.props.names.length) return false
for(let i = 0; i < this.props.names.length; i++) {
if(!this.props.finishs[i]) return false
}
return true //如果全部的checkbox都被點選,全選鈕自動被勾選
}
//每次點選checkbox按鈕
changeDone(e,d,i) {
//傳入名稱、編號
const check = this.props.finishs[i]
this.props.fetchPutDataRequest(d,i,check)
}
```
render
```javascript=
//全選的按鈕
<input type="checkbox" onChange={this.handleSelectAll} checked={this.isSelectAll()}/>
<span>全選</span>
//列表以及勾選狀態
<ul>
{ this.props.names.map((d, i) => {
return this.isShow(this.props.finishs[i]) && <li key={i}>
<input type="checkbox" onChange={(e) =>this.changeDone(e,d,i)} checked={this.props.finishs[i]}/>
<span>{d}</span>
</li> })}
</ul>
```
#### 程式碼(actions/index.js)
列表勾選
```javascript=
//發出修改勾選狀態請求
export const FETCH_PUT_DATA_REQUEST = "FETCH_PUT_DATA_REQUEST"
export const fetch_put_data_request = (name,idx,check) => action(FETCH_PUT_DATA_REQUEST, {name, idx, check })
//修改勾選狀態成功
export const FETCH_PUT_DATA_SUCCESS = "FETCH_PUT_DATA_SUCCESS"
export const fetch_put_data_success = () => action(FETCH_PUT_DATA_SUCCESS)
//修改勾選狀態失敗
export const FETCH_PUT_DATA_FAILURE = "FETCH_PUT_DATA_FAILURE"
export const fetch_put_data_failure = (error) => action(FETCH_PUT_DATA_FAILURE, { error })
```
全選鈕
```javascript=
//發出勾選全選鈕請求
export const FETCH_PUT_ALL_DATA_REQUEST = "FETCH_PUT_ALL_DATA_REQUEST"
export const fetch_put_all_data_request = (done) => action(FETCH_PUT_ALL_DATA_REQUEST, { done })
//勾選全選鈕成功
export const FETCH_PUT_ALL_DATA_SUCCESS = "FETCH_PUT_ALL_DATA_SUCCESS"
export const fetch_put_all_data_success = () => action(FETCH_PUT_ALL_DATA_SUCCESS)
//勾選全選鈕失敗
export const FETCH_PUT_ALL_DATA_FAILURE = "FETCH_PUT_ALL_DATA_FAILURE"
export const fetch_put_all_data_failure = (error) => action(FETCH_PUT_ALL_DATA_FAILURE, { error })
```
#### 程式碼(reducers/getapi.js)
```javascript=
import {
//列表勾選
FETCH_PUT_DATA_SUCCESS,
FETCH_PUT_DATA_FAILURE,
FETCH_PUT_DATA_REQUEST,
//全選
FETCH_PUT_ALL_DATA_SUCCESS,
FETCH_PUT_ALL_DATA_REQUEST,
FETCH_PUT_ALL_DATA_FAILURE
} from '../actions/index'
const InitialState = {
err: '',
check_isdoing: false,
check_all_isdoing: false
}
export default (state = InitialState, action) => {
switch(action.type) {
//發出勾選列表按鈕請求
case FETCH_PUT_DATA_REQUEST:
return {
...state,
check_isdoing: true
}
//勾選列表按鈕成功
case FETCH_PUT_DATA_SUCCESS:
return {
...state,
check_isdoing: false
}
//勾選列表按鈕失敗
case FETCH_PUT_DATA_FAILURE:
return {
...state,
err: action.err,
check_isdoing: false
}
//發出勾選全選按鈕請求
case FETCH_PUT_ALL_DATA_REQUEST:
return {
...state,
check_all_isdoing: true
}
//勾選全選按鈕成功
case FETCH_PUT_ALL_DATA_SUCCESS:
return {
...state,
check_all_isdoing: false
}
//勾選全選按鈕失敗
case FETCH_PUT_ALL_DATA_FAILURE:
return {
...state,
err: action.err,
check_all_isdoing: false
}
default:
return state
}
}
```
#### 程式碼(sagas/index.js)
列表勾選
```javascript=
function* fetchPutData(action) {
var idx = action.idx + 1 //編號
const name = action.name //名稱
const check = !action.check //勾選
const form = {name,check,idx}
const data = yield call(apiCallToFetchPut,form)
if (data) { //判斷是否有回傳資料
yield put(actionname.fetch_put_data_success()) //勾選修改成功
}
else {
yield put(actionname.fetch_put_data_failure(data.error)) //勾選修改失敗
}
}
function apiCallToFetchPut(form) {
const {name, check, idx} = form
const data = { title: name ,completed: check}
return (
fetch(`http://192.168.1.130:3001/items/${idx}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(res => {
if (res.ok) return res.json()
else return null
})
.catch((error) => {
console.error(error)
})
)
}
```
全選
```javascript=
function getItem(i) {
return (
fetch(`http://192.168.1.130:3001/items/${i}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(res => {
if (res.ok) return res.json()
else return null
})
.catch((error) => {
console.error(error)
})
)
}
function* fetchPutAllData(action) {
const alldata = yield call(apiCallToFetchGet)
const alldatalen = alldata.length //計算目前API中資料有多少
if(alldata) {
for(let i = 1; i <= alldatalen; i++){ //將全部資料修改勾選狀態
const data = yield call(getItem,i)
const name = data.title
const check = action.done
const idx = i
const form = {name,check,idx}
yield call(apiCallToFetchPut,form)
}
yield put(actionname.fetch_put_all_data_success()) //修改成功
}
else{
yield put(actionname.fetch_put_all_data_failure(alldata.error)) //修改失敗
}
}
```
### 功能三(資料狀態分類)
只修改state上的filter而已
#### 程式碼(components/App.jsx)
```javascript=
//計算Active有幾個
count() {
let count = 0;
this.props.finishs.forEach(item=>{
if(item!=true)
count++
})
return count;
}
//改變分類
changeFilter(filter) {
this.setState({
filter: filter
})
}
//顯示目前所在分類
renderItem(name) {
const {filter,changeFilter} = this.state
return name===filter?
<span>{name}</span> :
<a href={'#'+name} onClick={e=>this.changeFilter(name)}>{name}</a>
}
//顯示分類內的清單列表
isShow(done) {
const { filter } = this.state
switch(filter) {
case 'Active': return done === false
case 'Completed': return done === true
default: return true
}
}
```
### 功能四(等待資料顯示)
每次送出request時,畫面上會顯示資料讀取中

如果request失敗,畫面則顯示資料讀取失敗
