Preamble: The next step is to open up a small volume of knowledge about best practices for applets, which are used as a starting point, but by no means limited to this. There will be several chapters “Named,” “Single Test,” “Code Style,” “Comments,” “Safe Production,” “Code Design,” “Performance,” “Internationalization,” and “Why Applets Development Best Practices.” Each will contain positive and negative examples and detailed explanations.

The examples in this article are typical problems found during CR. Hopefully, you will be able to avoid such problems in advance with this booklet and focus our CR more on business logic rather than such trivialities or lack of basic ideas, resulting in a high quality CR.

Safety production 👷🏻

I’m going to turn to the most special of them all, important because it’s about online safety, but easy to overlook for novices or veterans. First introduce several principles of safe production:

1, online change must conform to the “three plate axe”, namely: can be monitored, gray, emergency.

2. Not all time periods can perform online changes.

Generally, the company will standardize the release time, avoid meals and non-working days, to avoid failure after all parties (front and back end test SRE, etc.) are not at the station can not be timely processed.

3. The fault on line shall be held responsible and there must be a clear fault grade and responsibility division. For example, when the number of affected people triggers the Pn fault within the range of XX, the impact on the individual causing the fault is YY.

Below, we will make efforts to ensure online safe production 👷🏻♂️ from various angles and means.

Report service flow logs

Front-end custom service flow log reporting helps quickly troubleshoot online problems. There are usually two ways:

  • Method 1: Send click or exposure events (recommended click for timeliness reasons) to a virtual C.d bit via buried spot, such as C00000.d00000, and upload other information via extended parameters. The traditional method relies on the backend to retrieve the returned value from logs, which takes a long time and is reported in buried mode. Logs are reported and analyzed in real time.
  • Method 2: Through the customized JSAPI, if there is a suggestion to use method 2, because method 1 occupies the traffic of the buried point, adding unnecessary burden to it.

So let’s look at an example, just to give you an intuition.

Bad

In view of the online problem, the back end returned A dirty value, which led to A release module A showing the source code ${title} that was not interpolated. After improvement, the front end decided not to show the module in this case, but at this time, it was difficult to tell whether the return value of the back end was wrong or there was no release. The online problem could not be quickly located. The front end of the default hidden processing can not be immediately exposed, the surface of a peaceful.

if(! isDirtyValue(title) && ! isDirtyValue(subtitle) && ! isDirtyValue(actionUrl)) {// Display the drop module A
}
Copy the code
Good

After dirty data logs are reported and monitored, you can quickly locate the cause that 👍 is caused by incorrect fields returned by the backend.

if (hasDirtyValue[title, subtitle, actionUrl]) {
    reportLog({
    code: 'DATA_INVALID'.msg: `dirtyValue: title (${title}), subtitle (${subtitle}) or actionUrl (${actionUrl}) contains "\$\{"`.response: resp,
  });

  return;
}

// The module A can be displayed normally.Copy the code

Buried point log reporting specifications

Different projects or applications require a logging specification. Once you are familiar with this set of specifications, you can significantly reduce maintenance costs when taking on new projects.

/** * Log spec. * From best practices */
interface ILog {
  /** Monitor code */code? :number./** Path of other custom requests or HTTP request API, jsAPI name */api? :string;
  /** Brief description */msg? :string;

  /** Log source */
  from? :string;
  /** Log occurrence time */at? :number.type? :'RPC' | 'HTTP' | 'JSAPI' | 'MTOP' | 'JS_Builtin_Func';

  /** complete error */error? :Error | object;
  /** For example, HTTP request method */subType? :string;
  /** Request body */request? :object;
  /** Response body */response? :object;
}
Copy the code

How to report

Method one: through the burial point

/** * User-defined service burying point */
export const reportLog = (log: ILog = {}): void= > {
  try {
    const $spm = getSpm();

    const {
      type,
      api,
      msg,
      subType,
      error = {},
      request = {},
      response = {},
    } = log;

    /** Prevent log length from causing performance problems */
    const MAX_LOG_LENGTH = 2000;

    const errorStr = jsonStringifySafely(error).slice(0, MAX_LOG_LENGTH); $spm? .click('c00000.d00000', {
      // The _ prefix is used to distinguish it from other built-in buried fields
      _type: type,
      _api: api,
      _msg: msg,
      _subType: subType,

      _error: `name:${error.name}|message:${error.message}|error:${errorStr}`._request: jsonStringifySafely(request).slice(0, MAX_LOG_LENGTH),
      _response: jsonStringifySafely(response).slice(0, MAX_LOG_LENGTH),
    });
  } catch (err) {
    console.error('reportLog:', log, 'failed:', err); }};Copy the code

