小程序开发入门

配置 tabBar

tabBar 相当于路由,最少配置两个,每个配置项会匹配一个 page,配置后会在页面底部生成导航菜单。当点击下方的导航时,会跳转到对应的页面。

"tabBar": {
  "selectedColor": "#d81e06",
  "color": "#515151",
   "list": [
     {
       "pagePath": "pages/playlist/playlist",
       "text": "音乐",
       "iconPath": "images/bottomNavIcon/music.png",
       "selectedIconPath": "images/bottomNavIcon/music-checked.png"
     },{
       "pagePath": "pages/blog/blog",
        "text": "动态",
        "iconPath": "images/bottomNavIcon/release.png",
        "selectedIconPath": "images/bottomNavIcon/release-checked.png"
    }
  ] 
},

配置项中,pagePath 表示对应的 page 路径;text 表示要显示的文案;

  • iconPath 表示没有选中时的图标路径;
  • selectedIconPath 表示选中时的图标路径(选中后,页面会切换)
  • color 为本选中时的文案颜色;
  • selectedColor 选中后的文案颜色;

事件绑定

使用 bind 绑定事件默认会有事件冒泡;而如果使用 catch 绑定事件,则不会冒泡;

原生组件,比如 textareainput,在绑定事件时,不应使用 bind: 的方式绑定,而应使用 bindxxx 的方式绑定(不在中间加 :)。

页面与组件通信

小程序中可以使用自定义事件的方式实现页面与组件通信。组件中使用 this.triggerEvent 调用自定义的事件。

this.triggerEvent('eventName', ...args);

在页面级组件中定义事件函数,并把事件函数传给子组件。

<component bind:myEvent="eventName" />

父组件可以使用 this.selectComponent 获取到子组件的实例,然后就可以调用子组件中的方法。

// 通过类名的方式获取子组件,并调用其 updateTime 方法
this.selectComponent('.curTime').updateTime(new Date());

子组件:

<component class="curTime" />

对于在同一页面中组件与组件之间的通信,可以用页面容器组件为桥接,使用自定义事件或 selectComponent 的方式进行通信。

公共数据

A 页面在 B 页面跳转时,可以在 URL 上传递数据,但如果 B 页面的操作会影响到 A 页面(回退时页面的内容会发生变化)时,如何把 B 页面的数据回传到 A 页面?

可以使用微信小程序提供的 API,在 app.js 文件中,onLaunch 事件中可以定义 globalData 对象,它是存放公共数据的地方。

// app.js

App({
  onLaunch: function () {
    // 存放公共数据
    this.globalData = {
      a: 1
    };
  },
  // 
  setA(id){
    this.globalData.a = id;
  },
  getA(){
    return this.globalData.a;
  }
});

在其他页面或者组件中就可以访问到数据了。

const app = getApp();
console.log(app.getA());

引入样式类

场景:组将想要使用页面的 CSS 样式,比如使用字体图标的 class 类名,默认情况下,在组件中使用了字体图标的 class 类名,图标是是用不了的。要想使用可以这么做:

首先页面组件需要传入对应的 css 类名,例如:

<component iconfont="iconfont" />

上面代码,容器组件中给子组件 component 传入了一个 iconfont 属性,值是某个 css 的类名,也叫 iconfont。子组件要想使用这个类名,可以在自己的脚本中设置:

Component({
  externalClasses: [
    "iconfont"
  ],
  // ...
});

需要注意的是,引入的 class 类名是“只读的”,即在组件的 wxss 中我们不能再使用该类名编辑样式了,这是没有效果的,你应该再起一个类名。

除了使用 externalClasses 之外,还可以给组件设置 styleIsolation 配置项:

Component({
  options: {
    styleIsolation: 'isolated'
  }
});

设置完之后,就可以直接使用页面级组件中的 css 样式了,也不用设置 externalClassesisolated 表示表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响。

