小程序开发入门

配置 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">获取openidbutton>

会发现,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)
    };
});

使用云存储

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

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

网页开发 小程序开发
结构 HTML WXML
样式 CSS WXSS
逻辑 JavaScript JavaScript
DOM操作 DOM API
渲染层和逻辑层 互斥的 分开的

小程序运行环境

运行环境 逻辑层 渲染层
iOS JavaScriptCore WKWebView
安卓 V8 定制内核
小程序开发者工具 NW.js Chromiun 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 之外定义变量;

你可能感兴趣的:(小程序,前端,小程序)