笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
todolist案例
初始化项目
- 新建react项目
- 对自动创建的react项目进行初始化,删除不需要文件并修改相应内容
组件拆分
- 老师给了一份静态的HTML怠慢,结构可以直接拷贝
- 分为header、main和footer三个组件
- 样式文件也直接使用老师给的css文件,使用全局引入即可
需要创建的目录结构如下:
src目录
- Comprnents --------------------- 组件目录
- Header.js
- Main.js
- Footer.js
// src/index.js 引入全局样式
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
// 引入全局Css样式文件
import './css/index.css'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
// src/App.js 引入三个组件并使用
import {Component} from 'react'
// 引入三个子组件
import Header from './Components/Header'
import Main from './Components/Main'
import Footer from './Components/Footer'
class App extends Component {
render() {
return (
<div className="todoapp">
{/* 使用子组件 */}
<Header />
<Main />
<Footer />
</div>
)
}
}
export default App
//src/Components/Header.js Header组件
import {Component} from 'react'
class Header extends Component {
render() {
return (
<header className="header">
<h1>todos</h1>
<input className="new-todo" placeholder="还有什么任务没有完成?" autoFocus />
</header>
)
}
}
export default Header
// src/Components/Main.js Main组件
import {Component} from 'react'
class Main extends Component {
render() {
return (
<section className="main">
<input className="toggle-all" type="checkbox" />
<ul className="todo-list">
<li className="completed">
<div className="view">
<input className="toggle" type="checkbox" checked readOnly/>
<label>品味JavaScript</label>
<button className="destroy"></button>
</div>
<input className="edit" value="Create a TodoMVC template" readOnly/>
</li>
<li>
<div className="view">
<input className="toggle" type="checkbox" readOnly />
<label>买一个宠物</label>
<button className="destroy"></button>
</div>
<input className="edit" value="Rule the web" readOnly/>
</li>
</ul>
</section>
)
}
}
export default Main
// src/Components/Footer.js Footer组件
import {Component} from 'react'
class Footer extends Component {
render() {
return (
<footer className="footer">
<span className="todo-count">
<strong>0</strong> item left
</span>
<ul className="filters">
<li>
<span>All</span>
</li>
<li>
<span>Active</span>
</li>
<li>
<span>Completed</span>
</li>
</ul>
<button className="clear-completed">Clear completed</button>
</footer>
)
}
}
export default Footer
任务列表显示
- 这里使用redux创建store管理数据,获取数据使用老师提供的接口
- 先书写指令action - 使用redux-actions简化代码
- 发送请求获取数据
- 异步操作结束后触发新指令
- 然后书写reducer - 同样适用redux-actions简化代码 - 注意reducer需要进行拆分合并
- 默认给一个空数组,当异步请求执行后,把数据添加给空数组,然后组件获取数据就可以了
- 将store的创建、中间件saga单独创建一个文件,放在store下面的index.js中
- 将saga异步获取数据和触发另一个指令单独书写在saga目录中,注意老师这里书写的指令名称使用的action输出的
- main组件中调用数据,设置好dispatch
- 在组件挂载完成后获取数据,将数据交给props
需要创建的目录结构如下:
src目录
- Comprnents --------------------- 组件目录
- Header.js
- Main.js
- Footer.js
- store -------------------------- redux数据管理目录
- Actions ---------------------- 指令目录
- todos.action.js
- Reducers --------------------- reducer目录
- index.js
- todos.reducer.js
- Saga ------------------------- redux-saga目录
- todos.saga.js
- index ------------------------ store创建和saga文件
// src/Store/Actions/todos.action.js todos的指令
// 引入 createAction 对 action 进行简化
import {createAction} from 'redux-actions'
// 导出action
export const getTodos = createAction('getTodos')
export const addTodos = createAction('addTodos')
//src/Store/Reducers/todos.reducer.js 设置todos的数据和指令函数
// 引入 handleActions 简化书写,注意这里是 handleActions 而不是 handleAction
import {handleActions} from 'redux-actions'
// 引入实际更改数据的指令,另一个 getData 不能直接更改数据就不需要引入了
import {addTodos} from '../Actions/todos.action'
// 原始数据
const data = {
// 空的列表
todos: []
}
// 创建指令函数
const todosReducer = handleActions({
[addTodos]: (state, action) => ({todos: action.payload})
}, data)
// 导出
export default todosReducer
// src/Store/Reducers/index.js 合并并导出reducer
// 引入 combineReducers 用来合并 reducer
import {combineReducers} from 'redux'
// 引入需要合并的 reducer
import todosReducer from './todos.reducer'
// 执行合并并导出
export default combineReducers({
todosReducer: todosReducer
})
// src/Store/index.js 仓库的创建、中间件创建、saga倒入单独书写在一个js文件中
// 引入 createStore 创建仓库,applyMiddleware 创建中间件
import {createStore, applyMiddleware} from 'redux'
// 引入中间件 saga
import reduxSage from 'redux-saga'
// 引入仓库需要使用的 reducer
import reducer from './Reducers'
// 引入需要使用 saga
import todoSaga from './Sagas/todos.saga'
// 创建 saga
const saga = reduxSage()
// 创建 store 数据仓库,并创建中间件
const store = createStore(reducer, applyMiddleware(saga))
// saga 调用 run 方法使 saga 生效
saga.run(todoSaga)
export default store
// src/Store/Sagas/todos.saga.js todos的saga中间件
// 引入 saga 的两个方法,takeEvery 捕捉指令并拦截,put将指令传递给 store
import {takeEvery, put} from 'redux-saga/effects'
// 引入 axios 请求接口数据
import axios from 'axios'
// 捕捉指令后要执行的函数
function* loaderData() {
// 获取数据
const {data} = yield axios.get('http://localhost:3005/api/todos')
// 调用 put 方法将数据传递给其他指令
yield put({type: 'addTodos', payload: data})
}
// 导出 saga
export default function* getTodos() {
// 捕捉 getTodos指令,捕捉到之后执行 loaderData 方法
yield takeEvery('getTodos', loaderData)
}
// src/Components/Main.js main组件使用指令和数据
import {Component} from 'react'
// 引入 bindActionCreators 用来自动生成 action 指令函数
import {bindActionCreators} from 'redux'
// connect 获取数据并添加指令方法
import {connect} from 'react-redux'
// 获取全部指令
import * as todoAction from '../Store/Actions/todos.action'
class Main extends Component {
// 生命周期:组件挂载后
componentDidMount() {
// 调用组件的指令函数 getTodos() 获取数据并让 props 拿到
this.props.getTodos()
}
render() {
return (
<section className="main">
<input className="toggle-all" type="checkbox" />
<ul className="todo-list">
{this.props.todos.map(item => {
return (
// 遍历全部的待办事项列表,注意这里设置一下是否添加 completed 类名,如果已完成添加,未完成不添加
<li key={item.id} className={item.isCompleted ? "completed" : ''}>
<div className="view">
{/* 是否选中复选框 - 根据数据判断是否选中 */}
<input className="toggle" type="checkbox" checked={item.isCompleted === true} readOnly/>
<label>{item.taskName}</label>
<button className="destroy"></button>
</div>
<input className="edit" value="Create a TodoMVC template" readOnly/>
</li>
)
})}
</ul>
</section>
)
}
}
// connect第一个参数 - 数据
const myData = store => ({
todos: store.todosReducer.todos
})
// connect第二个参数 - 指令函数
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
// 导出并调用 connect 注入数据和指令
export default connect(myData, myAction)(Main)
//src/index.js 将之前创建的 store 传递给 app
import React from 'react'
import ReactDOM from 'react-dom'
// 引入 Provider 将 store 向后传递
import {Provider} from 'react-redux'
import App from './App'
// 引入全局Css样式文件
import './css/index.css'
// 引入创建的 store
import store from './Store/index'
ReactDOM.render(
<React.StrictMode>
{/* 传递store */}
<Provider store={store}><App /></Provider>
</React.StrictMode>,
document.getElementById('root')
)
为了统一指令名称,这里把addTodos改名为getTodos_success,后面也会使用_success作为后缀成对命名指令
添加任务实现
添加任务在header组件中实现
- 抬起键盘的时候判断是否是回车键(keyCode=13),如果是回车键调用后端接口添加任务
- 注意这时需要判断文本框内容是否为空,注意去掉两侧空白 - trim(),为空弹出提示要求填入
- 添加任务依旧使用指令完成,组件把添加的数据传递给指令,注意想要使用指令就要对组件进行redux处理
- 中间件添加依旧交给saga完成,saga可以获取到拦截的指令信息,调用接口后接口会把要添加的数据返回给我们,我们把返回的诗句传给下一个指令
- 在reducre中使用指令进行添加即可
- 最后清空文本框
// src/Store/Actions/todos.action.js 添加新指令用于后面使用
// 引入 createAction 对 action 进行简化
import {createAction} from 'redux-actions'
// 初始化数据指令
export const getTodos = createAction('getTodos')
export const getTodos_success = createAction('getTodos_success')
// 添加任务指令
export const addTodos = createAction('addTodos')
export const addTodos_success = createAction('addTodos_success')
// src/Store/Sagas/todos.saga.js 给saga添加指令拦截进行异步请求,拦截后调用新指令
// 引入 saga 的两个方法,takeEvery 捕捉指令并拦截,put将指令传递给 store
import {takeEvery, put} from 'redux-saga/effects'
// 引入 axios 请求接口数据
import axios from 'axios'
// 捕捉指令后要执行的函数
function* loaderData() {
// 获取数据
const {data} = yield axios.get('http://localhost:3005/api/todos')
// 调用 put 方法将数据传递给其他指令
yield put({type: 'getTodos_success', payload: data})
}
// 添加任务指令拦截后执行的函数
function* addTodos(action) {
// 调用接口添加任务
const {data} = yield axios.post('http://localhost:3005/api/todos', {taskName: action.payload})
// 将接口返回的数据交给下一个指令处理
yield put({type: 'addTodos_success', payload: data.task})
}
// 导出 saga
export default function* getTodos() {
// 捕捉 getTodos 指令,捕捉到之后执行 loaderData 方法
yield takeEvery('getTodos', loaderData)
// 捕捉a ddTodos 指令,捕捉后执行 addTodos 方法
yield takeEvery('addTodos', addTodos)
}
// src/Store/Reducers/todos.reducer.js 数据中真正的添加任务
// 引入 handleActions 简化书写,注意这里是 handleActions 而不是 handleAction
import {handleActions} from 'redux-actions'
// 引入实际更改数据的指令,中间件saga拦截的指令就不需要引入了
import {getTodos_success, addTodos_success} from '../Actions/todos.action'
// 原始数据
const data = {
// 空的列表
todos: []
}
// 创建指令函数
const todosReducer = handleActions({
// 初始化数据
[getTodos_success]: (state, action) => ({todos: action.payload}),
// 添加任务
[addTodos_success]: (state, action) => ({todos: [...state.todos, action.payload]})
}, data)
// 导出
export default todosReducer
// src/Components/Header.js Header组件中使用redux
import {Component} from 'react'
// 引入redux需要使用的方法和指令
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as todoAction from '../Store/Actions/todos.action'
class Header extends Component {
// input标签事件函数
addTodos(ev) {
// 01、判断抬起的案件是不是回车键
if(ev.keyCode === 13) {
// 02、如果是回车键获取内容并去除两侧空白
const value = ev.target.value.trim()
// 03、判断去除空白后的结果是为空
if (value.length > 0) {
// 04、如果有内容。把数据传递给指令方法
this.props.addTodos(value)
}
// 05、只要抬起回车键就清空内容,不管是不是为空
ev.target.value = ''
}
}
render() {
return (
<header className="header">
<h1>todos</h1>
{/* 给 input 添加键盘抬起事件 */}
<input className="new-todo" placeholder="还有什么任务没有完成?" autoFocus onKeyUp={(ev) => this.addTodos(ev)} />
</header>
)
}
}
// redux数据
const myData = store => ({
todos: store.todosReducer.todos
})
// 指令
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
export default connect(myData, myAction)(Header)
任务删除实现
删除的实现在main组件中实现
- 点击删除按钮的时候获取要删除的任务的ID值
- 在真正删除任务之前我们给一个确认步骤(confirm())
- 删除依旧使用指令完成,配合saga中间件,将要删除的id传递给指令
- saga拦截指令后调用后台接口执行删除,接口会吧要删除的数据返回给我们
- 在reducer中利用这个id使用findIndex方法查找所对应的索引值
- 使用深拷贝拷贝任务数组,然后删除指定任务最后替换原来的数组(因为我们不能直接修改列表)
// src/Store/Actions/todos.action.js 添加指令
// 引入 createAction 对 action 进行简化
import {createAction} from 'redux-actions'
// 初始化数据指令
export const getTodos = createAction('getTodos')
export const getTodos_success = createAction('getTodos_success')
// 添加任务指令
export const addTodos = createAction('addTodos')
export const addTodos_success = createAction('addTodos_success')
// 删除任务指令
export const removTodos = createAction('removTodos')
export const removTodos_success = createAction('removTodos_success')
// src/Store/Sagas/todos.saga.js 书写saga拦截指令和拦截后的回调函数
// 引入 saga 的两个方法,takeEvery 捕捉指令并拦截,put将指令传递给 store
import {takeEvery, put} from 'redux-saga/effects'
// 引入 axios 请求接口数据
import axios from 'axios'
// 捕捉指令后要执行的函数
function* loaderData() {
// 获取数据
const {data} = yield axios.get('http://localhost:3005/api/todos')
// 调用 put 方法将数据传递给其他指令
yield put({type: 'getTodos_success', payload: data})
}
// 添加任务指令拦截后执行的函数
function* addTodos(action) {
// 调用接口添加任务
const {data} = yield axios.post('http://localhost:3005/api/todos', {taskName: action.payload})
// 将接口返回的数据交给下一个指令处理
yield put({type: 'addTodos_success', payload: data.task})
}
// 删除任务指令拦截后执行的函数
function* removTodos(action) {
// 调用接口根据 ID 删除任务
const {data} = yield axios.delete('http://localhost:3005/api/todos', { params: {id: action.payload}})
// 调用另一个指令,把要删除的 id 传递过去
yield put({type: 'removTodos_success', payload: data.tasks.id})
}
// 导出 saga
export default function* getTodos() {
// 捕捉 getTodos 指令,捕捉到之后执行 loaderData 方法
yield takeEvery('getTodos', loaderData)
// 捕捉a ddTodos 指令,捕捉后执行 addTodos 方法
yield takeEvery('addTodos', addTodos)
// 捕捉 removTodos 指令,捕捉后执行 removTodos 方法
yield takeEvery('removTodos', removTodos)
}
// src/Components/Main.js 组件添加事件,事件内部调用指令
import {Component} from 'react'
// 引入 bindActionCreators 用来自动生成 action 指令函数
import {bindActionCreators} from 'redux'
// connect 获取数据并添加指令方法
import {connect} from 'react-redux'
// 获取全部指令
import * as todoAction from '../Store/Actions/todos.action'
class Main extends Component {
// 生命周期:组件挂载后
componentDidMount() {
// 调用组件的指令函数 getTodos() 获取数据并让 props 拿到
this.props.getTodos()
}
// 删除任务函数
removTodos(id) {
// 把要删除的任务的 id 传递给指令
this.props.removTodos(id)
}
render() {
return (
<section className="main">
<input className="toggle-all" type="checkbox" />
<ul className="todo-list">
{this.props.todos.map(item => {
return (
// 遍历全部的待办事项列表,注意这里设置一下是否添加 completed 类名,如果已完成添加,未完成不添加
<li key={item.id} className={item.isCompleted ? "completed" : ''}>
<div className="view">
{/* 是否选中复选框 - 根据数据判断是否选中 */}
<input className="toggle" type="checkbox" checked={item.isCompleted} readOnly/>
<label>{item.taskName}</label>
{/* 添加点击方法删除任务 */}
<button className="destroy" onClick={() => this.removTodos(item.id)}></button>
</div>
<input className="edit" value="Create a TodoMVC template" readOnly/>
</li>
)
})}
</ul>
</section>
)
}
}
// connect第一个参数 - 数据
const myData = store => ({
todos: store.todosReducer.todos
})
// connect第二个参数 - 指令函数
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
// 导出并调用 connect 注入数据和指令
export default connect(myData, myAction)(Main)
// src/Store/Reducers/todos.reducer.js 修改真正的数据
// 引入 handleActions 简化书写,注意这里是 handleActions 而不是 handleAction
import {handleActions} from 'redux-actions'
// 引入实际更改数据的指令,中间件saga拦截的指令就不需要引入了
import {getTodos_success, addTodos_success, removTodos_success} from '../Actions/todos.action'
// 原始数据
const data = {
// 空的列表
todos: []
}
// 创建指令函数
const todosReducer = handleActions({
// 初始化数据
[getTodos_success]: (state, action) => ({todos: action.payload}),
// 添加任务
[addTodos_success]: (state, action) => ({todos: [...state.todos, action.payload]}),
// 删除任务
[removTodos_success]: (state, action) => {
// 根据传递过来的 ID 获得在任务列表中的 下标
const index = state.todos.findIndex(item => item.id === action.payload)
// 深拷贝任务列表
const todos = JSON.parse(JSON.stringify(state.todos))
// 从任务列表中删除指定的任务
todos.splice(index, 1)
// 用修改后的 todos 替换掉原本的 todos
return { todos }
}
}, data)
// 导出
export default todosReducer
修改任务状态
状态修改在main组件中实现
- 任务状态取决于任务的 isCompleted属性,所以我们修改状态操作主要需要操作的就是这个属性
- 点击复选框按钮的时候获取当前任务id值
- 然后依旧使用指令功能
- saga拦截指令,进行异步请求,请求后后台会吧要修改状态的任务返回给我们
- 把要删除任务信息传递给下一个指令
- 在reducer中/进行数据修改操作,同样需要深拷贝,修改完之后替换掉现有数据
// src/Store/Actions/todos.action.js 添加修改状态的指令
// 引入 createAction 对 action 进行简化
import {createAction} from 'redux-actions'
// 初始化数据指令
export const getTodos = createAction('getTodos')
export const getTodos_success = createAction('getTodos_success')
// 添加任务指令
export const addTodos = createAction('addTodos')
export const addTodos_success = createAction('addTodos_success')
// 删除任务指令
export const removTodos = createAction('removTodos')
export const removTodos_success = createAction('removTodos_success')
// 修改状态指令
export const modifyTodos = createAction('modifyTodos')
export const modifyTodos_success = createAction('modifyTodos_success')
// src/Store/Sagas/todos.saga.js saga拦截指令并调用回调函数
// 引入 saga 的两个方法,takeEvery 捕捉指令并拦截,put将指令传递给 store
import {takeEvery, put} from 'redux-saga/effects'
// 引入 axios 请求接口数据
import axios from 'axios'
// 捕捉指令后要执行的函数
function* loaderData() {
// 获取数据
const {data} = yield axios.get('http://localhost:3005/api/todos')
// 调用 put 方法将数据传递给其他指令
yield put({type: 'getTodos_success', payload: data})
}
// 添加任务指令拦截后执行的函数
function* addTodos(action) {
// 调用接口添加任务
const {data} = yield axios.post('http://localhost:3005/api/todos', {taskName: action.payload})
// 将接口返回的数据交给下一个指令处理
yield put({type: 'addTodos_success', payload: data.task})
}
// 删除任务指令拦截后执行的函数
function* removTodos(action) {
// 调用接口根据 ID 删除任务
const {data} = yield axios.delete('http://localhost:3005/api/todos', { params: {id: action.payload}})
// 调用另一个指令,把要删除的 id 传递过去
yield put({type: 'removTodos_success', payload: data.tasks.id})
}
// 捕捉修改状态指令后的回调函数
function* modifyTodos(action) {
// 调用接口修改状态
const {data} = yield axios.put('http://localhost:3005/api/todos/isCompleted', action.payload)
// 调用另一个指令,把接口返回的数据传递给下一个指令
yield put({type: 'modifyTodos_success', payload: data.task})
}
// 导出 saga
export default function* getTodos() {
// 捕捉 getTodos 指令,捕捉到之后执行 loaderData 方法
yield takeEvery('getTodos', loaderData)
// 捕捉a ddTodos 指令,捕捉后执行 addTodos 方法
yield takeEvery('addTodos', addTodos)
// 捕捉 removTodos 指令,捕捉后执行 removTodos 方法
yield takeEvery('removTodos', removTodos)
// 捕捉 modifyTodos 指令,然后调用 modifyTodos 方法
yield takeEvery('modifyTodos', modifyTodos)
}
// src/Store/Reducers/todos.reducer.js 书写真正修改数据指令代码
// 引入 handleActions 简化书写,注意这里是 handleActions 而不是 handleAction
import {handleActions} from 'redux-actions'
// 引入实际更改数据的指令,中间件saga拦截的指令就不需要引入了
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success} from '../Actions/todos.action'
// 原始数据
const data = {
// 空的列表
todos: []
}
// 创建指令函数
const todosReducer = handleActions({
// 初始化数据
[getTodos_success]: (state, action) => ({todos: action.payload}),
// 添加任务
[addTodos_success]: (state, action) => ({todos: [...state.todos, action.payload]}),
// 删除任务
[removTodos_success]: (state, action) => {
// 根据传递过来的 ID 获得在任务列表中的 下标
const index = state.todos.findIndex(item => item.id === action.payload)
// 深拷贝任务列表
const todos = JSON.parse(JSON.stringify(state.todos))
// 从任务列表中删除指定的任务
todos.splice(index, 1)
// 用修改后的 todos 替换掉原本的 todos
return { todos }
},
// 修改状态
[modifyTodos_success]: (state, action) => {
// 根据传递过来的 ID 获得在任务列表中的 下标
const index = state.todos.findIndex(item => item.id === action.payload.id)
// 深拷贝任务列表
const todos = JSON.parse(JSON.stringify(state.todos))
// 从任务列表中替换指定位置数据
todos[index] = action.payload
// 用修改后的 todos 替换掉原本的 todos
return { todos }
}
}, data)
// 导出
export default todosReducer
// src/Components/Main.js 组件添加事件触发指令并传递参数
import {Component} from 'react'
// 引入 bindActionCreators 用来自动生成 action 指令函数
import {bindActionCreators} from 'redux'
// connect 获取数据并添加指令方法
import {connect} from 'react-redux'
// 获取全部指令
import * as todoAction from '../Store/Actions/todos.action'
class Main extends Component {
// 生命周期:组件挂载后
componentDidMount() {
// 调用组件的指令函数 getTodos() 获取数据并让 props 拿到
this.props.getTodos()
}
// 删除任务函数
removTodos(id) {
// 把要删除的任务的 id 传递给指令
this.props.removTodos(id)
}
modifyTodos(ev, id) {
// 把要修改状态的任务需要的数据传递过去(要修改为的状态, id值)
this.props.modifyTodos({id, isCompleted: ev.target.checked})
}
render() {
return (
<section className="main">
<input className="toggle-all" type="checkbox" />
<ul className="todo-list">
{this.props.todos.map(item => {
return (
// 遍历全部的待办事项列表,注意这里设置一下是否添加 completed 类名,如果已完成添加,未完成不添加
<li key={item.id} className={item.isCompleted ? "completed" : ''}>
<div className="view">
{/* 是否选中复选框 - 根据数据判断是否选中,添加状态修改事件 */}
<input className="toggle" type="checkbox" checked={item.isCompleted} onChange={(ev) => this.modifyTodos(ev, item.id)} />
<label>{item.taskName}</label>
{/* 添加点击方法删除任务 */}
<button className="destroy" onClick={() => this.removTodos(item.id)}></button>
</div>
<input className="edit" value="Create a TodoMVC template" readOnly/>
</li>
)
})}
</ul>
</section>
)
}
}
// connect第一个参数 - 数据
const myData = store => ({
todos: store.todosReducer.todos
})
// connect第二个参数 - 指令函数
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
// 导出并调用 connect 注入数据和指令
export default connect(myData, myAction)(Main)
计算未完成功能
计算功能在footer组件中实现
- 组件拿到全部列表
- 对列表进行过滤,筛选出全部未完成的任务进行统计即可
// src/Components/Footer.js 因为项目初始化会自动火球数据,所以footer直接使用即可
import {Component} from 'react'
// 引入 redux 相关组件
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as todoAction from '../Store/Actions/todos.action'
class Footer extends Component {
// 未完成任务统计方法
count(array) {
// 筛选全部未完成任务组成数组,并且返回该数组长度
return array.filter(item => !item.isCompleted).length
}
render() {
return (
<footer className="footer">
<span className="todo-count">
{/* 未完成任务统计,调用方法 */}
<strong>{this.count(this.props.todos)}</strong> 未完成
</span>
<ul className="filters">
<li>
<span>All</span>
</li>
<li>
<span>Active</span>
</li>
<li>
<span>Completed</span>
</li>
</ul>
<button className="clear-completed">Clear completed</button>
</footer>
)
}
}
const myData = store => ({
todos: store.todosReducer.todos
})
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
export default connect(myData, myAction)(Footer)
任务筛选实现
筛选功能在footer组件中实现
- 筛选不需要进行异步请求,所以可以不使用saga中间件处理
- 需要考虑修改状态不能直接修改原始数据,否则切换的时候可能出现问题
- 在数据上添加一个属性,用来表示当前数据筛选方式
- 在main组件获取数据的时候直接进行筛选(老师的方法),当然也可以在真正渲染的时候进行筛选
// src/Store/Actions/todos.action.js 添加筛选指令
// 引入 createAction 对 action 进行简化
import {createAction} from 'redux-actions'
// 初始化数据指令
export const getTodos = createAction('getTodos')
export const getTodos_success = createAction('getTodos_success')
// 添加任务指令
export const addTodos = createAction('addTodos')
export const addTodos_success = createAction('addTodos_success')
// 删除任务指令
export const removTodos = createAction('removTodos')
export const removTodos_success = createAction('removTodos_success')
// 修改状态指令
export const modifyTodos = createAction('modifyTodos')
export const modifyTodos_success = createAction('modifyTodos_success')
// 筛选指令
export const screenTodos = createAction('screenTodos')
// src/Components/Footer.js footer组件添加点击事件并且调用指令
import {Component} from 'react'
// 引入 redux 相关组件
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as todoAction from '../Store/Actions/todos.action'
class Footer extends Component {
// 未完成任务统计方法
count(array) {
// 筛选全部未完成任务组成数组,并且返回该数组长度
return array.filter(item => !item.isCompleted).length
}
// 三个筛选按钮点击事件
filters(text){
// 触发筛选指令,将参数传递过去
this.props.screenTodos(text)
}
render() {
return (
<footer className="footer">
<span className="todo-count">
{/* 未完成任务统计,调用方法 */}
<strong>{this.count(this.props.todos)}</strong> 未完成
</span>
<ul className="filters">
<li>
{/* 添加点击筛选事件 */}
<span onClick={() => this.filters('all')}>All</span>
</li>
<li>
{/* 添加点击筛选事件 */}
<span onClick={() => this.filters('active')}>Active</span>
</li>
<li>
{/* 添加点击筛选事件 */}
<span onClick={() => this.filters('completed')}>Completed</span>
</li>
</ul>
<button className="clear-completed">Clear completed</button>
</footer>
)
}
}
const myData = store => ({
todos: store.todosReducer.todos
})
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
export default connect(myData, myAction)(Footer)
// src/Store/Reducers/todos.reducer.js 原始数据添加一个筛选关键词,并书写指令结构
// 引入 handleActions 简化书写,注意这里是 handleActions 而不是 handleAction
import {handleActions} from 'redux-actions'
// 引入实际更改数据的指令,中间件saga拦截的指令就不需要引入了
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success, screenTodos} from '../Actions/todos.action'
// 原始数据
const data = {
// 空的列表
todos: [],
// 添加一个参数,作为筛选关键词,默认为all(全部)
show: 'all'
}
// 创建指令函数,修改下面全部 return 的数据,将筛选关键词也传递下去
const todosReducer = handleActions({
// 初始化数据
[getTodos_success]: (state, action) => ({todos: action.payload, show: state.show}),
// 添加任务
[addTodos_success]: (state, action) => ({todos: [...state.todos, action.payload], show: state.show}),
// 删除任务
[removTodos_success]: (state, action) => {
// 根据传递过来的 ID 获得在任务列表中的 下标
const index = state.todos.findIndex(item => item.id === action.payload)
// 深拷贝任务列表
const todos = JSON.parse(JSON.stringify(state.todos))
// 从任务列表中删除指定的任务
todos.splice(index, 1)
// 用修改后的 todos 替换掉原本的 todos
return { todos, show: state.show }
},
// 修改状态
[modifyTodos_success]: (state, action) => {
// 根据传递过来的 ID 获得在任务列表中的 下标
const index = state.todos.findIndex(item => item.id === action.payload.id)
// 深拷贝任务列表
const todos = JSON.parse(JSON.stringify(state.todos))
// 从任务列表中替换指定位置数据
todos[index] = action.payload
// 用修改后的 todos 替换掉原本的 todos
return { todos, show: state.show }
},
// 筛选
[screenTodos]: (state, action) => {
// 修改关键词并返回
return {...state, show: action.payload}
}
}, data)
// 导出
export default todosReducer
// src/Components/Main.js 组件创建一个方法用于根据筛选关键词筛选要显示的数据并使用
import {Component} from 'react'
// 引入 bindActionCreators 用来自动生成 action 指令函数
import {bindActionCreators} from 'redux'
// connect 获取数据并添加指令方法
import {connect} from 'react-redux'
// 获取全部指令
import * as todoAction from '../Store/Actions/todos.action'
class Main extends Component {
// 生命周期:组件挂载后
componentDidMount() {
// 调用组件的指令函数 getTodos() 获取数据并让 props 拿到
this.props.getTodos()
}
// 删除任务函数
removTodos(id) {
// 把要删除的任务的 id 传递给指令
this.props.removTodos(id)
}
modifyTodos(ev, id) {
// 把要修改状态的任务需要的数据传递过去(要修改为的状态, id值)
this.props.modifyTodos({id, isCompleted: ev.target.checked})
}
render() {
return (
<section className="main">
<input className="toggle-all" type="checkbox" />
<ul className="todo-list">
{this.props.todos.map(item => {
return (
// 遍历全部的待办事项列表,注意这里设置一下是否添加 completed 类名,如果已完成添加,未完成不添加
<li key={item.id} className={item.isCompleted ? "completed" : ''}>
<div className="view">
{/* 是否选中复选框 - 根据数据判断是否选中,添加状态修改事件 */}
<input className="toggle" type="checkbox" checked={item.isCompleted} onChange={(ev) => this.modifyTodos(ev, item.id)} />
<label>{item.taskName}</label>
{/* 添加点击方法删除任务 */}
<button className="destroy" onClick={() => this.removTodos(item.id)}></button>
</div>
<input className="edit" value="Create a TodoMVC template" readOnly/>
</li>
)
})}
</ul>
</section>
)
}
}
// connect第一个参数 - 数据
const myData = store => ({
// 参数调用自定义的函数,筛选需要的数据然后使用
todos: filterTodos(store.todosReducer.todos, store.todosReducer.show)
})
// connect第二个参数 - 指令函数
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
// 筛选函数
// 参数1:要筛选的数据,参数2:筛选关键词
function filterTodos(todos, text) {
// 判断筛选关键词
switch(text) {
// 关键词为all直接返回全部数据
case 'all':
return todos
// 关键词为active筛选出所有未完成
case 'active':
return todos.filter(item => !item.isCompleted)
// 关键词为completed筛选出所有已完成
case 'completed':
return todos.filter(item => item.isCompleted)
default:
return
}
}
// 导出并调用 connect 注入数据和指令
export default connect(myData, myAction)(Main)
删除已完成任务
清除功能在footer组件中实现
- 点击清除按钮的时候调用指令,需要二次确认
- saga拦截指令进行异步请求
- 清除完成后再次重新获取全部任务即可或者直接调用新指令,对原始数据进行筛选也可以(老师的方法)
// src/Store/Actions/todos.action.js 添加新指令
// 引入 createAction 对 action 进行简化
import {createAction} from 'redux-actions'
// 初始化数据指令
export const getTodos = createAction('getTodos')
export const getTodos_success = createAction('getTodos_success')
// 添加任务指令
export const addTodos = createAction('addTodos')
export const addTodos_success = createAction('addTodos_success')
// 删除任务指令
export const removTodos = createAction('removTodos')
export const removTodos_success = createAction('removTodos_success')
// 修改状态指令
export const modifyTodos = createAction('modifyTodos')
export const modifyTodos_success = createAction('modifyTodos_success')
// 筛选指令
export const screenTodos = createAction('screenTodos')
// 清除已完成任务指令
export const clearCompletedTodos = createAction('clearCompletedTodos')
export const clearCompletedTodos_success = createAction('clearCompletedTodos_success')
// src/Store/Sagas/todos.saga.js saga中间件拦截新指令处理后再触发另一个指令
// 引入 saga 的两个方法,takeEvery 捕捉指令并拦截,put将指令传递给 store
import {takeEvery, put} from 'redux-saga/effects'
// 引入 axios 请求接口数据
import axios from 'axios'
// 捕捉指令后要执行的函数
function* loaderData() {
// 获取数据
const {data} = yield axios.get('http://localhost:3005/api/todos')
// 调用 put 方法将数据传递给其他指令
yield put({type: 'getTodos_success', payload: data})
}
// 添加任务指令拦截后执行的函数
function* addTodos(action) {
// 调用接口添加任务
const {data} = yield axios.post('http://localhost:3005/api/todos', {taskName: action.payload})
// 将接口返回的数据交给下一个指令处理
yield put({type: 'addTodos_success', payload: data.task})
}
// 删除任务指令拦截后执行的函数
function* removTodos(action) {
// 调用接口根据 ID 删除任务
const {data} = yield axios.delete('http://localhost:3005/api/todos', { params: {id: action.payload}})
// 调用另一个指令,把要删除的 id 传递过去
yield put({type: 'removTodos_success', payload: data.tasks.id})
}
// 捕捉修改状态指令后的回调函数
function* modifyTodos(action) {
// 调用接口修改状态
const {data} = yield axios.put('http://localhost:3005/api/todos/isCompleted', action.payload)
// 调用另一个指令,把接口返回的数据传递给下一个指令
yield put({type: 'modifyTodos_success', payload: data.task})
}
// 捕捉 clearCompletedTodos 指令后的回调函数
function* clearCompletedTodos() {
yield axios.delete('http://localhost:3005/api/todos/clearCompleted')
yield put({type: 'clearCompletedTodos_success'})
}
// 导出 saga
export default function* getTodos() {
// 捕捉 getTodos 指令,捕捉到之后执行 loaderData 方法
yield takeEvery('getTodos', loaderData)
// 捕捉a ddTodos 指令,捕捉后执行 addTodos 方法
yield takeEvery('addTodos', addTodos)
// 捕捉 removTodos 指令,捕捉后执行 removTodos 方法
yield takeEvery('removTodos', removTodos)
// 捕捉 modifyTodos 指令,然后调用 modifyTodos 方法
yield takeEvery('modifyTodos', modifyTodos)
// 捕捉 clearCompletedTodos 指令,然后调用 clearCompletedTodos 方法
yield takeEvery('clearCompletedTodos', clearCompletedTodos)
}
// src/Store/Reducers/todos.reducer.js 书写添加清除已完成任务的逻辑
// 引入 handleActions 简化书写,注意这里是 handleActions 而不是 handleAction
import {handleActions} from 'redux-actions'
// 引入实际更改数据的指令,中间件saga拦截的指令就不需要引入了
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success, screenTodos, clearCompletedTodos_success} from '../Actions/todos.action'
// 原始数据
const data = {
// 空的列表
todos: [],
// 添加一个参数,作为筛选关键词,默认为all(全部)
show: 'all'
}
// 创建指令函数,修改下面全部 return 的数据,将筛选关键词也传递下去
const todosReducer = handleActions({
// 初始化数据
[getTodos_success]: (state, action) => ({todos: action.payload, show: state.show}),
// 添加任务
[addTodos_success]: (state, action) => ({todos: [...state.todos, action.payload], show: state.show}),
// 删除任务
[removTodos_success]: (state, action) => {
// 根据传递过来的 ID 获得在任务列表中的 下标
const index = state.todos.findIndex(item => item.id === action.payload)
// 深拷贝任务列表
const todos = JSON.parse(JSON.stringify(state.todos))
// 从任务列表中删除指定的任务
todos.splice(index, 1)
// 用修改后的 todos 替换掉原本的 todos
return { todos, show: state.show }
},
// 修改状态
[modifyTodos_success]: (state, action) => {
// 根据传递过来的 ID 获得在任务列表中的 下标
const index = state.todos.findIndex(item => item.id === action.payload.id)
// 深拷贝任务列表
const todos = JSON.parse(JSON.stringify(state.todos))
// 从任务列表中替换指定位置数据
todos[index] = action.payload
// 用修改后的 todos 替换掉原本的 todos
return { todos, show: state.show }
},
// 筛选
[screenTodos]: (state, action) => {
// 修改关键词并返回
return {...state, show: action.payload}
},
// 清除已完成任务
[clearCompletedTodos_success]: (state, action) => {
// 筛选出全部未完成任务组成新数组
const todos = state.todos.filter(item => !item.isCompleted)
return {...state, todos}
}
}, data)
// 导出
export default todosReducer
// src/Components/Footer.js Footer组件添加事件触发清除已完成
import {Component} from 'react'
// 引入 redux 相关组件
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as todoAction from '../Store/Actions/todos.action'
class Footer extends Component {
// 未完成任务统计方法
count(array) {
// 筛选全部未完成任务组成数组,并且返回该数组长度
return array.filter(item => !item.isCompleted).length
}
// 三个筛选按钮点击事件
filters(text){
// 触发筛选指令,将参数传递过去
this.props.screenTodos(text)
}
// 清除已完成点击事件
clearCompleted() {
// 触发指令,清除已完成不需要传递参数
this.props.clearCompletedTodos()
}
render() {
return (
<footer className="footer">
<span className="todo-count">
{/* 未完成任务统计,调用方法 */}
<strong>{this.count(this.props.todos)}</strong> 未完成
</span>
<ul className="filters">
<li>
{/* 添加点击筛选事件 */}
<span onClick={() => this.filters('all')}>全部</span>
</li>
<li>
{/* 添加点击筛选事件 */}
<span onClick={() => this.filters('active')}>未完成</span>
</li>
<li>
{/* 添加点击筛选事件 */}
<span onClick={() => this.filters('completed')}>已完成</span>
</li>
</ul>
{/* 按钮添加点击事件 */}
<button className="clear-completed" onClick={() => this.clearCompleted()} >清除已完成</button>
</footer>
)
}
}
// 数据
const myData = store => ({
todos: store.todosReducer.todos
})
// 指令
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
export default connect(myData, myAction)(Footer)