TodoMVC and story

TodoMVC

TodoMVC is a sample project that implements the same Demo using various popular JavaScript frameworks to help you familiarize yourself with and choose the most appropriate front-end framework.

How can I quickly learn and understand a JavaScript framework or learn the features of using a new framework? My experience is to make a TodoMVC app

TodoMVC React example

TodoMVC applies function points

  • The data of the CRUD
  • Management of data models
  • The theoretical practice of MVC
  • Component split
  • State management

TodoMVC is a fully functional and UI teaching example that is essential to a learning framework 🌰

Story synopsis

Redux is an implementation that follows the Flux pattern. It is a state management library that works with frameworks or libraries like React, Angular, VueJs, etc., rather than being limited to a specific UI library.

Redux Chinese document

Redux core concepts

+----------+
|          |                +-----------+       sliceState,action        +------------+
|  Action  |dispacth(action)|           +-------------------------------->            |
|          +---------------->   store   |                                |  Reducers  |
| Creators |                |           <--------------------------------+            |
|          |                +-----+-----+    (state, action) => state    +------------+
+-----^----+                      |
      |                           |
      |                           |subscribe
      |                           |
      |                           |
      |                           |
      |              +--------+   |
      +--------------+  View  <---+
                     +--------+
Copy the code

How to update the View

  • dispatchThe triggeraction
  • actionCarrying method nametype, and datapayloadstore(Asynchronous data processing may occur during this period)
  • storeTo receivetypepayloadGive to the appropriatereducer
  • reducerFind the corresponding method to updatestate

Store, Action and Reducer

  • StoreIs the state container for the entire Redux application, and is an object
  • ActionIs also an object that represents an event and requires the Type field
  • ReducerIs a function that returns different data depending on the Action

The target

  • Application TodoMVC UI
  • All use functional components
  • Use the TypeScript
  • Learn to use Hooks (useEffect, useContext, useRef, useMemo, useState)
  • Simulation Redux

Demand analysis

New Todo

  • Enter the new value in the input box and press Enter to add and clear the input box

Delete Todo

  • Click delete “Button X” to delete selected data
  • Click “Clear Complete” in the lower right corner to clear itcompleteThe data of

Modify the Todo

  • Double-click on a row to switch to edit state and automatically get focus
  • Press Enter or cursor away to modify the data

Query Todo

  • When the list has a value, the data can be displayed normally
  • “Item Left” in the lower left corner changes automatically according to the current item number
  • Click belowall active completeCan filter list, and highlighting can automatically switch
  • Check item to toggleactivecompletestyle
  • When all items are selected, the “small arrow” on the left of the new data box is highlighted
  • Click the “small arrow” on the left of the new data box to select all or none of item
  • When entering the home page, the new input box automatically gets the focus

The installationTodoMVCThe module

npm install todomvc-common todomvc-app-css

import 'todomvc-common/base.css';
import 'todomvc-app-css/index.css';
Copy the code

Use the VUE version of TodoMVC template directly, remove vUE related code, keep the structure

Using the TodomVC UI, you can dispense with style and DOM structure writing, and let’s focus on quickly implementing business logic using the JS framework

Component split

Basic page structure

// App.tsx
const Input: SFC<any> = (a)= >{... }constTodoItem: SFC<ITodoItemProps> = {... }const TodoList: SFC<any> = (a)= >{... }const Footer: SFC<any> = (a)= >{... }function TodoAPP() {
  return (
      <section className="todoapp">
        <header className="header">
          <h1>todos</h1>
          <Input />
        </header>
        <TodoList />
        <Footer />
      </section>
  );
}
Copy the code

Store

Determine application status and event methods

Apply global state

const initialState: IAPPState = {
  todos: todoStore.list,
  newTodo: ' ',
  editTodo: ' ',
  visibility: ShowType.ALL,
};
Copy the code

Local states are managed internally by components, as will be explained later. Therefore, TodoMVC’s global states are basically these four. In actual project applications, global states should be as few as possible to ensure that each state is globally necessary to avoid storing too many states globally. Components may be updated unnecessarily.

Event method

export enum ActionType {
  / / add Todo
  CREATE = 'create'./ / update the Todo
  UPDATE = 'update'./ / delete Todo
  DELETE = 'delete'.// Delete state is completed Tdo
  REMOVE_COMPLETED = 'removeCompleted'.// Set the Todo for the current edit
  EDIT_SET = 'setEdit'.// Change the current display type
  CHANGE_SHOW_TYPE = 'changeShowType'.// Update the Todo currently edited
  UPDATE_EDIT_TODO = 'updateEditTodo'.// All switches to completed/not completed
  TOGGLE_ALL = 'toggleAll',}Copy the code

Create the Context component using createContext

Use useReducer to manage complex states

The useReducer receives a Reducer of the form (state, action) => newState and returns the current state and its corresponding dispatch method

A component that uses < context. Provider> as its parent will trigger the update when state is updated

// Store.tsx
const initialState: IAPPState = {
  todos: todoStore.list,
  newTodo: '',
  editTodo: '',
  visibility: ShowType.ALL,
};

const reducer = (state: IAPPState, action: IAction): IAPPState => {
  const { type, payload } = action;
  return methods[type] ? methods[type](state, payload) : state;
};

const Context = React.createContext({
  state: initialState,
  dispatch: (() => 0) as React.Dispatch<IAction>,
});

const Provider: SFC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return <Context.Provider value={{ state, dispatch}}>{children}</Context.Provider>;
};

export { Provider, Context };
Copy the code

Methods above are the method objects that specifically update state

The use of the Store

The child components of the Provider can get global state and Dispatch from the Context

// App.tsx
import { Provider, Context } from './Store'

function TodoAPP() {
  return (
    <Provider>
      <section className="todoapp">
        ...
      </section>
    </Provider>
  );
}
Copy the code

Input component

Use useContext to receive the Context object from the Store and return state and dispatch

When the most recent < context. Provider> update is made on the upper layer of the component, the hook triggers the update and uses the latest state

Components that call useContext are always rerendered when the context value changes

UseState is used to create the component’s internal state title, which is used or destroyed within the component without affecting global state, receives an initial value of the initial state and the function changeTitle that updates the state

Trigger an action with Dispatch to create a TOdo

const Input: SFC<any> = (a)= > {
  const { state, dispatch } = useContext(Context);
  // Internal status title
  const [title, changeTitle] = useState<string> (' ');
  
  // Update title when typing
  function onChange(e: ChangeEvent<HTMLInputElement>) { 
    changeTitle(e.target.value.trim());
  }
  
  // 回车新增一条Todo
  function onKeyDown(e: KeyboardEvent<HTMLInputElement>) {
    / / new
    dispatch({
      type: ActionType.CREATE,
      payload: { id: utils.uuid(), title, completed: false}});/ / to empty
    changeTitle(' ');
  }

  return (
    <input
      className="new-todo"
      autoFocus
      autoComplete="off"
      placeholder="What needs to be done?"
      value={title}
      onChange={onChange}
      onKeyDown={onKeyDown}
    />
  );
};
Copy the code

TodoItem components

Editing is the internal state that belongs to each TodoItem

Use useRef to return a mutable ref reference object whose.current property is initialized as the parameter passed in. This is often used to access DOM objects, where the edit input box is used to get focus

UseEffect tells the component that something needs to be done after rendering. React saves the function you passed (we’ll call it “effect”) and calls it after a DOM update, passing in a second dependency to optimize the effect execution, and only when the editing changes

const TodoItem: SFC<ITodoItemProps> = ({ completed, title, index }) => { const [editing, changeEditing] = useState<boolean>(false); const iptRef = useRef(null); const { state, dispatch } = useContext(Context); const { editTodo } = state; UseEffect (() => (editing && iptref.current. Focus ()), [editing]); Function onDoubleClick() {function onDoubleClick() {... } // Todo function onEditBlur() {... Todo function onEditEnter(e: KeyboardEvent<HTMLInputElement>) {... } // Todo function onEditChange(e: ChangeEvent<HTMLInputElement>) {... Function onToggleComplete(e: ChangeEvent<HTMLInputElement>) {... Function onDestroy() {return (<li>... </li> ) }Copy the code

Footer component

Use useMemo to get calculated properties like those in Vue to avoid costly calculations every time you render, pass in toDOS, and recalculate when TODOS updates

const Footer: SFC<any> = () => { const { state, dispatch } = useContext(Context); const { todos, visibility } = state; // Calculate attributes: number in progress, Number completed, prompt const {activedNum, completedNum, activeTodoWord} = useMemo(() => {... Function onChangeShowType(type: ShowType) {... } // Todo function onClearCompleted() {... } return ( <footer className="footer"> ... </footer> ) }Copy the code

TodoList components

const TodoList: SFC<any> = () => { const { state, dispatch } = useContext(Context); const { todos, visibility } = state; const activedNum = useMemo(() => utils.filterTodos(todos, ShowType.ACTIVE).length, [todos]); const todoList = useMemo(() =>utils.filterTodos(todos, visibility), [todos, visibility]); function onToggleAll(e: ChangeEvent<HTMLInputElement>) {... } return ( <section className="main"> <input id="toggle-all" className="toggle-all" type="checkbox" onChange={onToggleAll} checked={activedNum === 0} /> <label htmlFor="toggle-all">Mark all as complete</label> <ul className="todo-list"> {todoList.map((v, i) => { return <TodoItem key={v.id} completed={v.completed} title={v.title} index={i} />; })} </ul> </section> ) }Copy the code

To prevent this article from getting too long, I haven’t posted all the code. This article mainly explains the use of hooks in TodoMVC, which are used in React

To optimize the

In the actual application of our company, WE do not use Redux to manage the application state, but mobx. In the actual development, I prefer to separate the local state and the global state. In the background management system, the global state is not much, but some user information, menu state and data, messages and so on. Stores are typically divided into pages, and complex components have their own stores that contain all states and actions.

The disadvantage of story

  • Boilerplate code management is decentralized and needs to be establishedactionTypes,actions,reducers
  • When multiple people collaborate, name conflicts arise and similar business processes are repeated
  • dispatchNeed to quoteactionsOr want to rememberAction TypeThe problems such as

Optimization dispatch

In the pseudo-REdux implemented by the hooks above, the dispatch method is still cumbersome to use and can be optimized for the following two points

  • Do not refer to ActionsTypes for UI logic processing
  • Calls Actions directly with method hints
import { ActionsTypes } from './types'
import { Context } from './store'

// How to call now
const {state, dispatch} = useContext(Context)

dispatch({
  type: ActionTypes.TOGGLE_ALL,
  payload: { completed },
});

// Optimized call method
const {state, actions} = useContext(Context)

actions.toggleAll({ completed })
Copy the code

The optimization here is mainly in the Provider implementation, simply by mounting the Actions method into the Context

const Provider: SFC<any> = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); IActions = {[name in ActionType]? : (data? : Record<string, any>) => void }; let actions: IActions = {}; for (let i in ActionType) { actions[ActionType[i] as ActionType] = data => dispatch({ type: ActionType[i] as ActionType, payload: data }); Return < context. Provider value={{state, dispatch, actions }}>{children}</Context.Provider>; };Copy the code

React uses setState to manage the application state. Compared with Vue, the code writing of Vue is a little complicated. Referencing third party libraries like Redux or Mobx would certainly add to React’s size, but hooks are promising to replace Mobx or Redux in relatively simple applications. Use it with care, however, because without life cycle control it is easy to create performance bottlenecks by using components that repeat many times, so think about how to use hooks wisely and how not to cause performance problems.