preface

To explain why I did this, I recently started developing applets again. I remember my previous team’s applets project, which adopted the mini+ Server model, where requests from applets go through Node Server (Egg \ Express \koa) and mock them back on the Node side. It also adds to the complexity of the project somewhat, creating dependency coupling while developing small programs to maintain another project. As a result, I began to think about how to implement Taro’s localization mock. However, I was slightly dissatisfied with the fact that only plug-ins were available in the Taro community and the relevant documents reviewed, so I wrote this article. The Taro template supports syntax such as TS, react, and less, but does not support mock requests. There are plug-ins available if you want to support mocks@tarojs/plugin-mock However, due to my obsessive compulsive disorder, I adhere to the principle of the fewer plug-ins the better for the project I maintain, because too many plug-ins will not only bring great maintenance costs and take over costs, but also the next person will undoubtedly have to look through the documents of the plug-ins they have never encountered before when they take over. Compared with code support Mock, The code is much more readable than the black box plug-in. Besides, mock itself belongs to the business layer. If you use this plug-in, the configuration file will also introduce the mock data of the business, which I think is a kind of pollution. Part of the code in this article uses TS. It is recommended to read the previous article for better resultsMore elegant asynchronous request handling after embracing TS;

Initialize the Taro

The following template project can be initialized using the Taro CLIThe project was very simple, starting with the Pages and Store modules. He needs to be modified if he wants to support mocks.

Encapsulate an Taro request

Localhost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost: localHost

function http(method: 'POST' | 'GET', url, data = {}) { return new Promise(function (resolve, reject) { Taro.request({ url: `${SERVERURL}${url}`, method, data, dataType: 'json', header: { 'Content-Type': 'application/json; charset=utf-8', 'token': token }, success: Function (res) {if (res.statuscode === 200) {if (res.data.code === 400) {console.log('400, reject ') reject(res)} if (res.data.code === 401) {console.log('401, login expired ') Taro. ShowToast ({title: 'login expired, re-login ', icon: 'none', duration: 2000}) reject(res)} if (res.data.code === 404) {console.log('404, reject(res) ')} if (res.data.code === 500) { Console.log ('500, daemon error ') reject(res)} else {resolve(res) console.log(res)} else {resolve(res) console.log(res)} }, fail: function (err) { console.log(err) reject(err) } }) }) }Copy the code

Managing Mock Data

A mock directory is added under the SRC directory to manage mock data separately and to divide mocks according to business needs by business module.Intex is responsible for collecting all mocks and packaging them as promises for output.

// index.ts import login from './login'; import isFunction from 'lodash/isFunction'; import isObject from 'lodash/isObject'; export const promiseMockData = (res) => { return Promise.resolve([ { data: res, error_code: 0, message: "ok", }, undefined ]); } function pack(... mocks) { const allMocks = mocks.reduce((totalMock, mock) => { let mockAPIs; if (isFunction(mock)) { mockAPIs = mock(); } else if (isObject(mock)) { mockAPIs = mock; } else { throw new Error('mock file require both Function or Object'); } return { ... totalMock, ... mockAPIs } }); ForEach (key => {if (isFunction(allMocks[key])) {allMocks[key] = allMocks[key](); } else { allMocks[key] = promiseMockData(allMocks[key]) } }); return allMocks; } export const allMockData = pack( login )Copy the code

Login is a common business, and you can write mock according to the API. It is very easy to use, because the internal information such as status is encapsulated, and only the data part of the external mock is exposed. If you need to customize the returned API, you can also use functions



const mock = {
    '/api/minilogin': {
        name: 'lemon',
        age: 26,
        phone: 15602281234,
        level: 1,
        token: '123213'
    }
}

export default mock;
Copy the code

Intercept the request for mock mapping

Back in our helpers/api.ts module, if you want to intercept a request, you can treat it as if it were a function or a method. There are many ways to intercept a request.

  • Set the hook, modeled after axios-mock-adaptor, which passes in axios and returns the mock data by intercepting the onGet/onPost hook on request.
  • Middleware, modeled after the chunk-middleware design of Redux’s Dispatch, places Mock data before returning to the request
  • A Decorator decorates the pattern, intercepts the result of a method, replaces it with Mock data and returns it.

Each of the three patterns has its own benefits. This article uses the decorator pattern, so let’s take a look at what the decorator does

import { allMockData } from '.. /mock'; function mockDecorator(target, key, descriptor) { const oldValue = descriptor.value; Mock Descriptor. Value = function (p: {url: string, data? : any, form? Mock if (process.env.node_env === 'development') {return allMockData[p.ull]; } else { return oldValue.apply(this, arguments); } } return descriptor; }Copy the code

Import the mock/index output of the mock data collection to determine the environment to change the output of the original method, but since the decorator can only be used for class classes, we need to modify the HTTP method we designed earlier.

PackPromise <T = any>(p: promise <any>): [T, any] { return p.then(res => [res, undefined]).catch(err => [undefined, err]) as unknown as [T, any]; } class Api { @mockDecorator async Post<T = any>(p: { url: string, data? : any, form? : boolean }): Promise<[FetchRes<T>, any]> { const { url, data = {}, form = false } = p; const [res, err] = await packPromise<FetchRes<T>>(http('POST', url, form ? assign(data, { form }) : data)); return [res, err] } @mockDecorator async Get<T = any>(p: { url: string, params? : any }): Promise<[FetchRes<T>, any]> { const { url, params = {} } = p; const [res, err] = await packPromise<FetchRes<T>>(http('GET', url, params)); return [res, err] } } const api = new Api(); export const { Get, Post } = api;Copy the code

Check out the mock effects.

Write the following business code on the Pages page

// @ts-nocheck import React, { Component } from 'react' import { View } from '@tarojs/components' import './index.less' export interface UserData { name: string code: string date: number avator: string level: number token: string } class Index extends Component { componentDidMount() { this.login() } login = async () => { const [res, err] = await Get<UserData>({ url: '/api/minilogin' }); switch (true) { case !! err: console.log('login err'); break; case !! res: console.log('login success'); console.log(res); break; default: console.log('login not res'); } } render () { return ( <View className='index'> login </View> ) } } export default IndexCopy the code

The console output is as follows

conclusion

After all the work is done, it is so comfortable to localize the mock that you don’t need to rely on other projects. When the background is ready to tune, change the global SERVERURL and environment variables to remove the mock.