开始

对于一个使用过Vue.js的前端来说,小程序和vue的语法很像,难道不大,增加了一些基于支付宝的内置功能,简单来说,支付宝就是一个浏览器,小程序是支付宝的Html而已.。

Tips, 开发工具编译经常不及时,故意写错代码,比如写错标签不闭合,或者乱写,就马上有效果 !!!

小程序的使用

小程序无需安装,用户第一次使用小程序时,支付宝 App 会从服务器下载小程序的资源,下载后的小程序资源会缓存在支付宝的客户端一段时间

当用户再次打开已经缓存资源的小程序时,会跳过下载过程,能够更快地打开小程序

当用户首次打开小程序时候,小程序会处于前台运行状态

用户点击右上角关闭按钮关闭小程序,或者按下设备 Home 键离开支付宝 App 时,小程序并不会直接销毁,而是进入后台运行状态

从后台运行切换为前台运行: 当未被系统销毁的小程序再度被打开或者激活时,会从后台运行切换为前台运行

用户点击右上角关闭按钮关闭小程序时,小程序仅是进入后台运行,不会被销毁。只有当小程序进入后台运行状态一定时间,或者占用系统资源过高时,才会被真正销毁

核心的业务能力

支付收单、营销服务、会员管理 、芝麻信用、位置服务、供应链 、资金管理、金融服务,如蚂蚁借呗



小程序启动和入口



小程序启动方式

冷启动: 当用户打开未启动过,或者是已经销毁的小程序时,称为冷启动。此时小程序会执行初始化,初始化完成后,会触发 onLaunch 回调函数。

热启动: 当用户打开已经关闭但仍处于后台运行的小程序时,称为热启动。在这种情况下,小程序并不会被销毁后重启,而仅是从后台切换到前台,此时,onShow 函数会触发,onLaunch 回调函数不会被触发。

小程序的入口

  1. 扫一扫
  2. 搜索
  3. 朋友tab页
  4. 支付成功页
  5. 小程序收藏
  6. 生活号关联
  7. 卡包

小程序入口的场景值

在相关生命周期里,可以获取到时怎么进入小程序的,详细的数据

App({
  onLaunch(options) {
    console.log('App onLaunch Scene:', options.scene);//options.scene 是 String 类型的 
  },
  onShow(options) {
    console.log('App onShow Scene:', options.scene);
  },
})

代码层面

可以使用npm包,app.acss 作为全局样式,作用于当前小程序的所有页面

