Note source: Hook Education – big front end employment Training camp

Content: Notes, thoughts and experiences in the process of learning

Todolist case

Modify the task name (double-click to edit)

Task screen Double-click a task to enter the editing state. The text input box is displayed. You can enter or modify the task name, and press Enter to save the configuration

Create two new directives: click and call the interface to modify data and modify local data

Control whether or not you are in editing state by controlling the class name. The editing state class is named Editing

In the edit state, the current task name is displayed by default and can be modified

Double click triggers the first instruction, passes the data, then invokes the interface modification task to edit the state, and then passes the data down again to formally modify the data

The new directive modifies the data based on the id value passed (deep copy required)

You need to change the structure of the main component so that the edit state can be displayed based on the data

/ / SRC/Store/Actions/todos. Action. Js new instructions

// Create createAction to simplify the action
import {createAction} from 'redux-actions'

// Initialize the data instruction
export const getTodos = createAction('getTodos')
export const getTodos_success = createAction('getTodos_success')

// Add task instructions
export const addTodos = createAction('addTodos')
export const addTodos_success = createAction('addTodos_success')

// Delete the task instruction
export const removTodos = createAction('removTodos')
export const removTodos_success = createAction('removTodos_success')

// Modify the status directive
export const modifyTodos = createAction('modifyTodos')
export const modifyTodos_success = createAction('modifyTodos_success')

// Filter instructions
export const screenTodos = createAction('screenTodos')

// Clear the completed task instruction
export const clearCompletedTodos = createAction('clearCompletedTodos')
export const clearCompletedTodos_success = createAction('clearCompletedTodos_success')

// Modify task edit status directive
export const modifyEditing = createAction('modifyEditing')
export const modifyEditing_success = createAction('modifyEditing_success')
Copy the code
/ / SRC/Store/Sagas/todos. Saga. Js written instruction to intercept and intercept after the callback function

// Introduce saga's two methods, takeEvery catches the instruction and intercepts it, and PUT passes the instruction to store
import {takeEvery, put} from 'redux-saga/effects'
// Import axios request interface data
import axios from 'axios'

// The function to execute after capturing the instruction
function* loaderData() {
  // Get data
  const {data} = yield axios.get('http://localhost:3005/api/todos')
  // Call the put method to pass data to other directives
  yield put({type: 'getTodos_success'.payload: data})
}

// Add the function executed after the task instruction intercepts
function* addTodos(action) {
  // Call the interface to add a task
  const {data} = yield axios.post('http://localhost:3005/api/todos', {taskName: action.payload})
  // Passes the data returned by the interface to the next instruction
  yield put({type: 'addTodos_success'.payload: data.task})
}

// Delete the function executed after the task instruction intercepts
function* removTodos(action) {
  // Call the interface to delete the task based on the ID
  const {data} = yield axios.delete('http://localhost:3005/api/todos', { params: {id: action.payload}})
  // Call another instruction to pass in the id to be deleted
  yield put({type: 'removTodos_success'.payload: data.tasks.id})
}

// Capture the callback function after modifying the status directive
function* modifyTodos(action) {
  // Call the interface to change the state
  const {data} = yield axios.put('http://localhost:3005/api/todos/isCompleted', action.payload)
  // Call another instruction, passing the data returned by the interface to the next instruction
  yield put({type: 'modifyTodos_success'.payload: data.task})
}


// Catch the callback function after the clearCompletedTodos directive
function* clearCompletedTodos() {
  yield axios.delete('http://localhost:3005/api/todos/clearCompleted')
  yield put({type: 'clearCompletedTodos_success'})}// Callback after task edit status modification command intercepts
function* modifyEditing(action) {
  yield axios.put('http://localhost:3005/api/todos/isEditing', action.payload)
  yield put({type: 'modifyEditing_success'.payload: action.payload})
}

/ / export saga
export default function* getTodos() {
  // Catch the getTodos directive and execute the loaderData method after catching it
  yield takeEvery('getTodos', loaderData)
  // Capture addTodos instruction and execute addTodos method after capture
  yield takeEvery('addTodos', addTodos)
  // Capture removTodos directive, execute removTodos method
  yield takeEvery('removTodos', removTodos)
  // Capture the modifyTodos directive and call the modifyTodos method
  yield takeEvery('modifyTodos', modifyTodos)
  // Catch the clearCompletedTodos directive and call the clearCompletedTodos method
  yield takeEvery('clearCompletedTodos', clearCompletedTodos)
  // Capture task edit status modification instructions
  yield takeEvery('modifyEditing', modifyEditing)
}
Copy the code
/ / SRC/Store/Reducers/todos. Reducer. Js writing practice instruction function of data

// Introduce handleActions to simplify writing. Note that this is handleActions and not handleAction
import {handleActions} from 'redux-actions'
// Introduce directives that actually change the data. The directives intercepted by middleware saga do not need to be introduced
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success, screenTodos, clearCompletedTodos_success, modifyEditing_success} from '.. /Actions/todos.action'

// Raw data
const data = {
  // Empty list
  todos: [].// Add a parameter as the filter keyword, default is all (all)
  show: 'all'
}

// Create an instruction function to modify all of the following return data and pass the filter keyword
const todosReducer = handleActions({
  // Initialize the data
  [getTodos_success]: (state, action) = > ({todos: action.payload, show: state.show}),
  // Add a task
  [addTodos_success]: (state, action) = > ({todos: [...state.todos, action.payload], show: state.show}),
  // Delete the task
  [removTodos_success]: (state, action) = > {
    // Get the subscript in the task list based on the ID passed
    const index = state.todos.findIndex(item= > item.id === action.payload)
    // Deep copy task list
    const todos = JSON.parse(JSON.stringify(state.todos))
    // Deletes the specified task from the task list
    todos.splice(index, 1)
    // Replace the todos with the modified todos
    return { todos, show: state.show }
  },
  // Change the status
  [modifyTodos_success]: (state, action) = > {
    // Get the subscript in the task list based on the ID passed
    const index = state.todos.findIndex(item= > item.id === action.payload.id)
    // Deep copy task list
    const todos = JSON.parse(JSON.stringify(state.todos))
    // Replace the specified location data from the task list
    todos[index] = action.payload
    // Replace the todos with the modified todos
    return { todos, show: state.show }
  },
  / / filter
  [screenTodos]: (state, action) = > {
    // Modify the keyword and return
    return{... state,show: action.payload}
  },
  // Clear completed tasks
  [clearCompletedTodos_success]: (state, action) = > {
    // Filter out all unfinished tasks to form a new array
    const todos = state.todos.filter(item= >! item.isCompleted)return{... state, todos} },// Change the edit status
  [modifyEditing_success]: (state, action) = > {
    // Get the task index value to be modified
    const index = state.todos.findIndex(item= > item.id === action.payload.id)
    / / copy
    const todos = JSON.parse(JSON.stringify(state.todos))
    / / modify
    todos[index].isEditing = action.payload.isEditing
    // Replace the original data
    return{... state, todos} } }, data)/ / export
export default todosReducer
Copy the code
// The component adds an event that changes the editing state by double-click, and changes the class name according to isEditing

import {Component} from 'react'
// Introduce bindActionCreators to generate the action instruction function automatically
import {bindActionCreators} from 'redux'
// connect retrieves data and adds instruction methods
import {connect} from 'react-redux'
// Get all instructions
import * as todoAction from '.. /Store/Actions/todos.action'

class Main extends Component {
	// Life cycle: after the component is mounted
	componentDidMount() {
		// Call the component's instruction function getTodos() to get the data and let props get it
		this.props.getTodos()
	}
	// Delete the task function
	removTodos(id) {
		// Pass the id of the task to be deleted to the directive
		this.props.removTodos(id)
	}
	modifyTodos(ev, id) {
		// Pass the data needed by the task to change the state (the state to be changed to, the id value)
		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 (// If completed is added, the completed class name is not added, and a new condition is added to determine whether to edit<li key={item.id} className={(item.isCompleted ? 'complete' :"') + (item.isEditing ? 'editing' :"')} >
								<div className="view">{/* Check the check box - Check the check box according to the data, add the status change event */}<input className="toggle" type="checkbox" checked={item.isCompleted} onChange={(ev)= >This.modifytodos (ev, item.id)} /> {this.modifyTodos(ev, item.id)}<label onDoubleClick={()= > this.props.modifyEditing({id: item.id, isEditing: true})}>{item.taskName}</label>{/* Add click method delete task */}<button className="destroy" onClick={()= > this.removTodos(item.id)}></button>
								</div>{/* Edit the default value of the current task */}<input className="edit" value={item.taskName} readOnly/>
							</li>)})}</ul>
			</section>)}}// connect the first parameter - data
const myData = store= > ({
	// Arguments call custom functions, filter the required data and then use
	todos: filterTodos(store.todosReducer.todos, store.todosReducer.show)
})
// connect the second argument - the instruction function
const myAction = dispatch= > ({
	...bindActionCreators(todoAction, dispatch)
})

// Filter function
// Parameter 1: data to filter, parameter 2: keywords to filter
function filterTodos(todos, text) {
	// Determine the filter keyword
	switch (text) {
		// Return all data directly
		case 'all':
			return todos
		// The keyword is active
		case 'active':
			return todos.filter(item= >! item.isCompleted)// The keyword is completed
		case 'completed':
			return todos.filter(item= > item.isCompleted)
		default:
			return}}// Export and call CONNECT to inject data and directives
export default connect(myData, myAction)(Main)
Copy the code

Change the task name (Save out of focus)

The task text box in edit state fires an event when it loses focus, passing the event object itself and the task ID value

Modify the state first and let the edited task return to the unedited state (you can use the instructions in the previous step)

Then you actually change the task name, passing the new task name and ID

The Saga intercepting directive calls the interface to modify the data and then triggers another directive to modify the local data

/ / SRC/Store/Actions/todos. Action. Js to add a new instruction

// Create createAction to simplify the action
import {createAction} from 'redux-actions'

