Hacker said: how to do a small program online in 4 days?

Since the launch of the "hacker talk" Web Version (hackertalk.net) on June 6, it has attracted many users. In order to further improve the terminal experience, we decided to reuse the existing technology stack to realize the wechat applet. It took only four days to develop it. This paper mainly discusses how to quickly launch the applet from a technical point of view.

What did the hacker say?

This is our customized communication platform for programmers. It has timely technical information, high-quality technical Q & A, practical programming experience sharing, and programmers' daily life. Nearly 500 programming related topics.

A highly customized Markdown editor: what you see is what you get, and you don't need split screen preview anymore ~

Interested partners can directly experience the following links 👇👇

Hacker said: an interesting programmer communication platform

Web side technology stack

In order to reuse and maintain the code better, we Vue And React React , the main technical stacks on the web side are as follows:

react + typescript + redux + immer + redux-saga + axios + tailwindcss + fakerjs

  • typescript is necessary for the project, which greatly improves the correctness and maintainability of the code
  • immer replaces the traditional immutablejs The scheme realizes the direct numerical operation similar to vue in reducer (simplicity), while maintaining the advantages of immutable data flow (maintainability)
  • saga maintains the simplicity and debugging of API interface calls
  • axios encapsulates http requests and can adapt to different terminal operating environments through custom adapter s
  • tailwindcss greatly reduces the volume of style files through atomized css, speeds up the loading speed of web pages, greatly reduces the volume of small packages (2MB limit), and more code space can be used for UI interface and JS logic
  • fakerjs is used to simulate data and inject data into redux in the development environment to facilitate debugging

Applet side technology stack

The applet side technology stack and the web side are highly coincident (which is why we can quickly go online). The biggest change is from react to react+ taro.

Taro is an open cross end cross framework solution, supporting the use of React/Vue/Nerv and other frameworks to develop WeChat / Jingdong / Baidu / Alipay / byte beat / QQ Applet / H5 / RN applications.

Small program side development is extremely chaotic. The native code is difficult to organize and maintain. It usually needs some frameworks to encapsulate. Taro is adopted after using several different schemes. It highly coincides with react. hook can be used directly to greatly improve the possibility of code reuse (this is the basis of previous experience).

APP side technology stack

At present, hackers say that there is no online related APP, and the reuse technology stack can directly change react to react native.

Code file organization

Well organized code is the key to high reuse. We adopt the code division mode of components + containers to strictly standardize the code organization mode:

  • UI interface related components can only be placed in the components folder without state, and can not be coupled with any state management library related code
  • The container component of data injection can only be placed in the containers folder and cannot contain any UI related code, such as div
  • Modularization and atomization: Code layered design to achieve high reuse of components and maintain application consistency

The folder layout is as follows:

├── assets     Fixed resource files: pictures, text svg etc.
├── components pure UI assembly
├── constants  Global constant
├── containers Pure container assembly
├── hooks      custom hooks
├── layout     Layout related UI logic
├── locales    Internationalization related
├── pages      Full page logic
├── services   API Interface code
├── store      Status management code
├── styles     Style code
├── types      ts Type declaration
└── utils      Public tools

Store status management

├── actions
├── reducers
├── sagas
├── selectors
└── types

The code of saga calling API is organized as follows: calling and debugging is very convenient

function* getPostById(action: ReduxAction): any {
  try {
    const res = yield call(postApi.getPostById, action.payload);
    yield put({ type: T.GET_POST_SUCCESS, payload: res.data.data });
    action.resolve?.();
  } catch (e) {
    action.reject?.();
  }
}

The postApi is from the services folder:

export function getPostById(id: string) {
  return axios.get<R<Post>>(`/v1/posts/by_id/${id}`);
}

Applet side special adaptation

Cookie

Since the applet cannot support HTTP cookies and cannot use the cookie mechanism like a browser to ensure security and maintain user login status, we need to manually simulate a cookie mechanism. Here we recommend using a scheme of JD open source: Practice of cookie scheme for Jingdong shopping applet , the functions of cookie expiration and multiple cookies can be realized. The principle uses localstorage instead of cookies.

Http Request

The applet side can only use Wx Request makes http requests. If a large number of API s are written directly using this interface, the code will be difficult to maintain and reuse. We use the adapter pattern of axios to encapsulate Wx Request, request result and error are processed according to axios data format. In this way, we can use axios directly on the applet side.

Conversion request parameters:

function toQueryStr(obj: any) {
  if (!obj) return '';
  const arr: string[] = [];
  for (const p in obj) {
    if (obj.hasOwnProperty(p)) {
      arr.push(p + '=' + encodeURIComponent(obj[p]));
    }
  }
  return '?' + arr.join('&');
}

axios adapter mode (for CookieUtil code, refer to the example of JD above)

axios.defaults.adapter = function(config: AxiosRequestConfig) {
    // Request field splicing
    let url = 'https://api.example.com' + config.url;
    if (config.params) {
      url += toQueryStr(config.params);
    }

    // General request encapsulation
    return new Promise((resolve: (r: AxiosResponse) => void, reject: (e: AxiosError) => void) => {
      wx.request({
        url: url,
        method: config.method,
        data: config.data,
        header: {
          'Cookie': CookieUtil.getCookiesStr(),
          'X-XSRF-TOKEN': CookieUtil.getCookie('XSRF-TOKEN')
        },
        success: (res) => {
          const setCookieStr = res.header['Set-Cookie'] || res.header['set-cookie'];
          CookieUtil.setCookieFromHeader(setCookieStr);

          const axiosRes: AxiosResponse = {
            data: res.data,
            status: res.statusCode,
            statusText: StatusText[res.statusCode] as string,
            headers: res.header,
            config
          };
          if (res.statusCode < 400) {
            resolve(axiosRes);
          } else {
            const axiosErr: AxiosError = {
              name: '',
              message: '',
              config,
              response: axiosRes,
              isAxiosError: true,
              toJSON: () => res
            };
            reject(axiosErr);
          }
        },
        fail: (e: any) => {
          const axiosErr: AxiosError = {
            name: '',
            message: '',
            config,
            isAxiosError: false,
            toJSON: () => e
          };
          reject(axiosErr);
        }
      });
    });
  };

After the axios adaptation is completed, the original API related code can be reused directly without changing one line.

Message

Message pop ups and toast cannot run on the applet side. We realize code reuse through interface compatibility:

/**
 * @author z0000
 * @version 1.0
 * message Pop up window, api interface refers to antd, and applet is compatible with this interface
 */
import Taro from '@tarojs/taro';
import log from './log';

const message = {
  info(content: string, duration = 1500) {
    Taro.showToast({ title: content, icon: 'none', duration })
      .catch(e => log.error('showToast error: ', e));
  },

  success(content: string, duration = 1500) {
    Taro.showToast({ title: content, icon: 'success', duration })
      .catch(e => log.error('showToast error: ', e));
  },

  warn(content: string, duration = 1500) {
    Taro.showToast({ title: content, icon: 'none', duration })
      .catch(e => log.error('showToast error: ', e));
  },

  error(content: string, duration = 1500) {
    Taro.showToast({ title: content, icon: 'none', duration })
      .catch(e => log.error('showToast error: ', e));
  },

  // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars
  loading(content: string, _duration = 1500) {
    Taro.showLoading({ title: content })
      .catch(e => log.error('showLoading error: ', e));
  },

  destroy() {
    Taro.hideLoading();
  }
};

export default message;

The interface here refers to the Message API of Antd to realize the compatibility between browser side and applet side.

History

The history mechanism of the applet side is different from that of the browser side. For code reuse, we convert the applet routing API to the browser side interface (history method of react router):

/**
 * common api The applet is compatible with the history method of the react router
 */
import Taro from '@tarojs/taro';
import log from "./log";

const history = {
  // TODO: add query object method
  push(path: string) {
    Taro.navigateTo({ url: '/pages' + path }).catch(e => log.error('navigateTo fail: ', e));
  },

  replace(path: string) {
    Taro.redirectTo({ url: path }).catch(e => log.error('redirectTo fail: ',e));
  },

  go(n: number) {
    if (n >= 0) {
      console.error('positive number not support in wx environment');
      return;
    }
    Taro.navigateBack({ delta: -1 * n }).catch(e => log.error('navigateBack fail: ',e));
  },

  goBack() {
    Taro.navigateBack({ delta: 1 }).catch(e => log.error('navigateBack fail: ',e));
  }
};

export default history;

Then, the hook code related to useHistory in the batch search code can be converted to the above implementation.

Router

The applet cannot directly use the route management scheme similar to the react router. It benefits from the modular division of the code. Most of the code is not coupled with the things related to the react router dom. The most common is the < Link > component. Here, we can make a small transformation of the Link component and replace it in batch:

import { FC, useCallback } from 'react';
import Taro from '@tarojs/taro';
import { View } from '@tarojs/components';
import { LinkProps } from 'react-router-dom';

const Index: FC<LinkProps> = ({ to, ...props}) => {

  const onClick = useCallback(e => {
    e.stopPropagation();
    Taro.navigateTo({ url: '/pages' + to as string });
  }, [to]);

  // @ts-ignore
  return <View {...props} onClick={onClick}>{props.children}</View>
};

export default Index;

It should be noted that taro Navigateto cannot directly jump to the tab page. After all the final code is completed, search + test coverage is required to check relevant problems. Of course, you can also check whether the to parameter is a tab page in the above code and switch to taro Switchtab method.

Path Params

The applet does not support routing parameters like / post/:id. we need to convert the routing parameters to: / post?id=xx, this conversion can be searched through IDE and replace d in batch.

CSS

The direct use of rpx and PX units on the applet side will cause great reuse problems, resulting in a large amount of HTML code transformation when moving from the web page side to the applet side, Here, we use sass to realize the similar functions of tailwindcss (transformation for small program end). By switching units through variable switches, we can make different design codes compatible (375px and 750px or rpx and rem units can be directly compatible).

Design reuse is sometimes more important than code reuse, which is the premise of the consistency of user experience. Fortunately, the selection of solutions such as tailwincss makes it easy for us to do this. We will open-source applet tailwindcss code later. Please look forward to it.

Teamwork

Collaboration is also a very important part. The success of the product is inseparable from efficient cooperation. We use google doc family bucket for collaboration, including project documents, requirements, task management and e-mail. The biggest advantage of google family bucket is multi-terminal support, which is the most convenient tool for supporting terminals and collaboration at present. linux + android + ios + ipad + windows + mac can work together seamlessly. It is convenient for designers, product managers and programmers to work together.

last

Welcome to experience!

Hackers say web version

HackerTalk (hackers say) the first post: Happy hacking!

Wechat applet search: hackers say, or scan the code:

Keywords: React TypeScript Mini Program

Added by hbradshaw on Wed, 29 Dec 2021 11:27:08 +0200