Redux entry and actual combat-todo-list2.0 implementation

Redux entry and actual combat-todo-list2.0 implementation

1 Introduction

In the previous blog, I wrote a blog about the implementation of todo-list, step by step a detailed record of how to use basic React knowledge to implement a React single-page application, through this article, you can have an introduction to React development Intuitive knowledge and superficial understanding.

Recently, I personally learned about Redux and implemented the project using React+Redux. The content of this film records the following practice process. Through this example, you can learn:

  • The core idea of ​​Redux;
  • 3.concepts of Redux;
  • React+Redux development method and process;

The following will explain and record from the following aspects.

2. Project demonstration

3. Redux basics

3.1 Understanding

3.1.1 Motivation

With the increasing complexity of JavaScript single-page application development, JavaScript needs to manage more state than ever. It is very difficult to manage the constantly changing state. When, for whatever reason, how the state changes is out of control. When the system becomes intricate, it becomes difficult to reproduce the problem or add new features.

Therefore, a more controllable way is needed to manage the state of the system to make the state of the system predictable. Redux is a tool used to manage the state of the system.

3.1.2 3.Principles

  • Single data source

The state of the entire application is stored in one object, and an application has only one unique state, which is stored in the store and managed uniformly through the store.

  • Status is read-only

The only way to change the state is to trigger action, which actionis an ordinary object used to describe an event that has occurred.

Redux does not modify the state directly, but returns to a brand new state when the state changes. The old state is not changed and can be retained. You can use redux-devtools-extensionthe tool to visualize view.

  • State modification is done by pure function

Reducer is just some pure functions, it receives the previous state and action, and returns the new state.

3.2 Basics

3.2.1 Store

ReduxThe core Store, Storeby the createStorecreation method,

createStore(reducer, [initState])//reducer represents a root reducer, initState is an initialization state

storeProvide methods to manipulate state

3.2.2 Action

actionIt is the payload that transfers data from the application to the store. It is the only source of store data . By store.dispatch()the action reached the store. If there is data to be added, pass it in the action.

Action needs an action creation function to be created. The following is an action creation function:

/*
 * action type
 */

export const ADD_TODO ='ADD_TODO';
export const TOGGLE_TODO ='TOGGLE_TODO'
export const SET_VISIBILITY_FILTER ='SET_VISIBILITY_FILTER'

/*
 * Other constants
 */

export const VisibilityFilters = {
  SHOW_ALL:'SHOW_ALL',
  SHOW_COMPLETED:'SHOW_COMPLETED',
  SHOW_ACTIVE:'SHOW_ACTIVE'
}

/*
 * action creation function
 */

export function addTodo(text) {
  return {type: ADD_TODO, text}
}

export function toggleTodo(index) {
  return {type: TOGGLE_TODO, index}
}

export function setVisibilityFilter(filter) {
  return {type: SET_VISIBILITY_FILTER, filter}
}

It returns an object, the object obtained by changing the reducer, according to actionthe corresponding type of operation.

3.2.3 Reducer

store by store.dispatch(某action(参数))to give reducer schedule tasks.

Simple to understand, a reduceris a function that accepts two parameters 当前stateand actionthen according actionto the current stateoperation, if there are changes to be made, it returns a new state, the old will not stateoperate any stage of stateIt can be viewed and monitored, which makes statemanagement becomes controllable, real-time tracking stateof changes.

When Redux React used, the need for a root Reducer, the root Reducerby conbineReducer()a plurality of sub- Reducercombination.

Root reducer:

import {combineReducers} from'redux'
import todos from'./todos'
import visibilityFilter from'./visibilityFilter'
//Root reducer
//rootReducer Root reducer, combining sub reducers
export default combineReducers({
  todos,//sub-state
  visibilityFilter//sub state
})

Sub reducer:

//Here state = [] is the current value of state
const todos = (state = [], action) => {
    switch (action.type) {
      case'ADD_TODO':
        return [
          ...state,//Object.assign() creates a new copy
          {
            id: action.id,
            text: action.text,
            completed: false
          }
        ]
      case'TOGGLE_TODO':
    //console.log(state);
        return state.map((value,index) => {
            return (value.id === action.id)? {...value,completed:!value.completed}: value;
        }) 
      default:
        return state;
    }
  }

export default todos;

3.2.4 Data flow

3.3 Display components and container components

3.3.1 Separation of display components and container components

