微信小程序開發(fā)筆記

0、雜記

0.1、在實際的開發(fā)中,圖片資源不會存儲在小程序的目錄中,因為小程序的大小不能超過1MB(現(xiàn)在改為2M)。超過則無法真機運行和發(fā)布項目。我們應該將圖片都存放在服務器上,讓小程序通過網(wǎng)絡來加載圖片資源。
0.2、在wxss中,本地資源是無法使用的,比如:background-image,如果使用本地的圖片是無法顯示的,可以使用網(wǎng)絡圖片來代替本地圖片,同時要加上background-size屬性,屬性值的單位為rpx。
0.3、小程序自適應尺寸單位rpx,在微信小程序中,尺寸單位可以使用px,也可以使用rpx,使用rpx可以使組件自適應屏幕的高度和寬度,但是使用px則不會。(官網(wǎng):規(guī)定屏幕寬為750rpx。如在 iPhone6 上,屏幕寬度為375px,共有750個物理像素,則750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素)。在開發(fā)過程中,到底使用rpx還是px?主要取決于業(yè)務需求,也就是需要元素隨著移動設備尺寸的變化而變化,還是讓元素始終保持不變,所以需要具體問題,具體分析。比如:一個元素的width、heigth、margin、 font-size,在很多的時候,需要隨著設備的尺寸不同,而動態(tài)發(fā)生變化,從而保持頁面元素之間的布局可以保持在一定的比例關系,在這種情況下就應該使用rpx,所以rpx作為小程序的自適應單位,它非常適合來控制圖片的寬和高以及元素之間的間距。但是border相關屬性不需要隨著移動設備的尺寸變化而動態(tài)發(fā)生變化,因為,如果border動態(tài)變化,那么會在屏幕尺寸較大的手機上會變的很粗,這個并不是我們想要的結果,所以應當將border相關屬性的單位設置為px。
0.4、關于分辨率和物理像素:在微信小程序的模擬器中,都給出了每種機型的分辨率,需要注意的是,這里的分辨率指的是邏輯分辨率pt,而非物理分辨率,以iphone6為例,模擬器的邏輯分辨率是375px×667px,設備像素比(Dpr:Device Pixel Ratio)為2,而iphone6的物理分辨率是1334px×750px,以上的意思是:iphone6的水平方向上有375個像素點,豎直方向上有667個像素點,而每個邏輯像素點包含2個物理像素點。所以開發(fā)人員一定要注意邏輯像素和物理像素的區(qū)別,1物理像素不等于1px,通常UI設計人員做出來的設計圖的像素是物理像素。假設有一張圖片的寬度是750像素(物理像素),我們想讓這張圖片充滿整個頁面,如果直接設置在頁面里面將圖片的寬度設置為750px,是不對的,正確的設置方法是為750rpx或者350px。

1、給container添加背景顏色顯示問題

微信小程序整個頁面設置樣式問題

2、image組件9種圖片裁剪與4種縮放的模式

如果一個元素的寬和高分別設置成340rpx和100%(在iphone6下就是750rpx),而雪糕圖片的素材原始高度分別為600px和750px。

在現(xiàn)實的項目中,我們經(jīng)常需要面對原始圖片的尺寸和設計圖里的尺寸不一樣的情況(尤其是原始圖片高度是未知和不固定的情況,比如動態(tài)從網(wǎng)絡獲取圖片)。在這種情況下,我們必須要有所舍棄,或放棄等比例,或者裁剪掉圖片的一部分。接受不完美,這也是編程中很重要的心態(tài),如和選擇,需要看業(yè)務上的需求。

小程序的image組件提供了4種縮放模式和9種裁剪模式,來支持我們的選擇。

小程序頁面設計的框架結構MINA(MVVM)

MINA(MINA IS NOT APP)就是微信小程序開發(fā)使用的框架

整個系統(tǒng)分為兩塊:視圖層(View) 和 邏輯層(App Service)。
MINA可以讓數(shù)據(jù)與視圖保持同步非常簡單。當做數(shù)據(jù)修改的時候,只需要在邏輯層修改數(shù)據(jù),視圖層就會做相應的更新。(通過this.setData進行同步數(shù)據(jù))

3、page頁面的生命周期

什么是頁面的生命周期?如同人的成長需要分為出生、童年、青年、中年、老年一樣,一個頁面從創(chuàng)建到卸載,同樣會經(jīng)歷以下5個周期:

  • 加載
  • 顯示
  • 渲染
  • 隱藏
  • 卸載

MINA框架分別提供了5個生命周期函數(shù)來監(jiān)聽這個5個特定的生命周期,以方便開發(fā)人員可以在這些特定的時刻執(zhí)行一些自己的代碼邏輯,它們分別是:

  • onLoad 監(jiān)聽頁面加載,一個頁面只能會調(diào)用一次。
  • onShow 監(jiān)聽頁面顯示,每次打開頁面都會被調(diào)用
  • onReady 監(jiān)聽頁面初次渲染完成,一個頁面只會調(diào)用一次,代表頁面已經(jīng)準備妥當,可以和視圖層進行交互
  • onHide 監(jiān)聽頁面隱藏
  • onUnload 監(jiān)聽頁面卸載

可以在Page()方法中去演示這些頁面的生命周期

Page({
    data:{},
    onLoad:function(options){
        console.log('onLoad:頁面被加載')
    },
    onShow: function() {
        console.log('onShow函數(shù)被加載')
    },
    onReady: function() {
        console.log('onReady函數(shù)被加載')
    },
    onHide: function() {
        console.log('onHide函數(shù)被加載')
    },
    onUnload: function() {
        console.log('onUnload函數(shù)被加載')
    }
})

onHide和onUnload這兩個函數(shù)需要執(zhí)行一些API操作,比如頁面執(zhí)行tab欄切換頁面、navigateTo方法、或者使用小程序切換后臺的按鈕時會執(zhí)行onHide函數(shù);當頁面執(zhí)行redirectTo或navigateBack的時候會執(zhí)行onUnload函數(shù)。

當然我們還可以添加任意的數(shù)據(jù)和函數(shù)到這個Page方法的Object參數(shù)中,在頁面的函數(shù)中用this就可以訪問這些自定義的數(shù)據(jù)或者函數(shù)

以下內(nèi)容你不需要立馬完全弄明白,不過以后它會有幫助。

生命周期

Page實例的生命周期

通過以上圖解可知

  • onLoad 、onShow和onReady是按照先后順序,依次執(zhí)行
  • onLoad、onReady在整個頁面的生命周期中只會執(zhí)行一次(除非這個頁面被執(zhí)行onUnload卸載掉了)
  • onHide和onShow在一次生命周期內(nèi)可能會執(zhí)行多次。
  • 除了頁面的First Render第一次渲染,頁面還有可能會Rerender 再次渲染多次,數(shù)據(jù)更新會造成頁面的重新渲染,這里要注意的是,小程序僅在第一次 First Render完成后,提供了監(jiān)聽函數(shù)onReady,對于以后的Rerednder并沒有提供相應的監(jiān)聽函數(shù)。所以,onReady僅用來監(jiān)聽“第一次渲染”完成。

4、數(shù)據(jù)綁定

小程序借鑒了比如像Angular、vue這些流行框架的思想,采用數(shù)據(jù)綁定的機制來做數(shù)據(jù)的初始化和更新,不同于像Angular和Vue的雙向數(shù)據(jù)綁定,小程序僅實現(xiàn)了單向數(shù)據(jù)綁定,即支持從邏輯層傳遞到視圖層的數(shù)據(jù)綁定,反之則不行。

小程序使用Page方法參數(shù)里的data變量作為數(shù)據(jù)綁定的橋梁,寫在data里的數(shù)據(jù),被稱為數(shù)據(jù)綁定的初始化數(shù)據(jù)。

數(shù)據(jù)的綁定有以下兩種:

  • 一種是初始化數(shù)據(jù)的數(shù)據(jù)綁定,通常將這些數(shù)據(jù)直接寫在Page方法參數(shù)的data對象下面。
  • 另外一種是使用setData方法來做數(shù)據(jù)綁定,這中方式也可以理解為數(shù)據(jù)更新。這樣的數(shù)據(jù)更新將引起頁面的重新渲染(Rerender)

說明:小程序的腳本是運行在JSCore中,JSCore是一個沒有DOM的環(huán)境,所以小程序只能使用數(shù)據(jù)綁定來做數(shù)據(jù)的相關操作。

4.1、初始化數(shù)據(jù)綁定
小程序使用Mustache語法雙大括號{{}}在微信,wxml組件里進行數(shù)據(jù)的綁定,通過Page實例的生命周期,解釋一下初始化數(shù)據(jù)綁定的過程

當頁面執(zhí)行了onShow函數(shù)后,邏輯層會收到一個通知(Notify),隨后邏輯層會將data對象以json的形式發(fā)動到view視圖層(Send Initial Data),視圖層接收到初始化數(shù)據(jù)后,開始第一次渲染,顯示初始化數(shù)據(jù)(First Render),最終將數(shù)據(jù)呈現(xiàn)在開發(fā)者的眼前。

說明:如果數(shù)據(jù)綁定是作用在組件的屬性中,比如<image src="{{pic}} />",一定要在{{}}外邊加上雙引號,否則小程序會報錯,如果是內(nèi)容型的數(shù)據(jù),則不需要加雙引號,比如<text>{{data}}</text>

4.2、查看數(shù)據(jù)綁定對象

通過AppData面板來查看和調(diào)試數(shù)據(jù)綁定變量,AppData下的數(shù)據(jù)以頁面為組織單位,更改這里的某一項數(shù)據(jù)的值,都是實時進行更新的。

4.3、數(shù)據(jù)綁定更新

可以通過setData函數(shù)來做數(shù)據(jù)綁定,這種方法可以理解為“數(shù)據(jù)更新”。setData方法位于Page對象的原型鏈上:Page.prototype.setData。大多數(shù)情況下,我們使用this.setData的方式來調(diào)用這個方法。

setData的參數(shù)接受一個對象,以key和value的形式將this.data中的key對應的值設置成value。

上面的話要注意兩點:

  • setData 會改變this.data變量里相同key的值。
  • setData執(zhí)行后會通知邏輯層執(zhí)行Rerenader,并立刻重新渲染視圖層。

this.setData所綁定或者更新的數(shù)據(jù),并不要求在this.data中已預先定義。

Page({
    data:{
        
    },
    onLoad: function(){
        var objData = {
            obj: {
                text:"hello"
            },
            name:"小明"
        }
        this.setData({
            pageData: objData
        })
    }
})
<text> {{pageData.obj.text}} </text>

5、列表渲染 wx:for

<block></block>標簽沒有實質意義,它并不是組件,所以我們稱作“標簽”,它僅僅是一個包裝,不會在頁面內(nèi)被渲染,在這里可以理解為一個JavaScript編程語言中的括號,在block標簽中被包裹的元素將被重復渲染。

wx:for-item="item" 指定數(shù)組當前子元素的變量名,我們將元素的變量名指定為item,當然這個變量名可以更改。

如果不定義item數(shù)組子元素的變量名,依然是可以正常顯示的,原因是小程序默認子元素的變量名就是item。

wx:for-index="idx" 指定當前元素在數(shù)組中索引號的變量名,我們命名為idx。

wx:for 并不是一定要作用在block標簽上的,也可以作用在view組件上,一樣可以照常運行,但是并不推薦使用view等組件來做列表渲染,因為我們希望標簽或者組件元素是語義明確的,view組件通常被用來當做視圖容器或者是區(qū)域的分隔,它有它的使命,不應該濫用。

6、事件

要從一個頁面跳轉到另外一個頁面,需要使用事件來響應點擊某個動作。

什么是事件?

事件定義:事件是視圖層(wxml)到邏輯層的通訊方式。簡單理解:事件可以讓我們在js里處理一些用戶在界面上的一些操作,并對這些操作做出反饋。比如:點擊一個按鈕,從一個頁面跳轉到另外一個頁面,在這個過程中,需要在js里調(diào)用MINA框架的API,使從一個頁面跳轉到另外一個頁面。

想要實現(xiàn)頁面跳轉的這個機制,需要做兩件事情

  • 在組件上注冊事件,告訴小程序要監(jiān)聽哪個組件的什么事件?
  • 在js中編寫事件處理函數(shù)響應事件,也就是說,監(jiān)聽到事件后,需要編寫自己的業(yè)務。
6.1、冒泡事件和非冒泡事件

冒泡事件是指某個組件上的事件被觸發(fā)后,事件還會向父級元素傳遞,一直到頁面的頂級元素。

非冒泡事件則不會向父級元素傳遞事件。

bind和catch的區(qū)別

bind不會阻止事件的傳播,而catch會阻止事件繼續(xù)向父節(jié)點傳播。

7、導航

小程序提供了5個導航API,從而幫助開發(fā)者實現(xiàn)頁面的跳轉

  • wx.navigateTo 保留當前頁面,跳轉到應用內(nèi)的某個頁面,使用wx.navigateBack可以返回到原頁面
  • wx.redirectTo 關閉當前頁面,跳轉到應用內(nèi)的某個頁面
  • wx.switchTab 只能用于跳轉到帶有 tabBar 頁面,并關閉其他所有非 tabBar 頁面
  • wx.navigateBack 關閉當前頁面,返回上一頁面或多級頁面??赏ㄟ^ getCurrentPages() 獲取當前的頁面棧,決定需要返回幾層
  • wx.reLaunch 關閉所有頁面,打開到應用內(nèi)的某個頁面

wx.navigateTowx.redirectTo的區(qū)別

tapHandle: function (){
    wx.navigateTo({
      url: '../main/main?name=小明',
    })
    // wx.redirectTo({
    //   url: '../footer/footer',
    // })
  },
  onUnload: function(){
    console.log("page is unload")
  },
  onHide: function (event) {
    console.log("page is hide")
  }

使用 wx.redirectTo 實現(xiàn)跳轉,將打印輸出 page is unload,但是不會輸出page is hide,跳轉到的頁面沒有返回按鈕,無法返回到之前的頁面了。

使用wx.navigateTo 實現(xiàn)跳轉,將打印輸出 page is hide,但是不輸出 page is unload,頁面左上角出現(xiàn)一個可以返回到之前頁面的按鈕。

所以總結出wx.redirectTo是將關閉當前頁面并將頁面卸載,無法返回到之前的頁面,而wx.navigateTo僅僅是隱藏當前頁面,還可以再次返回到之前的被隱藏的頁面。

再來考慮一個問題,當wx.navigateTo從一個頁面跳轉到另外一個頁面后(從A頁面跳轉到B頁面),再從B頁面返回到A頁面時,B頁面會執(zhí)行onHide還是onUnload呢?答案是執(zhí)行B頁面的onUnload函數(shù),也就是說當從子頁面返回到父頁面時,子頁面會被卸載。因為這樣設計就不會造成大量的子頁面殘留在小程序中了。(早期的小程序是不會卸載的,后來在版本更新的時候更改的。)

7.1、小程序最多只能有5層頁面

當我們使用wx.navigateTo 從父頁面跳轉到子頁面后,就形成了2個頁面層級,可以繼續(xù)在子頁面里使用wx.navigateTo 跳轉到子頁面。

