笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
todolist案例
修改任务名称(双击编辑)
任务界面双击选中某个任务,进入编辑状态,显示文本输入框,可输入或修改任务名称,回车确认保存
创建两个新指令:点击后调用接口修改数据、修改本地数据
通过控制类名来控制是否处于编辑状态,编辑状态类名为editing
编辑状态下会默认展示当前任务名称,可以进行修改
双击触发第一个指令,传递数据,之后调用接口修改任务编辑状态,然后将数据再向下传递正式修改数据
新指令根据传递过来的id值修改数据(需要深拷贝)
这时需要修改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')
// 清除已完成任务指令
export const clearCompletedTodos = createAction('clearCompletedTodos')
export const clearCompletedTodos_success = createAction('clearCompletedTodos_success')
// 修改任务编辑状态指令
export const modifyEditing = createAction('modifyEditing')
export const modifyEditing_success = createAction('modifyEditing_success')
// src/Store/Sagas/todos.saga.js 书写指令拦截和拦截后回调函数
// 引入 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'})
}
// 任务编辑状态修改指令拦截后回调
function* modifyEditing(action) {
yield axios.put('http://localhost:3005/api/todos/isEditing', action.payload)
yield put({type: 'modifyEditing_success', payload: action.payload})
}
// 导出 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)
// 捕捉任务编辑状态修改指令
yield takeEvery('modifyEditing', modifyEditing)
}
// 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, modifyEditing_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}
},
// 修改编辑状态
[modifyEditing_success]: (state, action) => {
// 获取要修改的任务索引值
const index = state.todos.findIndex(item => item.id === action.payload.id)
// 深拷贝
const todos = JSON.parse(JSON.stringify(state.todos))
// 修改
todos[index].isEditing = action.payload.isEditing
// 替换掉原来的数据
return {...state, todos}
}
}, data)
// 导出
export default todosReducer
// src/Components/Main. // 组件添加双击修改编辑状态的事件,并且根据isEditing修改类名
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 ? 'complete' : '') + (item.isEditing ? 'editing' : '')}>
<div className="view">
{/* 是否选中复选框 - 根据数据判断是否选中,添加状态修改事件 */}
<input className="toggle" type="checkbox" checked={item.isCompleted} onChange={(ev) => this.modifyTodos(ev, item.id)} />
{/* 添加双击事件,直接触发指令,传递两个参数,其中 isEditing 设置为固定值 */}
<label onDoubleClick={() => this.props.modifyEditing({id: item.id, isEditing: true})}>{item.taskName}</label>
{/* 添加点击方法删除任务 */}
<button className="destroy" onClick={() => this.removTodos(item.id)}></button>
</div>
{/* 编辑状态下默认value值为当前任务名 */}
<input className="edit" value={item.taskName} 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)
修改任务名称(失焦保存)
编辑状态下的任务文本框失去焦点后触发事件,传递事件对象本身和任务id值
先修改状态,让编辑的任务恢复未编辑状态(可以使用上一步的指令)
然后真正修改任务名称,传递新任务名和id
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')
// 修改任务编辑状态指令
export const modifyEditing = createAction('modifyEditing')
export const modifyEditing_success = createAction('modifyEditing_success')
// 修改保存新任务名指令
export const saveName = createAction('saveName')
export const saveName_success = createAction('saveName_success')
// src/Store/Sagas/todos.saga.js 书写新指令拦截和回调函数
// 引入 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'})
}
// 任务编辑状态修改指令拦截后回调
function* modifyEditing(action) {
yield axios.put('http://localhost:3005/api/todos/isEditing', action.payload)
yield put({type: 'modifyEditing_success', payload: action.payload})
}
// 修改保存新任务名回调函数
function* saveName(action) {
yield axios.put('http://localhost:3005/api/todos', action.payload)
yield put({type: 'saveName_success', payload: action.payload})
}
// 导出 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)
// 捕捉任务编辑状态修改指令
yield takeEvery('modifyEditing', modifyEditing)
// 补货修改保存新任务名指令
yield takeEvery('saveName', saveName)
}
// 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, modifyEditing_success, saveName_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}
},
// 修改编辑状态
[modifyEditing_success]: (state, action) => {
// 获取要修改的任务索引值
const index = state.todos.findIndex(item => item.id === action.payload.id)
// 深拷贝
const todos = JSON.parse(JSON.stringify(state.todos))
// 修改
todos[index].isEditing = action.payload.isEditing
// 替换掉原来的数据
return {...state, todos}
},
// 修改保存新任务名
[saveName_success]: (state, action) => {
// 获取要修改的任务索引值
const index = state.todos.findIndex(item => item.id === action.payload.id)
// 深拷贝
const todos = JSON.parse(JSON.stringify(state.todos))
// 修改
todos[index].taskName = action.payload.taskName
// 替换掉原来的数据
return {...state, 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})
}
// 修改保存新任务名
saveEdit(data) {
// 触发指令修改编辑状态
this.props.modifyEditing({id: data.id, isEditing: false})
// 触发指令保存新名称
this.props.saveName(data)
}
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 ? 'complete' : '') + (item.isEditing ? 'editing' : '')}>
<div className="view">
{/* 是否选中复选框 - 根据数据判断是否选中,添加状态修改事件 */}
<input className="toggle" type="checkbox" checked={item.isCompleted} onChange={(ev) => this.modifyTodos(ev, item.id)} />
{/* 添加双击事件,直接触发指令,传递两个参数,其中 isEditing 设置为固定值 */}
<label onDoubleClick={() => this.props.modifyEditing({id: item.id, isEditing: true})}>{item.taskName}</label>
{/* 添加点击方法删除任务 */}
<button className="destroy" onClick={() => this.removTodos(item.id)}></button>
</div>
{/* 编辑状态下默认value值为当前任务名,失去焦点后调用方法保存新名称 */}
<input className="edit" defaultValue={item.taskName} onBlur={(ev) => this.saveEdit({id: item.id, taskName: ev.target.value})}/>
</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)
实例代码优化
为了避免之后修改请求地址前缀方便,将前缀单独抽离出来
利用相应拦截器将需要使用的数据直接返回(老师使用了then,而我使用的是对象解构)
可以将reducer的函数进行单独提取,这样更易读
// src/Store/axios.js 设置URL地址前缀和响应拦截器
// 引入axios
import axios from 'axios'
// 设置默认的前缀
axios.defaults.baseURL = 'http://localhost:3005/api'
// 设置响应拦截器直接输出data,就可以在saga内部直接使用
// 因为我使用的对象解构方式,所以这里不使用了
// axios.interceptors.response.use(res => res.data)
// src/Store/index.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'
// 引入并直接运行axios设置
import './axios'
// 创建 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 修改全部接口删除前缀
// 引入 saga 的两个方法,takeEvery 捕捉指令并拦截,put将指令传递给 store
import {takeEvery, put} from 'redux-saga/effects'
// 引入 axios 请求接口数据
import axios from 'axios'
// 捕捉指令后要执行的函数
function* loaderData() {
// 获取数据
const {data} = yield axios.get('/todos')
// 调用 put 方法将数据传递给其他指令
yield put({type: 'getTodos_success', payload: data})
}
// 添加任务指令拦截后执行的函数
function* addTodos(action) {
// 调用接口添加任务
const {data} = yield axios.post('/todos', {taskName: action.payload})
// 将接口返回的数据交给下一个指令处理
yield put({type: 'addTodos_success', payload: data.task})
}
// 删除任务指令拦截后执行的函数
function* removTodos(action) {
// 调用接口根据 ID 删除任务
const {data} = yield axios.delete('/todos', { params: {id: action.payload}})
// 调用另一个指令,把要删除的 id 传递过去
yield put({type: 'removTodos_success', payload: data.tasks.id})
}
// 捕捉修改状态指令后的回调函数
function* modifyTodos(action) {
// 调用接口修改状态
const {data} = yield axios.put('/todos/isCompleted', action.payload)
// 调用另一个指令,把接口返回的数据传递给下一个指令
yield put({type: 'modifyTodos_success', payload: data.task})
}
// 捕捉 clearCompletedTodos 指令后的回调函数
function* clearCompletedTodos() {
yield axios.delete('/todos/clearCompleted')
yield put({type: 'clearCompletedTodos_success'})
}
// 任务编辑状态修改指令拦截后回调
function* modifyEditing(action) {
yield axios.put('/todos/isEditing', action.payload)
yield put({type: 'modifyEditing_success', payload: action.payload})
}
// 修改保存新任务名回调函数
function* saveName(action) {
yield axios.put('/todos', action.payload)
yield put({type: 'saveName_success', payload: action.payload})
}
// 导出 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)
// 捕捉任务编辑状态修改指令
yield takeEvery('modifyEditing', modifyEditing)
// 补货修改保存新任务名指令
yield takeEvery('saveName', saveName)
}
// src/Store/Reducers/todos.reducer.js 将全部saga没有拦截指令的逻辑单独封装
// 引入 handleActions 简化书写,注意这里是 handleActions 而不是 handleAction
import {handleActions} from 'redux-actions'
// 引入实际更改数据的指令,中间件saga拦截的指令就不需要引入了
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success, screenTodos, clearCompletedTodos_success, modifyEditing_success, saveName_success} from '../Actions/todos.action'
// 原始数据
const data = {
// 空的列表
todos: [],
// 添加一个参数,作为筛选关键词,默认为all(全部)
show: 'all'
}
// 将全部指令函数逻辑单独封装
const getTodos = (state, action) => ({todos: action.payload, show: state.show})
const addTodos = (state, action) => ({todos: [...state.todos, action.payload], show: state.show})
const removTodos = (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 }
}
const modifyTodos = (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 }
}
const screenTodosData = (state, action) => {
// 修改关键词并返回
return {...state, show: action.payload}
}
const clearCompletedTodos = (state, action) => {
// 筛选出全部未完成任务组成新数组
const todos = state.todos.filter(item => !item.isCompleted)
return {...state, todos}
}
const modifyEditing = (state, action) => {
// 获取要修改的任务索引值
const index = state.todos.findIndex(item => item.id === action.payload.id)
// 深拷贝
const todos = JSON.parse(JSON.stringify(state.todos))
// 修改
todos[index].isEditing = action.payload.isEditing
// 替换掉原来的数据
return {...state, todos}
}
const saveName = (state, action) => {
// 获取要修改的任务索引值
const index = state.todos.findIndex(item => item.id === action.payload.id)
// 深拷贝
const todos = JSON.parse(JSON.stringify(state.todos))
// 修改
todos[index].taskName = action.payload.taskName
// 替换掉原来的数据
return {...state, todos}
}
// 创建指令函数,修改下面全部 return 的数据,将筛选关键词也传递下去
const todosReducer = handleActions({
// 初始化数据
[getTodos_success]: getTodos,
// 添加任务
[addTodos_success]: addTodos,
// 删除任务
[removTodos_success]: removTodos,
// 修改状态
[modifyTodos_success]: modifyTodos,
// 筛选
[screenTodos]: screenTodosData,
// 清除已完成任务
[clearCompletedTodos_success]: clearCompletedTodos,
// 修改编辑状态
[modifyEditing_success]: modifyEditing,
// 修改保存新任务名
[saveName_success]: saveName
}, data)
// 导出
export default todosReducer
immutable使用
安装:npm install immutable
react中如果直接修改state中的数据,界面是不会更新的,所以我们每次修改的时候都需要对数据进行一次深拷贝,比较麻烦而且也不属于我们的业务逻辑
immutable是facebook推出的工具库,会把数据变成一个不可变的对象,避免误操作也不可打点调用,想修改书需要使用内部方法,其中常用的方法有:
- fromJS(数据对象):可将对象变为不可变对象
- setIn(修改数据,[修改属性],修改值):修改数据
- getIn(读取数据,[读取属性]):使用数据
- mergeDeep(修改数据,合并的属性:合并的值):合并/添加数据
- removeIn(修改数据,[要删除的数据,删除索引]):删除数据(可以使用getIn获取索引)
- updateIn(修改数据,[要修改数据,索引值],更新的值(函数返回)):替换/更新数据
注意:不能使用打点访问数据,需要使用getIn()
当一些数据无法满足方法的时候可能需要修改saga传递过来的数据
// src/Store/Reducers/todos.reducer.js 使用immutable方法改造
// 引入 handleActions 简化书写,注意这里是 handleActions 而不是 handleAction
import {handleActions} from 'redux-actions'
// 引入实际更改数据的指令,中间件saga拦截的指令就不需要引入了
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success, screenTodos, clearCompletedTodos_success, modifyEditing_success, saveName_success} from '../Actions/todos.action'
// 引入 immutable 相关方法
import { fromJS, setIn, mergeDeep, getIn, removeIn, updateIn } from 'immutable'
// 原始数据
const data = fromJS({
// 空的列表
todos: [],
// 添加一个参数,作为筛选关键词,默认为all(全部)
show: 'all'
})
// 将全部指令函数逻辑单独封装
// 获取任务列表,使用 setIn 修改
const getTodos = (state, action) => setIn(state, ['todos'], action.payload)
// 添加任务,使用 mergeDeep 合并
const addTodos = (state, action) => mergeDeep(state, {todos: [action.payload]})
// 删除任务
const removTodos = (state, action) => {
// 获取索引值,使用getIn
const index = getIn(state, ['todos']).findIndex(item => item.id === action.payload)
// 使用 removeIn 删除
return removeIn(state, ['todos', index])
}
// 修改任务状态
const modifyTodos = (state, action) => {
// 获取索引值,使用getIn
const index = getIn(state, ['todos']).findIndex(item => item.id === action.payload.id)
// 使用 updateIn 修改/更新
return updateIn(state, ['todos', index], () => action.payload)
}
// 过滤任务,使用setIn修改
const screenTodosData = (state, action) => setIn(state, ['show'], action.payload)
// 清除全部已完成任务
const clearCompletedTodos = (state, action) => {
// 获取全部未完成任务
const todos = getIn(state, ['todos']).filter(item => !item.isCompleted)
// 使用 setIn 全部修改
return setIn(state, ['todos'], todos)
}
// 修改编辑状态
const modifyEditing = (state, action) => {
// 获取索引值,使用getIn
const index = getIn(state, ['todos']).findIndex(item => item.id === action.payload.id)
// 使用 updateIn 修改/更新
return updateIn(state, ['todos', index], () => action.payload)
}
// 保存新名称
const saveName = (state, action) => {
// 获取索引值,使用getIn
const index = getIn(state, ['todos']).findIndex(item => item.id === action.payload.id)
// 使用 updateIn 修改/更新
return updateIn(state, ['todos', index], () => action.payload)
}
// 创建指令函数,修改下面全部 return 的数据,将筛选关键词也传递下去
const todosReducer = handleActions({
// 初始化数据
[getTodos_success]: getTodos,
// 添加任务
[addTodos_success]: addTodos,
// 删除任务
[removTodos_success]: removTodos,
// 修改状态
[modifyTodos_success]: modifyTodos,
// 筛选
[screenTodos]: screenTodosData,
// 清除已完成任务
[clearCompletedTodos_success]: clearCompletedTodos,
// 修改编辑状态
[modifyEditing_success]: modifyEditing,
// 修改保存新任务名
[saveName_success]: saveName
}, data)
// 导出
export default todosReducer
// 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('/todos')
// 调用 put 方法将数据传递给其他指令
yield put({type: 'getTodos_success', payload: data})
}
// 添加任务指令拦截后执行的函数
function* addTodos(action) {
// 调用接口添加任务
const {data} = yield axios.post('/todos', {taskName: action.payload})
// 将接口返回的数据交给下一个指令处理
yield put({type: 'addTodos_success', payload: data.task})
}
// 删除任务指令拦截后执行的函数
function* removTodos(action) {
// 调用接口根据 ID 删除任务
const {data} = yield axios.delete('/todos', { params: {id: action.payload}})
// 调用另一个指令,把要删除的 id 传递过去
yield put({type: 'removTodos_success', payload: data.tasks.id})
}
// 捕捉修改状态指令后的回调函数
function* modifyTodos(action) {
// 调用接口修改状态
const {data} = yield axios.put('/todos/isCompleted', action.payload)
// 调用另一个指令,把接口返回的数据传递给下一个指令
yield put({type: 'modifyTodos_success', payload: data.task})
}
// 捕捉 clearCompletedTodos 指令后的回调函数
function* clearCompletedTodos() {
yield axios.delete('/todos/clearCompleted')
yield put({type: 'clearCompletedTodos_success'})
}
// 任务编辑状态修改指令拦截后回调
function* modifyEditing(action) {
const { data } = yield axios.put('/todos/isEditing', action.payload)
yield put({type: 'modifyEditing_success', payload: data.task})
}
// 修改保存新任务名回调函数
function* saveName(action) {
const { data } = yield axios.put('/todos', action.payload)
yield put({type: 'saveName_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)
// 捕捉 clearCompletedTodos 指令,然后调用 clearCompletedTodos 方法
yield takeEvery('clearCompletedTodos', clearCompletedTodos)
// 捕捉任务编辑状态修改指令
yield takeEvery('modifyEditing', modifyEditing)
// 补货修改保存新任务名指令
yield takeEvery('saveName', saveName)
}
// src/Components/Main.js 组件使用immutable改造
import {Component} from 'react'
// 引入 bindActionCreators 用来自动生成 action 指令函数
import {bindActionCreators} from 'redux'
// connect 获取数据并添加指令方法
import {connect} from 'react-redux'
// 获取全部指令
import * as todoAction from '../Store/Actions/todos.action'
// 引入immutable
import { getIn } from 'immutable'
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})
}
// 修改保存新任务名
saveEdit(data) {
// 触发指令修改编辑状态
this.props.modifyEditing({id: data.id, isEditing: false})
// 触发指令保存新名称
this.props.saveName(data)
}
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' : '') + ' ' + (item.isEditing ? 'editing' : '')}>
<div className="view">
{/* 是否选中复选框 - 根据数据判断是否选中,添加状态修改事件 */}
<input className="toggle" type="checkbox" checked={item.isCompleted} onChange={(ev) => this.modifyTodos(ev, item.id)} />
{/* 添加双击事件,直接触发指令,传递两个参数,其中 isEditing 设置为固定值 */}
<label onDoubleClick={() => this.props.modifyEditing({id: item.id, isEditing: true})}>{item.taskName}</label>
{/* 添加点击方法删除任务 */}
<button className="destroy" onClick={() => this.removTodos(item.id)}></button>
</div>
{/* 编辑状态下默认value值为当前任务名,失去焦点后调用方法保存新名称 */}
<input className="edit" defaultValue={item.taskName} onBlur={(ev) => this.saveEdit({id: item.id, taskName: ev.target.value})}/>
</li>
)
})}
</ul>
</section>
)
}
}
// connect第一个参数 - 数据
const myData = store => ({
// 参数调用自定义的函数,筛选需要的数据然后使用,使用immutable的getIn方法
todos: filterTodos(getIn(store.todosReducer, ['todos']), getIn(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)
// src/Components/Footer.js 组件使用immutable改造
import {Component} from 'react'
// 引入 redux 相关组件
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as todoAction from '../Store/Actions/todos.action'
// 引入immutable
import { getIn } from 'immutable'
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 => ({
// 使用immutable的getIn方法
todos: getIn(store.todosReducer, ['todos'])
})
// 指令
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
export default connect(myData, myAction)(Footer)
// src/Components/Header.js 组件使用immutable改造
import {Component} from 'react'
// 引入redux需要使用的方法和指令
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as todoAction from '../Store/Actions/todos.action'
// 引入immutable
import { getIn } from 'immutable'
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 => ({
// 使用immutable的getIn方法
todos: getIn(store.todosReducer, ['todos'])
})
// 指令
const myAction = dispatch => ({
...bindActionCreators(todoAction, dispatch)
})
export default connect(myData, myAction)(Header)