7

让微信小程序开发如鱼得水

 3 years ago
source link: https://segmentfault.com/a/1190000038917928
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

qeuM32Y.jpg!mobile

关于微信小程序开发一直想写一篇相关的文章总结和记录下,结果拖延症犯了迟迟没有下笔;这不最近天气不错,于是找一个空闲的下午将这篇文章输出下(好像跟天气没啥关系:sweat:),那我们就开始吧!

注意: 本文默认开发者对微信小程序开发有一定语法基础。 小程序开发文档

微信小程序小结

在接触的微信小程序开发过程中,不难发现微信小程序为了方便开发人员入手对很多底层api进行了很好的封装,比如针对接口请求的 wx.request() ,针对路由跳转和页面导航的 wx.switchTab、wx.navigateTo··· 等。虽然在一定程度上简化了开发,但是对于项目工程的系统化构建还是不够的,因此本人在对比以前基于Vue开发项目的经验和自身的开发习惯,总结出如下3点可供参考:

  • 1、全局变量和配置信息统一管理;
  • 2、封装路由守卫相关api: vue-routerrouter.beforeEach()router.afterEach() 真的香;
  • 3、接口请求公共信息进一步提取封装;
  • 4、封装接口的请求和响应拦截api: axiosaxios.interceptors.request.use()axios.interceptors.response.use() 用过的都说好;

从上述四点出发,对微信小程序初始化工程进行规范优化,能够很大程度提高开发效率和进行项目维护管理。封装的好处不只体现在调用的方便上,也体现在管理的方便上,同时,公共操作集中处理,很大程度减少繁杂重复代码。

一、项目初始化

新建微信小程序项目,在项目下新建如下目录和文件:

  • config文件夹:统一管理可配置的信息和变量;

    错误码
    全局变量
    
  • pages文件夹:小程序页面文件管理文件夹(每个页面一个子文件夹目录);
  • router文件夹:路由管理文件件;

    5种路由导航
    拦截
    
  • servers文件件:接口请求服务管理文件夹;

    • apis文件夹:request请求封装管理和接口api配置管理文件夹;

      • request.js:对 wx.requestPromise 封装;
      • xxx.js:对应模块的接口管理文件;
    • requestFilter.js:接口 请求和响应拦截 封装文件;
  • 其他都是初始化默认文件;

UfYRBzi.png!mobile

二、路由跳转和路由守卫封装

1、路由跳转封装

微信小程序官方文档为开发者提供了5种路由跳转的api,每一种都有其特殊的用法:

3mE3Ev.png!mobile

根据其用法,我们对路由api进行如下封装:微信小程序路由跳转最后对应 push、replace、pop、relaunch、switchTabroutes 对应routeConfig.js中路由路径的配置; routerFilter 对应routerFilter.js文件,对路由跳转之前的逻辑进行处理;

routeConfig.js(每次新增页面后需要手动添加):

export const routes = 
  {
    INDEX: "/pages/index/index",
    TEST: "/pages/test/test",
  }
export default {...routes};

routerFilter.js:

export default () => {
  ···
  //路由跳转前逻辑处理
}

router.js(routerFilter负责路由跳转前公共操作处理,在success和fail中对路由跳转后的公共操作进行处理):

import routes from "../router/routerConfig";
import routerFilter from "./routerFilter"

/**
 * 对wx.navigateTo的封装
 * @param {路由} path 
 * @param {参数} params 
 * @param {事件} events 
 */
const push = (path, params, events) => {
  routerFilter()
  wx.navigateTo({
    url: routes[path] + `?query=${JSON.stringify(params)}`,
    events: events,
    success(res) {
      console.log(res);
    },
    fail(err) {
      console.log(err);
    }
  })
}

/**
 * 对wx.redirectTo的封装
 * @param {路由} path 
 * @param {参数} params 
 */
const replace = (path, params) => {
  routerFilter()
  wx.redirectTo({
    url: routes[path] + `?query=${JSON.stringify(params)}`,
    success(res) {
      console.log(res);
    },
    fail(err) {
      console.log(err);
    }
  })

}

/**
 * 对wx.navigateBack的封装
 * @param {返回的层级} number 
 */
const pop = (number) => {
  routerFilter()
  wx.navigateBack({
    delta: number,
    success(res) {
      console.log(res);
    },
    fail(err) {
      console.log(err);
    }
  })
}

/**
 * 对wx.reLaunch的封装
 * @param {路由} path 
 * @param {参数} params 
 */
const relaunch = (path, params) => {
  routerFilter()
  wx.reLaunch({
    url: routes[path] + `?query=${JSON.stringify(params)}`,
    success(res) {
      console.log(res);
    },
    fail(err) {
      console.log(err);
    }
  })
}

/**
 * 对tabbar的封装
 * @param {路由} path 
 */
const switchTab = (path) => {
  routerFilter()
  wx.switchTab({
    url: routes[path],
    success(res) {
      console.log(res);
    },
    fail(err) {
      console.log(err);
    }
  })
}

module.exports = {
  push,
  replace,
  pop,
  relaunch,
  switchTab
}

2、全局注册和使用

app.js 中对封装的路由api进行全局注册:

import router  from "./router/router.js"
//全局注册
wx.router = router

在页面逻辑中使用:

//index页面跳转test页面 
gotoTest(){
   wx.router.push("TEST")
}

三、接口请求Promise封装

对于同一个项目而言,微信小程序api wx.request() 中很多参数都是相同的,如果直接使用,需要将这些重复参数一遍又一遍的copy,虽然copy很简单,但是当有一个参数改变了需要找到所有接口一个一个修改,维护起来费劲,再者看着也难受呀;

