In this section, we will strike while the iron is hot and use the three core concepts of Redux learned in the previous tutorial to refactor the rest of our to-do list, which involves refactoring the TodoList and Footer sections into Redux. Redux combineReducers API is used for logical separation and combination, which enables us to use the convenience of Redux without making the application logic look bloated. By using the convenience of React componentalization, we can make the state processing “componentalization”.
Refactoring code: Migrate the TodoList part to Redux
Welcome to the Redux Package series:
- Redux Redux Redux Redux Redux Redux Redux Redux Redux Redux
- Redux: Strike while the Iron is Hot, complete refactoring (aka this one)
- Redux Education Package Association (3) : To perform their respective duties and regain the original purpose
This tutorial is part of the React Front Engineer tutorial. Click here to see it all.
In the previous sections, we covered the core concepts of Redux and used these concepts to refactor some of our to-do applications. In this section, we will strike while the iron is hot and continue to refactor our applications with Redux, taking full advantage of what we have learned.
At this point, if you try out the to-do widget in your browser, you’ll see that it can only add new items to your to-do list. For the complete and redo to-do list and filter to view to-do list features, we don’t use Redux yet. So when you click on a single to-do item, the browser displays an error; When you click the three filter buttons at the bottom, the browser doesn’t react.
In this section, we’ll use Redux to refactor the Complete and Redo to-do feature, which means you can click on a to-do item to complete it.
We will refactor this functionality using Redux best practices:
- Define the Action Creators
- Define the Reducers
connect
Components and within componentsdispatch
Action
You can use this three-step process to develop new functionality or improve existing functionality over and over again as you develop your Redux application.
Define the Action Creators
First we need to define the Action for the “Complete To-do” function. Open SRC /actions/index.js and modify the Action as follows:
let nextTodoId = 0;
export const addTodo = text= > ({
type: "ADD_TODO".id: nextTodoId++,
text
});
export const toggleTodo = id= > ({
type: "TOGGLE_TODO",
id
});
Copy the code
As you can see, we defined and exported a toggleTodo arrow function that takes the ID and returns an Action of type “TOGGLE_TODO”.
Define the Reducers
Then we can define the Reducers of the response dispatch(Action), open SRC /index.js, and modify the rootReducer function as follows:
import React from "react";
import ReactDOM from "react-dom";
import App, { VisibilityFilters } from "./components/App";
import { createStore } from "redux";
import { Provider } from "react-redux";
const initialState = {
todos: [{id: 1.text: "Hello, tootlark.".completed: false
},
{
id: 2.text: "I am a little little tootlark.".completed: false
},
{
id: 3.text: "If small birds, can also show a grand plan!".completed: false}].filter: VisibilityFilters.SHOW_ALL
};
const rootReducer = (state, action) = > {
switch (action.type) {
case "ADD_TODO": {
const { todos } = state;
return {
...state,
todos: [
...todos,
{
id: action.id,
text: action.text,
completed: false}}; }case "TOGGLE_TODO": {
const { todos } = state;
return {
...state,
todos: todos.map(todo= >
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
)
};
}
default:
returnstate; }};const store = createStore(rootReducer, initialState);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>.document.getElementById("root"));Copy the code
As you can see, we added a “TOGGLE_TODO” statement to the switch statement, and determined the toDO of the corresponding operation based on action.id. The current completed property was reversed to indicate completed to unfinished or unfinished operations.
Connect and dispatch (action)
After defining the Action and declaring the Reducers that respond to the Action, we started defining the interface for React and Redux to communicate: Connect integrates Redux Store content into React, and Dispatch issues instructions to operate the Redux Store from React.
We open the SRC/components/TodoList. Js file, the file content to make the following changes:
import React from "react";
import PropTypes from "prop-types";
import Todo from "./Todo";
import { connect } from "react-redux";
import { toggleTodo } from ".. /actions";
const TodoList = ({ todos, dispatch }) = > (
<ul>
{todos.map(todo => (
<Todo
key={todo.id}
{. todo}
onClick={()= > dispatch(toggleTodo(todo.id))}
/>
))}
</ul>
);
TodoList.propTypes = {
todos: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}).isRequired
).isRequired
};
export default connect()(TodoList);
Copy the code
As you can see, we made the following changes to the file:
- First of all, from the
react-redux
exportconnect
Function, which is responsible for givingTodoList
The incomingdispatch
Delta function, which allows us to be inTodoList
In the componentdispatch
The Action. - And then we derived
toggleTodo
Action Creators, which will previously be received from the parent componenttoggleTodo
Method and call when Todo is clickeddispatch(toggle(todo.id))
。 - We delete
propsTypes
Is no longer neededtoggleTodo
.
Delete useless code
SRC/Components/app.js: SRC/Components/app.js: SRC/Components/app.js: SRC /components/ app.js
import React from "react";
import AddTodo from "./AddTodo";
import TodoList from "./TodoList";
import Footer from "./Footer";
import { connect } from "react-redux";
export const VisibilityFilters = {
SHOW_ALL: "SHOW_ALL".SHOW_COMPLETED: "SHOW_COMPLETED".SHOW_ACTIVE: "SHOW_ACTIVE"
};
const getVisibleTodos = (todos, filter) = > {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos;
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(t= > t.completed);
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(t= >! t.completed);default:
throw new Error("Unknown filter: "+ filter); }};class App extends React.Component {
constructor(props) {
super(props);
this.setVisibilityFilter = this.setVisibilityFilter.bind(this);
}
setVisibilityFilter(filter) {
this.setState({
filter: filter
});
}
render() {
const { todos, filter } = this.props;
return (
<div>
<AddTodo />
<TodoList todos={getVisibleTodos(todos, filter)} />
<Footer
filter={filter}
setVisibilityFilter={this.setVisibilityFilter}
/>
</div>
);
}
}
const mapStateToProps = (state, props) => ({
todos: state.todos,
filter: state.filter
});
export default connect(mapStateToProps)(App);
Copy the code
As you can see, we removed the toggleTodo method and, accordingly, the toggleTodo definition defined in Constructor and the toggleTodo property passed to TodoList in the Render method.
Save the modified code above, open your browser, and you should again be able to click on a single to-do item to complete and redo it:
summary
In this section, we introduce best practices for developing Redux applications and practice them in detail by refactoring the “Complete and redo to-do list” feature.
Refactoring code: Migrate the Footer section to Redux
In this section, we’ll continue refactoring the rest of the way. We will continue to follow the best practices for Redux development mentioned in the previous section:
- Define the Action Creators
- Define the Reducers
connect
Components and within componentsdispatch
Action
Define the Action Creators
Open the SRC /actions/index.js file and make the following changes:
let nextTodoId = 0;
export const addTodo = text= > ({
type: "ADD_TODO".id: nextTodoId++,
text
});
export const toggleTodo = id= > ({
type: "TOGGLE_TODO",
id
});
export const setVisibilityFilter = filter= > ({
type: "SET_VISIBILITY_FILTER",
filter
});
Copy the code
As you can see, we created an Action Creators called setVisibilityFilter, which received the Filter parameter and returned an Action of type “SET_VISIBILITY_FILTER.”
Define the Reducers
Open the SRC /index.js file and modify the code as follows:
import React from "react";
import ReactDOM from "react-dom";
import App, { VisibilityFilters } from "./components/App";
import { createStore } from "redux";
import { Provider } from "react-redux";
const initialState = {
todos: [{id: 1.text: "Hello, tootlark.".completed: false
},
{
id: 2.text: "I am a little little tootlark.".completed: false
},
{
id: 3.text: "If small birds, can also show a grand plan!".completed: false}].filter: VisibilityFilters.SHOW_ALL
};
const rootReducer = (state, action) = > {
switch (action.type) {
case "ADD_TODO": {
const { todos } = state;
return {
...state,
todos: [
...todos,
{
id: action.id,
text: action.text,
completed: false}}; }case "TOGGLE_TODO": {
const { todos } = state;
return {
...state,
todos: todos.map(todo= >
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
)
};
}
case "SET_VISIBILITY_FILTER": {
return {
...state,
filter: action.filter
};
}
default:
returnstate; }};const store = createStore(rootReducer, initialState);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>.document.getElementById("root"));Copy the code
As you can see, we added a case statement in response to the “SET_VISIBILITY_FILTER” Action to update the state in the Store by receiving the new filter.
Connect and dispatch (action)
Open SRC /components/ footer.js and make the following changes:
import React from "react";
import Link from "./Link";
import { VisibilityFilters } from "./App";
import { connect } from "react-redux";
import { setVisibilityFilter } from ".. /actions";
const Footer = ({ filter, dispatch }) = > (
<div>
<span>Show: </span>
<Link
active={VisibilityFilters.SHOW_ALL= = =filter}
onClick={()= > dispatch(setVisibilityFilter(VisibilityFilters.SHOW_ALL))}
>
All
</Link>
<Link
active={VisibilityFilters.SHOW_ACTIVE= = =filter}
onClick={()= >
dispatch(setVisibilityFilter(VisibilityFilters.SHOW_ACTIVE))
}
>
Active
</Link>
<Link
active={VisibilityFilters.SHOW_COMPLETED= = =filter}
onClick={()= >
dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))
}
>
Completed
</Link>
</div>
);
export default connect()(Footer);
Copy the code
As you can see, the file above mainly does these things:
- First of all, from the
react-redux
exportconnect
Function, which is responsible for givingFooter
The incomingdispatch
Delta function, which allows us to be inFooter
In the componentdispatch
The Action. - And then we derived
setVisibilityFilter
Action Creators, which will previously be received from the parent componentsetVisibilityFilter
Method and call when Link is clickeddispatch
Corresponding Action.
Delete useless code
SRC/Components/app.js: SRC/Components/app.js: SRC/Components/app.js: SRC /components/ app.js
import React from "react";
import AddTodo from "./AddTodo";
import TodoList from "./TodoList";
import Footer from "./Footer";
import { connect } from "react-redux";
export const VisibilityFilters = {
SHOW_ALL: "SHOW_ALL".SHOW_COMPLETED: "SHOW_COMPLETED".SHOW_ACTIVE: "SHOW_ACTIVE"
};
const getVisibleTodos = (todos, filter) = > {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos;
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(t= > t.completed);
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(t= >! t.completed);default:
throw new Error("Unknown filter: "+ filter); }};class App extends React.Component {
render() {
const { todos, filter } = this.props;
return (
<div>
<AddTodo />
<TodoList todos={getVisibleTodos(todos, filter)} />
<Footer filter={filter} />
</div>
);
}
}
const mapStateToProps = (state, props) => ({
todos: state.todos,
filter: state.filter
});
export default connect(mapStateToProps)(App);
Copy the code
As you can see, we have removed the setVisibilityFilter method and, accordingly, the setVisibilityFilter definition defined in constructor and the Render method, SetVisibilityFilter property passed to the Footer.
Since we no longer need to define content in the constructor method, we delete it.
Save the modified code above, open your browser, and you should be able to filter completed and unfinished to-do items again by clicking the button at the bottom:
summary
In this section, we introduce best practices for developing Redux applications and practice them in detail by refactoring the “Filter view to-do” feature.
Since then, we’ve refactored the entire backlog applet using Redux, but the refactored code is still a bit messy, with different types of component states mixed up. As our application became more complex, our rootReducer became very verbose, so it was time to consider splitting the state of the different components.
In the next section, we’ll show you how to split up the state of different components to make sure you’re comfortable writing large applications.
CombineReducers: Combines the Reducers of the split state
When the application logic becomes increasingly complex, we need to consider splitting the huge Reducer function into independent units, which is called “divide and conquer” in the algorithm.
Reducers in Redux is actually used to deal with a certain part of the State stored in the Store. A Reducer corresponds to a certain attribute in the State object tree one by one, and a Reducer is responsible for dealing with the corresponding attribute in the State. For example, let’s look at the current structure of our State:
const initialState = {
todos: [{id: 1.text: "Hello, tootlark.".completed: false
},
{
id: 2.text: "I am a little little tootlark.".completed: false
},
{
id: 3.text: "If small birds, can also show a grand plan!".completed: false}].filter: VisibilityFilters.SHOW_ALL
};
Copy the code
Because Reducer corresponds to state-related parts, here we have two parts of State: TODOS and filter, so we can compile two corresponding Reducer.
Compile Reducer: todos
In Redux best practice, because Reducer corresponds to modifying relevant parts of State, when the State object tree is large, our Reducer will also have many, so we generally build a separate reducers folder to store these “reducers “.
We create the reducers folder under the SRC directory, and then create a new todos.js file in it, which represents the Reducer of the corresponding TODOS attribute in the State:
const initialTodoState = [
{
id: 1.text: "Hello, tootlark.".completed: false
},
{
id: 2.text: "I am a little little tootlark.".completed: false
},
{
id: 3.text: "If small birds, can also show a grand plan!".completed: false}];const todos = (state = initialTodoState, action) = > {
switch (action.type) {
case "ADD_TODO": {
return [
...state,
{
id: action.id,
text: action.text,
completed: false}]; }case "TOGGLE_TODO": {
return state.map(todo= >
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
}
default:
returnstate; }};export default todos;
Copy the code
As you can see, the code above does these things:
- First we will be original
initialState
The inside of thetodos
Part of it is broken downsrc/reducers/todos.js
In the file, we define oneinitialTodoState
Stands for the previousinitialState
的todos
Part, it’s an array and assigns it totodos
In the functionstate
The default value of the argument, when this function is called, if the state argument passed isundefined
ornull
When thestate
isinitialState
. - And then we defined one
todos
The arrow function, its structure androotReducer
Similarly, two parameters are received:state
和action
And then into oneswitch
Judge statement, according toaction.type
Determine the appropriate Action type, and then pairstate
Perform the required operation.
Pay attention to
Our Todos Reducers is only responsible for processing the ToDOS part of the original initialState, so its state here is the original TODOS attribute, which is an array, so when we change the data in the switch statement, we need to operate the array. And finally returns a new array.
Compile Reducer: filter
Previously, we solved the operation problem of the original initialState’s ToDOS attribute by using Todos Reducer. Now we will explain the operation problem of the remaining filter attribute.
Create filter.js in the SRC /reducers folder and add the following:
import { VisibilityFilters } from ".. /components/App";
const filter = (state = VisibilityFilters.SHOW_ALL, action) = > {
switch (action.type) {
case "SET_VISIBILITY_FILTER":
return action.filter;
default:
returnstate; }};export default filter;
Copy the code
As you can see, we define a filter arrow function that takes two arguments: State and Action. Because the Filter Reducer is only responsible for processing the filter attribute of the original initialState, the state parameter here is the original filter attribute, and we gave it a default value here.
Pay attention to
The rest of the filter function is similar to rootReducer, but note that its state operates on the filter property, so when determining the “SET_VISIBILITY_FILTER” action type, It simply returns action.filter.
Combine multiple Reducer
After we split the rootReducer logic and dealt with the properties in the State saved in the Store, we were able to ensure that each reducer was small. At this point we had to consider how to combine these small reducers. The resulting rootReducer, like the React component, ends up with just one root-level component, which in our to-do widget is the app.js component.
Redux provides combineReducers API for us, which is used to combine several small reducers. We created an index.js file under the SRC /reducers folder and added the following contents to it:
import { combineReducers } from "redux";
import todos from "./todos";
import filter from "./filter";
export default combineReducers({
todos,
filter
});
Copy the code
As you can see, we exported the combineReducers function from the Redux module, and then exported the previously defined toDOS and Filter Reducer.
Then we combine todos and filter as object attributes through simple object representation, and then pass it to combineReducers function. Here combineReducers will operate todos and filter internally. It then generates a rootReducer form similar to our previous one. Finally, we export the generated rootReducer.
CombineReducers have two main functions:
1) Combine all reducer states and finally combine them into an object state tree similar to the initialState defined before.
That is, the state of the todos reducer is:
state = [ { id: 1.text: "Hello, tootlark.".completed: false }, { id: 2.text: "I am a little little tootlark.".completed: false }, { id: 3.text: "If small birds, can also show a grand plan!".completed: false}];Copy the code
The state of filter reducer is:
state = VisibilityFilters.SHOW_ALL Copy the code
Then the final result obtained by combineReducers combining these two reducer states is as follows:
state = { todos: [{id: 1.text: "Hello, tootlark.".completed: false }, { id: 2.text: "I am a little little tootlark.".completed: false }, { id: 3.text: "If small birds, can also show a grand plan!".completed: false}].filter: VisibilityFilters.SHOW_ALL }; Copy the code
The final state after the combineReducers combination is the state tree of the state JavaScript object stored in the Store.
2) Dispatch actions.
After combining the Todos and filter Reducer through combineReducers, dispatch actions from the React component will traverse the Todos and filter reducer. Check if there are case statements that respond to action.type. If there are, all of these case statements will respond.
Remove unnecessary code
After splitting the original rootReducer into two reducer — Todos and Filter — and combining them through combineReducers API provided by Redux, InitialState and rootReducer we defined earlier in SRC /index.js are no longer needed, so we will delete them immediately:
import React from "react";
import ReactDOM from "react-dom";
import App, { VisibilityFilters } from "./components/App";
import { createStore } from "redux";
import { Provider } from "react-redux";
import rootReducer from "./reducers";
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>.document.getElementById("root"));Copy the code
As you can see, we removed the rootReducer previously defined in SRC /index.js and used the rootReducer exported from SRC /reducers/index.js instead.
As we mentioned before, the first function of combineReducers is to combine multiple reducer states and finally merge them into a large JavaScript object state tree, which is automatically stored in the Redux Store. So we no longer need to explicitly pass the second initialState parameter to createStore.
Save the changes, open the browser, and you can still operate all the functions. You can add items to your to-do list, click on a to-do list to complete it, and view items in different states through the three filter buttons at the bottom:
summary
In this section, we take a look at the combineReducers API provided by Redux, which addresses two main problems:
- When the application becomes increasingly complex, we need to split the Reducer, then we need to combine the Reducer and merge all the states.
- The actions dispatched by each React component are sent to the corresponding Reducer.
With combineReducers, no matter how complex our application is, we can split the logic for processing application state into simple and easy to understand small files, and then combine these small files to complete complex application logic. This is similar to the composition idea of React component. As you can imagine, How powerful component programming is!
This tutorial is part of the React Front Engineer tutorial. Click here to see it all.
Want to learn more exciting practical skills tutorial? Come and visit the Tooquine community.