Since writing H5 mini-games in Typescript, WE’ve relied on Ts (smart tips and friendly refactoring tips), but we need more practice with its Type System.

Recently developed H5 small game, in mobile terminal debugging, for convenience, did not use inspect mode. Ts + React code: Ts + React code: Ts + React code: Ts + React

Learn from this tutorial:

  • Ts + React + Mobx development process
  • Basic Type System
  • Some basic JavaScript concepts
  • Knowledge of the browser console
    • Console
    • NetWork, XHR
    • Storage
    • DevTool core rendering code

Project source code for the first time using Typescript + React code project, record the process of iteration, interested in the hole can star look forward to CodeReview.

start

In line with the concept of rapid development (I have to take care of my children), AntMobile, also written by Ts, was used for the UI framework based on the scaffolding project of Create React App. Before starting the project, it is obvious that you need to have a certain understanding of these two things (recommended as a reference for further learning Ts + React).

Next, take a look at the preview images

UI is very simple, divided by function

  • The Log, the System
  • Network
  • Elemnet
  • Storage

Mainly from the above several functional modules

PS: The tutorial will skip over things like how to support stylus (project that executes Yarn Run ject), whether to add I to interface, whether to add Public to render, how to remove some Tslint, etc. Git history, git history, git history

Basic Code Style

Components are written in this style (which is not a best practice) throughout the article (with fewer stateless components and no application of higher-order components).

import React, { Component } from 'react';

interface Props {
  // props type here
}

interface State {
  // state type here
}

export default class ClassName extends Component<Props, State> {
  // state: State = {... }; I prefer to write state here.

  constructor(props: Props) {
    super(props);
    this.state = {
      // some state
    };
  }

  // some methods...

  render() {
    // return}}Copy the code

Log

The most common debugging console is Log, and the API that is integral to it is window.console. Common methods are [‘log’, ‘info’, ‘WARN ‘, ‘debug’, ‘error’]. The UI presentation can be divided into Log, Warn, and Error categories.

How do YOU implement a console console panel yourself? It’s as simple as “overriding” the window.console methods and then calling the console method. This allows you to add the desired actions to the existing method. (Unfortunately, this has some side effects, which we’ll cover later.)

The code logic is as follows:

const methodList = ['log'.'info'.'warn'.'debug'.'error'];

methodList.map(method= > {
  // 1. Save the window console method.
  this.console[method] = window.console[method];
});

methodList.map(method= > {
  window.console[method] = (. args:any[]) = > {
    // 2. Perform some operations to save and display data.

    // 3. Call the native console method.
    this.console[method].apply(window.console, infos);
  };
});
Copy the code

Since React is used in the project, we only need to care about data because it is data-driven.

The data in the Log is actually the parameters in console. Log, which are managed in mobx as an array and rendered by the List component.

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

export interface LogType {
  logType: string;
  infos: any[]; // Arguments from the console method.
}

export class LogStore {
  @observable logList: LogType[] = [];
  @observable logType: string = 'All';

  // some action...
}

export default new LogStore();
Copy the code

Now that you have data and list presentations, how do you use a tree structure to show basic data types and reference types

Basic types (undefined, NULL, String, Number, Boolean, symbol) display relatively simple, here will talk about the reference type (Array, Object) display implementation. The corresponding project is the logView component.

LogView components

From the previous preview image, you can roughly see the entire data display structure in the form of key-value.

Unlike the Pc browser console, __proto__ is not shown here. Function, then, is simply presented as a square name in parentheses, such as log().

Let’s take a look at the HTML structure of this UI.

All we need to show is the key and the value and the parent and child indentation, typical tree structure, recursion will take care of that.

An Object is a key-value and an Array is an index and a value.

Basic logic:

<li className="my-code-wrap"> <div className="my-code-box"> // 1. Check whether the expansion icon {opener} <div className="my-code-key"> // 2. <div > <div className="my-code-val"> // 3. </div> </div> // 4. 1. {children} </li>Copy the code

At this point, a simple log presentation logic is complete. Let’s talk about the JS command line execution in the console.

  sendCMD() {
    return (cmd: string) = > {
      let result = void 0;
      try {
        result = eval.call(window.'(' + cmd + ') ');
      } catch (e) {
        try {
          result = eval.call(window, cmd);
        } catch(e) { ; }}// Action in mobx
      logStore.addLog({ logType: 'log'.infos: [result] })
    }
  }
Copy the code

The eval() function executes the string passed as JavaScript code. But it is a dangerous function, and the code it executes has executor rights. This means that the user can decide what code to execute (including malicious code), so this browser console should never be in production.

summary

Log is not difficult to implement, just on the basis of the original Winodw. console method, add parameter collection function, and mobx management. The parameters are then presented to the user in a tree structure. However, this approach can cause a lot of unnecessary rendering, and every time console methods (including error and warning) are called, the corresponding render is triggered. If you call console in the Render method of the log component, it will cause a stack overflow (equivalent to calling setState in render), but this is only for debugging, and for online bug checking, We can inject code using Charles proxies without affecting the original code. Even so, the browser console that the front-end implements itself is not as good as the native console (at least to see if there are errors and not to want to use the cumbersome inspect mode) for things like tracking call stacks and script errors. So why use Typescript? It’s important to avoid as many bugs as possible during development. However, in the face of massive users, mobile phones are strange and strange. At this time, we can only use front-end abnormal monitoring, professional fundebug or simply deal with it. Anyway, let’s go back to the next part of system.

System

System is mainly used to display information that is not easy to view on the browser side, such as the user Agent string of the current browser or the actual URL (which may be changed for some reason). Of course, the information to be displayed is more relevant to the business and the content to be debugged, so the panel is still a custom comparison. It is important to note that detecting the userAgent value to determine the browser type is unreliable and not recommended because the user can change the userAgent value. (Fortunately, we only use it for debugging, for developers, not for other users)

PS: As an extension, feature detection can be used to check web feature support on mobile browsers (including some client WebViews), so as to do some degradation early in the development phase! In addition, some information about invoking the client protocol (JSbridge) can be displayed in system if necessary. Let’s skip this and move on to the next part of network, which we care more about.

Network

To implement network, take a look at XMLHttpRequest:

You can interact with the server using the XMLHttpRequest (XHR) object. You can get data from the URL without having to refresh the entire page. This allows Web pages to update only parts of the page without affecting user actions. XMLHttpRequest is widely used in Ajax programming.

Open, send, getAllResponseHeaders, onreadyStatechange, readyState, Status, Response etc.

If we want to capture the request sent by the user and use it for the front-end display, we need to use the open and send methods, and we need to use onReadyStatechange to listen to the transformation

. In addition, the XMLHttpRequest readyState property returns the current state of an XMLHttpRequest agent. An XHR agent is always in one of the following states:

value state describe
0 UNSENT The proxy is created, but the open() method has not yet been called.
1 OPENED The open() method has been called.
2 HEADERS_RECEIVED The send() method has been called and the header and state are available.
3 LOADING Download; The responseText property already contains some data.
4 DONE The download operation is complete.

With that in mind, let’s look at the code implementation logic:

  mockAjax() {
    // the (window as any).xmlhttprequest is used in a virtual way. It's too rough
    const XMLHttpRequest = (window as any).XMLHttpRequest;
    if(! XMLHttpRequest) {return;
    }
    const that = this;
    // Back up the open and send methods of native XMLHttpRequest
    const XHRnativeOpen = XMLHttpRequest.prototype.open;
    const XHRnativeSend = XMLHttpRequest.prototype.send;

    // open ()
    XMLHttpRequest.prototype.open = function (. args:any) {
      // 3. Get the parameter passed by the open method
      const [method, url] = args;

      // 4. Save the original onreadyStatechange
      const userOnreadystatechange = this.onreadystatechange;

      this.onreadystatechange = function (. stateArgs:any) {
        // do something

        // readyState performs corresponding processing, mainly saving the data to be displayed, such as Response and header

        // 6. Call onreadyStatechange
        return (
          userOnreadystatechange &&
          userOnreadystatechange.apply(this, stateArgs)
        );
      };

      // Call the native xmlHttprequest. open method
      return XHRnativeOpen.apply(this, args);
    };
    XMLHttpRequest.prototype.send = function (. args:any) {
      // override the xmlHttprequest. send method and save the data
      return XHRnativeSend.apply(this, args);
    };
  }
Copy the code

In this way, the collection of network data is basically completed, and then it is the matter of table presentation. However, it was still too rough, I changed prototype for the first time in my coding project, and I did it with XMLHttpRequest, so I didn’t know the basics well enough to cause more bugs. I’m going to take a look at the axios source code, see how they play with XMLHttpRequest, and see if I can optimize it. (Later…) The point here is that if you use fetch to send a request, it’s GG. Give yourself enough reason to iterate, (of course the premise is necessary, in case I go to the PC end!)

Element

While using VConsole, I was particularly concerned about how the Element panel was implemented. Here’s how to tease:

Let’s review the UI

If the data source is document.documentElement, it looks like this.

Familiarize yourself with HTML5 tags and DOM nodes, if necessary

There are only three types of nodes we need to care about: elements, text, and comments (see nodeType).

For elements (tags) we only need to know that there are two different types of presentation, self-closing tags and non-self-closing (for UI, the difference is just indentation), and that they are both composed of tag names and attributes, such as: < body style = “background: # 000” > < / body > or < img SRC = “…” >. Take a look at the HTML structure for elemnt:

The corresponding implementation is the htmlView component in the project, and the main code logic is as follows:


import { parseDOM } from 'htmlparser2';

// 1. Parse the HTML text into JSON format
const tree = parseDOM(document.documentElement.outerHTML);


// 2. Convert to JSON format, which is easy to display, and to Immutable data

  getRoot() {
    const { tree, defaultExpandedTags } = this.props;

    transformNodes(tree, [], true);
    return Immutable.fromJS(tree[0]);

    function transformNodes(trees: any[], keyPath: any, initial? :boolean) {
      trees.forEach((node: any, i: number) = > {
        // 3. Data conversion logic}); }}// 3. Differentiate render UI by type

if (type= = ='text' || type= = ='comment') {}Copy the code

For the conversion rules of HTMLParser2, you can see this demo. The data obtained by HTMLParser2 may not be suitable for rendering. After processing, the structure of the data used for rendering is as follows:

It’s still data-driven thinking, and all that’s left is the logic of rendering.

Storage

Storage implementation is also relatively simple. The front-end is generally concerned with localstorage and cookies. They all have their own methods for getting, modifying, and cleaning. We just need to get the data to render the table.

About the Typescript

So far, I’ve talked more about the console implementation. I’m sorry for the clickbait Ts + React + Mobx, but to be honest, there aren’t too many tricks to play with this project. Here’s my experience with Typescript. As stated at the beginning of this article, the biggest feeling is the improvement of the development experience. Another is:

Definition of component props and state

// Ts makes the code easier to read.
// Which attributes the component accepts and its internal state, and can know what types they accept.

interface Props {
  togglePane: (a)= > void;
  logList: LogType[]
}

interface State {
  searchVal: string
}

// Component generics
export default class ClassName extends PureComponent<Props, State> {
  // ...
}
Copy the code

See The High-quality Type Definitions for other common types related to React

"DevDependencies" : {" @ types/jest ":" ^ 23.3.9 ", "@ types/node" : "^ 10.12.5", "@ types/react" : "^ 16.7.2", "@ types/react - dom" : "^ 16.0.9", "typescript" : "^ 3.1.6"}Copy the code
Export default class Log extends Component<Props, State> { private searchBarRef = createRef<SearchBar>() sendCMD = ()=> { this.searchBarRef.current! .focus() } render() { return ( <Flex> <SearchBar ref={this.searchBarRef} onclic={this.sendCMD} /> </Flex> ); }}Copy the code

There is really very little that can be summarized. The feeling for type system in Ts is to use less any. React and Window types. (under the vscode editor. To see all type declarations, go directly to the window or React definition.

In addition, when the type is not known, type inference can be used to obtain the type.

I’m just starting out in Typescript, too! I don’t want to confuse you, but I’ll end there.

yarn run eject

After creating the project using the Create React App scaffolding, a command is provided in package.json

{
  "scripts": {
    "eject": "react-scripts eject"}}Copy the code

After executing this command, all the encapsulated configuration is decompiled into the current project so that the user can take full control of the Webpack file. For learning purposes, let it out!

Create React App The water is deep enough to study on its own!

conclusion

I have to admit, this is a training program. It’s probably totally inappropriate to use Ts + React, but I just want to take the leap and embrace Ts. The entire tutorial focuses on how the front end implements the browser console, rather than the TS + React technique. This is a conservative implementation (not sure if it’s a best practice), and I hope it’s a good idea. In addition, I hope this tutorial has brought you some knowledge expansion function.

reference

  • create-react-app
  • vConsole
  • react-devtools