// Initialize the data instruction
export const getTodos = createAction('getTodos')
export const getTodos_success = createAction('getTodos_success')

// Add task instructions
export const addTodos = createAction('addTodos')
export const addTodos_success = createAction('addTodos_success')

// Delete the task instruction
export const removTodos = createAction('removTodos')
export const removTodos_success = createAction('removTodos_success')

// Modify the status directive
export const modifyTodos = createAction('modifyTodos')
export const modifyTodos_success = createAction('modifyTodos_success')

// Filter instructions
export const screenTodos = createAction('screenTodos')

// Clear the completed task instruction
export const clearCompletedTodos = createAction('clearCompletedTodos')
export const clearCompletedTodos_success = createAction('clearCompletedTodos_success')

// Modify task edit status directive
export const modifyEditing = createAction('modifyEditing')
export const modifyEditing_success = createAction('modifyEditing_success')

// Modify the save new task name directive
export const saveName = createAction('saveName')
export const saveName_success = createAction('saveName_success')
Copy the code
/ / SRC/Store/Sagas/todos. Saga. Js writing new instructions to intercept and callback function

// Introduce saga's two methods, takeEvery catches the instruction and intercepts it, and PUT passes the instruction to store
import {takeEvery, put} from 'redux-saga/effects'
// Import axios request interface data
import axios from 'axios'

// The function to execute after capturing the instruction
function* loaderData() {
  // Get data
  const {data} = yield axios.get('http://localhost:3005/api/todos')
  // Call the put method to pass data to other directives
  yield put({type: 'getTodos_success'.payload: data})
}

// Add the function executed after the task instruction intercepts
function* addTodos(action) {
  // Call the interface to add a task
  const {data} = yield axios.post('http://localhost:3005/api/todos', {taskName: action.payload})
  // Passes the data returned by the interface to the next instruction
  yield put({type: 'addTodos_success'.payload: data.task})
}

// Delete the function executed after the task instruction intercepts
function* removTodos(action) {
  // Call the interface to delete the task based on the ID
  const {data} = yield axios.delete('http://localhost:3005/api/todos', { params: {id: action.payload}})
  // Call another instruction to pass in the id to be deleted
  yield put({type: 'removTodos_success'.payload: data.tasks.id})
}

// Capture the callback function after modifying the status directive
function* modifyTodos(action) {
  // Call the interface to change the state
  const {data} = yield axios.put('http://localhost:3005/api/todos/isCompleted', action.payload)
  // Call another instruction, passing the data returned by the interface to the next instruction
  yield put({type: 'modifyTodos_success'.payload: data.task})
}


// Catch the callback function after the clearCompletedTodos directive
function* clearCompletedTodos() {
  yield axios.delete('http://localhost:3005/api/todos/clearCompleted')
  yield put({type: 'clearCompletedTodos_success'})}// Callback after task edit status modification command intercepts
function* modifyEditing(action) {
  yield axios.put('http://localhost:3005/api/todos/isEditing', action.payload)
  yield put({type: 'modifyEditing_success'.payload: action.payload})
}

// Modify the save new task name callback function
function* saveName(action) {
  yield axios.put('http://localhost:3005/api/todos', action.payload)
  yield put({type: 'saveName_success'.payload: action.payload})
}

/ / export saga
export default function* getTodos() {
  // Catch the getTodos directive and execute the loaderData method after catching it
  yield takeEvery('getTodos', loaderData)
  // Capture addTodos instruction and execute addTodos method after capture
  yield takeEvery('addTodos', addTodos)
  // Capture removTodos directive, execute removTodos method
  yield takeEvery('removTodos', removTodos)
  // Capture the modifyTodos directive and call the modifyTodos method
  yield takeEvery('modifyTodos', modifyTodos)
  // Catch the clearCompletedTodos directive and call the clearCompletedTodos method
  yield takeEvery('clearCompletedTodos', clearCompletedTodos)
  // Capture task edit status modification instructions
  yield takeEvery('modifyEditing', modifyEditing)
  // Save the new task name command
  yield takeEvery('saveName', saveName)
}
Copy the code
/ / SRC/Store/Reducers/todos. Reducer. Js writing new instructions to modify data logic

// Introduce handleActions to simplify writing. Note that this is handleActions and not handleAction
import {handleActions} from 'redux-actions'
// Introduce directives that actually change the data. The directives intercepted by middleware saga do not need to be introduced
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success, screenTodos, clearCompletedTodos_success, modifyEditing_success, saveName_success} from '.. /Actions/todos.action'

// Raw data
const data = {
  // Empty list
  todos: [].// Add a parameter as the filter keyword, default is all (all)
  show: 'all'
}

