This article is also available on my blog platform: betamee.github. IO

directory

  • 1. The introduction
  • Redux solution for state management
  • MobX solution for state management
  • This is an example
  • 5. Classic React App with Redux
  • 6. Classic React App with Mobx
  • 7. React Hooks App with Redux
  • 8. React Hooks App with Mobx
  • 9. To summarize
  • reference

1. The introduction

In the increasingly complex Web applications, state management is always a very important topic. The React stack also has a number of solutions, such as Redux and Mobx, and with the release of React Hooks, a new code writing paradigm, and a number of new changes in the use of the state management library.

To illustrate React state management, I’ve written a few examples and compared their use differences.

The same Todo App is developed, but the usage is divided into the following four ways:

  • Use the React + Redux class mode
  • Use the React + Mobx class mode
  • React + Redux using Hooks mode
  • React + Mobx using Hooks mode

React Usage examples Github-betamee /react usage-examples: React Usage examples

Redux solution for state management

Redux is a hot technology solution that has attracted a lot of attention since its launch. Redux evolved from Flux, but was inspired by Elm to avoid Flux’s complexities. It provides a simple but effective API that provides predictable state management. But the learning difficulty is very steep, because it introduces new concepts and conventions, and only when understood can it be better used.

To understand Redux, it can be broken down into the following points:

  1. All states are stored in a single store as a tree of objects
  2. UI is a mapping of the state of this state tree at any given time
  3. The only way to change the state tree is to trigger an action, an object that describes what happens, okay
  4. The specific changes to the state tree are implemented by pure functions called reducers
  5. The updated state tree maps to the new UI

The three principles of Redux can be summarized as follows:

  • Single data source
  • State is read-only
  • Use pure functions to perform modifications

With these concepts in mind, you can basically get started with Redux. However, these concepts alone cannot solve more complex scenarios of Web applications, such as asynchronously loading data update interfaces.

This involves the concept of middleware in Redux.

With middleware, data can be monitored, captured, and changed as state changes, and asynchronous operations can be extended. This gives us a lot of freedom to use Redux to its full potential.

Redux’s middleware is essentially a function that transforms the Store. dispatch method by adding specific capabilities between the actions that are sent and the Reducer steps.

The Redux community has developed many mature middleware, such as Redux-Thunk and Redux-Soga, that need only be used on demand.

See redux#ecosystem#middleware and cnredux#ecosystem

MobX solution for state management

Another state management solution that appears on the front end similar to Redux is MobX.

MobX’s website says:

MobX is a battle-hardened library that makes state management simple and extensible through transparently Applying Functional Programming-TFRP. The philosophy behind MobX is simple: anything derived from application state should be automatically acquired. These include UI, data serialization, server communication, and so on.

MobX is much simpler and more flexible than Redux’s strong-rule convention, and the core of MobX is to trigger state changes through action, which in turn triggers state-derived objects (Computed Value & Reactions). All you need to do is define data for Observe and Computed value or Reactions, and MobX will do the rest.

The MobX/Redux comparison roughly illustrates these differences:

  1. Redux is functional while MobX is object-oriented
  2. Redux’s state returns new data every time, ideally immutable, whereas MobX keeps a reference all the way through
  3. Redux supports data backdating, while MobX does not support backdating because it has only one reference
  4. Redux specifies a single data source, and MobX can split the state tree into multiple stores
  5. Redux requires middleware to handle asynchrony, MobX can handle async/await directly

In general, MobX is easier to get started with than Redux and doesn’t require a lot of boilerplate code, but Redux’s “complexity” isn’t necessarily useless. Strong convention rules can provide reliable support for large projects and complex data state management. MobX does offer a more efficient alternative to the Redux project in some scenarios.

However, Redux should not be complicated, MobX should be simplified, or it depends on the right scenario. We should pay more attention to what problems they solve, what concerns they solve, or how they are implemented, what other advantages and disadvantages they have, and which one is more suitable for the current project and the future development of the project.

This is an example

The Todo we did involved simple additions and deletions:

5. Classic React App with Redux

Next, I will introduce four examples, learn about project organization and code writing, and see how React’s state management library has evolved.

The first is Classic React App with Redux, which is developed using the “traditional” class mode + Redux. This model has been around since Redux was first launched.

We used the create-react-app scaffolding to set up our startup application, which saved us a lot of time:

npx create-react-app classicreactwithredux --typescript
Copy the code

The Typescript template is used here, and the entire application will be developed in TS, so this is an opportunity to get your hands on TS.

Here is the code file for the entire application:

├ ─ ─ the README. Md ├ ─ ─ package. The json ├ ─ ─ public │ ├ ─ ─ the favicon. Ico │ ├ ─ ─ index. The HTML │ └ ─ ─ the manifest. Json ├ ─ ─ the SRC / / the source code files, All the code in here │ ├─ app.css │ ├─ app.test.tsx │ ├── app.tsx │ ├─ Components │ ├─ toDoAdd.tsx │ ├─ TSX │ ├─ ├─ ToDoView.tsx │ ├─ ToDoView.tsx │ ├─ ToDoView.tsx │ ├─ ToDoView.svg │ ├─ ToDoView.tsx │ ├─ ToDoView.tsx │ ├─ ToDoView.tsx │ ├─ ToDoView.tsx │ ├─ TodoCounter │ │ └ ─ ─ select2. SVG │ │ └ ─ ─ styles │ │ ├ ─ ─ TodoAdd. CSS │ │ ├ ─ ─ TodoCounter. CSS │ │ ├ ─ ─ TodoView. CSS │ │ └ ─ ─ TodoViewList. CSS │ ├ ─ ─ containers / / container components │ │ ├ ─ ─ TodoAddContainer. Ts │ │ ├ ─ ─ TodoCounterContainer. Ts │ │ └ ─ ─ TodoViewListContainer. Ts │ ├─ index.css │ ├─ TSX │ ├── TodoViewListContainer Component. The ts │ │ └ ─ ─ but ts │ ├ ─ ─ the react - app - env. Which s │ ├ ─ ─ serviceWorker. Ts │ └ ─ ─ store / / place store files here │ ├ ─ ─ └. │ ├ ─ garbage // │ ├─ ├─ exercises, exercises, exercises, exercises, exercises, exercises, exercises, exercises, exercisesCopy the code

As an entry file, index. TSX is responsible for global initialization. You can place initialization store, render app and other tasks here.

We use the Provider provided by React-Redux to wrap App components and inject store data into global applications. The following components can obtain Redux data in certain ways. I’ll show you how to use it in container components later.

import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; Import configureStore from './store/store' // initialize redux store const AppStore = configureStore() reactdom.render ( <Provider store={AppStore}> <App /> </Provider>, document.getElementById('root') ); serviceWorker.unregister();Copy the code

In the configureStore function, which performs the task of combining root state and loading middleware, all the configuration for Redux is basically here:

import {
  createStore,
  combineReducers
} from 'redux';

import { todoReducer } from './todo/';

/ / root rootReducer
const rootReducer = combineReducers({
  todoReducer: todoReducer
})

// Export the store type
export type AppState = ReturnType<typeof rootReducer>

const configureStore = (a)= > {
  // Combination middleware, not used in this example
  // const middlewares = [thunkMiddleware];
  // const middleWareEnhancer = applyMiddleware(... middlewares);
  / / create a store
  const store = createStore(rootReducer);
  return store;
}

export default configureStore

export * from './todo'
Copy the code

Here, we divide the state of Redux according to roles, such as the state data toDO here, we configure action and reducers items under the folder of ToDO, and then export them uniformly:

│ └ ─ ─ todo / / according to the role the state tree, divided into a folder a character │ ├ ─ ─ actions. The ts │ ├ ─ ─ but ts │ ├ ─ ─ reducers. The ts │ └ ─ ─ types. The tsCopy the code

The advantage of this is that the dependency is clear, and subsequent extensions are convenient, such as adding a new state data, just need to add a new folder, the logic of the “role” can be written clearly.

Of course, Redux can be divided not only according to roles, but also according to functions. For example, all actions can be put under the Action folder and reducers under the renduers folder:

│ ├─ ├─ types │ ├─ index. // Example: │ ├─ │ ├─ indexCopy the code

Let’s look at how to compile Reducer and Action logic under the TODO file:

reducers.ts:

import {
  TodoReducerType,
  TodoActionType
} from './types'

import {
  ADD_TODO,
  REMOVE_TODO,
  SELECT_TODO
} from './types'

// Initialization state
const initalState: TodoReducerType = []

export const todoReducer = (state = initalState, action: TodoActionType): TodoReducerType= > {
  switch(action.type)  {
    case ADD_TODO:
      return [...state, {
        id: Math.random(),
        name: action.name,
        finished: false
      }]

    case REMOVE_TODO:
      return state.filter(todo= >todo.id ! == action.id)case SELECT_TODO:
      return state.map(todo= > {
        if (todo.id === action.id) {
          return{... todo, finished: ! todo.finished } }else {
          return todo
        }
      })
    default:
      returnstate; }}Copy the code

actions.ts:

import {
  ADD_TODO,
  REMOVE_TODO,
  SELECT_TODO
} from './types'

import {
  TodoActionType
} from './types'

export const addTodo = (name: string) :TodoActionType= > ({
  type: ADD_TODO,
  name: name
})

export const removeTodo = (id: number) :TodoActionType= > ({
  type: REMOVE_TODO,
  id: id
})

export const selectTodo = (id: number) :TodoActionType= > ({
  type: SELECT_TODO,
  id: id
})
Copy the code

These two files determine the following two key points for each role state, which is the essence of using Redux:

  1. Action pure function: Describes how to change the state (what it is)
  2. Reducer Pure functions: Describe the logic that changes the state specifically (how)

The actions. Ts function is the one we need to import into the UI component to make it work.

So how do we actually import actions and state into the component?

This is the container component in the Container folder that needs to answer:

│ ├ ─ ─ containers / / container components │ │ ├ ─ ─ TodoAddContainer. Ts │ │ ├ ─ ─ TodoCounterContainer. Ts │ │ └ ─ ─ TodoViewListContainer. TsCopy the code

Don’t be fooled by Redux’s container components and UI components. This is just an intermediate step to connect Redux and React components.

Todoviewlistcontainer.ts is used as an example:

import { connect } from 'react-redux'
import {
  Dispatch
} from 'redux'

import TodoViewList from '.. /components/TodoViewList'

import {
  AppState,
  TodoActionType,

  removeTodo,
  selectTodo
} from '.. /store/store'

const mapStateToProps = (state: AppState) = > ({
  todos: state.todoReducer
})

const mapDispatchToProps = (dispatch: Dispatch<TodoActionType>) = > ({
  removeTodoById: (id: number) = > dispatch(removeTodo(id)),
  selectTodoById: (id: number) = > dispatch(selectTodo(id))
})

export default connect(mapStateToProps, mapDispatchToProps)(TodoViewList)
Copy the code

TodoViewList is the React component, and mapStateToProps and mapDispatchToProps import action and state as React props as their names indicate.

Then in the TodoViewList component, we use it like this:

import React from 'react' import TodoView from './TodoView' import './styles/TodoViewList.css' import { IViewListProp } from '.. /interfaces' const TodoViewList: React.FC<IViewListProp> = ({ todos, selectTodoById, removeTodoById }) => ( <div className="viewlist"> {todos.map((item, index) => ( <TodoView todo={item} selectTodoById={selectTodoById} removeTodoById={removeTodoById} key={index} /> ))} </div> ) export default TodoViewListCopy the code

This is how React and Redux work together. At this point, the component will update whenever state changes, and the only way to change state is to pass in the action function. In this way, the entire application depends on clear, data flow changes can be clearly regulated, for troubleshooting is very convenient.

6. Classic React App with Mobx

Let’s take a look at Todo applications using the class pattern + MobX architecture.