如果 styleIsolation 的值设置成 shared 则表示自定义组件 wxss 中指定的样式和页面的样式会相互影响。

获取用户信息

四种方式:

  1. 使用 open-data

例如:

<!-- 展示用户头像 -->
<open-data type="userAvatarUrl"></open-data>
<!-- 展示用户昵称 -->
<open-data type="userNickName"></open-data>
<!-- 展示用户所在城市 -->
<open-data type="userCity"></open-data>

open-data 获取的用户信息只能用作展示,并且只能展示用户自己的信息。这不需要通知用户授权,因为这些信息只有用户自己可见。

  1. 使用 wx.getUserInfo API。

它可以获取到用户的昵称、头像、性别、所在城市等信息。但这种情况下必须是在用户已经授权的情况下,才可以获取到。

wx.getUserInfo({
  success: (res) => {
    console.log();
  }
});
  1. 创建一个按钮,由用户点击,小程序弹出模态框,询问用户是否授权获取用户信息。比如:
<button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">
  获取用户信息
</button>

上面代码中的 button 按钮是特殊的,他有 open-type 属性和 bindgetuserinfo 属性,前者表示这时一个获取用户信息的按钮,后者表示当用户点击后触发的事件函数。事件函数需要我们自己定义,它有一个 event 对象参数。

onGetUserInfo(event){
  console.log(event);
}
  1. 使用云函数获取用户的 openid

前两种方式是获取不到 openid 的。传统模式下,即有后端服务器,可以再小程序端调用 wx.login 从微信服务器中获取 code,然后调用 ex.request 将 code 传递给后端服务器,后端服务器拿到 code 后,需要请求微信服务器获取 openidsession_key,获取到之后就可以把用户标识(openid)发给小程序,并本地存储。

在云开发模式下,用户可以点击按钮,然后触发事件,从云函数中获取到用户信息,在小程序端还可以将用户信息存储到小程序的云数据库中。

在云开发中,小程序自带一个 login 云函数,我们可以让用户点击按钮后,调用云函数获取 openid

<button bindtap="getOpenid">获取openid</button>

会发现,button 只是一个普通的按钮,其实我们也并不需要用户点击,直接调用云函数即可。

getOpenid(){
  wx.cloud.callFunction({
    name: 'login'
  }).then(res => {
    console.log(res);
  });
}

需要注意的是,返回的 res 中会有 openid,如果你想使用 openid 获取用户的个人信息,比如昵称、头像等,目前是做不到的,因为获取 openid 是不需要用户授权的,在用户没有授权的情况下(不知情)就可以获取到用户信息,对于用户信息是不安全的(涉及到用户的隐私)。

返回上一个页面并进行刷新

在 A 页面跳转到 B 页面后,在 B 页面进行了一些操作,然后回退到 A 页面,这时候我们想在回退后刷新 A 页面的内容,如何做到?

这是很常见的场景,比如在 B 页面发布了一些内容,A 页面是展示内容的,我们想在发布后回到 A 页面看到最新的内容。

可以使用 getCurrentPages 这个全局 API,代码如下:

wx.navigateBack();
// 获取到当前页面和上一个页面的对象
const pages = getCurrentPages();
// 取到上一个页面
const prevPage = pages[pages.length - 2];
// onPullDownRefresh 是下拉刷新的事件
// 开启下拉刷新功能需要来到页面的配置文件,添加 `enablePullDownRefresh` 为 `true`。
// 你应在该事件中请求新的数据,更新页面
prevPage.onPullDownRefresh();   // 刷新

图片预览和文件上传

图片预览使用下面的API:

wx.previewImage({
  urls: this.data.images,
  current: imageUrl
});

previewImage 中的 urls 表示要预览的图片列表,可以是超链接或者云文件ID,是图片的,current 表示当前被预览的图片 url 或云文件ID。

要把文件上传到云存储上可以使用 wx.cloud.uploadFile 这个 API,具体可以参考官网:wx.cloud.uploadFile