// Create an instruction function to modify all of the following return data and pass the filter keyword
const todosReducer = handleActions({
  // Initialize the data
  [getTodos_success]: (state, action) = > ({todos: action.payload, show: state.show}),
  // Add a task
  [addTodos_success]: (state, action) = > ({todos: [...state.todos, action.payload], show: state.show}),
  // Delete the task
  [removTodos_success]: (state, action) = > {
    // Get the subscript in the task list based on the ID passed
    const index = state.todos.findIndex(item= > item.id === action.payload)
    // Deep copy task list
    const todos = JSON.parse(JSON.stringify(state.todos))
    // Deletes the specified task from the task list
    todos.splice(index, 1)
    // Replace the todos with the modified todos
    return { todos, show: state.show }
  },
  // Change the status
  [modifyTodos_success]: (state, action) = > {
    // Get the subscript in the task list based on the ID passed
    const index = state.todos.findIndex(item= > item.id === action.payload.id)
    // Deep copy task list
    const todos = JSON.parse(JSON.stringify(state.todos))
    // Replace the specified location data from the task list
    todos[index] = action.payload
    // Replace the todos with the modified todos
    return { todos, show: state.show }
  },
  / / filter
  [screenTodos]: (state, action) = > {
    // Modify the keyword and return
    return{... state,show: action.payload}
  },
  // Clear completed tasks
  [clearCompletedTodos_success]: (state, action) = > {
    // Filter out all unfinished tasks to form a new array
    const todos = state.todos.filter(item= >! item.isCompleted)return{... state, todos} },// Change the edit status
  [modifyEditing_success]: (state, action) = > {
    // Get the task index value to be modified
    const index = state.todos.findIndex(item= > item.id === action.payload.id)
    / / copy
    const todos = JSON.parse(JSON.stringify(state.todos))
    / / modify
    todos[index].isEditing = action.payload.isEditing
    // Replace the original data
    return{... state, todos} },// Save the new task name
  [saveName_success]: (state, action) = > {
    // Get the task index value to be modified
    const index = state.todos.findIndex(item= > item.id === action.payload.id)
    / / copy
    const todos = JSON.parse(JSON.stringify(state.todos))
    / / modify
    todos[index].taskName = action.payload.taskName
    // Replace the original data
    return{... state, todos} } }, data)/ / export
export default todosReducer
Copy the code
// SRC /Components/ main.js Loses focus in edit state to save writing event function

import {Component} from 'react'
// Introduce bindActionCreators to generate the action instruction function automatically
import {bindActionCreators} from 'redux'
// connect retrieves data and adds instruction methods
import {connect} from 'react-redux'
// Get all instructions
import * as todoAction from '.. /Store/Actions/todos.action'

class Main extends Component {
	// Life cycle: after the component is mounted
	componentDidMount() {
		// Call the component's instruction function getTodos() to get the data and let props get it
		this.props.getTodos()
	}
	// Delete the task function
	removTodos(id) {
		// Pass the id of the task to be deleted to the directive
		this.props.removTodos(id)
	}
	modifyTodos(ev, id) {
		// Pass the data needed by the task to change the state (the state to be changed to, the id value)
		this.props.modifyTodos({id, isCompleted: ev.target.checked})
	}
	// Save the new task name
	saveEdit(data) {
		// Trigger the command to modify the edit state
		this.props.modifyEditing({id: data.id, isEditing: false})
		// Trigger the instruction to save the new name
		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 (// If completed is added, the completed class name is not added, and a new condition is added to determine whether to edit<li key={item.id} className={(item.isCompleted ? 'complete' :"') + (item.isEditing ? 'editing' :"')} >
								<div className="view">{/* Check the check box - Check the check box according to the data, add the status change event */}<input className="toggle" type="checkbox" checked={item.isCompleted} onChange={(ev)= >This.modifytodos (ev, item.id)} /> {this.modifyTodos(ev, item.id)}<label onDoubleClick={()= > this.props.modifyEditing({id: item.id, isEditing: true})}>{item.taskName}</label>{/* Add click method delete task */}<button className="destroy" onClick={()= > this.removTodos(item.id)}></button>
								</div>{/* Edit state default value is the current task name, after losing focus call method save new name */}<input className="edit" defaultValue={item.taskName} onBlur={(ev)= > this.saveEdit({id: item.id, taskName: ev.target.value})}/>
							</li>)})}</ul>
			</section>)}}// connect the first parameter - data
const myData = store= > ({
	// Arguments call custom functions, filter the required data and then use
	todos: filterTodos(store.todosReducer.todos, store.todosReducer.show)
})
// connect the second argument - the instruction function
const myAction = dispatch= > ({
	...bindActionCreators(todoAction, dispatch)
})

// Filter function
// Parameter 1: data to filter, parameter 2: keywords to filter
function filterTodos(todos, text) {
	// Determine the filter keyword
	switch (text) {
		// Return all data directly
		case 'all':
			return todos
		// The keyword is active
		case 'active':
			return todos.filter(item= >! item.isCompleted)// The keyword is completed
		case 'completed':
			return todos.filter(item= > item.isCompleted)
		default:
			return}}// Export and call CONNECT to inject data and directives
export default connect(myData, myAction)(Main)
Copy the code

Example code optimization

To avoid changing the requested address prefix later, separate the prefix

Use the corresponding interceptor to return the required data directly (the teacher used THEN, while I used object deconstruction)