This part has not been studied in depth by the author. Here is the link to the in-depth analysis article written by the author of redux and the link to the translation on the Internet. Readers can check it by themselves.

Original link: separate display component and container component

Translation link: separation of display components and container components

3.3.2 Comparison of display components and container components

Display components

Container component

effect

Describe how to display the skeleton, style

Describe how it works (data acquisition, status update)

Use Redux directly

no

Yes

Data Sources

props

Monitor Redux state

Data modification

Call callback function from props

Dispatch action to Redux

Call method

Manual

Usually generated by React Redux

Most of the components should be showing type, but generally require a handful of container components and put them Redux storeconnected.

React ReduxUsing a connect()method to generate the container assembly.

import {connect} from'react-redux'
import {setVisibilityFilter} from'../actions'
import Link from'../components/Link'

//The state in the mapStateToProps parameter is the state of the store.
//In the container component, through the mapStateToProps method, transfer data and execute action between the display component and the store
//ownProps represents the properties of the component itself, that is, the properties passed by the parent component
const mapStateToProps = (state, ownProps) => {
    return {
        active: ownProps.filter === state.setVisibilityFilter
    }
}
//ownProps represents the properties of the component itself, that is, the properties passed by the parent component
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
       //Write the method name here, and use this method name in the display component to execute the action dispatch function inside
        onClick: () => {
           //Execute the action of setVisibilityFilter
            dispatch(setVisibilityFilter(ownProps.filter))
        }
    }
}
//The link component can connect to the store through connect, and obtain the active data and the execution body of the onClick method from the store.
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

connect()The core are two methods: mapActionToPropsand mapDispatchToProps, through the container assembly, display assembly and may be storetransmitted between the data and execution action.

4. Actual combat of React project based on Redux

4.1 Directory structure

According to several major components of Redux, when developing, under the previous basic React development mode, several folders will be added to form a new development directory structure. The specific directory structure is as follows:

│ App.css

│ App.js

│ App.test.js

│ index.css

│ index.js

│ logo.svg

│ readme.txt

│ serviceWorker.js

│ setupTests.js

├─actions

├─components

├─containers

└─reducers

As shown in the previous structure, added actions, reducers, containersthese three folders.

4.2 Configure React-Redux development environment

4.2.1 Procedure

After the file directory is created, you can start development. Because it is based on Redux for React development, the first step is of course to configure the Redux development environment.

  • Installation react-reduxpackage

npm install --save react-redux

  • Write the entry file index.js

Previously mentioned, redux with a unique storeto the project status management, first we need to create this store, and this storeas a property passed to the lower sub-assembly.

The specific code is as follows:

import React from'react';

import ReactDOM, {render} from'react-dom';

//redux ----------------------------------------------- -----

import {Provider} from'react-redux';

import {createStore} from'redux';

import {rootReducer} from'./reducers';

//Introduce the project root component App.jsx

import App from'./App';

//Create a store and pass the root Reducer to the store. The redux application has only a single store

const store = createStore(rootReducer);

render(

<Provider store = {store}>

<App/>

</Provider>,

document.getElementById('id')

)

As shown in the above code, to use Redux, the files that need to be imported are:

ProviderAssembly createStoremethod root reducer project root component App.jsx

createstore : createStoreMethod pharmaceutically two parameters, the first item is the root reducer, is a mandatory parameter, the other is an optional parameter, the initial item may be input statevalue. Create a method by which storeinstance, is the only project store.

Provider component : Providercomponents are enclosed in an outer layer with components App.jsx, the project storepassed as an attribute to Provider. Use Providercan achieve all subassemblies directly storeaccess. In the following in-depth talk about Providerimplementation and how it works.

The root of the reducer : increasing along with the project, the more complex the program state, only one reduceris difficult to meet actual demand, redux used to reducersplit the final before the state change by root reducerthe individual split sub- reducermerged ways for processing.

App.jsx : The follow component of the project, write the first-level sub-components in App.jsx.

4.2.2 Provider

providerWrapped in the outer layer of the root component, so that all sub-components can get the state. It accepts store as props, and then passes it down through context, so that any component in react can get store through context.

Provider principle:

The principle is the context property of the React component

The component source code is as follows:

The principle is the context property of the React component

export default class Provider extends Component {
  getChildContext() {
     //Return an object, this object is the context
    return {store: this.store}
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }
  render() {
    return Children.only(this.props.children)
  }
}

Provider.propTypes = {
  store: storeShape.isRequired,
  children: PropTypes.element.isRequired
}