Method 2: JSAPI

// src/common/utils/remoteLog.js
// Initialize the RemoteLogger instance in a public file
import { RemoteLogger } from '@yourcompany/RemoteLogger';

const remoteLogger = new RemoteLogger({
  bizType: 'BIZ_TYPE'.appName: 'Application name'});const withdrawRemoteLogger = new RemoteLogger({
  bizType: 'BIZ_TYPE'.appName: 'Application name - Page name'});/ * * * *@param {ILog} log
 * @param {'info' | 'error'} level* /
function send(log, level) {
  if (typeoflog ! = ='object') {
    // eslint-disable-next-line no-console
    console.warn(
      'remoteConsole.info: log must be an object, but see: typeof log'.typeof log,
      ', log:',
      log,
    );

    return;
  }

  const formatted = formatLog(log);

  // eslint-disable-next-line no-console
  console[level] && console[level](` [The ${Date.now()}] RemoteLog:`, formatted);

  const logger = resolveLogger(log.from);

  logger[level] && logger[level](log.api || log.msg, log.msg || ' ', formatted);
  
  // Report to monitor
  if(MonitorCodeEnum[log.code]) { reportLog(toMonitorLog(formatted)); }}/ * * *@param {ILog['from']} from
 * @returns {RemoteLogger}* /
function resolveLogger(from) {
  return loggers[from] || remoteLogger;
}