The reducer function can be extracted separately to make it more readable

// SRC /Store/axios.js sets the URL address prefix and response interceptor

/ / introduce axios
import axios from 'axios'

// Set the default prefix
axios.defaults.baseURL = 'http://localhost:3005/api'

// Set the response interceptor to output data directly, which can be used directly within saga
// Because of the way I use object deconstruction, I don't use it here
// axios.interceptors.response.use(res => res.data)
Copy the code
// SRC /Store/index.js is introduced and run directly

// createStore creates the repository and applyMiddleware creates the middleware
import {createStore, applyMiddleware} from 'redux'
// Introduce middleware saga
import reduxSage from 'redux-saga'
// Reduce the reducer needed to import the repository
import reducer from './Reducers'
// The introduction needs to use saga
import todoSaga from './Sagas/todos.saga'
// Import and run axiOS Settings directly
import './axios'

/ / create a saga
const saga = reduxSage()
// Create store data warehouse and create middleware
const store = createStore(reducer, applyMiddleware(saga))
// saga calls the run method for saga to take effect
saga.run(todoSaga)

export default store
Copy the code
/ / SRC/Store/Sagas/todos. Saga. Js modify delete prefix all interfaces

// Introduce saga's two methods, takeEvery catches the instruction and intercepts it, and PUT passes the instruction to store
import {takeEvery, put} from 'redux-saga/effects'
// Import axios request interface data
import axios from 'axios'

// The function to execute after capturing the instruction
function* loaderData() {
  // Get data
  const {data} = yield axios.get('/todos')
  // Call the put method to pass data to other directives
  yield put({type: 'getTodos_success'.payload: data})
}

// Add the function executed after the task instruction intercepts
function* addTodos(action) {
  // Call the interface to add a task
  const {data} = yield axios.post('/todos', {taskName: action.payload})
  // Passes the data returned by the interface to the next instruction
  yield put({type: 'addTodos_success'.payload: data.task})
}

// Delete the function executed after the task instruction intercepts
function* removTodos(action) {
  // Call the interface to delete the task based on the ID
  const {data} = yield axios.delete('/todos', { params: {id: action.payload}})
  // Call another instruction to pass in the id to be deleted
  yield put({type: 'removTodos_success'.payload: data.tasks.id})
}

// Capture the callback function after modifying the status directive
function* modifyTodos(action) {
  // Call the interface to change the state
  const {data} = yield axios.put('/todos/isCompleted', action.payload)
  // Call another instruction, passing the data returned by the interface to the next instruction
  yield put({type: 'modifyTodos_success'.payload: data.task})
}


// Catch the callback function after the clearCompletedTodos directive
function* clearCompletedTodos() {
  yield axios.delete('/todos/clearCompleted')
  yield put({type: 'clearCompletedTodos_success'})}// Callback after task edit status modification command intercepts
function* modifyEditing(action) {
  yield axios.put('/todos/isEditing', action.payload)
  yield put({type: 'modifyEditing_success'.payload: action.payload})
}

// Modify the save new task name callback function
function* saveName(action) {
  yield axios.put('/todos', action.payload)
  yield put({type: 'saveName_success'.payload: action.payload})
}

/ / export saga
export default function* getTodos() {
  // Catch the getTodos directive and execute the loaderData method after catching it
  yield takeEvery('getTodos', loaderData)
  // Capture addTodos instruction and execute addTodos method after capture
  yield takeEvery('addTodos', addTodos)
  // Capture removTodos directive, execute removTodos method
  yield takeEvery('removTodos', removTodos)
  // Capture the modifyTodos directive and call the modifyTodos method
  yield takeEvery('modifyTodos', modifyTodos)
  // Catch the clearCompletedTodos directive and call the clearCompletedTodos method
  yield takeEvery('clearCompletedTodos', clearCompletedTodos)
  // Capture task edit status modification instructions
  yield takeEvery('modifyEditing', modifyEditing)
  // Save the new task name command
  yield takeEvery('saveName', saveName)
}
Copy the code
/ / SRC/Store/Reducers/todos. Reducer. Js will all saga not intercept instruction logic separate packaging

// Introduce handleActions to simplify writing. Note that this is handleActions and not handleAction
import {handleActions} from 'redux-actions'
// Introduce directives that actually change the data. The directives intercepted by middleware saga do not need to be introduced
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success, screenTodos, clearCompletedTodos_success, modifyEditing_success, saveName_success} from '.. /Actions/todos.action'

// Raw data
const data = {
  // Empty list
  todos: [].// Add a parameter as the filter keyword, default is all (all)
  show: 'all'
}