云调用

云调用是小程序·云开发提供的在云函数中调用微信开放接口的能力。它用于小程序云开发。

比如有时候用户在小程序中发表了评论,发表后应该提醒用户,如何做到这样的消息推送能力呢?

首先新建一个云函数,它的 config.json 配置如下:

{
  "permissions": {
    "openapi": [
      "templateMessage.send"
    ]
  }
}

templateMessage.send 是一个云调用函数,开发者可使用订阅消息功能。

编写的云函数内容如下:

exports.main = async (event, context) => {
  // 拿到用户的 openid
  const { OPENID } = cloud.getWXContext();
  // 云调用,函数名称要与 config.json 中配置的一样
  const result = await cloud.openapi.templateMessage.send({
    touser: OPENID,   // 用户 openid
    // 点击模板卡片后的跳转页面,仅限本小程序内的页面。
    page: `/pages/blogComment/blogComment?blogId=${event.blogId}`,
    // 模板内容,不填则下发空模板。
    data: {
      time1: {
        value: new Date().toLocaleString()
      },
      phrase2: {
        value: '评价完成'
      },
      thing3: {
        value: event.content
      }
    },
    templateId: '所需下发的模板消息的id',
    // 表单提交场景下,为 submit 事件带上的 formId
    formId: event.formId
  });
  return result;
}

templateId 需要在微信公众平台订阅消息 中添加,里面有很多模板。添加好后就会生成一个 模板ID

用户输入了评论内容,并点击了发送,这时候就要把数据存入数据库中,评论成功后给用户发通知。代码如下:

wx.cloud.callFunction({
  // 调用上面的云函数
  name: 'sendMessage',
  // 传入数据
  data: {
    content,
    formId,
    blogId: this.properties.blogId
  }
}).then(res => {
  console.log(res);
}).catch(err => {
  // console.log(err);
});

分享

有时候为了推广我们的小程序,在小程序内容通常会有分享功能,让用户把小程序的内容分享给他的好友。这个如何实现?

小程序中给 button 按钮提供了一个属性:open-type 属性,当值是 share 时,表示点击这个按钮会触发分享事件。

<button open-type="share">分享</button>

用户点击后会触发 Page.onShareAppMessage 事件,即:页面级组件中的 onShareAppMessage,如果按钮在一个普通组件中,那么它所在的页面组件的 onShareAppMessage 会被触发。

{
  onShareAppMessage: function () {
    const blog = this.data.blog;
    return {
      // 分享的标题
      title: blog.content,
      // 用户点击分享的内容时会跳转到哪个页面
      path: `/pages/content?articleId=${blog._id}`
    }
  }
}

用户授权

当用户发表评论时,可能并不知道用户的一些个人信息,我们想要获取到用户的一些信息,这可能需要用户的授权才能获取到,毕竟是用户的隐私信息。

点击一个按钮,弹出用户授权框:

onComment(){
  // 发表评论,如果没有登录,就不能发表评论
  // 点击评论时,判断用户是否已经授权
  this.getUserAuth(res => {
    userInfo = res.userInfo;
    // 用户信息可以拿到,做一些其他的操作,比如把用户评论内容上传到云端
  }, () => {
    // 未授权,就弹出一个模态框告诉用户要授权才能发布评论
    this.setData({
      // 弹出授权对话框
      showModal: true
    });
  });
}

getUserAuth 接收两个回调:已经授权的回调和未被授权的回调。getUserAuth 代码实现如下:

getUserAuth(yes, no){
  wx.getSetting({
    success(res){
      if(res.authSetting['scope.userInfo']){
        wx.getUserInfo({
          success(res){
            yes(res);
          }
        });
      }else{  // 没有就弹出警告
        no();
      }
    }
  });
}

需要注意的是,button 按钮有专门用于获取用户数据的属性:

<button
  open-type="getUserInfo"
  bindgetuserinfo="onGetUserInfo"
