In this section, we’ll introduce the concepts of container components, which take over the state, and display components, which render the interface. The Display component was also the heart of React and focuses on writing an efficient user interface.
Refactoring code: Separate TodoList state from rendering
Welcome to the Redux Package series:
- Redux Redux Redux Redux Redux Redux Redux Redux Redux Redux
- Redux: Strike while the iron is hot and completely refactor
- Redux Education Package (3) : Each To perform their duties, to regain the original purpose (namely this article)
This tutorial is part of the React Front Engineer tutorial. Click here to see it all.
Show components and container components
You’d think Redux would be good enough to keep State sources predictable by stripping them from the React component and storing them in the Store. But Redux isn’t done yet. It further introduces the concepts of Presentational Components and Container Components, separating the purely Presentational React component from state.
When we break down the View layer in the Redux state loop diagram further, it looks like this:
That is, we add a layer between the final rendering component and the State stored in the Store. We call this layer responsible for receiving State from the Store and assembling the State changes that the component wants to initiate into actions, which are then issued through the Dispatch function.
The layer that remains after the state has been stripped completely is called the presentation component, which takes the data from the container component, renders it into a UI, and tells the container component to dispatch actions on its behalf when the state needs to change.
First, we move VisibilityFilters from app.js to SRC/Actions /index.js. Because VisibilityFilters define three operations that filter and display TodoList that are a little more similar to actions, we put together similar things. Change SRC /actions/index.js as follows:
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
});
export const VisibilityFilters = {
SHOW_ALL: "SHOW_ALL".SHOW_COMPLETED: "SHOW_COMPLETED".SHOW_ACTIVE: "SHOW_ACTIVE"
};
Copy the code
Writing container components
The Container component is also a React component, which simply removes the store-to-view state and dispatch Action logic from the component.
According to Redux best practices, container components are generally stored in containers folders. We create a Containers folder under SRC and create a new visibleTodolist.js file inside. To represent the original todolist.js container component and add the following code to the file:
import { connect } from "react-redux";
import { toggleTodo } from ".. /actions";
import TodoList from ".. /components/TodoList";
import { VisibilityFilters } from ".. /actions";
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); }};const mapStateToProps = state= > ({
todos: getVisibleTodos(state.todos, state.filter)
});
const mapDispatchToProps = dispatch= > ({
toggleTodo: id= > dispatch(toggleTodo(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
Copy the code
As you can see, the above code mainly does these things:
- We defined one
mapStateToProps
This function is used to obtain the State from the Redux Store and the original Props of the component itself, combine the two to form new Props, and pass it to the component. This function is the only interface from the Store to the component. Here we define the previous asApp.js
In thegetVisibleTodos
The delta function moves over, and the delta function moves overstate.filter
Filter criteria return the corresponding displaytodos
. - And then we define one that we haven’t seen before
mapDispatchToProps
Function, which takes two arguments:dispatch
和ownProps
The former is the familiar function that issues the update action, and the latter is the Props of the original component, which is an optional parameter that we don’t declare here. We mainly define all the requirements in this function declarationdispatch
Action function, and pass it to the component as Props. So here we’ve defined onetoggleTodo
Function that makes the call through the componenttoggleTodo(id)
You candispatch(toggleTodo(id))
。 - Finally we go through the familiar
connect
The function to receivemapStateToProps
和mapDispatchToProps
And call the TodoList component, which then receives and calls the TodoList component, returning the final exported container component.
Writing presentation components
Once we have written the TodoList container component, the next thing we need to consider is pulling out the TodoList presentation component from State and Dispatch.
Open the SRC/components/TodoList. Js make corresponding changes to the file is as follows:
import React from "react";
import PropTypes from "prop-types";
import Todo from "./Todo";
const TodoList = ({ todos, toggleTodo }) = > (
<ul>
{todos.map(todo => (
<Todo key={todo.id} {. todo} onClick={()= > 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,
toggleTodo: PropTypes.func.isRequired
};
export default TodoList;
Copy the code
In the code above, we remove the Connect and toggleTodo actions and remove the dispatch attribute received by TodoList and replace it with the toggleTodo function passed in via mapDispatchToProps. And the toggleTodo function is called when Todo is clicked.
Of course our toggleTodo property is back, so we restore the toggleTodo we removed earlier in propTypes. 🙂
Finally, we no longer need connect()(TodoList) because the corresponding container component of TodoList defined in VisibleTodolist.js takes the State in the Redux Store and passes it to TodoList.
As you can see, TodoList doesn’t have to worry about state-related operations any more. It just needs to concentrate on displaying the interface and responding to actions. We further separated state from rendering, letting the right people do what they do best.
A few loose ends
Because we have stripped the original TodoList into container components and presentation components, we need to change the corresponding TodoList in app.js into our VisibleTodoList, and the container component provides the external interface of the original TodoList.
We open SRC /components/ app.js and make the following changes:
import React from "react";
import AddTodo from "./AddTodo";
import VisibleTodoList from ".. /containers/VisibleTodoList";
import Footer from "./Footer";
import { connect } from "react-redux";
class App extends React.Component {
render() {
const { filter } = this.props;
return (
<div>
<AddTodo />
<VisibleTodoList />
<Footer filter={filter} />
</div>
);
}
}
const mapStateToProps = (state, props) => ({
filter: state.filter
});
export default connect(mapStateToProps)(App);
Copy the code
You can see that we did a couple of things:
- Replace the previous TodoList with VisibleTodoList.
- Delete VisibilityFilters because it’s already in there
src/actions/index.js
中 - Delete getVisibleTodos as it is already placed in VisibleTodoList.
- delete
mapStateToProps
To derivetodos
Because we already got it in VisibleTodoList. - Delete the corresponding App component
todos
.
Next, let’s deal with the packet guide of several other files caused by changes in VisibilityFilters.
Open SRC /components/ footer.js to change the package path:
import React from "react";
import Link from "./Link";
import { VisibilityFilters } from ".. /actions";
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
Open SRC /reducers/filter.js to modify the package guide path:
import { VisibilityFilters } from ".. /actions";
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
Since the nextTodoId in SRC/Actions /index.js is increment from 0, there will be some problems with the initialTodoState we defined before. For example, the ID of the newly added TOdo will overlap with the initial one, leading to problems. Therefore, we deleted the corresponding initialTodoState in SRC /reducers/todos.js, and then gave the state of the todos Reducer a default value of [].
const todos = (state = [], 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
summary
Save the changes and you’ll see that our to-do applet still runs intact, but we’ve successfully separated the original TodoList into the Container component VisibleTodoList and the presentation component TodoList.
Refactoring code: Separate the state of the Footer from the render
Let’s strike while the iron is hot and use what we learned in the previous section to immediately pull the state and render out of the Footer component.
Writing container components
We create a filterlink.js file under the SRC /containers folder and add the corresponding contents as follows:
import { connect } from "react-redux";
import { setVisibilityFilter } from ".. /actions";
import Link from ".. /components/Link";
const mapStateToProps = (state, ownProps) = > ({
active: ownProps.filter === state.filter
});
const mapDispatchToProps = (dispatch, ownProps) = > ({
onClick: (a)= > dispatch(setVisibilityFilter(ownProps.filter))
});
export default connect(mapStateToProps, mapDispatchToProps)(Link);
Copy the code
As you can see, we did the following:
- define
mapStateToProps
, which is responsible for comparing states stored in the Redux Storestate.filter
Properties and components receive descends from the parentownProps.filter
Whether the attributes are the same, if the same, thenactive
Set totrue
. - define
mapDispatchToProps
, it does this by returning aonClick
Function that generates one when the component is clickeddispatch
Action that receives the component from the parentownProps.filter
Parameter passed insetVisibilityFilter
To generate theaction.type
为"SET_VISIBILITY_FILTER"
The Action, anddispatch
This Action. - And finally we go through
connect
Combine the two and merge the corresponding properties intoLink
Component and export. We should be here right nowLink
Component that we defined in the two functions aboveactive
和onClick
Properties.
Writing presentation components
SRC/Components/footer.js file and make the following changes:
import React from "react";
import FilterLink from ".. /containers/FilterLink";
import { VisibilityFilters } from ".. /actions";
const Footer = (a)= > (
<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>Completed</FilterLink>
</div>
);
export default Footer;
Copy the code
As you can see, the code changes above do several things:
- So let’s export the previous one
Link
Replaced with aFilterLink
. Note that when component state and rendering are separated, we will use container components for components exported for use by other components. - We use the
FilterLink
Component and pass the corresponding threeFilterLink
Filter type. - Then we delete what is no longer needed
connect
和setVisibilityFilter
The export. - Finally delete what is no longer needed
filter
和dispatch
Properties as they are already inFilterLink
Is defined and passed toLink
Components.
Delete unnecessary content
SRC /components/ app.js: 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 VisibleTodoList from ".. /containers/VisibleTodoList";
import Footer from "./Footer";
class App extends React.Component {
render() {
return (
<div>
<AddTodo />
<VisibleTodoList />
<Footer />
</div>); }}export default App;
Copy the code
As you can see, we did the following:
- delete
App
Component corresponding tofilter
Properties andmapStateToProps
Delta function, because we already have delta functionFilterLink
So we no longer need to pass it directly from the App component to the Footer component. - Delete the corresponding
connect
Function. - Delete the corresponding
connect(mapStateToProps)()
Because apps no longer need to get content directly from the Redux Store.
summary
Save the changes and you’ll see that our to-do widget still works, but we’ve successfully separated the original Footer into the container component’s FilterLink and the display component’s Footer.
Refactoring code: Separate AddTodo state from rendering
Let’s finish up by separating the state and rendering of the AddTodo component.
Writing container components
We create the addTodoContainer.js file in the SRC /containers folder and add the following to it:
import { connect } from "react-redux";
import { addTodo } from ".. /actions";
import AddTodo from ".. /components/AddTodo";
const mapStateToProps = (state, ownProps) = > {
return ownProps;
};
const mapDispatchToProps = dispatch= > ({
addTodo: text= > dispatch(addTodo(text))
});
export default connect(mapStateToProps, mapDispatchToProps)(AddTodo);
Copy the code
You can see that we did a few familiar things:
- define
mapStateToProps
Because theAddTodo
I don’t need to pull anything from the Redux Store, somapStateToProps
Just fill it inconnect
And simply return the original of the componentprops
, has no other effect. - define
mapDispatchToProps
We defined oneaddTodo
Function, it acceptstext
And thendispatch
aaction.type
为"ADD_TODO"
The Action. - And finally we go through
connect
Combine the two and merge the corresponding properties intoAddTodo
Component and export. We should be here right nowAddTodo
Component that we defined in the two functions aboveaddTodo
Properties.
Writing presentation components
Then we write AddTodo display component part, open the SRC/components/AddTodo js file, make the following change to the corresponding content:
import React from "react";
const AddTodo = ({ addTodo }) = > {
let input;
return (
<div>
<form
onSubmit={e= >{ e.preventDefault(); if (! input.value.trim()) { return; } addTodo(input.value); input.value = ""; }} ><input ref={node= > (input = node)} />
<button type="submit">Add Todo</button>
</form>
</div>
);
};
export default AddTodo;
Copy the code
As you can see, the above code does a few things:
- We deleted the exported
connect
Function, and get rid of the pairAddTodo
The parcel. - We will be
AddTodo
Receive attributes fromdispatch
Replace fromAddTodoContainer
To get theaddTodo
Function that will be called when the form is submitted,dispatch
aaction.type
为"ADD_TODO"
.text
为input.value
The Action.
Modify the corresponding content
Since we split the original TodoList into the container component AddTodoContainer and the presentation component TodoList, we need to make the following changes to SRC/Components/app.js:
import React from "react";
import AddTodoContainer from ".. /containers/AddTodoContainer";
import VisibleTodoList from ".. /containers/VisibleTodoList";
import Footer from "./Footer";
class App extends React.Component {
render() {
return (
<div>
<AddTodoContainer />
<VisibleTodoList />
<Footer />
</div>); }}export default App;
Copy the code
You can see that we replaced the AddTodo export with AddTodoContainer and rendered the AddTodoContainer component in the Render method.
summary
Save the changes and you’ll see that our to-do applet still runs intact, but we’ve successfully separated the original AddTodo into AddTodoContainer for the container component and AddTodo for the presentation component.
conclusion
So far, we’ve learned all the basic concepts of Redux and used them to refactor a react-only backlog into Redux step by step.
Let’s take a final look at the Redux state loop diagram and review what we’ve learned in this tutorial:
In this tutorial we first proposed the three concepts of Redux: Store, Action, Reducers:
- Store is used to hold the State of the entire application, which is a JavaScript object called State. All app states are retrieved from the Store, so any state change is a change of state in the Store, hence the Store has the title of “the only source of truth for data”.
- Action is the only means used in Redux to change the state of the Store, and all state changes are similar
{ type: 'ACTION_TYPE', data1, data2 }
Such a formal declaration defines an Action and then passesdispatch
This Action is going to happen. - Reducers are used in response to actions that emit changes through actions
switch
Statement matchaction.type
Return a new State operation by adding, deleting, changing, or checking the properties of State. It is also a pure function, that is, it does not change directlyState
Itself.
Specifically reflected in our reconfigured to-do items, we used Store state to replace this.state in React, and used Action to replace the Action of changing this.state in React. Initiate the operation of modifying the State in Store through the Dispatch Action, use Reducers to replace the this.setState operation of updating the State in React, and purify and update the State saved in Store.
We then struck while the iron was hot, using the three concepts we had learned, and refactoring the rest of the entire to-do list into Redux.
However, after refactoring, we found that our rootReducer function was already a bit bloated, containing todos and Filter status attributes, and adding different status attributes if we wanted to extend the to-do application. When the operation of various state attributes is mixed together, it is easy to cause confusion and reduce the readability of the code, which is not conducive to maintenance. Therefore, we propose combineReducers method, which is used to cut the rootReducer into a Reducer with a single state attribute scattered in different files. Then combine these split Reducers through combineReducers.
After explaining the concept of combineReducers in detail, we then reconstructed the previous incomplete Redux code again, and split rootReducer into two Reducer: Todos and filter.
Finally, we went one step further and asked React to focus on user interface writing and separate application state from rendering. We proposed the concept of display components and container components. The former is completely React, receives data from React, and is responsible for rendering data efficiently and correctly. Redux is responsible for responding to the user’s actions and then instructs the user to issue specific commands. When we use Redux, we add a layer of logic on React that takes care of the state.
I hope you can have a good understanding of Redux and use React and Redux flexibly. Thank you for reading!
One More Thing!
Careful readers may have noticed that the Redux state cycle graph we drew is one-way with a clear arrow pointing to it, which is actually the philosophy of Redux, namely “one-way data flow”, and the design mode recommended by the React community. Combined with the pure function convention of Reducer, This allows us to record and reproduce every state change throughout the application, or state is predictable and can be traced back to an Action that initiated a state change, hence Redux’s title as a “predictable state management container.”
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.