// Encapsulate all instruction function logic separately
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) = > {
  // Get the subscript in the task list based on the ID passed
  const index = state.todos.findIndex(item= > item.id === action.payload)
  // Deep copy task list
  const todos = JSON.parse(JSON.stringify(state.todos))
  // Deletes the specified task from the task list
  todos.splice(index, 1)
  // Replace the todos with the modified todos
  return { todos, show: state.show }
}
const modifyTodos = (state, action) = > {
  // Get the subscript in the task list based on the ID passed
  const index = state.todos.findIndex(item= > item.id === action.payload.id)
  // Deep copy task list
  const todos = JSON.parse(JSON.stringify(state.todos))
  // Replace the specified location data from the task list
  todos[index] = action.payload
  // Replace the todos with the modified todos
  return { todos, show: state.show }
}
const screenTodosData = (state, action) = > {
    // Modify the keyword and return
    return{... state,show: action.payload}
  }
const clearCompletedTodos = (state, action) = > {
    // Filter out all unfinished tasks to form a new array
    const todos = state.todos.filter(item= >! item.isCompleted)return{... state, todos} }const modifyEditing = (state, action) = > {
    // Get the task index value to be modified
    const index = state.todos.findIndex(item= > item.id === action.payload.id)
    / / copy
    const todos = JSON.parse(JSON.stringify(state.todos))
    / / modify
    todos[index].isEditing = action.payload.isEditing
    // Replace the original data
    return{... state, todos} }const saveName = (state, action) = > {
    // Get the task index value to be modified
    const index = state.todos.findIndex(item= > item.id === action.payload.id)
    / / copy
    const todos = JSON.parse(JSON.stringify(state.todos))
    / / modify
    todos[index].taskName = action.payload.taskName
    // Replace the original data
    return{... state, todos} }// Create an instruction function to modify all of the following return data and pass the filter keyword
const todosReducer = handleActions({
  // Initialize the data
  [getTodos_success]: getTodos,
  // Add a task
  [addTodos_success]: addTodos,
  // Delete the task
  [removTodos_success]: removTodos,
  // Change the status
  [modifyTodos_success]: modifyTodos,
  / / filter
  [screenTodos]: screenTodosData,
  // Clear completed tasks
  [clearCompletedTodos_success]: clearCompletedTodos,
  // Change the edit status
  [modifyEditing_success]: modifyEditing,
  // Save the new task name
  [saveName_success]: saveName
}, data)

/ / export
export default todosReducer
Copy the code

Immutable use

Installation:npm install immutable

If you directly modify the data in State in React, the interface will not be updated. Therefore, we need to make a deep copy of the data every time we modify it, which is troublesome and does not belong to our business logic

Immutable is a library of tools that make data immutable objects. To modify a book, use internal methods, such as:

  • FromJS (data objects) : Objects can be made immutable
  • SetIn (modify data, [modify attributes], modify values) : Modify data
  • GetIn (read data, [read properties]) : Use data
  • MergeDeep (modify data, merge attributes: merge values) : Merge/add data
  • RemoveIn (modify data, [delete data, delete index]) : Delete data (use getIn to get index)
  • UpdateIn (Modify data, [to modify data, index value], updated value (function returns)) : Replace/update data

Note: Data cannot be accessed using dots, getIn () is required

The data passed by Saga may need to be modified if some data does not satisfy the method

/ / SRC/Store/Reducers/todos. Reducer. Js immutable method to be used

// Introduce handleActions to simplify writing. Note that this is handleActions and not handleAction
import {handleActions} from 'redux-actions'
// Introduce directives that actually change the data. The directives intercepted by middleware saga do not need to be introduced
import {getTodos_success, addTodos_success, removTodos_success, modifyTodos_success, screenTodos, clearCompletedTodos_success, modifyEditing_success, saveName_success} from '.. /Actions/todos.action'
// Introduce immutable methods
import { fromJS, setIn, mergeDeep, getIn, removeIn, updateIn } from 'immutable'

// Raw data
const data = fromJS({
  // Empty list
  todos: [].// Add a parameter as the filter keyword, default is all (all)
  show: 'all'
})

// Encapsulate all instruction function logic separately
// Get the task list, modify it using setIn
const getTodos = (state, action) = > setIn(state, ['todos'], action.payload)
// Add tasks using mergeDeep merge
const addTodos = (state, action) = > mergeDeep(state, {todos: [action.payload]})
// Delete the task
const removTodos = (state, action) = > {
  // To get the index value, use getIn
  const index = getIn(state, ['todos']).findIndex(item= > item.id === action.payload)
  // Delete using removeIn
  return removeIn(state, ['todos', index])
}
// Change the task status
const modifyTodos = (state, action) = > {
  // To get the index value, use getIn
  const index = getIn(state, ['todos']).findIndex(item= > item.id === action.payload.id)
  // Use updateIn to modify/update
  return updateIn(state, ['todos', index], () = > action.payload)
}
// The filter task is modified using setIn
const screenTodosData = (state, action) = > setIn(state, ['show'], action.payload)
// Clear all completed tasks
const clearCompletedTodos = (state, action) = > {
  // Get all unfinished tasks
  const todos = getIn(state, ['todos']).filter(item= >! item.isCompleted)// Use setIn to make all changes
  return setIn(state, ['todos'], todos)
}
// Change the edit status
const modifyEditing = (state, action) = > {
    // To get the index value, use getIn
    const index = getIn(state, ['todos']).findIndex(item= > item.id === action.payload.id)
    // Use updateIn to modify/update
    return updateIn(state, ['todos', index], () = > action.payload)
  }