/** * Report flow log */
export const remoteConsole = {
  /** * Reports normal flow logs *@param {ILog} log* /
  info(log) {
    send(log, 'info');
  },

  /** * Abnormal flow log *@param {ILog} log* /
  error(log) {
    send(log, 'error'); }};const PAGE_NAMES = {
  withdraw: {
    home: 'withdraw',}};const loggers = {
  [PAGE_NAMES.withdraw.home]: withdrawRemoteLogger,
};

/** * remote console */
export const withdrawHomeRemoteConsole = {
  /** * Reports normal flow logs *@param {ILog} log* /
  info(log){ remoteConsole.info({ ... log,from: PAGE_NAMES.withdraw.home });
  },

  /** * Abnormal flow log *@param {ILog} log* /
  error(log){ remoteConsole.error({ ... log,from: PAGE_NAMES.withdraw.home }); }};/ * * *@param {ILog} log
 * @returns {ILog | ILog & { error: { name: string; message: string; stack: string; error: string } }} * /
function formatLog(log) {
  const { error } = log || {};

  if (error instanceof Error) {
    // eslint-disable-next-line no-console
    console.error(error);

    return {
      ...log,

      error: {
        name: error.name,
        message: error.message,
        stack: error.stack,
        errorToString: error.toString(),
        error,
      },
    };
  }

  return log;
}
Copy the code

The sample

Report error flow log to withdrawal process

import { withdrawHomeRemoteConsole as rc } from '/common/utils/remoteLog';

withdraw()
  .catch((error) = > {
    const isExpectedError = showErrorModal(error);
  
    if(! isExpectedError) { rc.error({msg: 'withdraw unknown error', error, }); }});Copy the code

You only need to add a code to report alarms

import { withdrawHomeRemoteConsole as rc } from '/common/utils/remoteLog'; withdraw() .catch((error) => { const isExpectedError = showErrorModal(error); if (! isExpectedError) { rc.error({+ code: 'WITHDRAW_UNKNOWN_ERROR'msg: 'unknown error', error, }); }});Copy the code

How do I query reported logs

Depending on the company, the general company has its own buried platform.

JS compatible

The small program must support iOS 9+, so the development must refer to caniuse: community Caniuse, small program caniuse.

Bad

Values are not supported on iOS 9 or even some iOS 10.

Good

Use Lodash Values for better compatibility

import values from 'lodash/values';

values(obj);
Copy the code

The JSAPI compatibility

If the document indicates the need for compatibility, business compatibility must be made, not limited to JSAPI, such as some native components. If incompatible, reasons should be annotated.

Bad

HideBackHome +is+not+a+function”

onLoad() {
  my.hideBackHome();
}
Copy the code
Good
onLoad() {
  my.hideBackHome && my.hideBackHome();
}
Copy the code
Better

Further encapsulation

// utils/system.ts
export function canIUse(jsapiName: string) :boolean {
  return my.canIUse(jsapiName) && typeof my[jsapiName] === 'function';
}

// use in index.ts
onLoad() {
  // Hide the auto Charge Home button, auto charge is a separate application
  canIUse('hideBackHome') && my.hideBackHome();
}
Copy the code

CSS compatibility

Hair effect

1rpx (0.5px) has compatibility issues, so it is recommended to use hair effect

Different models of word weight corresponding table

Word again font-weight English
Regular body 400 (normal) PingFang-Regular
Very fine body 200 PingFang-Ultralight
The slender body 100 PingFang-Thin
The thin body 300 PingFang-Light
In the black 500 PingFang-Medium
In bold 600 PingFang-Semibold
The bold 700 (bold) PingFang-Bold
  1. The PingFang font exported with Sketch is unique to iOS and not supported by Android, so there is no need to specify font family in CSS.
  2. Text that is “half-bold” (font-weight 600) in Chinese on iOS will not work on Android, while text in bold (font-weight 700) will work fine.
Bad

1RPX will have compatibility problems, it is recommended to use hair effect

.item-content{
  height: 145rpx;
  border-bottom: 1rpx solid #eeeeee;
}
Copy the code
Good

Define the hair effect

// mixins.less

// Move the 1px border on the hair
// NOTICE: The parent element must be added position relative or fixed
.hairline(@color: #eee.@position: top) {
  &::before {
    content: ' ';
    width: 200%;
    top: 0;
    left: 0;
    position: absolute;
    border-@{position}: 1px solid @color;
    
    -webkit-transform: scale(0.5);
    transform: scale(0.5);
    -webkit-transform-origin: left top;
    transform-origin: left top; }}Copy the code

use

.item-content{
  height: 145rpx;
  
  .hairline(@position: bottom);
}
Copy the code

Disallow the use of synchronous JSAPI

For example, my.getSystemInfosync/my.getStoragesync, synchronization will block JS worker execution and greatly affect user experience. You must use the corresponding asynchronous method my.getSystemInfo/my.getStorage, and we recommend wrapping it as a Promise.

Bad
const { version } = my.getSystemInfoSync();
Copy the code
Good

Use asynchronous getSystemInfo after promisify, with caching and failure reporting logic to prevent concurrent and repeated calls 👍🏻.

// lib/system.ts

/ * * * *@param {any} obj
 * @returns {boolean}* /
function isPromise(obj) {
  return obj && typeof obj.then === 'function';
}

let cachedSystemInfoPromise;

/** * getSystemInfo with cache * do not use sync getSystemInfoSync * default 500ms timeout **@throws no error
 * @returns {Promise<{ platform: 'iOS' | 'iPhone OS' | 'Android', version: string, }>} return `{}` on error
 */
export function getSystemInfo({ timeout = 1 * 1000 } = {}) {
  if (cachedSystemInfoPromise) {
    return cachedSystemInfoPromise;
  }

  cachedSystemInfoPromise = new Promise((resolve, reject) = > {
    setTimeout(() = > {
      reject(new RangeError(`getSystemInfo timeout for ${timeout}ms`));
    }, timeout);

    my.getSystemInfo({
      success(res) {
        resolve(res);
      },

      fail(error){ reject(error); }}); }).catch((error) = > {
    const isTimeout = error instanceof RangeError;
    const msg = isTimeout ? error.message : 'getSystemInfo failed';

    rc.error({
      msg,
      request: { timeout },
      error,
    });

    return {};
  });

  return cachedSystemInfoPromise;
}
Copy the code

use

const { version } = await getSystemInfo();
Copy the code

Carefully introduce your own util dependency package

Introduce an industry mature tripartite NPM package with a single test, such as LoDash. Bipartite packages packaged for business specificity must be compiled into ES5 and cannot be published as SRC

User input parameters are not trusted

Disable brainless skipping based on user-defined parameters

Security risks need to be controlled through whitelist, because third-party pages are redirected through our app. For users, this is the default secure page of our app or Alipay.

Solution: In whitelist mode, the whitelist can be a strict match whitelist or a rule match whitelist. Strict match takes precedence

💡 naming Tips: The name of a whitelist should not be whitelist, but allowList. The name of a blacklist is denyList.

Bad

Application A will redirect to the target address passed in by query. This requirement has security risks. If A phishing website jumps through this application and users believe that the website is trusted by Application A, trust will be passed (PageRank algorithm in users’ minds), which will mislead users that the three-party website is A legitimate website.

Page({
  onLoad(options: IOptions) {
    const { goPage } = options;
    
    if (goPage) {
      this.commit('setState', { goPage, }); }},// Go to the page after completing an operation without checking the page address
  onExitCamera() {
    const { goPage } = this.state;

    if (goPage) {
      jump(goPage, {}, {
        type: 'redirectTo'}); }else{ goBack(); }}})Copy the code
Good
Page({
  onLoad(options: IOptions) {
    const { goPage } = options;
    
    // If strict matching is used, links in the whitelist can be jumped
    if (isTrustedRedirectUrl(goPage)) {
      this.commit('setState', { goPage, }); }}});function isTrustedRedirectUrl(url) {
  return ALLOWLIST.includes(url);
}
Copy the code

Version number comparison

Version numbers don’t do string comparisons directly, use your company’s custom library or use the snippets below to further encapsulate gt, GTE, LT, LTE, EQ, and more readable methods.

Because 1 < 9 is compared by character, thus ‘10.9.7’ < ‘9.9.7’.

function compareInternal(v1: string, v2: string, complete: boolean) {
  // If v2 is undefined, v1 is the client version
  if (v2 === undefined) {
    v2 = v1;
    v1 = getClientVersion();
  }
  
  v1 = versionToString(v1);
  v2 = versionToString(v2);
  
  if (v1 === v2) {
    return 0;
  }
  
  const v1s: any[] = v1.split(delimiter);
  const v2s: any[] = v2.split(delimiter);
  const len = Math[complete ? 'max' : 'min'](v1s.length, v2s.length);
  
  for (let i = 0; i < len; i++) {
    v1s[i] = typeof v1s[i] === 'undefined' ? 0 : parseInt(v1s[i], 10);
    v2s[i] = typeof v2s[i] === 'undefined' ? 0 : parseInt(v2s[i], 10);
    
    if (v1s[i] > v2s[i]) {
      return 1;
    }
    
    if (v1s[i] < v2s[i]) {
      return -1; }}return 0;
}

export function compareVersion(v1, v2) {
  return compareInternal(v1, v2, true);
}

export function gt(v1, v2) {
  return compareInternal(v1, v2, true) = = =1;
}
Copy the code
Bad
if (version > '10.1.88') {
  // ...
}
Copy the code
Good
import version from "lib/system/version";

if (version.compare(version, '10.1.88') > 0) {
/ / or
if (version.gt(version, '10.1.88')) {
  // ...
}
Copy the code

Disallow modifying function input parameters

Modifying function inputs may introduce unexpected problems. It is recommended to learn the no side effects principle of functional programming and not modify parameters.

Bad

By default, sort modiates the original array, causing availableTaskList in origData to be tampered with.

 function mapTaskDataToState(origData) {
   return getIn(origData, ['availableTaskList'], [])
     .sort((a, b) = > {
       // The obtained tasks are sorted in descending order of priority
       return getIn(b, ['taskConfigInfo'.'priority']) - getIn(a, ['taskConfigInfo'.'priority']);
     });
 }
Copy the code
Good

Make a shallow copy by extending the operator

 function mapTaskDataToState(origData) {
   return [...getIn(origData, ['availableTaskList'], [])]
     .sort((a, b) = > {
       // The obtained tasks are sorted in descending order of priority
       return getIn(b, ['taskConfigInfo'.'priority']) - getIn(a, ['taskConfigInfo'.'priority']);
     });
 }
Copy the code

Defense-oriented programming

Try Catch

For catch, distinguish between stable code and unstable code. Stable code is code that will not fail no matter what. For the catch of unstable code, distinguish the exception type as far as possible, and then do the corresponding exception processing.

Note: A try-catch of large chunks of code prevents the program from responding correctly to different exceptions, and prevents it from locating problems. This is irresponsible.

Bad

Intended only for the asynchronous request fetchSelfOperationRecommendInfo do catch, but will be long impossible error code into the try-catch, seems there is no problem, but it’s not safe? This is irresponsible laziness. You shouldn’t let a try-catch catch catch you. If something fails, you should fix it when you write it, not when you try to catch it online.

export async function querySelfOperationRecommendInfo({ commit, dispatch }) {
  try {
    let selfOperationRecommendInfo = await fetchSelfOperationRecommendInfo();

    const stardSelfOperation = formatSelfOperationRecommendData(selfOperationRecommendInfo);
    commit('updateSelfOperationRecommendInfo', stardSelfOperation);

    // If there is no such thing as a backstop
    if (stardSelfOperation.filter(item= > item.value === 'TWZX').length === 0) {
      dispatch('fetchArticles');
    } else {
      commit('updateComputeSelfTabsHeight'.true); }}catch (error) {
    return console.error('querySelfOperationRecommendInfo error', error); }}Copy the code
Good

Request errors are out of control and therefore require error handling, but other code errors are caused by logic bugs that should not be caught and fixed as soon as possible.

export async function querySelfOperationRecommendInfo({ commit, dispatch }) {
  let selfOperationRecommendInfo: IRecommendInfo[];

  try {
    selfOperationRecommendInfo = await fetchSelfOperationRecommendInfo();
  } catch (error) {
    return console.error('querySelfOperationRecommendInfo error', error);
  }

  // Continue with normal logic
}
Copy the code

Don’t abuseget

Nested property access should avoid unnecessary GETS, which can compromise readability, variable jumps, property intelligence, auto-completion, and possibly performance.

  • Air defense preferred recommendation TS optional cascading method, i.eOptional Chaining:maybeObj? .a? .b? .c
  • orfoo.bar || ''
  • Second, use Lodash.get. Don’t write it yourself
  • The last thing to write is your own or internalgetIn
Bad
const tmallCarStoresList = getIn(result, [ 'data'.'data' ], [])
Copy the code
Good

The TS built-in syntax is more concise, maintaining a smooth reading experience without destroying readability, while still maintaining the intelligent hints of the properties 👍.

consttmallCarStoresList = result? .data? .data || []Copy the code
Bad

Repeated property getIn too many times, loss of performance

function foo(array) {
  return array.map(item= > {
    return {
      taskId: getIn(item, ['taskConfigInfo'.'id'].' '),
      iconUrl: getIn(item, ['taskConfigInfo'.'iconUrl'].' '),
      name: getIn(item, ['taskConfigInfo'.'name'].' '),
      actionText: getIn(item, ['taskConfigInfo'.'actionText'].'Go immediately'),
      actionUrl: getIn(item, ['taskConfigInfo'.'actionUrl'].' '),
      status: getIn(item, ['status'].' '),
      description: getIn(item, ['taskConfigInfo'.'description'].' '),}; }); }Copy the code
Good

Using deconstructed assignment, there is no performance cost, and additional intelligent hints are gained by adding parameter types 👍

/ * * *@param tasks {{ taskConfigInfo: { id: string; iconUrl: string; }}} [] * /
function foo(tasks) {
  return tasks.map(item= > {
    const {
      id = ' ',
      iconUrl = ' ',
      name = ' ',
      actionText = 'Go immediately',
      actionUrl = ' ',
      status = ' ',
      description = ' ',
    } = item.taskConfigInfo || {};

    return {
      taskId: id,
      iconUrl,
      name,
      actionText,
      actionUrl,
      status,
      description,
    };
  });
}
Copy the code

Dangerous low-level functions

Parse, jSON. stringify, encodeURIComponent, decodeURIComponent, etc.

[Mandatory] : A dangerous underlying method must be encapsulated into a method that can never be thrown wrong.

Bad

If the value returned from the back end does not meet the expectations, json. parse reports an error, and the function cannot be used, or even a blank screen is displayed.

const memberBenefits: IMemberBenefit[] = JSON.parse(data.memberBenefits || '[]')
  .map((benefit, index) = > {
    // ...});Copy the code
Good

Parse encapsulates jSON.parse as a generic jsonParseSafely.

export function jsonParseSafely<T> (str: string, defaultValue: any = {}) :T {
  try {
    return JSON.parse(str);
  } catch (error) {
    console.warn('JSON.parse', str, 'failed', error);

    returndefaultValue; }}Copy the code

use

const memberBenefits = jsonParseSafely<IMemberBenefit[]>(data.memberBenefits, [])
  .map((benefit, index) = > {
    // ...});Copy the code

Other Safe functions.

function decodeURIComponentSafely(url = ' ') {
  try {
    return decodeURIComponent(url);
  } catch (error) {
    console.error('decodeURIComponent error', error);
    
    return ' '; }}export function jsonStringifySafely(obj: any) :string {
  try {
    return JSON.stringify(obj);
  } catch (error) {
    console.warn('JSON.stringify obj:', obj, 'failed:', error);

    return ' '; }}Copy the code

Use safe and elegant deconstruction assignments

Using deconstructed assignment forces developers to consider air defense and centralizes all air defense logic.

Bad
Promise.all([this.dispatch('getNewTaskList'), this.dispatch('getNewAdTaskList')])
  .then(result= > {
    const taskOriginData = result[0];
    const adTaskOriginData = result[1];

    // ...
  });  
Copy the code
Better
Promise.all([this.dispatch('getNewTaskList'), this.dispatch('getNewAdTaskList')])
  .then(([ originalTasksResp = {}, originalAdTasksResp = {} ]) = > {
    // ...
  });
Copy the code

View rendering content is disabled&&

{{a && b}} will show the user undefined if the condition is not met. Therefore, it is forbidden to use && to render in view. Trinary operators and other solutions can be used. Similar to React.

Bad

Undefined % will appear.

<text>{{ feeModalInfo.feeRate && feeModalInfo.feeRate }}%</text>
Copy the code
Good

If the criteria are not met, strange characters that make the user look as if the page is buggy are not displayed. If the field is important to users, it is better to report the exception log.

<text>{{ feeModalInfo.feeRate ? `${feeModalInfo.feeRate}%` : '' }}</text>
Copy the code

Async descriptor is added to all asynchronous methods or functions

Adding async ensures that a function can return a Promise at all times, in order to avoid null Pointers to calls using THEN if the return value is not necessarily a promise.

Bad
function alertOnEmpty(title) {
  if(! title) { reportLog({api: 'alertOnEmpty'.msg: 'won\'t alert because title is empty. It must be a bug'});return;
  }
  
  return new Promise((resolve) = >{ resolve(); })}Copy the code

Uncaught TypeError: Cannot read property ‘then’ of undefined”

alertOnEmpty(' ').then(console.log('success'))
Copy the code

💡 : essentially violates the function return values are not consistent with the specification, if the faithful describes the return value Promise < void > | void is VSCode also complains.

Good

Bool promise.resolve (); . But there is no guarantee that all returns will return Promise 😓.

function alertOnEmpty(title) {
  if(! title) { reportLog({api: 'alertOnEmpty'.msg: 'won\'t alert because title is empty. It must be a bug'});return Promise.resolve();
  }

  return new Promise((resolve) = >{ resolve(); })}Copy the code
Better

As long as async is added asynchronously, simply adding a descriptor guarantees that the function will always return a Promise.

async function alertOnEmpty(title) {
  if(! title) { reportLog({api: 'alertOnEmpty'.msg: 'won\'t alert because title is empty. It must be a bug'});return false;
  }

  return new Promise((resolve) = > {
    resolve(true); })}Copy the code

Be careful with the front pocket

Front-end write bottom logic to be cautious, equivalent to data fraud, it is easy to appear online problems. The back-pedalling logic must be approved by PD before it can be used. Developers are forbidden to back-pedalling.

Bad

Copywriting bottom is very easy to cause user public opinion, if the bottom must be synchronized test and PD.

resultPageInfo: {
  arriveDateDes: 'Expected to arrive in two hours'.// Account arrival time description
  withdrawFee: '0.01'./ / the service fee
},
Copy the code
Good
resultPageInfo: {
  arriveDateDes: ' '.withdrawFee: ' ',},Copy the code

Don’t abuseforcycle

[Suggestion] Do not abuse for loops, including for-in and for-of. Use map Reduce filter forEach find includes, some any every, and other higher-order functions without side effects. If you do, please comment on your reasons.

Reason: The for loop is imperative code, cumbersome and error-prone. The first thing to do is set an index variable I, being careful not to overstep the bounds, incrementing each time through the loop. I < tabs. Length, I <= tabs. Length – 1, I ++, I += 1.

Loops loops Prefer JavaScript’s higher-order functions instead of loops like for-in or for-of.

Reference 2: Differences between procedural, functional, imperative, and declarative programming patterns

Bad
for (let i = 0; i <= tabs.length - 1; i++) {
  console.log(tabs[i]);
}
Copy the code
Good

Simple and error-proof

tabs.forEach(tab= > {
  console.log(tab);
});
Copy the code

Don’t abusemap

[Mandatory] EsLint rule array-callback-return.

  • Do not process the return value in scenarios where map is used
  • Do not modify items in the map
Bad

Use 👎 for map as forEach.

let invoiceDetailLst = [];

invoiceDetailType.map((item) = > {
  if (invoiceDetail[item.key]) {
    invoiceDetailLst.push({
      type: item.key,
      key: item.value,
      value: invoiceDetail[item.key], }); }});Copy the code
Good

Logic is divided into filtering or processing after clearer, more elegant without temporary variables 👍🏻.

const invoiceDetailLst = invoiceDetailType
  .filter(item= > invoiceDetail[item.key])
  .map(item= > ({
    type: item.key,
    key: item.value,
    value: invoiceDetail[item.key],
  }));
Copy the code

A ban onthisFeel free to add attributes to

For performance reasons, variables that are not required for direct rendering are forbidden to be placed in state or data. In addition, considering that the current object is not built by itself, it is forbidden to add new attributes during the use of this pointer to avoid unexpected situations that may be caused by overwriting the attributes in the original this pointer.

Bad

This is an example of a page in a small program that contains key attributes or methods. Adding attributes without authorization can override built-in attributes and cause unexpected runtime errors.

Page({
    onLoad(options) {
    this.options = options
  },
  
  // ...
  
  fetchInfo() {
    const { code } = this.options
  }
})
Copy the code
Good

Storage via page-level variables

let customData = {}

Page({
    onLoad(options) {
    customData = options
  },
  
  // ...
  
  fetchInfo() {
    const{ code } = customData ... }})Copy the code
Good

By customizing data objects, it is recommended to place them prominently at the top.

Page({
  customData: {},
  
    onLoad(options) {
    this.customData = options
  },
  
  / /...
  
  fetchInfo() {
    const{ code } = customData ... }})Copy the code

Don’t rely on synchronization of setData

Synchronization means that when you setData, you can retrieve the value from data immediately.

SetData is synchronized in the current practice test, but it is unstable. There are bugs caused by relying on synchronization in small program plug-ins, and it is difficult to check occasionally. The developers of small program framework have not promised that setData must be synchronized, so “do not rely on the synchronization of setData”.

Bad
{
    async bar() {
    const { subBizType } = await foo(bizType);

    this.setData({ subBizType });

    await this.fetchNotice();
  },

  async fetchNotice() {
    let res;

    try {
      res = await getCommonData(this.data.subBizType);
    } catch (e) {
      myLogger.error('fetchNotice failure', { e });
      
      return;
    }
    
    this.setData({
      noticeList: res.noticeList, }); }},Copy the code
Good

Do not depend on setData is synchronous, take subBizType as function argument. Another benefit is that dependence is more pronounced. Explicit is better than implicit.

{
    async bar() {
    const { subBizType } = await foo(bizType);

    this.setData({ subBizType });

    await this.fetchNotice(subBizType);
  },

  async fetchNotice(subBizType) {
    let res;

    try {
      res = await getCommonData();
    } catch (e) {
      myLogger.error('fetchNotice failure', { e });
      
      return;
    }
    
    this.setData({
      noticeList: res.noticeList, }); }},Copy the code

Desensitization specifications and tools

Do not implement desensitization logic on the front-end or client, and use the desensitization tool that complies with the latest desensitization specifications of the company.

💡 Note: desensitization should be done at the BFF layer, do not attempt to convert BFF layer libraries using Babel to facilitate desensitization on the client side.