小程式的核心語法

語言: CN / TW / HK

小程式的核心語法

開篇

小程式核心語法:

  1. 資料驅動原則
  2. 實現兩個案例,串聯核心知識點
  3. 商品案例
  4. 列表案例

小程式的資料驅動原則

問題:

  1. 什麼是資料驅動?
  2. 在小程式中如何完成資料繫結?

資料驅動:

js // 商品 let product = { price: 10, num: 5 } // 總價格 let total = 0; // 計算總價格的方法 function getTotal(product) { return product.price * product.num } // 計算商品的總價格 total = getTotal(product) // 進行列印 console.log('總價格:' + total); // 50 太貴了,所以我們少購買了兩個商品,也就是讓 num = 3 product.num = 3; // 問:總價格是多少? console.log('總價格:' + total); // 此時,打印發現總價格還是 50 元,如果要說原因的話,那麼應該很簡單,【因為我們沒有重新進行價格的計算嘛】 // 但是,此時大家有沒有想過一點?我們為什麼要進行價格的計算呢? // ---------------------------------------------------- // 當商品的數量發生變化時,商品的總價格【理應發生變化】,不是嗎?

上面的例子,就是我想要跟大家說的:【當數量發生變化時,商品的總價格理應發生改變】。

那麼同樣的道理,在我們的頁面中,假如:

某一個 DOM 依賴於某個資料進行展示,那麼【當資料發生變化時,檢視也理應發生變化】。

而這個就是【響應式資料驅動】。

小程式中完成響應式:

  • data 中定義資料

```js // index.js // 獲取應用例項 const app = getApp()

Page({ data: { product: { price: 10, num: 5 } } }) ```

  • wxml 中使用資料

html <view> <view> <!-- wxml 中訪問資料,必須使用 {{}} 語法,{{}} 語法中可以放置【任意的、單一的 JavaScript 表示式】 --> 商品的單價:{{product.price}} </view> <view> 商品的數量:{{product.num}} </view> <view> 商品的總價格:{{product.price * product.num}} </view> </view>

現在我們已經可以在 js 的 data 中定義資料,並且在 wxml 中通過 {{}} 語法使用資料。

那麼我們回過頭來看我們的問題:

答案:

  1. 什麼是資料驅動?
  2. 當資料發生變化時,檢視理應發生變化
  3. 在小程式中如何完成資料繫結?
  4. 在 data 中定義資料
  5. 在 wxml 中通過 {{}} 使用資料

但是在此時,大家心裡應該還有一個疑惑,那就是:【現在資料還沒有發生變化呀?我也沒有看到檢視的變化呀?】。

如果你心中確實有這麼一個困惑的話,那麼就繼續往下看!

小程式中的常用事件與屬性列表

問題:

  1. 如何為按鈕新增點選事件?
  2. 如何修改 data 中資料的值?

處理點選事件

接下來我們希望做一件事情:

建立一個按鈕

當用戶點選按鈕時

讓 product 的 num + 1

建立按鈕的方式非常簡單:

html <button type="primary">num + 1</button>

問題在於:我們如何給這個按鈕新增點選事件呢?

有過開發經驗的同學,可能會猜到:我們可以給 button 一個 click 事件來監聽按鈕的點選。

可是大家需要知道,現在我們是在【小程式】中,那麼如果想要給 button 新增點選事件則不可以使用 click 而是 bind:tap / bindtap

其中 bind: / bind 表示【繫結事件】,tap 為繫結的具體事件。小程式具體事件列表,可以點選 這裡 檢視。

html <button type="primary" bind:tap="onAddNum">num + 1</button>

接下來需要在 js 中定義對應的 事件

js /** * 定義事件處理的方法 */ onAddNum () { console.log('onAddNum') }

到目前:我們已經 監聽了按鈕的點選事件,並且寫入了對應的處理函式 ,接下來就需要 修改 num 的值

修改 data 的資料

想要修改 data 中的資料,那麼我們需要藉助一個函式 setData

setData 接收一個 物件作為引數,這個物件就是最新的 data 資料。

其中 key 為要修改的資料, value 為最新的值

訪問 data 的資料

因為我們想要讓 num + 1 ,所以我們還需要拿到 num 的當前值,想要訪問 num 的值,可以通過 this.data.product.num 的形式訪問

所以最終的修改 num 的程式碼為:

js /** * 定義事件處理的方法 */ onAddNum () { this.setData({ 'product.num': this.data.product.num + 1 })

此時,當我們點選 button ,可以發現:【當 num 發生改變時,總價格也發生了對應的變化】

答案:

  1. 如何為按鈕新增點選事件?
  2. bindtap || bind:tap
  3. 如何修改 data 中資料的值?
  4. 通過 this.setData({}) 定義新的值
  5. 通過 this.data 訪問具體的值

事件傳參

問題:

  1. 如果想要在【點選事件中】傳遞引數,那麼需要怎麼做?

新的需求

現在讓我們把需求變得更加複雜一些。

我們希望 onAddNum 方法可以接收一個引數,每次點選 num 增加的數量為傳入的引數

那麼如果想要實現這個需求的話,那麼就需要涉及到一個知識點:【事件傳參】。

如果大家有過開發經驗的話,那麼可能會認為這是一個非常簡單的需求,順便可以寫下如下程式碼:

```js // html

// js onAddNum (step) { this.setData({ 'product.num': this.data.product.num + step }) } ```

可是,假如我們真按照以上程式碼進行實現的話,那麼 你應該會收到以下如下的警告:

image-20210505114301165

這個警告的意思是:沒有一個叫做 onAddNum(5) 的方法用來處理當前的這個 tap 事件。

也即是說:onAddNum(5) 會被當做一個 完整的方法名字,而不是 方法名為:onAddNum,傳入了引數為 5

那麼如果我們想要傳遞引數應該怎麼做呢?


在小程式中,如果想要給 點選事件傳遞引數的話,那麼需要藉助 event 物件data- 屬性

引數的傳遞包含兩個部分:

  1. 形參
  2. 實參

形參:

首先先來看 形參,對於 點選事件的回撥方法 而言,預設會接收一個引數 event (事件物件)。這個 event 物件為:回撥方法的唯一引數

實參:

對於 小程式 中,我們不能直接為 回撥方法傳遞實參

而是需要通過:屬性繫結的形式,把需要傳遞的引數繫結到 當前 DOM 元素中,繫結資料的屬性需要以 data- 開頭。該屬性可以通過 e.target.dataset 進行訪問。

```js // html

// js onAddNum (e) { // 獲取 data-step 的值 let step = parseInt(e.target.dataset.step); this.setData({ 'product.num': this.data.product.num + step }) } ```

答案:

  1. 如果想要在【點選事件中】傳遞引數,那麼需要怎麼做?
  2. 通過屬性繫結(data-xx)的形式,把需要傳遞的引數繫結到 當前 DOM 元素中
  3. 在對應的回撥函式中,通過 e.target.dataset 進行訪問

實現【雙向資料繫結】

問題:

  1. 什麼叫做雙向資料繫結?
  2. 小程式中如何實現雙向資料繫結?

上一章節中我們通過【事件傳參】實現了【每次點選 + 5】 的功能,但是這樣的功能未免還是有些太單調了。

所以我們接下來希望實現一個新的功能:

建立一個數字輸入框,輸入框 與【商品數量】完成 【雙向資料繫結】。

即:

  1. 輸入框內容發生變化時,商品數量同步跟隨變化
  2. 商品數量發生變化時,輸入框內容同步跟隨變化

那麼這樣的功能我們應該如何去實現呢?


如果想要實現這個功能,那麼我們需要先把這個功能進行拆解,【把一個複雜的功能拆解成多個簡單的功能】是實現一個複雜邏輯的標準方式。

那麼如何進行拆解呢? 大家可以先進行以下思考,然後再繼續向下進行學習!


以上功能拆解如下:

  1. 建立一個【數字輸入框】
  2. 設定 【商品數量】 為輸入框的初始值
  3. 監聽使用者的輸入行為
  4. 獲取使用者輸入的值
  5. 賦值給【商品數量】

```js // html 商品的數量:

// js /* * 3. 監聽 input 的輸入事件 / onInput (e) { // 4. 獲取使用者輸入的值 const val = parseInt(e.detail.value); // 5. 賦值給【商品數量】 this.setData({ 'product.num': val })

```

那麼現在功能我們已經實現了,那麼大家在回憶一下我們的問題:

答案:

  1. 什麼叫做雙向資料繫結?
  2. 當檢視發生變化時,資料跟隨發生變化。
  3. 當資料發生變化時,檢視跟隨發生變化.
  4. 小程式中如何實現雙向資料繫結?
  5. 通過 valueinput 檢視繫結資料
  6. 通過監聽 bindinput 獲取檢視的變化,在回撥方法中修改資料

條件渲染

問題:

  1. v-if 和 hidden 的區別是什麼?

現在你已經買了很多的商品了,可是當你出去結賬的時候,售貨員小姐姐對你發出了一聲驚呼:

  1. 如果【總價格 <= 100 】:hello 帥哥
  2. 如果【總價格 > 100 && 總價格 < 1000】:哇哦 有錢人哦
  3. 如果【總價格 >= 1000】:土豪你好

如果想要實現這麼一個功能的話,那麼就需要使用【條件渲染】的功能了。

小程式中提供了兩個 API 都可以實現【條件渲染】的功能:

  1. wx:if ... wx:elif ... wx:else
  2. hidden

那麼下面我們就分別用這兩個語法來實現一下這個功能:

```html

售貨員小姐姐驚呼: <text wx:if="{{ product.price * product.num <= 100 }}">hello 帥哥 土豪你好

售貨員小姐姐驚呼: <text hidden="{{ !(product.price * product.num <= 100) }}">hello 帥哥 ```

答案:

  1. v-if 和 hidden 的區別是什麼?
  2. v-if 用來控制 【元件是否會被渲染】
  3. hidden 用來控制【元件是否會被隱藏】
  4. 一般來說,wx:if 有更高的切換消耗而 hidden 有更高的初始渲染消耗。因此,如果需要頻繁切換的情景下,用 hidden 更好,如果在執行時條件不大可能改變則 wx:if 較好。

列表渲染

問題:

  1. 使用 wx:for 時,當前項的【下標變數名】和【當前項變數名】預設分別是什麼?
  2. block 元件是否會被渲染?

新的需求:

如果我們有一組商品,並且希望把這組商品全部渲染出來得話,那麼就需要使用到【列表渲染】的功能。

小程式中為我們提供了 v-for 指令,讓我們進行【列表渲染】的實現。

同時也為我們提供了一個:包裹性質的容器 block 元件,當我們去迴圈多個元素時,可以使用 block 進行包裹,block 元件只起到包裹的其他元件的作用,本身並不會進行渲染。

```js // html

商品名:{{item.name}} 價格:{{item.price}}

// js data: { products: [ { name: '蘋果', price: 3.2 }, { name: '麵包', price: 5.0 }, { name: '可樂', price: 2.5 } ] } ```

答案:

  1. 使用 wx:for 時,當前項的【下標變數名】和【當前項變數名】預設分別是什麼?
  2. 預設陣列的當前項的下標變數名預設為 index
  3. 陣列當前項的變數名預設為 item
  4. block 元件是否會被渲染?
  5. block 只是一個包裹性質的容器,不會被渲染。

配置檔案解讀

  1. app.json 配置檔案:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html
  2. pages 陣列:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#pages
    1. 建立 list 頁面
  3. window 物件:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#window
  4. tabbar 物件:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#tabBar
    1. index 頁面
    2. list 頁面
  5. 頁面.json 配置檔案:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/page.html

資料請求

場景

先去試想一個場景,現在你是【慕課網的前端開發工程師】,然後你開發了這樣的一個【小程式】 image-20210508102436831

現在系統已經上線了。

有一天,你想要修改裡面的一塊資料,比如:把【C語言系統化精講】改成【C語言精講】,那麼你應該怎麼做?

記住,現在你的專案已經發布上線了!你想要修改線上版本的內容,那麼你怎麼做呢?難道要為了修改這個文字釋出一個新的版本嗎?如果以後再有了類似的文字修改呢?

那麼此時面對這樣的場景,我們就需要使用到【資料請求】了。

問題

  1. 小程式中的資料請求有什麼限制?以及如何解決這種限制
  2. 小程式的資料請求會存在跨域問題嗎?為什麼?
  3. 小程式的資料請求可以叫做 ajax 請求嗎?為什麼?

內容

wx.request 發起網路請求,請求的方式主要分為兩種:

  1. get 請求
  2. post 請求

這裡準備了兩個資料請求介面,可以用來測試 wx.request 的資料請求(詳見介面文件):

  1. /api/test/getList
  2. /api/test/postData

那麼接下來我們就根據 wx.request 來完成一個基本的介面請求

```js // html // js // index.js // 獲取應用例項 onGetClick () { wx.request({ url: 'https://api.imooc-blog.lgdsunday.club/api/test/getList', method: 'GET', success: (res) => { console.log(res); } }) }

```

這樣的程式碼看起來沒有任何問題,但是我們卻得到了一個錯誤(可測試的 APPID:wxf01e2ce0eb588aac):

image-20210508113751611

而要解決這個問題,我們就需要明確一個問題:小程式中的資料請求有什麼限制?

  1. 只能請求 HTTPS 型別的介面
  2. 必須將介面的域名新增到信任列表中

解決方案:

  1. 生產環境:將想要請求的域名協議【更改為 HTTPS】並【新增到域名信任列表】
  2. 開發環境:通過勾選image-20210508103939068

get 請求完成,接下來來測試一下 post 請求:

js // html <button type="primary" bindtap="onPostClick">發起 post 請求</button> // js onPostClick () { wx.request({ url: 'https://api.imooc-blog.lgdsunday.club/api/test/postData', method: 'POST', data: { msg: '願大家心想事成,萬事如意' }, success: (res) => { console.log(res); } }) }

題外話(擴充套件內容:針對有 web 前端開發經驗的同學):

  1. 跨域問題: 跨域問題主要針對 瀏覽器 而言,而小程式宿主環境為【微信小程式客戶端】,所以小程式中不存在【跨域問題】
  2. ajax 請求: ajax 依賴於 XMLHttpRequest 物件,而小程式宿主環境為【微信小程式客戶端】,所以小程式中的【網路請求】不是 ajax 請求

答案

  1. 小程式中的資料請求有什麼限制?以及如何解決這種限制
  2. 限制:
    1. 只能請求 HTTPS 型別的介面
    2. 必須將介面的域名新增到信任列表中
  3. 解決方案:
    1. 生產環境:將想要請求的域名協議【更改為 HTTPS】並【新增到域名信任列表】
    2. 開發環境:通過勾選image-20210508103939068
  4. 小程式的資料請求會存在跨域問題嗎?為什麼?
  5. 不會
  6. 【跨域問題】只存在於基於瀏覽器的 Web 開發中
  7. 由於小程式的宿主環境不是瀏覽器,而是微信客戶端
  8. 所以小程式中不存在跨域問題
  9. 小程式的資料請求可以叫做 ajax 請求嗎?為什麼?
  10. 不可以
  11. ajax 的核心是依賴於 【瀏覽器端】 的 XMLHttpRequest 物件
  12. 由於小程式的宿主環境不是瀏覽器,而是微信客戶端
  13. 所以小程式的資料請求不可以叫做 ajax 請求

非同步程式設計新方案 - promise

場景

首先先去假設一個場景:

目前有一個需求,需要你按照以下的邏輯去進行介面請求:

  1. 先去請求介面 A
  2. 在介面 A 獲取到資料之後,再去請求介面 B
  3. 在介面 B 獲取到資料之後,再去請求介面 C
  4. 在介面 C 獲取到資料之後,再去請求介面 D

如果按照上一小節學習到的內容,那麼我們會得到以下的程式碼(介面程式碼請見:03-小程式核心語法/02-回撥地獄.html):

js A(function (res) { console.log(res); B(function (res) { console.log(res); C(function (res) { console.log(res); D(function (res) { console.log(res); }) }) }) })

在這個 顏值即正義 的世界裡面,我們這樣的程式碼結構應該是 沒有前途的。 因為它太醜了,並且太難以閱讀了。

假想一下,如果我們要請求 10 個介面的話,那麼程式碼會變成什麼樣子?

所以在程式設計圈裡對這樣的程式碼有一個非常學術的名字:回撥地獄 -> 回撥函式的大量巢狀導致出現 複雜且難以閱讀 的邏輯

問題

  1. promise 是如何解決回撥地獄的問題呢?
  2. Promise 的狀態分為幾種,分別是什麼?
  3. 如何讓 Promise 變成 已兌現(fulfilled)的狀態,如何接收已兌現(fulfilled)的結果

內容

點選 Promise 進入官方文件:

```js