Provider.childContextTypes = {
  store: storeShape.isRequired
}

4.3 List of src directory files

folder

file

src

index.js

src/actions

index.js

src/components (display components)

App.jsx

TodoList.jsx

Footer.jsx

Todo.jsx

Link.jsx

src/containers (container component)

AddTodo.js

FilterLink.js

VisibleTodoList.js

src/reducers

index.js

todo.jsx

visibilityFilter.js

4.4 Project code

note:

Most of the code description is written in the project code. Readers are advised to read the code carefully when viewing it. The function of this project is relatively simple, so the code is directly given according to the file catalog, not according to the function module display.

4.4.1 Entry file index.js

import React from'react';
import ReactDOM, {render} from'react-dom';
import'./index.css';
import App from'./components/App';

//redux
import {Provider} from'react-redux';
import {createStore} from'redux';
import rootReducer from'./reducers';

//Create store, createStore() The first parameter is the root reducer of the project, and the second parameter is optional and is used to set the initial state of the state
const store = createStore(rootReducer);

render(
 //The Provider component is wrapped in the outer layer of the component, so that all sub-components can get the state.
 //It accepts store as props, and then passes it down through the context, so that any component in react
 //You can get store through context.
  <Provider store = {store}>
    {/* App root component*/}
    <App/>
  </Provider>,
  document.getElementById('root')
)

4.4.2 actions file

  • index.js
let nextTodoId = 0;

//Define action constants For small projects, you can write action constants and action creation functions together. For complex projects, you can extract action constants and other constants and put them in a separate constant folder
const ADD_TODO ='ADD_TODO';
const SET_VISIBILITY_FILTER ='SET_VISIBILITY_FILTER';
const TOGGLE_TODO ='TOGGLE_TODO';

//Here are several action creation functions, the object in the function is the action, which returns an action
//text is the data passed following the action
//Call dispatch(addTodo(text)), which means that the action is dispatched and handed over to the reducer for processing
//action generation function
//In most cases, he simply collects information from the parameters, assembles it into an action object and returns it,
//But for more complex behaviors, he tends to accommodate more business logic and side effects, including interaction with the backend, etc.
export const addTodo = (text) => {
    return {
        type: ADD_TODO,
        id: nextTodoId ++,
        text
    }
}
export const setVisibilityFilter = (filter) => {
    return {
        type: SET_VISIBILITY_FILTER,
        filter
    }
}
export const toggleTodo = (id) => {
    return {
        type: TOGGLE_TODO,
        id
    }
}
//3.constants
export const VisibilityFilters = {
  SHOW_ALL:'SHOW_ALL',
  SHOW_COMPLETED:'SHOW_COMPLETED',
  SHOW_ACTIVE:'SHOW_ACTIVE'
}

4.4.3 components file (display components)

  • App.jsx
import React from'react'
import Footer from'./Footer'
import AddTodo from'../containers/AddTodo'
import VisibleTodoList from'../containers/VisibleTodoList'
//The root component of the application
const App = () => {
  return (
    <div>
      {/* Container component*/}
      <AddTodo/>
      {/* Container component*/}
      <VisibleTodoList/>
      {/* Display components*/}
      <Footer/>
    </div>
  )  
}
export default App
  • Footer.jsx
import React from'react'
import FilterLink from'../containers/FilterLink'
import {VisibilityFilters} from'../actions'
//Stateless components, this writing method may be difficult for beginners to understand, you can learn ES6 first, which is equivalent to
//function Footer(){
//return (<div>XXX</div>)
//}
const Footer = () => (
  <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
  • Link.jsx
import React from'react'
import PropTypes from'prop-types'
//prop-types is a component property verification package, importing this package can verify the format and other aspects of the data
const Link = (props) => {
    return (
     <button onClick={props.onClick} disabled={props.active} style={{marginLeft:'4px'}}>
       {props.children}
     </button>
    )
}

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}

export default Link
  • TodoList.jsx
import React, {createFactory} from'react'
import PropTypes from'prop-types'
import Todo from'./Todo'

