Recently, I was thinking about how to write a high quality React project, so I decided to write a practical tutorial about React and TS, using Monorepo + Lerna management package. How to quickly implement requirements while focusing on code quality and specifications.

Next, take you to quickly develop a Web version of the chat room. Impatient partners can directly look at the source code

PS: This tutorial is for developers who have experience with React, TS, and Node.

  • UI component library construction
  • Lerna + Monorepo development mode
  • React Hook based state management
  • Socket. IO application on client and server

The target

Realize multi-person online chat, can send text, expression, picture.

Now let’s look at what the page we want to implement looks like:

Development plan and project initialization

After the demand analysis, the development plan is as follows:

Basic configuration

The basic configuration is quickly constructed by self-built scaffolding, which includes:

  1. Add ESLint, prettier, husky for code specification, Git commit specification
  2. Add Lerna configuration and YARN Workspaces
  3. Created in the Packages directory@im/component,@im/app,@im/server

PrintWidth for Prettier is 120 when TS is used (the standard is 80). The goal is never to use two lines of code that can be expressed in a single line of code, or formatted in a single line of code.

The details of each package are then described

UI library

Following the pace of rapid development, the CREate-React-app CLI was used to initialize the UI library. The command is as follows:

  1. Initialize the React+TS environment
npx create-react-app component --typescript
Copy the code
  1. Initialize the Storybook
cd component
npx -p @storybook/cli sb init --story-format=csf-ts
Copy the code
  1. Add a storybook addons
{
  addons: [
    '@storybook/preset-create-react-app'.'@storybook/addon-viewport'.// Phone preview
    '@storybook/addon-notes/register-panel'./ / API documentation
    '@storybook/addon-actions'.'@storybook/addon-links',]};Copy the code

Finally, this pattern is used to standardize component library development (PS: component library without documentation, not called component library) :

The client

APP development follows the most familiar pattern, which is to initialize the environment directly with create-React-app.

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

The whole chat room project adopts multi-package management mode, so we will directly create soft links through lerna link command during development, so we don’t need to publish packages to complete the use of dependencies.

Note, however, that node_modules is ignored in the Babel configuration of the project generated by the create React app command. So, you have to override its WebPack configuration.

This is done simply by react-app-rewired, but it’s not a best practice.

The service side

Here, the server-side code is only for demonstration purposes, so robustness is not an issue. Ts-node, Nodemon, and Express are required.

The startup command is as follows:

nodemon --watch 'src/**/*.ts' --ignore 'src/**/*.spec.ts' --exec 'ts-node' src/index.ts
Copy the code

The core to realize

At this point, the basic environment and set up. Next talk about the chat room core implementation logic

You can use TODO to develop, for example:

Specification git Commit by splitting requirements into tasks, each of which is associated with a TODO.

Message Component Design

Although the project is based on The Material-UI development, considering the differences brought by the business, the component library may need to be highly customized, so the basic UI components are directly used by full export.

Chat room is used more message flow components, such as: pure text message component, pure picture message component, system message component, recommendation component and so on.

├ ─ ─ MessageBase TSX# Basic message component containing avatar and reverse display├ ─ ─ MessageMedia TSX# Pictures, audio, etc├ ─ ─ MessageSystem TSX# System message├ ─ ─ MessageText TSX# Text component├ ─ ─ __stories__# Document correlation│ ├─ ├─ ├─ ├─ im.├ ─ ├.htm, ├─ ├.txt, ├.txt, │ ├─ ├.txt, │ ├─ ├.txt, │ ├─ ├.txt, │ ├─ ├.txtCopy the code

Main design ideas:

  1. Develop components in a composite manner
  2. Maintain component API consistency
  3. Keep it as simple as possible without over-designing

At present, the message component is relatively simple, specific implementation, you can see the source code. Here mainly conveys the document organization and basic design ideas.

Data flow design

React Hook: How can the front end share state more elegantly

export const ChatContext = React.createContext<{
  state: typeof initialState;
  dispatch: (action: Action) => void;
}>({
  state: initialState,
  dispatch: () => {},
});