>

bindgetuserinfo 是小程序一个内置的事件,它可以这么使用:

{
    methods: {
      onGetUserInfo(event){
        const userInfo = event.detail.userInfo;
        // 如果用户信息能拿到,说明用户点击了对话框中的“确定”
        if(userInfo){
          this.setData({
            modalShow: false,
          });
          this.triggerEvent('loginSuccess', userInfo);
        }else{
          this.triggerEvent('loginFail');
        }
      }
  }
}

服务器端调用云函数

在开发完小程序后,一般还要做一个后台管理系统,如果是云开发模式,如何在后台获取云数据?

我们需要准备这几样东西:

  • app-env 小程序的云开发环境变量,这个在微信开发者工具的 云开发控制台 中可以设置;
  • AppID 在微信开发者工具中的 详情 可以找到,或者在微信公众平台中;
  • AppSecret 密钥,可以在微信公众平台开发 中找到;

流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Td4Z3glK-1601299117702)(./image/小程序登录流程.jpg)]

获取 access_token

它是后台接口调用凭据,调用绝大多数后台接口时都需使用 access_token 如何获取到它可以看官网:access_token

需要注意的是:access_token 的有效期目前为 2 个小时,需定时刷新,获取新的 access_token

比如下面的服务器端代码,定时获取 access_token,把数据保存到本地的 json 文件中。

const URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APP_ID}&secret=${APP_SECRET}`;
const TOCKEN_PATH = path.join(__dirname, './access_token.json');
const TIMER_DELAY = (7200 - 300) * 1000;    // 定时器更新时间间隔

// 更新token
async function updateAccessToken(){
    const json = await rp(URL);
    const result = JSON.parse(json);
    if(result.access_token){
        fs.writeFileSync(TOCKEN_PATH, JSON.stringify({
            access_token: result.access_token,
            createTime: new Date()
        }));
    }else{
        await updateAccessToken();
    }
}

// 获取token
async function getAccessToken(){
    let hasFile = fs.existsSync(TOCKEN_PATH);
    if(hasFile){
        const json = fs.readFileSync(TOCKEN_PATH, { encoding: 'utf8' });
        const result = JSON.parse(json);
        // 对比时间差
        const createTime = new Date(result.createTime).getTime();
        const nowTime = new Date().getTime();
        // 时间差是不是超过两个小时了
        const value = (nowTime - createTime) / 1000 / 60 / 60;
        if(value >= 2){
            await updateAccessToken();
            await getAccessToken();
        }

        return result.access_token;
    }else{
        await updateAccessToken();
        await getAccessToken();
    }
}

// 定时触发更新 token
setInterval(async () => {
  await updateAccessToken();
}, TIMER_DELAY);

module.exports = {
  getAccessToken,
  updateAccessToken
};

使用云函数

导出一个访问云数据库的函数:

// callCloudFn.js
const callCloudFn = async (ctx, fnName, params) => {
    const ACCESS_TOKEN = await getAccessToken();
    const APP_ENV = ctx.state.env;
    const URL = `https://api.weixin.qq.com/tcb/invokecloudfunction`;
    const options = {
        method: 'POST',
        uri: URL,
        // 查询参数,需要带上访问凭据、小程序的云开发环境变量和云函数名称
        qs: {
            access_token: ACCESS_TOKEN,
            env: APP_ENV,
            name: fnName
        },
        body: {
            // params 会传递到云函数的 event 参数中
            ...params
        },
        json: true
    };
    return await rp(options).then(res => {
        return res;
    }).catch(err => {
        // ...
    });
}

module.exports = callCloudFn;

服务器端路由书写如下:

const Router = require('koa-router');
const callCloudFn = require('../utils/callCloudFn');
const router = new Router();
const CLOUD_FUNC_NAME = 'music';      // 云函数名称