const TodoList = (props) => {
    return (
        <ul>
            {
                props.todos.map((value,index) => {
                    return <Todo key = {index} {...value} onClick = {() => props.toggleTodo(value.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
  • Todo.jsx
import React from'react'
import PropTypes from'prop-types'

const Todo = ({ onClick, completed, text }) => (
  <li
    onClick={onClick}
    style={ {
      textDecoration: completed?'line-through':'none'
    }}
  >
    {text}
  </li>
)

Todo.propTypes = {
  onClick: PropTypes.func.isRequired,
  completed: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired
}

export default Todo

4.4.4 containers file (container component)

Note: This part involves the connect() method. There are important knowledge points in the code comments. It is recommended to check it carefully. This article will not discuss the connect() in depth, and will analyze it separately in the follow-up.

  • FilterLink.js
import {connect} from'react-redux'
import {setVisibilityFilter} from'../actions'
import Link from'../components/Link'
import {createFactory} from'react'

//The state in the mapStateToProps parameter is the state of the store.
//In the container component, through the mapStateToProps method, transfer data and execute action between the display component and the store
//ownProps represents the properties of the component itself, that is, the properties passed by the parent component
const mapStateToProps = (state, ownProps) => {
    return {
        active: ownProps.filter === state.setVisibilityFilter
    }
}

//ownProps represents the properties of the component itself, that is, the properties passed by the parent component
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
       //Write the method name here, and use this method name in the display component to execute the action dispatch function inside
        onClick: () => {
           //Execute the action of setVisibilityFilter
            dispatch(setVisibilityFilter(ownProps.filter))
        }
    }
}

//The link component can connect to the store through connect, and obtain the active data and the execution body of the onClick method from the store.
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

////Put the content of the Link component on this page to combine and understand, the following code is not the functional code of this component
//const Link = ({ active, children, onClick }) => (
//<button
//onClick={onClick}
//disabled={active}
//style={{
//marginLeft: '4px',
//})
//>
//{children}
//</button>
//)
  
//Link.propTypes = {
//active: PropTypes.bool.isRequired,
//children: PropTypes.node.isRequired,
//onClick: PropTypes.func.isRequired
//}

It is recommended to closely integrate the container component and its corresponding display component to understand.

  • AddTodo.js
import React from'react'
import {connect} from'react-redux'
import {addTodo} from'../actions'

const AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault()
          if (!input.value.trim()) {
            return
          }
          dispatch(addTodo(input.value))
          input.value =''
        }}
      >
        <input ref={node => input = node}/>
        <button type="submit">
          Add Todo
        </button>
      </form>
    </div>
  )
}

export default connect()(AddTodo);
  • VisibleTodoList.js
import {connect} from'react-redux'
import {toggleTodo} from'../actions'
import TodoList from'../components/TodoList'

//Get qualified todo,
//todo data in todos state
//Filter conditions in filter state
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
    case'SHOW_ALL':
    default:
      return todos
  }
}
const mapStateToProps = (state) => {
    return {
        todos: getVisibleTodos(state.todos, state.visibilityFilter)
    }
}
const mapDispatchToProps = (dispatch) => {
    return {
        toggleTodo: (id) => {
            dispatch(toggleTodo(id))
        }
    }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

4.4.5 reducer folder

  • Root reducer/index.js
import {combineReducers} from'redux'
import todos from'./todos'
import visibilityFilter from'./visibilityFilter'
//rootReducer Root reducer, combining sub reducers
export default combineReducers({
  todos,//sub-state
  visibilityFilter//sub state
})
  • todo.js
//Here state = [] is the current value of state
const todos = (state = [], action) => {
    switch (action.type) {
      case'ADD_TODO':
        return [
          ...state,//Object.assign() creates a new copy
          {
            id: action.id,
            text: action.text,
            completed: false
          }
        ]
      case'TOGGLE_TODO':
    //console.log(state);
        return state.map((value,index) => {
            return (value.id === action.id)? {...value,completed:!value.completed}: value;
        }) 
      default:
        return state;
    }
  }

export default todos;
  • visibilityFilter.js
const visibilityFilter = (state ='SHOW_ALL', action) => {
    switch (action.type) {
      case'SET_VISIBILITY_FILTER':
        return action.filter
      default:
        return state
    }
  }
  
export default visibilityFilter

5. Summary

In this article, Caijibenji uses a todo-list example to systematically introduce some basic concepts of redux, basic usage and how to combine it with react to realize the functional development of react. The main content includes the basis of redux, the combination of redux and react, and examples. Completing the steps, complete code, project demonstration, etc., are more suitable for newcomers who are new to redux to read and learn, and hope to help students in need.

6 Reference

Reference: https://cloud.tencent.com/developer/article/1678033 Getting Started with Redux-Todo-list2.0 Implementation-Cloud + Community-Tencent Cloud