但是小程序強制規(guī)定,只允許有最多5層父子頁面,事實上,太多的頁面將嚴重影響用戶的產(chǎn)品體驗,建議頁面最多不要超過3層。

wx.redirectTo不存在這個問題,因為當跳轉到另外一個頁面上后,上一個頁面被強制卸載掉了。

8、小程序的模塊化

如果所有的數(shù)據(jù)都寫在js文件中,這樣會污染了我們的業(yè)務層,我們應該把這些數(shù)據(jù)分離到一個單獨的js文件中,

在項目的根目錄下新建一個文件夾,命名為data,然后再data目錄下新建一個js文件,命名為data.js。

// data.js

var postList = [
    {
        date:'1111',
        title:'hahha'
    }
]

然后將之前js文件中data里面的數(shù)據(jù)剪切到data.js中。所以我們提取出來的數(shù)據(jù)文件data.js可以看做是小程序的一個模塊。同時我們需要在data.js中使用module.exports向外部暴露一個接口

// data.js

var postList = [
    {
        data:'1111',
        title:'hahha'
    }
]

module.exports = {
    postList:postList
}

然后在其他js文件中引用這個data.js模塊,

// 其它js文件
var dataObj = require('../data/data.js')

Page({
    data:{
        
    },
    onLoad: funciton (){
        this.setData({
            postList:dataObj.postList
        })
    }
})

使用require 引用js模塊時,要注意以下幾點:

  • 被引用的文件一定要帶有拓展名.js,這一點是不同于頁面路徑的。
  • path路徑不可以使用絕對路徑,否則會報錯,應該使用相對路徑。
  • 在JavaScript文件中聲明的變量和函數(shù)只在該文件中有效,不同的文件可以聲明相同名字的變量和函數(shù),不會相互影響。

9、小程序的模板化

我們通??梢詫⒁恍┕驳?、經(jīng)常使用的業(yè)務邏輯提取成一個公共的函數(shù),當在多個地方需要使用函數(shù)時,只需要要調(diào)用這個函數(shù)即可。

事實上,有一句話是這么描述軟件開發(fā)的:編程世界里遇到的絕大多數(shù)問題都可以用封裝的思想來解決。

// post-item-tpl.wxml

<template name="postItemTpl">
    <view>
        {{item.date}}
    </view>
</template>

模板相關的內(nèi)容必須包裹在<template></template>標簽中,使用name屬性來指定模板的名稱

// post.wxml
// 使用import來引用模板
<import src="./post-item-tpl.wxml">
    
<block wx:for="{{postList}}" wx:for-item="item" wx:for-idx="idx">
    <template is="postItemTpl" data="{{item}}">
    
</block>

template的is屬性指定要使用哪個模板,template的data屬性,可以向template傳遞數(shù)據(jù),這里是將wx:for得到的item傳入到template里面,這樣就可以在template內(nèi)部使用這個item了。

模板的好處是它可以讓多個調(diào)用方來調(diào)用,不可能要求每個調(diào)用方都使用同樣的變量名來調(diào)用模板,這種由定義方要求調(diào)用方遵守變量名命名的做法是不合理的。

要解決這個問題,就必須消除template對于外部變量名的依賴,可以使用拓展運算符"…" 展開傳入對象變量來消除這個問題。

<template name="postItemTpl" data="{{...item}}">
</template>

然后在 post-item-tpl.wxml 中就可以去掉{{}}中的item

// post-item-tpl.wxml

<template name="postItemTpl">
    <view>
        {{date}}
    </view>
</template>
9.1 include與import引用模板的區(qū)別
  • import 需要先引入template,然后再使用template,但是include不需要預先引入template,直接在需要的地方引入模板即可。
  • include 模式非常簡單,就是簡單的代碼替換,不存在作用域,也不能像import一樣使用data傳遞變量。
// post.wxml

    
<block wx:for="{{postList}}" wx:for-item="item" wx:for-idx="idx">
  // 使用include來引用模板
  <include src="./post-item-tpl.wxml"/>
</block>
9.2 css模塊化

使用@import "post-item-tpl.wxss"引入樣式文件

10、不要在template上注冊事件

template標簽僅僅只是一個占位符,在編譯后,會被template的模板內(nèi)容替換,所以在template標簽上注冊事件是無效的。同理,也不能在block上注冊事件,因為block也會在編譯后消失。

解決的辦法就是在template標簽的外部增加一個view組件,將template包裹起來,并將事件注冊在view組件上。

11、頁面間的傳遞參數(shù)

從文章列表頁面跳轉到文章詳情頁頁面,需要正確展示被點擊所對應的文章內(nèi)容,首先就需要將文章的id號由index頁面?zhèn)鬟f到index-detail頁面,這樣index-detai頁面才能知道,到底要顯示哪篇文章。

所以在這里就涉及到頁面之前的參數(shù)傳遞與通信了,通過使用頁面導航url的query參數(shù)傳遞,就能夠實現(xiàn)頁面之間的參數(shù)傳遞。

12、組件自定義屬性名的規(guī)則

  • 必須以data-開頭。
  • 多個單詞由連接符 - 鏈接。
  • 單詞中最好不要有大寫字母,如果有大寫字母,除第一字母外,其余大寫字母都轉化成小寫。
  • 在js中獲取自定義屬性時,多個單詞將被轉化為駝峰命名。