// 背景色渐变
background-image: linear-gradient(90deg, rgb(5, 131, 68) 0%, #3264C5 99%);

// getApp方法获取顶层app实例
var app = getApp();
console.log(app.globalData); // 获取 globalData

app.js

App() 代表顶层应用,管理所有页面和全局数据,以及提供生命周期回调等

App({
// 比如 alipays://platformapi/startapp?appId=1999&query=number%3D1&page=x%2Fy%2Fz 打开小程序 
  onLaunch(options) {
    // 第一次打开
    console.log(options.query);
    // {number:1}
    console.log(options.path);
    // x/y/z
  },
  onShow(options) {
    // 小程序启动,或从后台被重新打开
  },
  onHide() {
    // 小程序从前台进入后台
  },
  onError(msg) {
    // 小程序发生脚本错误或 API 调用出现报错
    console.log(msg);
  },
  globalData: {
  // 全局数据
    name: 'alipay',
  },
});

app.json

整个应用的配置

{
  // 配置页面
  "pages": [
    "pages/index/index"
  ],
   // 配置插件
  "plugins": {
    "myPlugin": {
      "version": "*",
      "provider": "2019120769656826"
    }
  },
  "window": {
    "transparentTitle": "always", // 导航栏透明设置 默认 none,支持 always 一直透明 / auto 滑动自适应 / none 不透明。 
    "titlePenetrate": true,
    "backgroundImageColor": "#3264C5",
    "defaultTitle": "defaultTitle", // 默认标题
    "allowsBounceVertical":"NO", // 允许下拉。默认"yes"
    "pullRefresh" : true, // 支持下拉刷新吗,默认 true,需要允许下拉才可以
    "titlePenetrate": "YES" // 是否允许导航栏点击穿透。默认 NO,支持 YES / NO 
    "titleImage":  // 导航栏图片地址 ,
    "titleBarColor": "rgba(0,0,0,0.1)"  // 导航栏背景色
  },
  // 配置底部导航tabs
  "tabBar": {
    "textColor": "#111",
    "selectedColor": "blue",
    "backgroundColor": "#ffffff",
    "items": [
      {
        "pagePath": "pages/index/index",
        "name": "首页",
        "icon" : "", // 小图标
        "activeIcon" : "",
      },
      {
        "pagePath": "pages/logs/logs",
        "name": "日志"
      }
    ]
  }
}

page.json

在 /pages 目录中的 .json 文件用于配置当前页面的窗口表现。页面配置比 app.json 全局配置简单得多,只能设置 window 相关配置项,但无需写 window 这个键

// css 
// page页面 page元素声明整个页面的样式
page {
  background-color: #fff;
}

// json 
// 配置 optionMenu  点击后触发 onOptionMenuClick 
{
  "optionMenu": {
    "icon": "https://img.alicdn.com/tps/i3/T1OjaVFl4dXXa.JOZB-114-114.png"
  },
  "titlePenetrate": "YES",
  "barButtonTheme": "light"
}

page.js

page.js是每个页面的逻辑

// pages/index/index.js
Page({
  // 和 vue一样,是对象时,所有页面公用,用函数保证每个页面数据独立
  // this.data无法修改数据,this.setData修改
  data: (){
	  return {
	    title: "Alipay",
	  },
   }
  
  // 页面初始化时触发。一个页面只会调用一次。
  // query 为 my.navigateTo 和 my.redirectTo 中传递的 query 对象。
  // query 内容格式为:“参数名=参数值&参数名=参数值…”。
  onLoad(query) {
    // 页面加载
  },
  onShow() {
    // 页面显示
  },
 
  // onReady === didMount 
  onReady() {
    // 页面加载完成
  },
  onHide() {
    // 页面隐藏
    // 页面隐藏/切入后台时触发。 如 my.navigateTo 到其他页面或底部 tab 切换等。 
  },
  onUnload() {
    // 页面被关闭
    // 页面卸载时触发。 如 my.redirectTo 或 my.navigateBack 到其他页面等。 
  },
  onTitleClick() {
    // 标题被点击
  },
  onPullDownRefresh() {
    // 页面被下拉
  },
  onTabItemTap(){
   // 点击tabItem时触发
  }
  onPageScroll({scrollTop}){
  },
  onReachBottom() {
    // 页面被拉到底部
  },
  onShareAppMessage() {
   // 返回自定义分享信息
  },
  // 事件处理函数对象
  events: {
    onBack() {
      console.log('onBack');
    },
  },
  // 自定义事件处理函数
  viewTap() {
    this.setData({
      text: 'Set data for update.',
    });
  },
  // 自定义事件处理函数
  go() {
    // 带参数的跳转,从 page/ui/index 的 onLoad 函数的 query 中读取 type
    my.navigateTo({url:'/page/ui/index?type=mini'});
  },
  // 自定义数据对象
  customData: {
    name: 'alipay',
  },
});

page.axml

使用include 直接引入页面

// html 条件渲染 
<view a:if="{{length > 5}}"> 1 </view>
<view a:elif="{{length > 2}}"> 2 </view>
<view a:else> 3 </view>

<!-- index.axml -->
<include src="./header.axml"/>
<view> body </view>
<include src="./footer.axml"/>

<!-- header.axml -->
<view> header </view>
<!-- footer.axml -->
<view> footer </view>

使用import引入模板

<!-- item.axml -->
<template name="item">
  <text>{{text}}</text>
</template>

<import src="./item.axml"/>
<template is="item" data="{{text: 'forbar'}}"/>

页面跳转

// 1. 类似A链接的方式
<view class="page">
  <view class="page-description">导航栏</view>
  <navigator open-type="navigate" url="./navigate" hover-class="navigator-hover">跳转到新页面</navigator>
  <navigator open-type="redirect" url="./redirect" hover-class="navigator-hover">在当前页打开</navigator>
  <navigator open-type="switchTab" url="/page/API/index/index" hover-class="navigator-hover">跳转到另外一个 Tab - API</navigator>
  <navigator open-type="reLaunch" url="/page/component/index" hover-class="navigator-hover">重新打开</navigator>
  <navigator open-type="navigateBack" hover-class="navigator-hover">返回上一页面</navigator>
</view>

// 2. js跳转
 my.navigateTo({ url: './back' })
 my.redirectTo({ url: './back' })

acss

rpx(responsive pixel)可以根据屏幕宽度进行自适应,规定屏幕宽为 750rpx。以 Apple iPhone6 为例,屏幕宽度为 375px,共有 750 个物理像素,则 750 rpx = 375 px = 750 物理像素,1rpx = 0.5 px = 1 物理像素。

右上角的分享

Page ({

  data: {
   canIUseShareButton: true
  },
  // 用户点击分享按钮的时候会调用此事件需要返回一个对象(Object)类型,用于自定义分享内容
  onShareAppMessage () {
  return {
    title : '分享 View 组件' ,
    desc : 'View 组件很通用' ,
    path : 'page/component/view/view' ,
        };
      },
 });

event 事件

点击事件,如onTap,on开头就是想上冒泡,catchTap就是不冒泡

常用事件有,tap,longTap(长按,0.5s),touchStart,touchEnd,touchCancel,touchMove

event对象有 type,timeStamp,target,属性,自定义的数据,要 data-小写字母来传值,如data-ha-sa,取值的时候,会转成驼峰,haSa

不同的事件,如tap和touch的event不一样

缓存

set, get, clear, remove 4个方法
大小最多是10M
同步和异步两种方法
不建议使用cookie和session

my.setStorage('name','huahua')
my.setStorageAsync('name','huahua')

自定义组件(简直的vue的一样)

小程序基础库从 1.7.0 版本开始支持自定义组件功能。
通过 my.canIUse('component') 判断自定义组件功能是否在当前版本使用;

从 1.14.0 版本开始,自定义组件支持 component2 相关功能,通过调用 my.canIUse('component2') 可判断新自定义组件功能是否可在当前版本使用, component2 相关功能如下:新增 onInit、deriveDataFromProps 生命周期函数 ,支持使用 ref 获取自定义组件实例 。

[componentName].json 文件

{
  "component": true, // 必选,自定义组件的值必须是 true
  "usingComponents": {
    "other":"../other/index" // 依赖的组件
  }
}

组件的样式

组件的样式,如果写了page样式,是代表他使用者页面的样式,而不是他的样式


右键新建一个组件即可,使用时,在当前页面的json中进入组价声明即可

{
  "usingComponents": {
    "my-component":"/components/index/index"
  }
}

slot 插槽

// demo 组件
<view>
  this is demo component
  <slot>
    <view>this is default slot</view>
  </slot>
  haha
   <slot name="header"/>
</view>

// 使用自己的内容替换默认插槽
<demo>
  <view>this is my slot</view>
  <view slot="header">header</view>
</demo>

组件获取参数,通过在js文件中的data和props参数,可以使用组件自己的数据和外部传递的数据

// /components/index/index.js
Component({
  data: {
    x: 1,
  },
  props: {
    y: '',
  },
});

<!-- /components/index/index.axml -->
<view>component data: {{x}}</view>
<view>page data: {{y}}</view>

自定义组件声明周期

Component({
  data: {
    o: {
      value: "scope-value"
    }
  },
  onInit() { // 组件创建时触发
    console.log("i1 onInit", this.props, this.data);
  },
  deriveDataFromProps(nextProps) { // 组件创建时触发或更新时触发
    console.log("i1 deriveDataFromProps", nextProps, this.props, this.data);
  },
  didMount() { // 组件创建完毕时触发
    console.log("i1 didMount", this.props, this.data);
  },
  didUpdate(prevProps, prevData) { // 组件更新完毕时触发
    console.log("i1 didUpdate", prevProps, prevData, this.props, this.data);
  },
  didUnmount() { // 组件删除时触发
    console.log("i1 didUnmount");
  },
  methods: {
    change() {
      this.setData({ 'o.value': "changed-scope-value" });
    }
  }
});

mixin

用来封装组件的多个公用逻辑

// /mixins/lifecycle.js
export default {
  onInit(){
    console.log('init');
  }, 
  deriveDataFromProps(nextProps){},
  didMount(){},
  didUpdate(prevProps,prevData){},
  didUnmount(){},
};

// /components/index/index.js
import lifecycle from '/mixins/lifecycle';

const initialState = {
  data: {
    isLogin: false,
  },
};

const defaultProps = {
  props: {
    age: 30,
  },
};

const methods = {
  methods: {
  	onTapHandler() {},
  },
}

Component({
  mixins: [
    lifecycle,
    initialState,
    defaultProps,
    methods
  ],
  data: {
    name: 'alipay',
  },
});

ref

ref可以获取组件的实例,获取到组件实例之后,可以直接操作组件

// /pages/index/index.js
Page({
  plus() {
    this.counter.plus();
  },
  // saveRef 方法的参数 ref 为自定义组件实例,运行时由框架传递给 saveRef
  saveRef(ref) {
	// 存储自定义组件实例,方便以后调用
    this.counter = ref;
  },
});
<!-- /pages/index/index.axml -->
<my-component ref="saveRef" />
<button onTap="plus">+</button>

// 定义组件时。component2,可以指定ref返回的值,不是默认的组件的this
Component({
  ref() {
    return { key: 'value' }
  }
})

内置标签

<view> 约等于 div
scroll-view 约等于 div里边加了 scroll-auto
swiper 内置轮播图
文本要用text组件
表单form和html差不多,有原生picker支持
地图 map,内置高德地图
可以使用canvas画布

API

  • my.SDKVersion 查看当前基础库版本号,可以在控台中心设置小程序需要库的最低版本号
  • my.hideKeyboard 隐藏输入键盘
  • my.startPullDownRefresh 主动触发下拉刷新,my.stopPullDownRefresh 主动结束下拉刷新,onPullDownRefresh 监听下拉刷新

通讯录联系人

// 获取本地通讯录
 choosePhoneContact() {
    my.choosePhoneContact({
      success: (res) => {
        my.alert({
          content: `姓名:${res.name} 电话:${res.mobile}`
        });
      },
      fail: (res) => {
        my.alert({
          content: 'choosePhoneContact response: ' + JSON.stringify(res),
        });
      },
    });
  },
  // 获取支付宝通讯录
  chooseAlipayContact() {
    my.chooseAlipayContact({
      count: 1,
      success: (res) => {
        my.alert({
          content: `真实姓名:${res.contacts[0].realName} 邮箱:${res.contacts[0].email} 电话:${res.contacts[0].mobile}`
        });
      },
      fail: (res) => {
        my.alert({
          content: 'chooseAlipayContact response: ' + JSON.stringify(res)
        });
      },
    });
  },
  // 获取联系人
  chooseContact() {
    my.chooseContact({
      chooseType: 'multi', // 多选模式
      includeMe: true,     // 包含自己
      includeMobileContactMode: 'known',//仅包含双向手机通讯录联系人,也即双方手机通讯录都存有对方号码的联系人
      multiChooseMax: 1,  // 最多能选择1个联系人
      multiChooseMaxTips: '超过选择的最大人数了',
      success: (res) => {
        my.alert({
          content: `真实姓名:${res.contactsDicArray[0].realName} 展示姓名:${res.contactsDicArray[0].displayName} 电话:${res.contactsDicArray[0].mobile}`
        });
      },
      fail: (res) => {
        my.alert({
          content: 'chooseContact : ' + JSON.stringify(res)
        });
      },
    });
  },
  // 添加一条记录到通讯录
  addPhoneContact() {
    if (my.canIUse('addPhoneContact')) {
      my.addPhoneContact(this.data);
    } else {
      my.alert({ 
        title: '客户端版本过低',
        content: 'my.addPhoneContact() 需要 10.1.32 及以上版本'
      });
    }
  }

日期选择

datePicker() {
    my.datePicker({
      currentDate: '2016-10-10',
      startDate: '2016-10-9',
      endDate: '2017-10-9',
      success: (res) => {
        my.alert({
          content: '您选择的日期为: ' + res.date
        });
      },
    });
  },
    datePickerHMS() {
    my.datePicker({
      format: 'HH:mm:ss',
      currentDate: '12:12:12',
      startDate: '11:11:11',
      endDate: '13:13:13',
      success: (res) => {
        my.alert({
          content: '您选择的日期为: ' + res.date
        });
      },
    });
  },

可用node包

优化建议

  • 将数据请求提前到 onLoad
  • 体积大时,拆子包

分包加载

  • 支付宝小程序从客户端 10.1.60 版本开始支持分包加载功能
  • 开发者配置 subPackages 后,服务端将按 subPackages 配置的路径进行打包,
  • subPackages 配置路径外的目录将被默认打包到主包中。
  • 启动页面和 tabBar 的所有页面都必须放在主包中。
  • 每个分包的根目录不能是另外一个分包内的子目录。
  • 分包之间不能相互引用对方包中的资源(比如图片和 js 脚本等),分包可以引用主包和自己包内的资源。
  • 分包和主包是分别独立打包的,同一个js模块会在主包和分包中分别存在
  • 使用preloadRule,进行分包预下载
{
  "pages": ["pages/index"],
  "subPackages": [
    {
      "root": "sub1",
      "pages": ["page1"]
    },
    {
      "root": "sub2",
      "pages": ["page2"]
    },
    {
      "root": "sub3",
      "pages": ["page3"]
    },
    {
      "root": "path/sub4",
      "pages": ["page4"]
    }
  ],
  "preloadRule": {
    "pages/index": {
      "network": "all",
      "packages": ["sub1"]
    },
    "sub1/page1": {
      "packages": ["sub2", "sub3"]
    },
    "sub3/page3": {
      "network": "wifi",
      "packages": ["path/sub4"]
    }
  }
}