Take a look at the file structure as a whole:

├── Public │ ├─ ├.html │ ├─ manifest.├ ─ SRC // source │ ├── ├.src // source │ ├── ├.src // source │ ├── ├.src // source │ ├── .css │ ├── App.test. TSX │ ├── ├.tsX │ ├── ├.css │ ├── App.test. TSX │ ├── ├.tsX │ ├── │ ├─ ├─ ├.svg │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt └ ─ ─ styles │ │ ├ ─ ─ TodoAdd. CSS │ │ ├ ─ ─ TodoCounter. CSS │ │ ├ ─ ─ TodoView. CSS │ │ └ ─ ─ TodoViewList. CSS │ ├ ─ ─ index. The CSS │ ├ ─ ─ index. The TSX / / entrance │ ├ ─ ─ interfaces / / ts interfaces defined │ │ ├ ─ ─ base. The ts │ │ └ ─ ─ but ts │ ├ ─ ─ the react - app - env. Which s │ ├ ─ ─ ServiceWorker. Ts │ └ ─ ─ store / / deposit mobx store │ ├ ─ ─ TodoListStore. Ts │ └ ─ ─ but ts ├ ─ ─ tsconfig. Json └ ─ ─ yarn. The lockCopy the code

The big difference with Redux is that Redux is functionally oriented, while MobX is object-oriented. Let’s look at how a store is defined in MobX:

import {
  observable,
  computed,
  action
} from 'mobx'

import {
  ITodo
} from '.. /interfaces'

class Todo {
  id = Math.random();
  @observable name: string;
  @observable finished = false;
  constructor(name: string) {
    this.name = name; }}class TodoListStore {
  @observable todos: Array<ITodo> = []
  @computed get finishedTodoCount() {
    return this.todos.filter(todo= > todo.finished).length
  }
  @computed get totalCount() {
    return this.todos.length
  }
  @action
  public finishTodoById = (id: number) = > {
    this.todos.forEach(todo= > {
      if(todo.id === id) { todo.finished = ! todo.finished } }) }@action
  public addNewTodo = (name: string) = > {
    this.todos.push(new Todo(name))
  }
  @action
  public removeTodoById = (id: number) = > {
    // Find the position of an item
    const index = this.todos.findIndex(todo= > todo.id === id)
    this.todos.splice(index, 1)}}export default TodoListStore
Copy the code

Let’s define a class that wraps a state + function with an @Observable decorator for the state and an @Action decorator for the function. Because MobX is very free in constraints, the data of @Observable will be updated as soon as it is touched. However, in this case, the operation of direct update by status will be abused, making it difficult to track the data change process. For example, a handy one in the component: this.props.todos.push(…) , which triggers an entire application data update.

So MobX recommends strict mode, where we can only change the state through @action functions. This is the same idea as Redux’s action function to change the state, so that data changes can be tracked and controlled.

Another difference from Redux is that in MobX we can write @computed data logic directly. In Redux, we can only do this manually with the retrieved state, whereas in MobX, You can always get the latest computed values with the get function of @computed decoration. This is also MobX’s philosophy: anything that comes from application state should be automatically acquired.

Since MobX does not have a single data source as Redux does, it is easy to expand state by adding new classes to encapsulate the state required by @Observable, @computed, and @Action functions corresponding to data updates.

The process of connecting components to MobX is also much easier than Redux.

The first is configured in the entry component:

import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; Import {TodoListStore} from './store' import {Provider} from 'mobx-react' // state const rootStore = {TodoListStore: New TodoListStore()} reactDom.render (<Provider {... rootStore} > <App /> </Provider>, document.getElementById('root') as HTMLElement );Copy the code

Then use the observer and Inject higher-order functions in the Mobx-React package to inject data into components that need to use MobX state:

App.tsx:

import React from 'react'; import { observer, inject } from 'mobx-react' import TodoAdd from './components/TodoAdd' import TodoViewList from './components/TodoViewList' import TodoCounter from './components/TodoCounter' import './App.css'; import { IAppProp } from './interfaces' // ! Inject High-order method injection conflicts with Typescript because TS checks the props interface passed in by external calls, //! However, inject is equivalent to intermediate injection, avoiding the check, which leads to TS error. const App: React.FC<IAppProp> = inject('todoListStore')(observer(({ todoListStore }) => { return ( <div className="app"> <div className="app-title">Classic React App ( mobx )</div> <TodoAdd addNewTodo={todoListStore! .addNewTodo} /> <TodoViewList todos={todoListStore! .todos} finishTodoById={todoListStore! .finishTodoById} removeTodoById={todoListStore! .removeTodoById} /> <TodoCounter finishedCount={todoListStore! .finishedTodoCount} totalCount={todoListStore! .totalCount} /> </div> ); })) export default App;Copy the code

TodoViewList.tsx:

import React from 'react' import { observer } from 'mobx-react' import TodoView from './TodoView' import './styles/TodoViewList.css' import { IViewListProp } from '.. /interfaces' @observer class TodoViewList extends React.Component<IViewListProp, {}> { render() { const { removeTodoById, finishTodoById, todos } = this.props return ( <div className="viewlist"> {todos.map((item, index) => ( <TodoView todo={item} finishTodoById={finishTodoById} removeTodoById={removeTodoById} key={index} /> ))} </div> ) } } export default TodoViewListCopy the code

This is much simpler than Redux’s complex concept of “container components” by simply using the @Observer decorator in components that require MobX state. Simple and clear.

7. React Hooks App with Redux

The React 16.8 release brings a new Hooks API that brings a lot of new thinking to the React ecosystem. Here we look at how to write the same application with Hooks + Redux compared to the class pattern + Redux.

Application structure:

├── Public │ ├─ ├.html │ ├─ manifest.├ ─ SRC // source │ ├── ├.src // source │ ├── ├.src // source │ ├── ├.src // source │ ├── App. CSS │ ├ ─ ─ App. Test the TSX │ ├ ─ ─ App. The TSX │ ├ ─ ─ components │ │ ├ ─ ─ TodoAdd. The TSX │ │ ├ ─ ─ TodoCounter. The TSX │ │ ├ ─ ─ │ ├─ ├─ ├.svg │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt └ ─ ─ styles │ │ ├ ─ ─ TodoAdd. CSS │ │ ├ ─ ─ TodoCounter. CSS │ │ ├ ─ ─ TodoView. CSS │ │ └ ─ ─ TodoViewList. CSS │ ├ ─ ─ index. The CSS │ ├ ─ ─ index. The TSX │ ├ ─ ─ interfaces │ │ ├ ─ ─ base. The ts │ │ └ ─ ─ but ts │ ├ ─ ─ the react - app - env. Which s │ ├ ─ ─ serviceWorker. Ts │ └ ─ ─ Store / / redux store │ ├ ─ ─ store. The TSX │ └ ─ ─ todo │ ├ ─ ─ actions. The ts │ ├ ─ ─ but ts │ ├ ─ ─ reducers. The ts │ └ ─ ─ types. The ts ├ ─ ─ Tsconfig. Json └ ─ ─ yarn. The lockCopy the code

There is a big difference between Classic React App with Redux and no container folder! That means we don’t need to use containers to access Redux.

Yes, the reason is that we are using the Context API + Hooks API here.

The Context API replaces the Provider component in the React-Redux package to inject data globally. Providers use the React Context API in essence, but the React Context API was not officially accepted until React 16 came out. On the other hand, Hooks have support for Redux directly built into them so that you don’t have to install additional Redux packages.

In fact, the app uses only React features, without installing any external dependencies:

"Dependencies" : {" @ types/jest ":" 24.0.15 ", "@ types/node" : "12.6.8", "@ types/react" : "16.8.23", "@ types/react - dom" : "16.8.5" and "react", "^ 16.8.6", "the react - dom" : "^ 16.8.6", "the react - scripts" : "3.0.1", "typescript" : "3.5.3}"Copy the code

The core file in this application is stored in the Store folder store.tsx:

import React from 'react' import { TodoReducerType, TodoActionType, initalTodoState, TodoReducer} from './todo' type combineDispatchsType = react. Dispatch<TodoActionType combineDispatchs = (dispatchs: Array<combineDispatchsType>) => (obj: TodoActionType) => { for (let i = 0; i < dispatchs.length; I ++) {dispatchs[I](obj)}} // Root component state const AppState = {todoState: [] as TodoReducerType, dispatch: {} as ReturnType<typeof combineDispatchs> } // Context export const ContextStore = React.createContext(AppState) const HookContextProvider: React.FC = ({ children }) => { const [todoState, todoDispatch] = React.useReducer( todoReducer, initalTodoState ) return ( <ContextStore.Provider value={{ todoState, dispatch: combineDispatchs([ todoDispatch ]) }} > {children} </ContextStore.Provider> ) } export default HookContextProviderCopy the code

This file does this:

  1. Define AppState with state and Action
  2. Initialize React. CreateContext with AppState to generate the global ContextStore
  3. Use the React. UseReducer to generate specific todoState and todoDispatch
  4. TodoState and todoDispatch are injected into the entire application using the ContextStore.Provider component

TodoState, todoDispatch, todoDispatch, todoDispatch, todoDispatch, todoDispatch, todoDispatch The todoReducer and initalTodoState are the Reducer functions in normal Redux mode. In fact, the Todo folder here is the same as the todo folder from the class mode. The core concepts are the same.

React assimilates the Redux concept in its entirety via the Hooks API.

In components, it is also easy to use:

TodoViewList.tsx:

import React, { useContext } from 'react' import TodoView from './TodoView' import { ContextStore } from '.. /store/store' import { SELECT_TODO, REMOVE_TODO } from '.. /store/todo' import './styles/TodoViewList.css' const TodoViewList: React.FC = () => {const {todoState, dispatch} = useContext(ContextStore) // Action const selectTodoById = (id: number) => dispatch({ type: SELECT_TODO, id }) const removeTodoById = (id: number) => dispatch({ type: REMOVE_TODO, id }) return ( <div className="viewlist"> {todoState.map((item, index) => ( <TodoView todo={item} selectTodoById={selectTodoById} removeTodoById={removeTodoById} key={index} /> ))} </div> ) } export default TodoViewListCopy the code

Just use const {todoState, dispatch} = useContext(ContextStore), which is very convenient.

8. React Hooks App with Mobx

In contrast to Redux being absorbed by Hooks, MobX is not directly retooled and requires external dependencies.

"Dependencies" : {" @ types/jest ":" 24.0.15 ", "@ types/node" : "12.6.8", "@ types/react" : "16.8.23", "@ types/react - dom" : "" mobx 16.8.5", ", "^ 5.13.0", "mobx - react - lite" : "^ 1.4.1", "react" : "^ 16.8.6", "the react - dom" : "^ 16.8.6", "the react - scripts" : "3.0.1," "typescript" : "3.5.3"},Copy the code

Mobx-react-lite is a simplified version of Mobx-React. It was released to keep up with Hooks that only work with React 16.8+ and are not compatible with class patterns. According to official, lite packages will eventually converge in Mobx-React.

├── Public │ ├─ ├.html │ ├─ manifest.├ ─ SRC // source │ ├── ├.src // source │ ├── ├.src // source │ ├── ├.src // source │ ├── .css │ ├── App.test. TSX │ ├── ├.tsX │ ├── ├.css │ ├── App.test. TSX │ ├── ├.tsX │ ├── │ ├─ ├─ ├.svg │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt │ ├─ ├.txt └ ─ ─ styles │ │ ├ ─ ─ TodoAdd. CSS │ │ ├ ─ ─ TodoCounter. CSS │ │ ├ ─ ─ TodoView. CSS │ │ └ ─ ─ TodoViewList. CSS │ ├ ─ ─ index. The CSS │ ├ ─ ─ index. The TSX │ ├ ─ ─ interfaces │ │ ├ ─ ─ base. The ts │ │ └ ─ ─ but ts │ ├ ─ ─ the react - app - env. Which s │ ├ ─ ─ serviceWorker. Ts │ └ ─ ─ │ ├─ ├─ ├─ ├─ ├─ ├.txt // mobx store │ ├─ ├─ ├.txtCopy the code

Usetodostore.tsx: store: useTodoStore.tsx: store: useTodoStore.tsx: store: useTodoStore.tsx

import React, { useContext } from 'react' import { useLocalStore } from 'mobx-react-lite' import { observable } from 'mobx' import { ITodo } from '.. // interfaces' // define Store Shape const createStore = () => ({todos: [] as ITodo[], // Array<ITodo> get finishedTodoCount() {return this.todo.filter (todo => todo.finished).length}, get totalCount() { return this.todos.length }, finishTodoById(id: number) { this.todos.forEach(todo => { if (todo.id === id) { todo.finished = ! Todo.finished}})}, addNewTodo(name: string) {// * Add an Observable todo object this.todo.push (Observable ({id: Math.random(), name: name, finished: false })) }, removeTodoById(id: Number) {const index = this.todos.findIndex(todo => todo.id === ID) this.todos.splice(index, todo.splice) const index = this.todos.findIndex(todo => todo.id === ID) 1)}}) type TTodoStore = ReturnType<typeof createStore> const TodoStoreContext = The React. CreateContext < TTodoStore | null > (null) / / create the Provider, through the React. The Context to inject const TodoStoreProvider: React.FC = ({ children }) => { const store = useLocalStore(createStore) return ( <TodoStoreContext.Provider Value = {store} > {children} < / TodoStoreContext Provider >)} / / create a Hook const useTodoStore = () = > {const store = useContext(TodoStoreContext) if (! store) { throw new Error('You have forgot to use StoreProvider, shame on you.') } return store } export { TodoStoreProvider, useTodoStore }Copy the code

Here, the TodoStoreProvider component is a wrapper around the Context, useLocalStore is used to generate the MobX Store, and useTodoStore Hook is a wrapper around useContext. It becomes easy to use in components:

App.tsx:

import React from 'react';
import './App.css';

import TodoAdd from './components/TodoAdd'
import TodoViewList from './components/TodoViewList'
import TodoCounter from './components/TodoCounter'

import {
  TodoStoreProvider
} from './store'

const App: React.FC = () => {
  return (
    <TodoStoreProvider>
      <div className="app">
        <div className="app-title">React Hooks App ( mobx )</div>
        <TodoAdd />
        <TodoViewList />
        <TodoCounter />
      </div>
    </TodoStoreProvider>
  );
}

export default App;
Copy the code

TodoViewList.tsx:

import React from 'react' import { observer } from 'mobx-react-lite' import TodoView from './TodoView' import './styles/TodoViewList.css' import { useTodoStore } from '.. /store' const TodoViewList: React.FC = observer(() => { const todoStore = useTodoStore() return ( <div className="viewlist"> {todoStore.todos.map((item, index) => ( <TodoView todo={item} finishTodoById={todoStore.finishTodoById} removeTodoById={todoStore.removeTodoById} key={index} /> ))} </div> ) }) export default TodoViewListCopy the code

The react-hooks App with Redux is a bit more complex than the Redux example, but much simpler than the class pattern. UseTodoStore can be used in any component. You could write useXXXStore again.

9. To summarize

Four examples write down my personal feelings:

  1. Hooks are indeed much simpler than class patterns and are the future of the React technology stack
  2. MobX is not necessarily simpler than Redux. For example, Hooks for MobX access are a bit more cumbersome, unlike Redux, which are directly internalized into the official API
  3. The reducer and action functions of Redux can be painlessly migrated from the class schema to Hooks
  4. Typescript is true power!

reference

  • Dan Abramov – You Might Not Need Redux
  • MobX Chinese official website
  • MobX with React
  • Using MobX with React Hooks
  • Use React CreateContext with React useContext and useReducer to create the Global State Manager