router.get('/list', async (ctx, next) => {
    const query = ctx.request.query;
    // 传递给云函数的数据
    const params = {
      start: parseInt(query.start),
      count: parseInt(query.count),
      $url: 'playlist'
    };
    const result = await callCloudFn(ctx, CLOUD_FUNC_NAME, params);
    ctx.body = {
      data: JSON.parse(result.resp_data).data,
      code: 20000
    };
});

使用云数据库

除了上面的方式之外,还可以使用数据库的方式查询数据库,首先导出一个访问数据库的公共函数:

// callCloudDB.js
const callCloudDB = async (ctx, fnName, query = {}) => {
    const access_token = await getAccessToken();
    const APP_ENV = ctx.state.env;
    // fnName 表示数据库的查询方式,它可以有以下几个值:
    // databasequery: 数据库查询记录
    // databaseupdate: 数据库更新记录
    // databasedelete: 数据库删除记录
    // databaseadd: 数据库插入记录
    const URL = `https://api.weixin.qq.com/tcb/${fnName}?access_token=${access_token}`;
    const options = {
        method: 'POST',
        uri: URL,
        body: {
            // query 是数据库查询语句
            query,
            env: APP_ENV,
        },
        json: true
    };
    return await rp(options).then(res => {
        return res;
    });
}

module.exports = callCloudDB;

关于数据库的更多操作可以参考官方文档:数据库

调用如下:

router.get('/getById', async function fn(ctx) {
    // 定义查询语句
    const query = `db.collection('playlist').doc('${ctx.request.query.id}').get()`;
    const res = await callCloudDB(ctx, 'databasequery', query);
    ctx.body = {
      code: 20000,
      data: JSON.parse(res.data)
    };
});

使用云存储

小程序云开发中还可以使用云存储,存储一些上传的文件。服务器也可以操作云存储,比如上传文件、下载文件和删除文件。具体可以参考官方文档:云存储

小程序开发与网页开发对比

网页开发小程序开发
结构HTMLWXML
样式CSSWXSS
逻辑JavaScriptJavaScript
DOM操作DOM API
渲染层和逻辑层互斥的分开的

小程序运行环境

运行环境逻辑层渲染层
iOSJavaScriptCoreWKWebView
安卓V8定制内核
小程序开发者工具NW.jsChromiun WebView

渲染页面的技术选型

  • 纯客户端原生技术(Java、Object-C);
  • 纯 web 技术(HTML/CSS、JavaScript);
  • 用客户端原生技术与 web 技术结合的混合技术(Hybrid),这也是小程序开发的技术选型;

小程序性能优化

  • 在用户体验方面,合理设置可点击元素的响应区域大小;

  • 避免渲染页面耗时过长;

  • 避免执行脚本耗时过长;

  • 对网络请求做必要的缓存以避免多余的请求;

  • 不要引入未被使用的 wxss 样式;

  • 文字颜色与背景色搭配较好,适宜的颜色对比更方便用户阅读;

  • 所有资源请求建议使用 HTTPS;

  • 不使用废弃的小程序接口;

  • 避免过大的 WXML 节点数量:

    • 一个页面少于 1000 个 WXML
    • 节点树深度少于 30 层
    • 子节点数不大于 60 个
  • 避免将不可能访问到的页面打包到小程序包里;

  • 及时回收定时器;

  • 避免使用 :active 伪类来实现点击态(可以使用小程序提供的 hoverclass-hover);

  • 在滚动区域可以开启惯性滚动以增强体验:

    • iOS 上:-webkit-overflow-scrolling: touch
  • 避免出现任何 JavaScript 异常;

  • 所有请求应响应正常;

  • 所有请求的耗时不应太久;

  • 避免短时间内发送太多的图片请求,使用字体图标代替图片;

  • 避免在短时间内发起太多的请求;

  • 避免 setData 的数据过大;

  • 避免 setData 的调用过去频繁;

  • 避免将未绑定在 WXML 的变量传入 setData,你可以在 Page 之外定义变量;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值