// Save the new name
const saveName = (state, action) = > {
    // To get the index value, use getIn
    const index = getIn(state, ['todos']).findIndex(item= > item.id === action.payload.id)
    // Use updateIn to modify/update
    return updateIn(state, ['todos', index], () = > action.payload)
  }


// Create an instruction function to modify all of the following return data and pass the filter keyword
const todosReducer = handleActions({
  // Initialize the data
  [getTodos_success]: getTodos,
  // Add a task
  [addTodos_success]: addTodos,
  // Delete the task
  [removTodos_success]: removTodos,
  // Change the status
  [modifyTodos_success]: modifyTodos,
  / / filter
  [screenTodos]: screenTodosData,
  // Clear completed tasks
  [clearCompletedTodos_success]: clearCompletedTodos,
  // Change the edit status
  [modifyEditing_success]: modifyEditing,
  // Save the new task name
  [saveName_success]: saveName
}, data)

/ / export
export default todosReducer
Copy the code
/ / SRC/Store/Sagas/todos. Saga. Js some data does not meet the requirements may change on saga passed the data

// Introduce saga's two methods, takeEvery catches the instruction and intercepts it, and PUT passes the instruction to store
import {takeEvery, put} from 'redux-saga/effects'
// Import axios request interface data
import axios from 'axios'

// The function to execute after capturing the instruction
function* loaderData() {
  // Get data
  const {data} = yield axios.get('/todos')
  // Call the put method to pass data to other directives
  yield put({type: 'getTodos_success'.payload: data})
}

// Add the function executed after the task instruction intercepts
function* addTodos(action) {
  // Call the interface to add a task
  const {data} = yield axios.post('/todos', {taskName: action.payload})
  // Passes the data returned by the interface to the next instruction
  yield put({type: 'addTodos_success'.payload: data.task})
}

// Delete the function executed after the task instruction intercepts
function* removTodos(action) {
  // Call the interface to delete the task based on the ID
  const {data} = yield axios.delete('/todos', { params: {id: action.payload}})
  // Call another instruction to pass in the id to be deleted
  yield put({type: 'removTodos_success'.payload: data.tasks.id})
}

// Capture the callback function after modifying the status directive
function* modifyTodos(action) {
  // Call the interface to change the state
  const {data} = yield axios.put('/todos/isCompleted', action.payload)
  // Call another instruction, passing the data returned by the interface to the next instruction
  yield put({type: 'modifyTodos_success'.payload: data.task})
}


// Catch the callback function after the clearCompletedTodos directive
function* clearCompletedTodos() {
  yield axios.delete('/todos/clearCompleted')
  yield put({type: 'clearCompletedTodos_success'})}// Callback after task edit status modification command intercepts
function* modifyEditing(action) {
  const { data } = yield axios.put('/todos/isEditing', action.payload)
  yield put({type: 'modifyEditing_success'.payload: data.task})
}

// Modify the save new task name callback function
function* saveName(action) {
  const { data } = yield axios.put('/todos', action.payload)
  yield put({type: 'saveName_success'.payload: data.task})
}

/ / export saga
export default function* getTodos() {
  // Catch the getTodos directive and execute the loaderData method after catching it
  yield takeEvery('getTodos', loaderData)
  // Capture addTodos instruction and execute addTodos method after capture
  yield takeEvery('addTodos', addTodos)
  // Capture removTodos directive, execute removTodos method
  yield takeEvery('removTodos', removTodos)
  // Capture the modifyTodos directive and call the modifyTodos method
  yield takeEvery('modifyTodos', modifyTodos)
  // Catch the clearCompletedTodos directive and call the clearCompletedTodos method
  yield takeEvery('clearCompletedTodos', clearCompletedTodos)
  // Capture task edit status modification instructions
  yield takeEvery('modifyEditing', modifyEditing)
  // Save the new task name command
  yield takeEvery('saveName', saveName)
}
Copy the code
// SRC /Components/ main.js Components use immutable modification

import {Component} from 'react'
// Introduce bindActionCreators to generate the action instruction function automatically
import {bindActionCreators} from 'redux'
// connect retrieves data and adds instruction methods
import {connect} from 'react-redux'
// Get all instructions
import * as todoAction from '.. /Store/Actions/todos.action'
/ / into the immutable
import { getIn } from 'immutable'