export const useChatStore = () => React.useContext(ChatContext);

export function ChatProvider(props: any) {
  const [state, dispatch] = React.useReducer(reducer, initialState);
  const value = { state, dispatch };
  return <ChatContext.Provider value={value}>{props.children}</ChatContext.Provider>;
}
Copy the code
  1. throughReact.createContextTo create the context
  2. throughReact.useReducerManage reducer and generate state and dispatch
  3. throughReact.useContextObtaining the state source

In this way, we can easily maintain local or global state. Whether to put all the states in the root tree and whether the domain data needs to be stateful is another story that is left to the reader.

Next, let’s design the data structure needed for a chat room:

interface State {
  messages: Message[]; // Store all messages in an array, keeping them in order
  members: { [id: string]: Member }; // Store all users in the current chat room in the form of map for easy query
}
Copy the code

Keep the data as simple as possible, such as the structure of a message:

interface Message {
  id: string;
  type: MESSAGE_TYPE; // Message type, used to render unused message components
  userId: string; // The user id that sent the message
  content: object; // The data structure converges according to the message component type
}
Copy the code

MESSAGE_TYPE Enumeration of message types, used for one-to-one correspondence with message flow component insinuations, and type data when socket messages are sent. It is recommended to maintain such constants in @im/ Helper.

interface Member {
  id: string;
  avatar: string;
  name: string;
}
Copy the code

The userId in the message goes to Members for user data to render avatars, nicknames, etc.

Press above agreement can satisfy a simple chat room basically. In addition, if there are many layers of components and component granularity is relatively fine, some shared states such as currentUserId, Socket, and activeTool can be introduced without considering the reuse of service components, which can effectively avoid state communication between parent and child components. However, developers need to weigh the reuse by themselves.

The client Socket

  1. After the component is mounted, establish a socket link and save the current socket instance. Disconnect the socket after the component is unmounted.
React.useEffect(() => {
  const socket: SocketIOClient.Socket = io('http://localhost:3002');
  dispatch({ type: Type.INSERT_SOCKET, payload: socket });
  return () => {
    socket.close();
  };
}, [dispatch]);
Copy the code
  1. Notify the server, for example, to join a chat room
state.socket.emit('add user', username);
Copy the code
  1. Listen for server events, such as users sending messages
React.useEffect((a)= > {
  if(! state.socket) {return;
  }
  state.socket.removeAllListeners();

  state.socket.on('login', handleLogin);
  state.socket.on('user joined', handleUserJoin);
  state.socket.on('user left', handleUserLeft);
  state.socket.on('new message', handelNewMessage);
}, [state.socket, handleLogin, handleUserJoin, handelNewMessage, handleUserLeft]);
Copy the code

The server Socket

This is a socket official demo, relatively simple. No other scenarios, that’s it!

import express from 'express';
import socket from 'socket.io';
const server = require('http').createServer(app);
const io = socket(server);

server.listen(port);

io.on('connection'.socket= > {
  // Process incoming new messages
  socket.on('new message'.data= > {
    // Notify other clients
    socket.broadcast.emit('new message', {
      id: v4(),
      username: socket.username,
      userId: socket.userId,
      message: data.message,
      type: data.type,
    });
  });
});
Copy the code

Client and server socket communication has been completed, paste code is always very tired, see the source code for details.

QA

In this section, I’m going to take a quick look at some of the questions you might encounter in developing a chat room through a q&A:

  1. How to realize expression sending

Simple expressions can be treated as text, or images if compatibility is a concern. I’m not going to do the expansion here

  1. How do I scroll to the latest news
React.useEffect((a)= > {
  if (lastMessage) {
    // Get the last message element
    lastMessage.scrollIntoView();
  }
}, [lastMessage]);
Copy the code

conclusion

Fast with everyone to achieve a simple Web version of the chat room, from the requirements analysis, to code specification organization, in the data flow design, and finally introduced the socket in the client and server application, I think we have a general understanding of how to quickly develop the chat room. I hope you found this tutorial helpful. Thank you.