13、文章id號流向圖

文章id號流向圖

14、動態(tài)設置導航欄標題

前提:在某些情況下,我們希望導航欄的文字可以根據(jù)頁面內(nèi)容的不同而有所變化,比如在文章詳情頁中,我們希望導航欄可以實時顯示當前文章的標題,不同文章顯示不同的標題。

小程序提供了wx.setNvigationBarTitle()來動態(tài)設置導航欄標題,這個方法無論是在onLoad或者onShow函數(shù)中調(diào)用,都可以成功的設置導航欄標題,但是建議在onReady函數(shù)中設置此方法,因為onReady在onShow發(fā)生之后才觸發(fā),onShow將標題設置完畢后,onReady會重新渲染頁面,并覆蓋導航欄的標題。

15、實現(xiàn)圖片預覽功能

wx.previewImage只能預覽位于網(wǎng)絡中的圖片,無法預覽本地的圖片。

16、wx:if和hidden控制元素顯示和隱藏

 <view hidden="{{falg}}"</view> 
 <view hidden="{{!falg}}"</view>

 <view wx:if="{{falg}}"></view>

wx:if有較高的切換消耗,而hidden有更高的初始消耗(使用hidden話,組件始終會被渲染,只是簡單的控制顯示和隱藏),因此,在需要頻繁切換的情景的話,使用hidden更好,更合適,在運行條件不大可能改變的時候,使用wx:if比較好。

17、實現(xiàn)回車發(fā)送評論消息的功能

  • bindinput
  • bindfocus
  • bindblur
  • bindconfirm

以上事件都屬于非冒泡事件

  • bindinput具有以下特定
    • 當用戶輸入字符時觸發(fā)
    • 每當用戶輸入或者刪除一個字符時,bindinput事件就會觸發(fā)一次
    • 可以在事件響應函數(shù)中使用return 返回一個字符或者字符串,該字符串將替換input輸入框中的顯示文本
    • 非常適合做“即時搜索”功能
  • bindfocus 當input組件獲取焦點時觸發(fā)
  • bindblur 當input組件失去焦點時觸發(fā)
  • bindconfirm 響應真機上點擊鍵盤“完成”按鈕事件?;蛘哝I盤上的回車事件,

input輸入值都是在事件對應的響應函數(shù)中使用event.detail.value

20、解決真機運行時頁面卡頓問題

給頁面的容器加上-webkit-overflow-scrolling:touch;css屬性

21、雜項知識

1、小程序提供了一個全局方法,getAppp(),用于獲取小程序的APP對象,我們可以通過app.globalData來訪問全局變量
2、分享按鈕是頁面行為,而不是應用程序的行為,每個頁面都可以調(diào)用分享API,并設置分享的參數(shù)。onShareAPPMessage,這個方法是一個頁面方法,不是wx.onShareAPPMessage。
3、微信小程序動畫
animation.scale(2,2).rotate(45).step().translate.step()

以上是兩個動畫組,它們之間使用step()作為分隔,每個動畫組中的動畫方法執(zhí)行順序是:同一組中的動畫方法會同時執(zhí)行,但動畫組必須是先后執(zhí)行。也就是說一組動畫先執(zhí)行完成后,后面的動畫組中的動畫才能執(zhí)行。

可不可以在不同的動畫組中設置不同的動畫效果參數(shù)?答案是可以的。每個step方法都可以接受一個obiect對象:可以傳入一個跟wx.createAnimation()一樣的配管.參數(shù),用于指定當前組動畫的配置。

step接受動畫配置

//創(chuàng)建一個動畫實例,

var animation = wx.createAnimation(!timingFunction: 'ease-in-out

// 設置動畫組(以step()分隔),每個隊列,

animation.scale(2, 2).rotate(45).step().translate(30).step(( duration: 1000 )

小程序提供了6類動畫方法:

  • 樣式opacity, backgroundColor, width. height. top. left. bottom. right.
  • 旋轉rotate, rotateX. rotateY, rotatez. rotate3d,
  • 縮放scale. scaleX. scaleY, scalez. scale3d.
  • 偏移 translate, translateX. translateY. translateZ. translate3d.
  • 傾斜 skew, skewX. skewY.
  • 矩陣變形matrix, matrix3d.
4、電影
movie
5、實現(xiàn)頁面下拉刷新“ 三部曲”

實現(xiàn)一個頁面的刷新需要三步

  • 在當前頁面json文件中配置enablePullDownRefresh選項為true,打開開關
  • 在當前頁面的js中編寫onPullDownRefresh函數(shù),在函數(shù)中完成下拉刷新邏輯
  • 編寫完下拉刷新邏輯代碼后,主動調(diào)用wx.stopPullDownRefresh函數(shù)停止當前頁面的下拉刷新
6、scroll-view組件的注意要點
  • 如果scroll-view下排列的多個子元素是塊級元素(比如view) ,就直接對sctoll-view設置display:fex和flex-direction:row,不會使子素首動成為水平列,如果不使用y而將容器元素換成view,那么設置display:flex和flex-directionrow是可以使子元素自動成水平排列的.
  • 如果想讓scroll-view下的view元素水平排列,一種可行的方法是將子元素view設置為有inline-block或者inline-flex。
  • 子元素有可能出現(xiàn)換行的情況,需要在容器上設置white-space:nowrap;
  • 請勿在scroll-view中使用textarea、map、Canvas、video組件
  • scoll-into-view的優(yōu)先級高于scroll-top。
  • 在滾動scroll-view時會阻止頁面回彈,所以在scroll-view中滾動是無法觸發(fā)onPullDownRefresh的。
  • 若要使用下拉刷新,請使用頁面的滾動(wx.pageScrollTo),而不是scroll-view,這樣也能通過點擊頂部狀態(tài)欄回到頁面頂部。

22、獲取用戶基本信息

小程序提供一個wx.getUserInfo()方法來獲取用戶信息,用戶信息分為用戶基本信息和用戶 openId UnionId?;拘畔⑹敲魑牡?,而openId和unionid是加密數(shù)據(jù)。這兩種類型的數(shù)據(jù)都由wx.getUserInfo()方法返回。

在小程序匯中,用戶的基本信息可以輕易獲得,他們是明文的、不加密的。但是openid和unionid是加密的,什么是openid和unionid?可以將openid和unionid理解為用戶在微信應用中的id號,他們的區(qū)別是:openid只代表用戶的在某個微信應用下的id號,而unionid是跨應用的。同一個用戶在同一個開發(fā)者的多個應用里面,unionod是唯一的。

如果開發(fā)者擁有多個移動應用、網(wǎng)站應用和公眾賬號(包括小程序),可以通過unionid區(qū)分用戶的唯一性,因為只要是同一個微信開放平臺賬號下的移動應用、網(wǎng)站應用和公眾賬號(包括微信小程序),用戶的unionid就是唯一的。也就是同一個用戶在同一個微信開放平臺下的不同應用中,unionid是相同的。

所以openid不能跨應用,如果要在多應用間統(tǒng)一用戶身份,請使用unionid。這里要注意的是,在微信小程序中使用unionid首先需要前往微信開放平臺綁定小程序。

如果要開發(fā)真實的項目,一定要考慮各種調(diào)用失敗的情況,并加強安全性。

23、用戶登錄

微信小程序登錄是為了讓開發(fā)者的服務器獲取用戶的openid以及session_key的令牌。微信小程序提供了wx .login()方法用于用戶登錄。wx.login的方法的主要目的是拿到用戶的openid和用戶本次登錄的session_key。

openid是用戶對于當前小程序的身份標識。

session_key是本次用戶登錄的會話密鑰,通常用來對用戶的通信數(shù)據(jù)進行加密。通常用來對用戶的通信數(shù)據(jù)進行加密。

wx_login

要獲取session_key和openId,首先需要在小程序中調(diào)用wx.login,并獲取code;隨后將code發(fā)送到開發(fā)者服務器并同appid和appsecret一起發(fā)送到微信服務器中,微信服務器會返回我們需要的session_key和openId。

code是一把鑰匙,是得到openid和session_key的關鍵。code的有效期只有5分鐘,如果在5分鐘之內(nèi)還沒有用code換取openid和session_key,那么就不能在使用了。

code是一把鑰匙,是得到openid和session_key的關鍵,code有效期只有5分鐘,如果在5分鐘之內(nèi)還沒有用code換取openid和session_key,那么就不能再使用了。

session_key 肯定是有失效期的,在session_key有效期內(nèi),開發(fā)者最好不要重復調(diào)用wx.login接口,不斷用code換取session_key,而應該將session_key保存在服務器中,等到session_key失效后,再重新獲取新的session_key。

怎么檢查session_key是否失效了呢?小程序提供了wx.checkSession();來校驗session_key是否失效,只有在session_key確認失效后,才能再次調(diào)用wx.login。

wx.login得到的code只能使用一次,一旦你使用code換取了openid和session_key,這個code就馬上失效,不能再次使用。當然如果5分鐘內(nèi)這個code還沒有使用,那么也會失效。

在實際的項目中,將openid和session_key返回到客戶端是非常危險的,也完全沒有必要。因為需要使用openid和session_key的場景都會被放到服務器進行的,所以返回到小程序中沒有任何意義,反而會增加數(shù)據(jù)的泄露風險。

24、用戶信息校驗

調(diào)用wx.getUserInfo接口拿到了用戶的明文基本信息數(shù)據(jù)和用戶加條新?lián)?并使用了明文數(shù)據(jù)。下面回顧一下wx.getUserInfo返回的數(shù)據(jù):

  • userlnfo 用戶信息對象。
  • awData 不包括敏感信息的原始數(shù)據(jù)字符串。
  • signature 使用shal( rawData + sessionkey )得到字符串,用于校驗用戶信息。
  • encryptedData 包括敏感數(shù)據(jù)在內(nèi)的完整用戶信息的加密數(shù)據(jù).
  • iv加密算法的初始向量。

,我們使用了userlnfo對象,包括userInfo和rawData在內(nèi)的明文數(shù)據(jù)。都可能存在被篡改的風險。如何知道明文數(shù)據(jù)是否被篡改了呢?

這個時候rawData和singature就可以發(fā)揮作用了.rawData和signature用于校驗用戶數(shù)據(jù)到底有被篡改過(沒有絕對安全的網(wǎng)絡,據(jù)極有可能被抓包或者通過其他萬式 .通常來說,想要實現(xiàn)這個校驗必在服務器編碼才能進行。這需要小程序將獲取的rawData和signature一并提交到服務器,由服務器完成校驗工作。

校驗的基本原理是: rawData是用戶原始明文數(shù)據(jù), signature是使用shal (rawData +sessionkey)得到的字符串。理論上講,如果數(shù)據(jù)沒有被篡改,那么signature等于shal(rawData+ sessionkey);如果rawData或者signature被修改了,那么signature必然不再等于shal (rawData.+ sessionkey).

是否存在signature和rawData同時被修改的情況呢?理論上是不可能的,因為session key并不在網(wǎng)絡上傳輸,篡改者不知道這個變量,被篡改且校驗通過的概率很小。

有可能從signature中 session key論E,這是不可能的。因為shal算法是不可逆的,無法在已知rawDatasignature的情況下推算出session kev,不知道session key就無法通過同時修改rawData和signature達到“欺騙校驗的目的”。如果知道了session key,只需要修改rawData并重新用session key計算一下新的shal (rawData+session kev)就又可以讓新的rawData等于新的shal (rawData+session key)了。這樣,開發(fā)者就無法知道rawData是被修改過的。

這也是為什么官方文檔一再強調(diào),不要在網(wǎng)絡上傳輸session kev,而應該將其保存在服務器上使用,以降低session key被泄露的風險。

session key有點類似于我們在數(shù)據(jù)庫中保存用戶密碼時所使用的“鹽(salt) "。在數(shù)據(jù)庫保存用戶密碼時,并不是直接將用戶的密碼以明文的方式存放在數(shù)據(jù)庫表中,通常都會使用SHA-1或者MD5算將用戶密碼和salt隨機字符串拼接在一起,重新計算一下再存入數(shù)據(jù)庫中。被重新使用SHA-1或MD5算法計算的用戶密碼誰都不知道是什么,開發(fā)者也只能E對每次登錄時輸入的密碼和數(shù)據(jù)庫保存的密碼是否一致,判斷是否為合法用戶,卻無法知道密碼到底是什么。

用戶校驗流程圖

上圖是在服務器中沒有保存session_key的,因為我們需要拿到session_key才能進行用戶數(shù)據(jù)校驗,所以上圖的流程圖再一次重復了用戶的登錄流程。

在實際的開發(fā)中,用戶登錄在session_key的有效時間內(nèi)只應該執(zhí)行一次,session_key也應該保存在服務器中,其實小程序只需要使用wx.request將raw.Data和signature發(fā)送到服務器即可,服務器無須使用code換取session_key。直接和SHA-1或者MD5算法的簽名對比即可。如下圖所示

服務器已保存session_key的用戶數(shù)據(jù)校驗流程圖

以上兩個圖對比可以得出,服務器保存session_key后整個流程變得更加簡單,完全不需要在于微信服務器進行交互。

建議開發(fā)者在客戶端使用用戶明文數(shù)據(jù)時(通過wx.getUserInfo獲得),使用rawData,而不要使用userInfo。因為數(shù)據(jù)驗證的是rawData有沒有被篡改,而不是驗證的是userInfo是否被篡改。所以建議開發(fā)者使用rawData作為用戶的基本信息。

25、解析用戶加密數(shù)據(jù)獲取openId及UnionId

調(diào)用wx.getUserInfor后將返回encryptedData和iv等數(shù)據(jù),encryptedData是包含敏感數(shù)據(jù)在內(nèi)的完整用戶信息的加密數(shù)據(jù),iv是用于解密這兩個數(shù)據(jù),整個解密的過程和用戶信息校驗的流程基本相同,不同的是,我們提交到服務器的數(shù)據(jù)是encryptedData和iv而不是rawData和signature。

26、微信小程序模板消息

要成功發(fā)送模板消息,必須要有一個formld的id號,但經(jīng)過測試發(fā)現(xiàn)開發(fā)工具中法獲取formld,也就是說在開發(fā)工具中不能產(chǎn)生模板逍息,只有在真機中方能拿到formld,formld是發(fā)送模板消息的關鍵。模板消息是被動的,只有用戶本人在小程序中有一定交互行為后,服務器才能夠向用戶推送模板消息。下面來解釋一下用戶的什么行為,才能讓服務器可以向小程序推一條模板消息。

(1)用戶在小程序內(nèi)完成過支付行為, 7天內(nèi)可允許開發(fā)者向用戶推送有限制的模板消息(一次支付可下發(fā)一條,多次支付可發(fā)條數(shù)獨立,互相不影響) 。

-(2)當用戶在小程序內(nèi)發(fā)生過提交表單行為且該表單聲明為要發(fā)模板消息時, 7天內(nèi)可許開發(fā)者向用戶推送有限條數(shù)的模板消息(一次提交表單可發(fā)一條,多次提交可發(fā)條數(shù)獨立,互相不影響)

發(fā)送模板消息流程

以上流程是建立在已經(jīng)在小程序中拿到了formid且formid沒有被使用的前提下。發(fā)送模板消息需要知道用戶的openid,獲取openid的流程不是必須的。如果用戶已經(jīng)在小程序中登錄,服務器也保存了openid和session_key,就沒有必要再使用code換取openid。

發(fā)送模板消息的流程還需要一個access_token。access_token在微信服務號或者訂閱號里經(jīng)常被作為令牌使用,access_token也是有失效期的,開發(fā)者應該在真實的項目中像管理session_key一樣管理access_token。獲取access_token同樣需要攜帶微信小程序的appid和appsecret調(diào)用微信服務器。

流程圖中沒有標示出formid的獲取過程,那么formid是怎么來的?

當用戶提交表單時,表單提交函數(shù)的event事件對象中將包含一個formid,這個formid只能使用一次,有效期為7天,一旦使用formid推送一條模板消息,這個formid就不可以再次使用。如果還想推送模板消息,就只能等用戶再一次提交表單并產(chǎn)生新的formid。

27、form表單組件

<form report-submit="true" bindsubmit="formSubmit" bindreset="formReset">
    <button formType="submit">Submit</button>
     <button formType="reset">Reset</button>
</form>

report-submit Boolean 是否返回 formId 用于發(fā)送模板消息

用戶點擊formType類型為submit的button之后,將執(zhí)行bindsubmit屬性所指定的響應函數(shù),在響應函數(shù)的event事件對象中將可以獲取form下所有表單元素的用戶輸入值。report-submit屬性為true,那么觸發(fā)bindsubmit后,event事件對象中將包含一個formid,在開發(fā)工具中獲取到的formid的值為 the formid is a mock one[圖片上傳失敗...(image-c6aada-1541424452744)]

這個并不是一個可以使用的formid,只有在真機上才能獲取真實的formid。

28、微信支付

微信支付流程

支付流程看起來比較復雜,但微信提供了一個SDK,使用SDK基本上不需要編寫太多支付相關的代碼。事實上,在真實的項目中,支付相關的代碼復雜的不是支付本身,而是我們自己的業(yè)務邏輯。

以下是微信支付的最簡單流程,該流程不包括開發(fā)者自己的業(yè)務邏輯。

  • 首先,小程序客戶端調(diào)用開發(fā)者自己的服務器,將一系列訂單信息發(fā)送到服務器,比如商品的id等信息。
  • 開發(fā)者服務器接受被購買商品的信息后,調(diào)用微信服務器的統(tǒng)一下單API,生成一個預付單,并將預付單信息返回開發(fā)者服務器。統(tǒng)一下單API需要用戶的openid,如果已經(jīng)在服務器保存了openid,就不需要獲取用戶的openid了,如果沒有當前支付用戶的openid,那么需要在第一步中攜帶code,以方便在這一步中使用code換取用戶的openid。
  • 開發(fā)者服務器需要對預付單的信息簽名,并將預付單信息和簽名一起返回小程序客戶端。
  • 小程序客戶端在收到預付單信息及簽名后,再調(diào)用wx.requestPayment,將預付單信息和簽名一起提交到微信服務器。
  • 微信服務器會驗證這些預付單信息,如果驗證通過,那么小程序將拉起支付界面(在開發(fā)工具中,這一步首先會彈出一個二維碼,開發(fā)者掃描這個二維碼將在開發(fā)者的微信中拉起支付界面)。
  • 支付完成后,微信會主動調(diào)用開發(fā)者服務器將支付結果推送到開發(fā)者服務器中,開發(fā)者可根據(jù)支付結果處理自己的業(yè)務邏輯。這一步需要開發(fā)者有自己的外網(wǎng)服務器,否則微信無法推送通知。

29、真實的微信小程序登錄狀態(tài)維護

在session_key的有效時間內(nèi)(未過期),開發(fā)者應該自行維護session_key而不是重復獲取。

同時,openid最好不要作為用戶的id被傳遞到小程序客戶端。開發(fā)者應該有自己生成令牌作為自己業(yè)務的用戶id,而不應該使用微信的openid。

以下是小程序登錄及登錄狀態(tài)維護流程圖

小程序登錄及登錄狀態(tài)維護流程圖

在小程序登錄及登錄狀態(tài)維護流程圖中,獲取到session_key和openid后不要將這兩個變量發(fā)送回客戶端。此時應該生成一個令牌(隨機字符串,也就是圖中描述的3rd_session)作為用戶的session_key和openid的鍵,將令牌(鍵)和session_key、openid(值)保存到服務器的Redis或者Memcache中。同時,這個自己生成的令牌應當具有一定的時效性,時效性一定要小于30天,開發(fā)者可根據(jù)自己的小程序的安全性要求自行調(diào)整這個令牌的有效期。開發(fā)者應當將自己生成的令牌發(fā)動到微信小程序并存入小程序的緩存中,每次請求服務器時都攜帶這個令牌,服務器接收令牌后在Redis中查找用戶真實的openid和session_key。

以上流程只是一個指導性的流程,在真實的項目中還有很多變化和細節(jié),但在總體思路上應當遵守上述流程。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容