class Main extends Component {
	// Life cycle: after the component is mounted
	componentDidMount() {
		// Call the component's instruction function getTodos() to get the data and let props get it
		this.props.getTodos()
	}
	// Delete the task function
	removTodos(id) {
		// Pass the id of the task to be deleted to the directive
		this.props.removTodos(id)
	}
	modifyTodos(ev, id) {
		// Pass the data needed by the task to change the state (the state to be changed to, the id value)
		this.props.modifyTodos({id, isCompleted: ev.target.checked})
	}
	// Save the new task name
	saveEdit(data) {
		// Trigger the command to modify the edit state
		this.props.modifyEditing({id: data.id, isEditing: false})
		// Trigger the instruction to save the new name
		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 (// If completed is added, the completed class name is not added, and a new condition is added to determine whether to edit<li key={item.id} className={(item.isCompleted ? 'completed' :'' + '' + (item.isEditing ? 'editing' :"')} >
								<div className="view">{/* Check the check box - Check the check box according to the data, add the status change event */}<input className="toggle" type="checkbox" checked={item.isCompleted} onChange={(ev)= >This.modifytodos (ev, item.id)} /> {this.modifyTodos(ev, item.id)}<label onDoubleClick={()= > this.props.modifyEditing({id: item.id, isEditing: true})}>{item.taskName}</label>{/* Add click method delete task */}<button className="destroy" onClick={()= > this.removTodos(item.id)}></button>
								</div>{/* Edit state default value is the current task name, after losing focus call method save new name */}<input className="edit" defaultValue={item.taskName} onBlur={(ev)= > this.saveEdit({id: item.id, taskName: ev.target.value})}/>
							</li>)})}</ul>
			</section>)}}// connect the first parameter - data
const myData = store= > ({
	// Arguments call custom functions, filter the required data, and then use the immutable getIn method
	todos: filterTodos(getIn(store.todosReducer, ['todos']), getIn(store.todosReducer, ['show'))})// connect the second argument - the instruction function
const myAction = dispatch= > ({
	...bindActionCreators(todoAction, dispatch)
})

// Filter function
// Parameter 1: data to filter, parameter 2: keywords to filter
function filterTodos(todos, text) {
	// Determine the filter keyword
	switch (text) {
		// Return all data directly
		case 'all':
			return todos
		// The keyword is active
		case 'active':
			return todos.filter(item= >! item.isCompleted)// The keyword is completed
		case 'completed':
			return todos.filter(item= > item.isCompleted)
		default:
			return}}// Export and call CONNECT to inject data and directives
export default connect(myData, myAction)(Main)
Copy the code
// SRC /Components/ footer.js Components use immutable modification

import {Component} from 'react'
// Introduce redux-related components
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as todoAction from '.. /Store/Actions/todos.action'
/ / into the immutable
import { getIn } from 'immutable'

class Footer extends Component {
	// Uncompleted task count method
	count(array) {
		// Filter all unfinished tasks into an array and return the length of the array
		return array.filter(item= >! item.isCompleted).length }// Three filter button click events
	filters(text){
		// Trigger the filter instruction to pass the parameter
		this.props.screenTodos(text)
	}
	// Clear the click event
	clearCompleted() {
		// Trigger the command, the cleanup is complete without passing parameters
		this.props.clearCompletedTodos()
	}
  render() {
    return (
      <footer className="footer">
				<span className="todo-count">{/* Unfinished task statistics, call method */}<strong>{this.count(this.props.todos)}</strong>unfinished</span>
				<ul className="filters">
					<li>{/* Add click filter event */}<span onClick={()= >This filters (' all ')} > all of them</span>
					</li>
					<li>{/* Add click filter event */}<span onClick={()= >This. Filters (' active ')} > unfinished</span>
					</li>
					<li>{/* Add click filter event */}<span onClick={()= >This. Filters (' completed ')} > has been completed</span>
					</li>
				</ul>{/* button adds click event */}<button className="clear-completed" onClick={()= >This.clearcompleted ()} > The cleanup is complete</button>
			</footer>)}}/ / data
const myData = store= > ({
	// Use the getIn method immutable
	todos: getIn(store.todosReducer, ['todos'])})/ / instructions
const myAction = dispatch= > ({
	...bindActionCreators(todoAction, dispatch)
})

export default connect(myData, myAction)(Footer)
Copy the code
// SRC /Components/ header. js Components use immutable modification

import {Component} from 'react'
// Introduce methods and directives that redux needs to use
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as todoAction from '.. /Store/Actions/todos.action'
/ / into the immutable

import { getIn } from 'immutable'

class Header extends Component {
  // input tag event function
  addTodos(ev) {
    // 01, determine whether the case is a return key
    if(ev.keyCode === 13) {
      // 02, if enter is used to get the content and remove the white space on both sides
      const value = ev.target.value.trim()
      // 03, determine the result is empty after removing whitespace
      if (value.length > 0) {
        // 04, if there is any content. Pass the data to the instruction method
        this.props.addTodos(value)
      }
      // 05, just lift the enter key to empty the content, whether or not empty
      ev.target.value = ' '}}render() {
    return (
      <header className="header">
        <h1>todos</h1>{/* Add keyboard lift event to input */}<input className="new-todo" placeholder="What remains to be done?" autoFocus onKeyUp={(ev)= > this.addTodos(ev)} />
      </header>)}}/ / redux data
const myData = store= > ({
  // Use the getIn method immutable
  todos: getIn(store.todosReducer, ['todos'])})/ / instructions
const myAction = dispatch= > ({
  ...bindActionCreators(todoAction, dispatch)
})
export default connect(myData, myAction)(Header)
Copy the code