a2qmMrB.png!mobile

借鉴 axios 对请求的封装,将 wx.request() 封装为 Promise 形式岂不美哉:

request.js:

import formatError from "../requestFilter"
const app = getApp()

/**
 * 接口请求封装
 * @param {请求方式} method 
 * @param {请求的url} url 
 * @param {请求传递的数据} data 
 */
const request = (method, url, data) => {
  //设置请求头
  const header = {
    ···
  }
  //promise封装一层,使得调用的时候直接用then和catch接收
  return new Promise((resolve, reject) => {
    wx.request({
      method: method,
      url: app.globalData.host + url, //完整的host
      data: data,
      header: header,
      success(res) {
        //对成功返回的请求进行数据管理和统一逻辑操作
        ···
        resolve(res.data)
      },
      fail(err) {
        wx.showToast({
          title: '网络异常,稍后再试!',
          mask: true,
          icon: 'none',
          duration: 3000
        })
      }
    })
  })
}
export default request;

具体使用

以user.js为例:

import request from "./request";

// 获取用户openid
export const usrInfos = data => request("POST", "/user/usrInfos", data);

index页面调用:

//index.js
//获取应用实例
const app = getApp()
import { usrInfos } from "../../servers/apis/user"

Page({
  onLoad: function () {
    //获取用户信息
    usrInfos({
      uid: "xxxx"
    })
      .then(res => {
        console.log(res)
      })
      .catch(err => {
        console.log(err)
      })
  }
})

四、接口的请求和响应拦截封装

axiosaxios.interceptors.request.use()axios.interceptors.response.use() 分别对应接口请求前的拦截处理和数据响应后的拦截处理;根据这个原理我们对微信小程序的响应也做拦截封装,对接口请求返回错误进行统一管理输出:

request.js

import formatError from "../requestFilter"
const app = getApp()
···
const request = (method, url, data) => {
  ···
  return new Promise((resolve, reject) => {
    wx.request({
      ···
      success(res) {
        //对成功返回的请求进行数据管理和统一逻辑操作
        if(res.statusCode === 200){ //请求返回成功
          if(res.data && res.data.code === "SUCCESS"){ //后端对接口请求处理成功,返回数据给接口调用处
            resolve(res.data)  //then接收
          }else{        //后端对也请求判断后认为不合逻辑报错
            formatError(res)   //统一的报错处理逻辑
            reject(res.data)     //catch接收
          } 
        }else{
          reject(res.data)        //catch接收
        }
      },
      fail(err) {        //请求不通报错
        wx.showToast({
          title: '网络异常,稍后再试!',
          mask: true,
          icon: 'none',
          duration: 3000
        })
      }
    })
  })
}
export default request;

requestFilter.js

requestFilter.js中可以做很多对报错的处理,这里用一个简单的toast处理示范下:

/**
 * 对接口返回的后端错误进行格式转化
 * @param {接口成功返回的数据} res 
 */
const formatError = (err =>{
  wx.showToast({
    title: err.message,
    mask: false,
    icon: 'none',
    duration: 3000
  })
}

export default formatError;

对报错进行统一处理需要明确数据规:

  • 制定统一的报错码管理规范;
  • 制定前后端统一的接口请求数据返回格式;

五、全局数据管理

对于数据的管理在小项目的开发中显得不那么重要,但是随着项目越来越大,数据越来越多,一个很好的数据管理方案能够有效地避免很多bug,这也是vuex能够在vue生态中占有一席之地的原因。秉承着合理管理数据的原则,对于该封装的数据坚决封装,对于该分模块管理的配置坚决分块管理:

globalData.js

微信小程序中全局的数据管理放在 app.jsglobalData 属性中,当数据太多或者app.js逻辑太复杂时,将全局数据提取出来单独管理的确是个好方案:

export default {
  ···
  host: "http://www.wawow.xyz/api/test", //接口请求的域名和接口前缀 
  hasConfirm: "" //是否已经有了confirm实例
  currentPage: ""
  ···
}

keys.js

keys.js属于个人开发中的习惯操作,将项目中可能用到的一些常量名称在此集中管理起来,十分方便调用和修改维护:

export default {
  ···
  TOKEN: "token",
  STORAGEITEM: "test"
  ···
}

全局引用和注册

引入app.js:

import router  from "./router/router.js"
import keys from "./config/keys"
import globalData from "./config/globalData"
//全局注册
wx.router = router
wx.$KEYS = keys

//app.js
App({
  //监听小程序初始化
  onLaunch(options) {
    //获取小程序初始进入的页面信息
    let launchInfos = wx.getLaunchOptionsSync()
    //将当前页面路由存入全局的数据管理中
    this.globalData.currentPage = launchInfos.path
  },
  ···
  //全局数据存储
  globalData: globalData
})

使用

在页面代码逻辑中可以通过 app.globalData.hostwx.$KEYS.TOKEN 方式进行调用;

六、总结

上述关于微信小程序开发的几个方面都是在实践中学习和总结的,技术层面的实现其实很容易,但是个人觉得开发规范项目工程构建才是一个项目的重要基础;完善的规范能够有效的提高开发效率和开发者之间非必要的 扯皮 !合理的项目工程构建能够优化开发逻辑,提高代码逻辑易读性,减少后期项目的管理时间,同时给予项目更大的扩展性。

有需要源码的可以关注微信公众号 哇喔WEB 回复 "wxmp"获取;

欢迎大家讨论留言、进行补充!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK