Hello and happy New Year! Today, I opened a project called React. This project is small, but it has all the guts.

Let’s start with the technical stack for this project:

  • React 16 + Redux + React-Router 4.0 + Immutable
  • ES6 + ES7 syntax
  • Network request: Axios + socket.io
  • UI framework: ANTD-Mobile
  • Back-end: Express + MongoDB

What’s the React

React is actually just a UI framework, and the cost of frequent DOM operation is very expensive. Therefore, React uses the technology of virtual DOM. Whenever the state changes, a new virtual DOM will be generated and changed with the original one, so that the changed place can be rendered. And for the sake of performance, only shallow comparisons of states are made (which is a big optimization point).

React has become one of the most popular frameworks today, but it is not cheap to learn and requires you to have a good JS foundation. Since React is just a UI framework, if you want to complete a project, you have to use the whole family bucket, which increases the learning cost even more. Therefore, this course is also aimed at beginners, so that beginners can quickly learn React.

The React components

How well you write and plan a component determines how well your React works. You need to consider how many exposed interfaces a component provides and whether its internal state should be changed locally or globally. And your components should be reusable and maintainable.

The lifecycle of the component

  • renderFunctions are called when the UI is rendered, and they are called many times as many times as you render, so controlling the repetition of a component’s rendering is important for performance optimization
  • componentDidMountThe function is called only once, after the component is rendered, and this is usually where the data request is made
  • shouldComponentUpdateIs an important function whose return value determines whether a new virtual DOM needs to be generated to compare with the previous one. The usual performance problems you encounter can be well solved here
  • componentWillMountThis function is called when a component is about to be destroyed and is used in the project to clear chat unread messages
Parent component parameter passing

The approach I used in the project is that the single module top level parent communicates with Redux via Connect. The child component obtains the required parameters by means of parameter passing. We should have good rules for parameter types to facilitate later debugging.

For performance reasons, we try to pass only the necessary parameters during parameter passing.

routing

In version 4.0 of the React-Router, officials also chose to write routes as components.

Here’s a look at the higher-order components of load on Demand routing used in the project

import React, { Component } from "react";
// A higher-order component is a component that generates a new component by passing parameters
export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    constructor(props) {
      super(props);
        // Storage components
      this.state = {
        component: null
      };
    }

    async componentDidMount() {
    // Importing a component requires a file download, so it is an asynchronous operation
      const { default: component } = await importComponent();

      this.setState({
        component: component
      });
    }
    // Make sure the file is finished and render it
    render() {
      const C = this.state.component;

      return C ? <C {. this.props} / > : null;
    }
  }

  return AsyncComponent;
}

Copy the code

Redux

Redux is usually a confusing point for beginners. First of all, not every project needs to use Redux, the communication between components is not much, the logic is not complex, you do not need to use the library, after all, the use of the library development costs.

Redux is decouple from React, so if you want to communicate with Redux you need to use React-Redux, and if you want to use asynchronous requests in actions you need to use redux-Thunk, because actions only support synchronous operations.

The composition of the story

Redux consists of three parts: Action, Store, and Reducer.

An Action, as the name implies, means that you initiate an Action, which is used as follows:

export function getOrderSuccess(data) {
// Return an action, except for the first argument
  return { type: GET_ORDER_SUCCESS, payload: data };
}
Copy the code

After the Action is sent, it is thrown to the Reducer. Reducer is a pure function (one that does not depend on or change the state of variables outside its scope) that accepts a previous state and action argument and returns a new state to the Store.

export default function(state = initialState, action) {
  switch (action.type) {
    case GET_ALL_ORDERS:
      return state.set("allOrders", action.payload);
    default:
      break;
  }
  return state;
}
Copy the code

Store is easily confused with state. You can think of a Store as a container in which state is stored. The Store provides apis that allow you to access state, change it, and so on.

PS: State can be changed only in reducer.

Having explained these basic concepts, I felt it was time to dig a little deeper into Redux.

Implement Redux yourself

Since Store is a container, write the following code

class Store {
  constructor() {}
    
    // The following two apis are common for the store
  dispatch() {}

  subscribe() {}
}
Copy the code

If the Store holds state and the value of state is accessible at any time, write the following code

class Store {
  constructor(initState) {
  // _ represents private, but not really private
    this._state = initState
  }
  
  getState() {
    return this._state
  }
    
    // The following two apis are common for the store
  dispatch() {}

  subscribe() {}
}
Copy the code

Next, let’s consider dispatch logic. First dispatch should receive an action parameter and send the updated state to reducer. Then if the user subscribes to state, we should also call the function, so write the following code

dispatch(action) {
    this._state = this.reducer(this.state, action)
    this.subscribers.forEach(fn= > fn(this.getState()))
}
Copy the code

The reducer logic is very simple, just save the reducer for constructor, then write the following code

constructor(initState, reducer) {
    this._state = initState
    this._reducer = reducer
}
Copy the code

Now that a simple work-in-progress of Redux is complete, we can execute the following code

const initState = {value: 0}
function reducer(state = initState, action) {
    switch (action.type) {
        case 'increase':
            return{... state,value: state.value + 1}
        case 'decrease': {
            return{... state,value: state.value - 1}}}return state
}
const store = new Store(initState, reducer)
store.dispatch({type: 'increase'})
console.log(store.getState()); / / - > 1
store.dispatch({type: 'increase'})
console.log(store.getState()); / / - > 2
Copy the code

The last step is to complete the subscribe function, which is called as follows

store.subscribe((a)= >
  console.log(store.getState())
)
Copy the code

So the subscribe function should take a function argument, push that function argument into the array, and call that function

subscribe(fn) {
    this.subscribers = [...this.subscribers, fn];
    fn(this.value);
}
constructor(initState, reducer) {
    this._state = initState
    this._reducer = reducer
    this.subscribers = []
}
Copy the code

From there, a simple Redux internal logic is complete, you can run the code to try.

The implementation of Redux middleware will be explained in the course, but I’ll leave it there. After this analysis, I’m sure you won’t be too confused about Redux.

Immutable.js

I used the library in this project, and you can see it in the project, and here’s what it solves.

First of all, javascript objects are all referential. Of course, you can deep copy an object, but this operation can be quite costly for complex data structures.

Immutable is the solution to this problem. The data types of the library are immutable, and when you want to change the data in the library, it will clone the node and its parent node, so the operation is quite efficient.

The benefits of this library are considerable: – Prevention of asynchronous security issues – high performance and great help in doing React rendering optimizations – powerful syntax sugar – time travel (undo recovery)

Of course, there are some disadvantages: – project inclination is too large (not recommended for old projects) – there are learning costs – often forget to reassign values…

The use of immutable.js is also discussed in the video

Performance optimization

  • Reduce unnecessary render times
  • Use good data structures
  • Data cache, using Reselect

How do you do that, which we’ll talk about later in the course

Chat related

I use the socket. IO library for chat. The library will use Websocket on the supported browsers, and those that are not will degrade to another protocol.

Websocket uses THE TCP protocol. In a production environment, the only thing you need to do for a long TCP link is to ensure that the server receives the message and responds with an ACK.

In the design of the chat database structure of this project, I store each chat as a Document, so that the message field of this Document needs to be pushed later.

const chatSchema = new Schema({
  messageId: String.// Both sides of the chat
  bothSide: [
    {
      user: {
        type: Schema.Types.ObjectId
      },
      name: {
        type: String
      },
      lastId: {
        type: String}}].messages: [{/ / the sender
      from: {
        type: Schema.Types.ObjectId,
        ref: "user"
      },
      / / receiver
      to: {
        type: Schema.Types.ObjectId,
        ref: "user"
      },
      // The message sent
      message: String.// Send date
      date: { type: Date.default: Date.now }
    }
  ]
});
// Chat the specific backend logic
module.exports = function() {
  io.on("connection".function(client) {
    // Store the users together
    client.on("user", user => {
      clients[user] = client.id;
      client.user = user;
    });
    // Disconnect and clear user information
    client.on("disconnect", () = > {if (client.user) {
        deleteclients[client.user]; }});// Send the chat object nickname
    client.on("getUserName", id => {
      User.findOne({ _id: id }, (error, user) => {
        if (user) {
          client.emit("userName", user.user);
        } else {
          client.emit("serverError", { errorMsg: "This user cannot be found"}); }}); });// Receive the message
    client.on("sendMessage", data => {
      const { from, to, message } = data;
      const messageId = [from, to].sort().join("");
      const obj = {
        from,
        to,
        message,
        date: Date()
      };
      // Find both sides of the chat asynchronously
      async.parallel(
        [
          function(callback) {
            User.findOne({ _id: from }, (error, user) => {
              if(error || ! user) { callback(error,null);
              }
              callback(null, { from: user.user });
            });
          },
          function(callback) {
            User.findOne({ _id: to }, (error, user) => {
              if(error || ! user) { callback(error,null);
              }
              callback(null, { to: user.user }); }); }].function(err, results) {
          if (err) {
            client.emit("error", { errorMsg: "Can't find someone to talk to." });
          } else {
            // Check whether the messageId exists
            Chat.findOne({
              messageId
            }).exec(function(err, doc) {
              // Create your own save if it does not exist
              if(! doc) {var chatModel = new Chat({
                  messageId,
                  bothSide: [{user: from.name: results[0].hasOwnProperty("from")? results[0].from
                        : results[1].from
                    },
                    {
                      user: to,
                      name: results[0].hasOwnProperty("to")? results[0].to
                        : results[1].to
                    }
                  ],
                  messages: [obj]
                });
                chatModel.save(function(err, chat) {
                  if(err || ! chat) { client.emit("serverError", { errorMsg: "Back-end error" });
                  }
                  if (clients[to]) {
                    // If the messageId does not exist, the sender nickname must be sent
                    io.to(clients[to]).emit("message", {
                      obj: chat.messages[chat.messages.length - 1].name: results[0].hasOwnProperty("from")? results[0].from
                        : results[1].from }); }}); }else {
                doc.messages.push(obj);

                doc.save(function(err, chat) {
                  if(err || ! chat) { client.emit("serverError", { errorMsg: "Back-end error" });
                  }
                  if (clients[to]) {
                    io.to(clients[to]).emit("message", {
                      obj: chat.messages[chat.messages.length - 1]}); }}); }}); }}); }); }); };Copy the code

This feature will be highlighted in the course, and there will be a separate video on what the application layer and transport layer must know.

courses

The video is expected to be more than 20 hours, but I am not a full-time lecturer, but a first-line developer, so I will only update 2-3 hours of video a week, and the link will be updated in the group at the first time.

People were so enthusiastic that they added more than 600 people in less than a few days, so they opened a subscription account to post video updates.

The last

This is the project address. If you think it’s good, you can give me a Star.

This article is also my first blog in 18 years. I wish you all a happy New Year and learn more in the New Year!