Since the launch of “Hackertalk.net” web version 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 micro program on the wechat side. The development only took 4 days.

What did the hacker say?

This platform is specially designed for the programmer community, with timely technical information, high-quality technical questions and answers, practical programming experience sharing, as well as programmers’ daily life. Nearly 500 programming related topics.

A highly customized Markdown editor: What you see is what you get, no more split screen previews

If you are interested, please click the link below to experience 👇👇

Hacker said: an interesting programmer exchange platform

Web technology stack

In order to reuse and maintain the code better, we choose React from Vue and React. The main technology stack on the webpage is as follows:

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

  • A typescript project requirement that greatly improves code correctness and maintainability
  • Immer replaces the traditional IMMutableJS scheme and implements vUe-like direct numerical operations (simplicity) in reducer while maintaining the advantages of IMmutable data streams (maintainability).
  • Saga keeps API calls simple and debuggable
  • Axios encapsulates HTTP requests and can be customized to accommodate different terminal environments
  • Tailwindcss greatly reduces the size of style files with atomized CSS, speeds up web page loading, and also greatly reduces the size of small packages (2MB limit), allowing more code space for UI and JS logic
  • Fakerjs is used to simulate data and inject data into Redux in the development environment for easy debugging

Small program side technology stack

The applets stack and web stack overlap (which is why we were able to launch apps quickly). The biggest change is the change from React to React + Taro.

Taro is an open cross-end cross-framework solution that supports the use of React/Vue/Nerv frameworks to develop wechat/JD.com/Baidu/Alipay/Bytedance/QQ mini program/H5 / RN and other applications

Small program development is chaos, native code organization, difficult to maintain, usually need to encapsulate some framework, Taro is we decided to adopt after using several different schemes, and the react of highly overlap, can directly use the hooks, greatly enhance the possibility of code reuse (this is the accumulation of experience before).

App-side technology stack

At present, hackers say that there is no relevant APP online, and technology stack reuse can directly change React to React – Native.

Code file organization

Well-organized codes are the key to high reuse. We adopt the code segmentation method of Components + containers to strictly regulate the code organization method:

  • UI interface related components can only be placed in the Components folder, stateless, can not be coupled to any state management library related code
  • Container components for data injection can only be placed in containers folders and cannot contain any UI-related code, such asdiv
  • Modularization and atomization: code is designed in layers to achieve high reuse of components and maintain application consistency

The folder layout is as follows:

├── Assets Document: ├─ Pure UI Components ├─ Constants Global Constants ├─ Hooks Custom Hooks ├─ Layout Related UI Logic ├─ ├─ Exercises ├─ Styles ├─ Types ├─ Public Tools class ├─ Exercises ├─ Exercises API ├─ State Management ├─ Styles ├─ Types ├─ Public Tools classCopy the code

Store Status Management

└── Exercises ── Exercises ── exercises ── exercises ── exercisesCopy the code

The saga call API code is organized as follows: Call 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? . (); }}Copy the code

The postApi comes from the Services folder:

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

Small program side special adaptation

Cookie

Since the small program side cannot support HTTP cookie and cannot use cookie mechanism to ensure security and maintain user login status like the browser, we need to manually simulate a cookie mechanism. Here we recommend a solution of JINGdong open Source: Jingdong shopping applet cookie scheme practice, can realize cookie expiration, multi-cookie function. Its principle uses localStorage instead of cookies.

Http Request

The small program side can only use Wx. request for HTTP requests. If a large number of apis are written directly using this interface, the code will be difficult to maintain and reuse. We use the Adapter mode of AXIOS to encapsulate Wx. Request. Request results and errors are processed in axiOS data format. So 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('&');
}
Copy the code

Axios adapter pattern (CookieUtil code refer to jd example above)

axios.defaults.adapter = function(config: AxiosRequestConfig) {
    // Request field stitching
    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); }}); }); };Copy the code

After the AXIOS adaptation is complete, the original API code can be reused without changing a line.

Message

Message popovers and toast cannot run on the small program side, so we implement code reuse through interface compatibility:

/ * * *@author z0000
 * @version 1.0 * Message popup, API interface reference ANTD, applet to this interface compatible */
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;
Copy the code

This interface refers to Antd’s Message API to achieve compatibility between browser side and small program side.

History

The React Router’s history mechanism is different from the browser-side history mechanism. For code reuse, we convert the applet routing API to the browser-side history method:

/** * The common API applet is compatible with the React Router's history method
import Taro from '@tarojs/taro';
import log from "./log";

const history = {
  // TODO:Add a 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;
Copy the code

After the batch search code useHistory related hook code, convert to the above implementation.

Router

The react-router-dom is not coupled to most of the code. The most important component is the component. Here we will modify the Link component. Batch replacement can be:

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;
Copy the code

NavigateTo (taron. navigateTo) can’t jump directly to the Tab page. Of course, you can also check if the to argument is a TAB page in the code above, and switch to Taro. SwitchTab method.

Path Params

Applets do not support route parameters like /post/:id. We need to convert route parameters to: /post? Id =xx, this conversion is searched by IDE, batch replace can be.

CSS

Since direct use of RPX unit and PX unit on the small program side will cause great reuse problems, resulting in a large amount of HTML code modification when migrating from the web side to the small program side. Here, we use SASS to achieve similar functions of TailwindCSS (modification for the small program side), and switch units by variable switch. It is possible to make different design code compatible (375px and 750px or RPX, REM units are directly compatible).

Design reuse is sometimes more important than code reuse. This is the premise of user experience consistency. Fortunately, the selection of tailwinCSS and the like makes it easy to do this.

Team collaboration

We use Google Doc bucket for collaboration, including project documents, requirements, task management, email. The biggest benefit of Google Doc bucket is multi-side support, which is the tool that supports the most terminals and the most convenient collaboration. Linux + Android + ios + iPad + Windows + MAC all work seamlessly. It is convenient for designers, product managers and programmers to work together.

The last

Welcome to experience!

Hackers say web version

HackerTalk: Happy hacking!

Wechat small program search: hacker said, or scan code: