你與小程式開發的距離有多遠?

這波能反殺發表於2017-01-10

你與小程式開發的距離有多遠?
你與小程式開發的距離有多遠

2017年1月9日凌晨。小程式正式釋出。

對焦10年前iPhone的釋出時間,產品之神張小龍顯然是想讓這一天具有十分重要的歷史意義。小程式釋出之後,它終於揭開了最終面目,我們不得不承認,這一天,必定是一個新時代的開端。

作為一個第一批小程式的開發者,從小程式內測之初通過開發工具破解版開始嘗試小程式,見證了小程式官方文件的每一次更新,踩過小程式的大多數坑,也見證了好幾個小程式社群的逐步發展,到最終把自己的小程式過審上線,這個過程對我而言,收穫良多。

為了對這段時間的收穫做一個總結,也希望能將自己的經驗分享給大家,所以有了這篇文章,讓想要開發小程式的小夥伴們對比一下自己掌握的知識,距離開發一個成熟的小程式,到底還差多少。也讓想要學習小程式的朋友,有一個大的方向。

一、小程式開發工具為新手朋友提供了什麼樣的便利?

2016這一整年來,前端開發發生了很重要的變化,比如ES6的全面普及,react的持續火熱,vue的爆發式增長,所有人都知道學習這些東西很重要,但是為什麼很多很多的新手朋友們往往只能瞭解皮毛而難以真正掌握呢?

那就是因為構建工具的門檻太高。

很多人在學習別人的程式碼的時候,會很驚訝的發現,為什麼別人的程式碼,和瀏覽器能識別的樣子,差距那麼大?其實全都是構建工具的功勞。無論是ES6,還是react,元件化,模組化,這些東西統統都會通過構建工具,最終變成瀏覽器能識別的樣子,也就是我們最初學習時所知道的那個簡單樣子,一個頁面,包括css,html,js,image等

對於大多數團隊來說,只要公司中有大神能搭建一個成熟的開發環境,對於這些知識的學習其實是容易很多的,這也是為什麼,很多人在進入某個團隊的時候成長速度會變得更快的原因所在。但是對於一個剛入門的新手來說,想要搭建一個能用的構建工具是非常困難的。

而對於這個門檻,小程式的開發工具,則完美的幫大家解決了這個問題。我們不需要額外搭建任何開發環境,只需要下載《微信web開發工具》,就可以看著小程式的文件開始寫demo了。

所以如果你想要低成本的學習ES6,體驗元件化的開發模式,小程式應該算是一個不錯的選擇。

二、 需要什麼樣的基礎知識

熟悉文件

由於小程式是中文文件,所以我相信對大多數人來說,學習成本非常低。我們需要實現什麼效果,需要用到什麼元件和api,這些基礎的東西就在文件裡,我們只需要熟悉他們即可,如果你連文件都不熟悉,真的談不上開發小程式了,但是小程式文件確實足夠簡單以致於對大多數人來說,過一遍就知道應該怎麼玩了。

需要對html,css的知識有足夠的熟悉程度

小程式的元件,仍然是基於html與css的知識來完成,我們只需要對html與css足夠熟悉,就能夠很輕鬆的完成小程式的佈局。諸如文件流,包含塊,BFC,定位系統,怪異盒模型,彈性盒模型等等知識,都能夠用得到。

需要對JavaScript足夠熟悉

和網頁應用一樣,在小程式裡,所有的功能都由js完成。而由於三方外掛的匱乏,對於js的封裝能力和對資料的處理能力就會要求高一點。並且需要你對模組化有一定的認知。

需要有控制資料就能改變UI的思維

沒有接觸過類MVC模式框架的朋友,往往對於這個思維轉變有一定程度上的難以接受。所以經常看到有人在學習angular和react時到處詢問如何把jQuery引入進來,其實在99%的場景下,我們都不再需要獲取到DOM節點,只需要運算元據和方法就能完成所有的事情,我們需要有這樣一個心理準備。

二、開發小程式,我們面臨著什麼樣的挑戰

如果你只是想要寫一個demo,看著官方文件把一些元件,api體驗一下,那是沒有什麼挑戰的。但是如果我們想要開發一個成熟健全的小程式,那麼面臨的挑戰就很多。

比如:
如何儲存登入狀態,UI狀態等?
為了給使用者節約流量,應該如何規劃快取機制?
不支援webview,我們如何展示html文章?
如何應對三方外掛匱乏的狀況?
沒有提供明確的元件機制,我們應該如何處理元件?
不支援promise,我們如何處理非同步?
小程式體積限制為1024k,我們應該如何優化程式碼與處理靜態資源?
具體功能應該如何實現等等... ...

當然,並不是每一個小程式都要考慮所有的問題,這裡我從我自己開發的小程式的角度,跟大家分享一下,我遇到了哪些問題,如何解決,用了什麼樣的方案。

如何儲存各種輕量快取資料以及狀態值

假如在一個簡單的學習demo中,我們想要改變一個矩形的寬高,並把結果儲存起來,只需要設定一個全域性變數即可。這樣我們就可以在任何地方知道這個矩形改變之後的寬高了,但是我們知道,為了防止變數汙染,以及在多人開發中出現命名衝突等問題,我們的原則上是不能有全域性變數的。那麼如果不用全域性變數,我們應該怎麼做?

如果學習過react的同學,應該會對redux有所耳聞。redux就能夠解決這個問題。但是redux對於小程式而言,由於功能過於強大,反而不太適合。因此,嘗試自己造一個簡單的輪子來解決這個問題。

小程式中,我們可以使用es6的模組化規範,來建立模組,引入模組等,如果對於模組化的開發思維還不太熟悉,建議先學習一下。

建立一個叫state.js的檔案,該檔案就是一個獨立的模組,專門用來處理輕量的資料快取

首先建立一個state物件,準備將資料以json的格式存起來

let states = {} // 儲存變數複製程式碼

分別提供一個獲取資料的方法,一個獲取states全部資料的方法

function get (name) {
    if(states[name]) { return states[name] }
    return ''
}

function getStates () {
    return states
}複製程式碼

再提供一個儲存資料的方法,我們知道如果只儲存一個key-value的資料很簡單,但是我們想要實現react中,setState同樣功能的話,則需要做一些特殊的處理。

function set (options, target) {
    let keys = Object.keys(options)
    let o = target ? target : states
    keys.map( item => {
        if(typeof o[item] == 'undefined') {
            o[item] = options[item]
        } 
        else {
            if(utils.type(o[item]) == 'object') {
                set(options[item], o[item])
            } else {
                o[item] = options[item]
            }
        }
        return item
    })
}複製程式碼

對外提供訪問的介面,這個模組就算完成了。

module.exports = {
    get: get,
    getStates: getStates,
    set: set
}複製程式碼

在別的模組中,使用方式如下

// 先引用
import state from './utils/state'

// 儲存一個值
state.set('single', { c: 1, d: 2 })
// 檢視一下single中的資料
state.get('single')
// 只修改single中的c值
state.set({
  single: { c: 20 }
})
// 修改完成之後再檢視一下結果,這也是set方法的厲害之處,這對於我們儲存提供了極大的便利複製程式碼

雖然這個模組程式碼簡單,但是state能夠儲存足夠多的資料,它提供了全域性變數給我們帶來的便利,也避免了全域性變數的弊端,甚至還給元件之間的互動提供了可能。如果你沒有意識到這個模組的重要性,那麼你還得多寫幾個demo細細的體會一下。

上面的state模組還需要什麼重要的擴充嗎?

我們試想一下,如果我們想要修改一個皮膚設定項,或者我們要切換白天/黑夜模式的外觀,我們應該怎麼做?為什麼我的按鈕一改變,全域性的皮膚就能立即做出響應?

這個時候,我們需要掌握js設計模式中,一個極為重要的模式, 訂閱-通知模式,或者叫做觀察者模式,監聽者模式都可以。在一個app中,如果我們想要做好快取和使用者體驗,就會大量用到它。

在修改皮膚這個例子中,我們改變了按鈕的狀態值,這個狀態改變值觸發了一個事件,這個事件完成了對皮膚的修改。當然在此之前,我們還得將這個事件,與這個狀態值繫結起來。

繫結事件 -> 點選按鈕狀態值改變 -> 觸發事件 -> 皮膚修改完成

因此,該模式的原理也大概如下,我們首先需要將對應的事件儲存起來與變數值的key,這個過程就叫做繫結,類似事件繫結。在狀態值改變時,我們就傳送一個通知,告訴我們的模組,狀態值改變了,應該執行事件了,於是事件執行。

宣告一個儲存資料的陣列

let events = [] // 儲存事件複製程式碼

每一個繫結,都會以物件的形式,儲存在陣列中

events = [{
    name: 'changeToNight',
    handler: changeFn,
    page: targetPage
}]複製程式碼

新增一個繫結事件

function bind (name, notification, targetPage) {
    if (name && notification) {
        if (!targetPage) {
            console.error('bind error: 沒有繫結頁面物件')
            return;
        } 
        events.push({
            name: name,
            handler: notification,
            page: targetPage
        })
    } else {
        console.error('bind error: no name or handler')
    }
}複製程式碼

新增一個通知事件

function dispatch (name, value) {
    if(!value) {
        value = get(name)
    }
    else {
        set({
            [name]: value
        })
    }
    if(!events.length) { return }

    events.map( (item, i) => {
        if(item.name == name) {
            item.handler(value)
        }
    })
}複製程式碼

簡單的實現了一下,如果大家想要將該模式運用的更加自如,還需要專門花精力去研究他。我這裡只是簡單的暫時了一種實現方式。

如何解決html文章的展示

由於小程式中不支援html標籤,它有自己的一套標籤元件,因此我們在使用時,並不能直接把html文章顯示出來。為了能夠解決這個問題,我們需要將html標籤轉換為小程式支援的標籤。

有一個叫做html2json.js的元件。它遍歷html的標籤結構,並根據便利結果將標籤內容以json的資料形式儲存起來,我們就可以通過該json資料生成對應的小程式標籤。

當然如果是我們自己做的話還比較麻煩,好在有大神在第一時間提供了一個叫做wxParse的三方外掛。大家可以去搜尋使用一下。

由於該外掛功能太齊全,很多我的小程式用不上,所以就根據上面我說的思路自己實現了一個輕量級的,剛好夠自己用。

如何考慮快取機制

上面的state元件,能夠在一定程度上快取一些輕量的資料。但是該元件的生命週期短暫,在小程式退出之後資料就會消失,而且對於資料量比較大的情況,也不適合用state元件來快取。

每一個小程式有10M的本地快取空間。而且小程式也提供了快取和清除快取的api,因此這不是我們的難點,我們的難點在於,如何分清哪些資料應該快取,每一種資料應該快取多久?如何更新?伺服器資料如果更新了應該如何同步本地快取?

快取策略做得好不好,在很大程度上會決定你小程式的整體質量。當然這裡我就不詳細展開解讀了,涉及到很多東西,一時半會兒感覺說不清楚了,大家只要考慮清楚了上面的幾個問題,相信都能夠結合自己的實際情況,弄出一個合理的方案。

如何處理元件以及元件的資料傳遞,元件的互動等

小程式並沒有提供明確的元件機制。但是我們知道,在小程式裡,一個頁面可以由xx.wxml, xx.wxjs, xx.wxss, xx.json組成,這是一個基本結構。而且小程式提供了js的模組引入,wxml的模板引入,這就為我們自定義元件創造了可能。

+ component
   + rect
      - rect.wxml
      - rect.wxss
      - rect.js   

+ pages
   + index
      - index.wxml
      - index.wxss
      - index.js複製程式碼

我們建立了一個rect元件,並希望我們自己能夠自定義矩形的顏色,點選之後,還會再修改一次顏色

一切從簡,rect元件中程式碼

// rect.wxml
<template name="rect">
  <view class="single-rect" bindtap="changeColor" style="background-color: {{rectColor}}"></view>  
</template>

// rect.wxss
.single-rect {
  width: 100px;
  height: 100px;
  background-color: red;
}

// rect.js
module.exports = {
  changeColor: function (_this) {
    _this.setData({
      rectColor: 'orange'
    })
  }
}複製程式碼

index頁面中引入該元件

// index.wxml
<import src="../../component/rect/rect.wxml" />
<template is="rect" data="{{rectColor}}"></template>

// index.wxss
@import "../../component/rect/rect.wxss";

// index.js
import rect from '../../component/rect/rect'

Page({
    data: {},
    onLoad: function () {
        console.log(rect)
    },
    changeColor: function () {
        rect.changeColor(this)
    }
})複製程式碼

這個例子演示了元件的建立與使用,其中包含了資料傳遞。當然看上去有點麻煩,因此我們可以通過構建工具來簡化這個過程。這裡就不多說了,在不增加額外構建的情況下,這樣使用是完全沒有問題的。至於不同元件之間的互動,就得通過上面的state元件來完成。

如何使用Promise

Promise的重要性不言而喻,這裡就不再介紹promise了,如果不明白的同學可以去其他文章裡學習一下。但是小程式經過幾次改變,已經決定取消對Promise的支援,在實際應用中,promise幾乎無處不在使用。因此,我們需要自己引入一個polyfill來支援promise。

小程式對於介面的返回結果進行了一層統一的封裝,但是我們並不想使用這樣的結果,這個結果會導致程式碼量的增加,因此我們可以通過promise的過濾設定,只返回我們想要的東西即可。

如果能夠準確理解resolve與reject,那麼我們就能夠隨意的定製過濾規則了。

function wxPromise (cb) {
    return function (result = {}) {
        return new Promise ((resolve, reject) => {
            result.success = _res => {
                if(_res.statusCode) {
                    /(2|3)\d+/.test(_res.statusCode) ? resolve(_res.data) : reject(_res.data)
                } else {
                    resolve(_res)
                }
            }
            result.fail = (...args) => {
                reject(...args)
            }
            cb(result)
        })
    }
}複製程式碼

當然還有許多具體功能的具體實現方案,比如如何實現載入更多下拉重新整理,如何實現圖片懶載入,如何實現k線圖的繪製,如果實現本地資料同步重新整理等等等,由於時間關係,就不一一介紹了,以後有時間再跟大家分享吧。

本文主要目的是希望給大家提供一個學習方向,因此很多知識點都是一筆帶過,並未詳細講解,需要大家去其他地方加深學習

ps,姍姍來遲,由我獨立開發的《老虎淘股》小程式終於過審釋出了。[撒花],歡迎大家搜尋體驗,多提意見~ ~

你與小程式開發的距離有多遠?

相關文章