實踐)
1. 項目概述從“一鍵授權(quán)”到“用戶主動選擇”的變遷最近在重構(gòu)一個老的小程序用戶中心模塊發(fā)現(xiàn)之前獲取用戶頭像昵稱的代碼完全失效了。這讓我意識到微信小程序在用戶信息獲取這個核心功能上已經(jīng)經(jīng)歷了一場靜默但深刻的“范式轉(zhuǎn)移”。如果你還在用老舊的wx.getUserInfo接口或者對新的chooseAvatar、getUserProfile感到困惑那么這篇文章正是為你準備的。我將從一個一線開發(fā)者的視角帶你徹底理清微信小程序獲取用戶頭像和昵稱的完整路徑、背后的設(shè)計邏輯以及那些官方文檔里不會寫的“坑”和實戰(zhàn)技巧。簡單來說現(xiàn)在的規(guī)則是頭像和昵稱的獲取被徹底分離且都必須由用戶主動觸發(fā)操作才能獲得。你不能在用戶一進入小程序時就彈窗索要也不能靜默獲取。這背后是平臺對用戶隱私保護的強化。對于開發(fā)者而言這意味著我們的產(chǎn)品邏輯和交互設(shè)計需要做出相應(yīng)調(diào)整。本文將圍繞“如何合規(guī)、優(yōu)雅地獲取用戶頭像昵稱”這一核心拆解從基礎(chǔ) API 使用到高級優(yōu)化策略的全過程無論你是剛?cè)腴T的新手還是遇到適配問題的老手都能找到可落地的解決方案。2. 核心思路與方案選型理解“主動觸發(fā)”與“分離獲取”在動手寫代碼之前我們必須先理解微信設(shè)計這套新規(guī)則的核心思想。這決定了我們代碼的架構(gòu)和用戶體驗的走向。2.1 為什么放棄wx.getUserInfo在老版本中一個wx.getUserInfo接口可以一次性拿到包含avatarUrl頭像和nickName昵稱的完整用戶信息對象甚至包含性別、地區(qū)等。這種方式對開發(fā)者非常方便但帶來了嚴重的隱私問題小程序可以在用戶無感知的情況下獲取信息。因此微信逐步廢棄了這種“靜默授權(quán)”模式。注意雖然部分老小程序在特定條件下如已授權(quán)過可能還能用但所有新開發(fā)或重構(gòu)的項目絕對不要再使用wx.getUserInfo來獲取頭像昵稱。它已被官方明確標記為“即將廢棄”兼容性無法保證。2.2 新規(guī)則下的“分離獲取”模型新的模型將頭像和昵稱視為兩種獨立的用戶數(shù)據(jù)需要分別通過不同的交互由用戶主動提供頭像獲取通過button組件的open-typechooseAvatar屬性引導(dǎo)用戶點擊按鈕調(diào)起手機本地相冊或拍照界面選擇或拍攝一張圖片作為頭像。這是一個“選擇”動作。昵稱獲取通過input組件的typenickname屬性當用戶點擊這個輸入框時會自動彈出微信的昵稱填寫面板其中會展示用戶當前的微信昵稱并允許用戶修改或直接選用。這是一個“填寫/確認”動作。兩者的共同點是都必須有一個明確的 UI 組件被用戶點擊沒有任何 API 可以繞過這個交互直接獲取數(shù)據(jù)。這確保了用戶的知情權(quán)和選擇權(quán)。2.3 方案選型基礎(chǔ)表單 vs. 引導(dǎo)式彈窗基于上述規(guī)則我們通常有兩種實現(xiàn)方案方案一基礎(chǔ)表單式在“個人中心”或“信息編輯”頁面直接放置一個選擇頭像的按鈕和一個昵稱輸入框。這是最直接、最合規(guī)的方式。優(yōu)點是邏輯清晰符合用戶對表單的認知。缺點是可能不夠有引導(dǎo)性用戶可能忽略填寫。方案二引導(dǎo)式彈窗推薦在用戶首次進入小程序的關(guān)鍵流程如首次下單、發(fā)布內(nèi)容前通過自定義的彈窗Modal友好地提示用戶“為了給您更好的體驗請設(shè)置頭像和昵稱”。彈窗內(nèi)包含選擇頭像按鈕和昵稱輸入框。這種方式用戶體驗更佳轉(zhuǎn)化率更高是當前的主流實踐。在接下來的實操中我將以“引導(dǎo)式彈窗”方案為例進行詳解因為它更復(fù)雜也更能體現(xiàn)設(shè)計技巧和邊界情況處理。3. 環(huán)境準備與基礎(chǔ)代碼結(jié)構(gòu)在開始核心邏輯前我們先搭建好頁面基礎(chǔ)結(jié)構(gòu)。這里假設(shè)我們有一個profile.wxml頁面用于信息編輯同時會在首頁index.js中判斷并彈出引導(dǎo)窗。3.1 WXML 結(jié)構(gòu)定義頭像選擇與昵稱輸入首先在profile.wxml或你的彈窗組件中編寫如下結(jié)構(gòu)。關(guān)鍵點在于button和input組件的特殊屬性。!-- 部分代碼頭像選擇區(qū)域 -- view classavatar-section text點擊設(shè)置頭像/text !-- 核心1open-typechooseAvatar 的按鈕 -- button classavatar-button open-typechooseAvatar bindchooseavataronChooseAvatar image wx:if{{avatarUrl}} src{{avatarUrl}} modeaspectFill/image text wx:else/text /button /view !-- 部分代碼昵稱輸入?yún)^(qū)域 -- view classnickname-section text你的昵稱/text !-- 核心2typenickname 的輸入框 -- input classnickname-input typenickname value{{nickName}} placeholder請輸入昵稱 bindinputonNickNameInput bindbluronNickNameBlur / /view !-- 提交按鈕 -- button classsubmit-btn bindtaponSubmit disabled{{!isFormValid}}保存信息/button代碼解析與注意事項open-typechooseAvatar這是讓按鈕具備調(diào)起頭像選擇能力的魔法屬性。注意這個功能必須在button組件上使用在其他組件上無效。bindchooseavataronChooseAvatar用戶選擇頭像后的事件回調(diào)。事件對象event.detail中包含了選中的頭像圖片的臨時路徑avatarUrl。typenickname這是input組件的特殊類型。當它獲得焦點時在 iOS 和 Android 端會調(diào)起微信原生的昵稱鍵盤面板而不是普通輸入法。bindinput和bindblur用于實時監(jiān)聽昵稱輸入和失焦以便更新數(shù)據(jù)和進行驗證。一個巨坑typenickname的輸入框在微信開發(fā)者工具中模擬器上可能不會彈出原生面板而是直接允許鍵盤輸入。這容易讓開發(fā)者誤以為代碼寫錯了。請務(wù)必在真機上進行測試真機上才會出現(xiàn)標準的微信昵稱選擇界面。3.2 JS 邏輯事件處理與數(shù)據(jù)綁定在對應(yīng)的.js文件中我們需要定義事件處理函數(shù)和數(shù)據(jù)。// profile.js 或相應(yīng)頁面的 JS 文件 Page({ data: { avatarUrl: , // 頭像臨時路徑 nickName: , // 用戶輸入的昵稱 isFormValid: false // 表單是否有效用于控制提交按鈕 }, // 1. 頭像選擇事件 onChooseAvatar(e) { console.log(頭像選擇事件詳情:, e.detail); const { avatarUrl } e.detail; // avatarUrl 是一個臨時路徑形如http://tmp/wx{random}.jpg // 注意這個臨時路徑在本次小程序會話內(nèi)有效如果需要永久保存必須上傳到自己的服務(wù)器或云存儲。 this.setData({ avatarUrl: avatarUrl }); this._checkFormValid(); }, // 2. 昵稱輸入事件 onNickNameInput(e) { const value e.detail.value.trim(); // 通常建議去除首尾空格 this.setData({ nickName: value }); // 可以在這里做實時校驗比如長度限制、敏感詞過濾需后端配合 if (value.length 12) { wx.showToast({ title: 昵稱過長, icon: none }); // 可以截斷或提示 } this._checkFormValid(); }, // 3. 昵稱輸入框失焦事件可選用于最終校驗 onNickNameBlur(e) { console.log(昵稱輸入完成:, this.data.nickName); }, // 4. 檢查表單是否有效 _checkFormValid() { const { avatarUrl, nickName } this.data; // 簡單的有效性檢查頭像和昵稱都不為空 const isValid !!avatarUrl !!nickName nickName.length 0; this.setData({ isFormValid: isValid }); }, // 5. 提交表單 async onSubmit() { if (!this.data.isFormValid) { wx.showToast({ title: 請完善信息, icon: none }); return; } wx.showLoading({ title: 保存中... }); try { // 第一步上傳頭像臨時文件到永久存儲 let permanentAvatarUrl this.data.avatarUrl; // 判斷是否為臨時路徑臨時路徑通常包含 /tmp/ 字樣 if (this.data.avatarUrl.includes(/tmp/)) { const uploadResult await wx.uploadFile({ url: https://your-server.com/api/upload-avatar, // 你的服務(wù)器上傳接口 filePath: this.data.avatarUrl, name: file, formData: { userId: 123 } // 根據(jù)你的業(yè)務(wù)傳遞用戶標識 }); const res JSON.parse(uploadResult.data); if (res.code 0) { permanentAvatarUrl res.data.url; // 服務(wù)器返回的永久URL } else { throw new Error(頭像上傳失敗); } } // 第二步將昵稱和永久頭像URL發(fā)送到服務(wù)器保存 const saveResult await wx.request({ url: https://your-server.com/api/save-profile, method: POST, data: { nickName: this.data.nickName, avatarUrl: permanentAvatarUrl } }); if (saveResult.data.code 0) { wx.showToast({ title: 保存成功 }); // 保存成功后可以更新本地緩存并跳轉(zhuǎn)或關(guān)閉彈窗 getApp().globalData.userInfo { nickName: this.data.nickName, avatarUrl: permanentAvatarUrl }; wx.setStorageSync(userProfile, { nickName: this.data.nickName, avatarUrl: permanentAvatarUrl }); // 例如關(guān)閉當前彈窗或返回上一頁 wx.navigateBack(); } else { throw new Error(saveResult.data.msg || 保存失敗); } } catch (error) { console.error(保存用戶信息失敗:, error); wx.showToast({ title: 保存失敗: ${error.message}, icon: none }); } finally { wx.hideLoading(); } }, // 頁面加載時可以嘗試從緩存中讀取已保存的信息進行回顯 onLoad() { const cachedProfile wx.getStorageSync(userProfile); if (cachedProfile) { this.setData({ avatarUrl: cachedProfile.avatarUrl, nickName: cachedProfile.nickName }); this._checkFormValid(); } } })關(guān)鍵點與避坑指南臨時路徑與永久存儲chooseAvatar返回的avatarUrl是手機本地的一個臨時文件路徑這個文件在小程序本次運行期間有效關(guān)閉小程序后可能就無法訪問了。因此如果你需要持久化使用這個頭像必須調(diào)用wx.uploadFile將其上傳到你自己的服務(wù)器或云存儲如騰訊云COS、阿里云OSS并保存返回的永久URL。昵稱的typenickname這個類型的輸入框在真機上會強制調(diào)起微信的昵稱面板。用戶可以選擇使用當前微信昵稱也可以手動輸入一個新的。你獲取到的值就是用戶最終決定的內(nèi)容。開發(fā)者無法獲取用戶未選擇前的原始微信昵稱這進一步保護了隱私。異步操作與用戶體驗上傳頭像是一個網(wǎng)絡(luò)I/O操作可能會耗時。務(wù)必使用wx.showLoading給用戶等待反饋并在finally塊中隱藏。錯誤處理也要細致給用戶明確的失敗提示。數(shù)據(jù)緩存將成功保存后的信息存入globalData和Storage可以在小程序其他頁面快速讀取避免重復(fù)請求服務(wù)器。4. 高級實現(xiàn)引導(dǎo)彈窗與全局狀態(tài)管理基礎(chǔ)表單做好了現(xiàn)在我們來實現(xiàn)更優(yōu)的“引導(dǎo)式彈窗”方案。核心思路是在應(yīng)用啟動或關(guān)鍵頁面檢查用戶是否已設(shè)置頭像昵稱若未設(shè)置則彈出模態(tài)彈窗引導(dǎo)設(shè)置。4.1 創(chuàng)建全局用戶狀態(tài)檢查函數(shù)在app.js中我們可以封裝一個檢查用戶資料的函數(shù)。// app.js App({ globalData: { userProfile: null // 存儲用戶資料 }, onLaunch() { // 小程序啟動時嘗試從緩存加載用戶資料 this._loadUserProfile(); }, // 加載用戶資料 _loadUserProfile() { const profile wx.getStorageSync(userProfile); if (profile profile.avatarUrl profile.nickName) { this.globalData.userProfile profile; } }, // 檢查用戶資料是否完整可在任何頁面調(diào)用 checkUserProfileComplete() { const profile this.globalData.userProfile; // 判斷邏輯profile存在且包含必要的頭像和昵稱字段 return !!(profile profile.avatarUrl profile.nickName); }, // 更新全局用戶資料在用戶成功保存后調(diào)用 updateUserProfile(profile) { this.globalData.userProfile profile; wx.setStorageSync(userProfile, profile); } })4.2 在首頁或關(guān)鍵頁面觸發(fā)引導(dǎo)在需要引導(dǎo)用戶的頁面例如index.js可以在onShow生命周期中檢查并彈出引導(dǎo)窗。// index.js Page({ data: { showProfileModal: false // 控制引導(dǎo)彈窗顯示 }, onShow() { // 每次頁面顯示時都檢查考慮用戶可能從設(shè)置頁返回但未設(shè)置 const app getApp(); if (!app.checkUserProfileComplete()) { // 延遲一下再彈出避免與頁面加載動畫沖突提升體驗 setTimeout(() { this.setData({ showProfileModal: true }); }, 500); } }, // 關(guān)閉彈窗例如用戶點擊了“稍后再說” onCloseModal() { this.setData({ showProfileModal: false }); // 可以記錄一次跳過下次不再頻繁彈出或者設(shè)置一個過期時間 wx.setStorageSync(profile_guide_skipped, Date.now()); }, // 用戶從引導(dǎo)彈窗成功保存信息后的回調(diào) onProfileUpdateSuccess() { this.setData({ showProfileModal: false }); // 更新本頁面的用戶信息顯示 this._loadAndDisplayUserInfo(); } })對應(yīng)的index.wxml中需要添加彈窗組件!-- index.wxml 部分代碼 -- view classcontainer !-- 你的首頁主要內(nèi)容 -- text歡迎來到小程序/text /view !-- 用戶資料引導(dǎo)彈窗 -- modal wx:if{{showProfileModal}} title完善個人信息 show-cancel bindconfirmonCloseModal bindcancelonCloseModal confirm-text稍后再說 cancel-text去設(shè)置 view slotcontent !-- 這里可以嵌入一個自定義組件或者直接寫表單結(jié)構(gòu) -- !-- 為了維護方便強烈建議將頭像昵稱表單抽成自定義組件 profile-form -- profile-form bind:successonProfileUpdateSuccess / /view /modal4.3 將頭像昵稱表單抽離為自定義組件為了復(fù)用和更好的代碼組織我們將之前的表單邏輯封裝成自定義組件profile-form。1. 創(chuàng)建組件在項目根目錄創(chuàng)建components/profile-form文件夾包含profile-form.wxml,profile-form.wxss,profile-form.js,profile-form.json。2. 組件 JSON 配置// components/profile-form/profile-form.json { component: true, usingComponents: {} }3. 組件 WXML 模板將之前profile.wxml中的表單部分移植過來。4. 組件 JS 邏輯將之前profile.js中的核心邏輯移植過來但需要調(diào)整移除頁面生命周期函數(shù)如onLoad。通過properties接收外部傳入的初始值。通過triggerEvent在保存成功或失敗時向父組件發(fā)送事件。// components/profile-form/profile-form.js Component({ properties: { // 可以接收父組件傳入的初始值用于編輯模式 initAvatar: { type: String, value: }, initNickName: { type: String, value: } }, data: { avatarUrl: , nickName: , isFormValid: false }, lifetimes: { attached() { // 組件掛載時用 properties 初始化 data this.setData({ avatarUrl: this.properties.initAvatar, nickName: this.properties.initNickName }); this._checkFormValid(); } }, methods: { // onChooseAvatar, onNickNameInput, _checkFormValid 等方法與之前類似 // ... async onSubmit() { // ... 保存邏輯與之前類似 try { // ... 上傳頭像、保存到服務(wù)器 // 保存成功后 wx.showToast({ title: 保存成功 }); // 更新全局狀態(tài) getApp().updateUserProfile({ nickName: this.data.nickName, avatarUrl: permanentAvatarUrl }); // 觸發(fā)成功事件通知父組件 this.triggerEvent(success, { nickName: this.data.nickName, avatarUrl: permanentAvatarUrl }); } catch (error) { // 觸發(fā)失敗事件 this.triggerEvent(fail, { error: error.message }); } } } })這樣在首頁彈窗或獨立的個人中心頁面都可以復(fù)用這個profile-form組件極大提高了代碼的可維護性。5. 實戰(zhàn)避坑與性能優(yōu)化實錄在實際開發(fā)中我遇到了不少官方文檔沒細說的問題。這里分享幾個典型案例和解決方案。5.1 頭像選擇后的圖片處理與優(yōu)化問題用戶選擇的原圖可能非常大例如超過5MB直接上傳耗時長、浪費流量在前端顯示也可能造成卡頓。解決方案在選擇頭像后先進行本地壓縮和裁剪。onChooseAvatar(e) { const { avatarUrl } e.detail; wx.getImageInfo({ src: avatarUrl, success: (res) { // res.width, res.height 是圖片原始寬高 // 1. 使用 canvas 進行壓縮 const ctx wx.createCanvasContext(avatarCanvas); // 需要一個隱藏的canvas const canvasWidth 200; // 目標寬度 const canvasHeight 200; // 目標高度 // 繪制并壓縮圖片 ctx.drawImage(avatarUrl, 0, 0, canvasWidth, canvasHeight); ctx.draw(false, () { wx.canvasToTempFilePath({ canvasId: avatarCanvas, quality: 0.8, // 壓縮質(zhì)量 0-1 fileType: jpg, success: (compressRes) { // compressRes.tempFilePath 是壓縮后的臨時路徑 this.setData({ avatarUrl: compressRes.tempFilePath }); this._checkFormValid(); }, fail: (err) { console.error(圖片壓縮失敗:, err); // 壓縮失敗則使用原圖 this.setData({ avatarUrl }); this._checkFormValid(); } }); }); }, fail: () { // 獲取圖片信息失敗直接使用原路徑 this.setData({ avatarUrl }); this._checkFormValid(); } }); }注意使用canvas需要頁面中有一個對應(yīng)的canvas元素且注意其層級問題canvas 是原生組件層級最高??梢詫⑵涠ㄎ坏狡聊煌鈖osition: fixed; left: -9999px;隱藏。5.2 昵稱輸入框的體驗細節(jié)問題1typenickname的輸入框在 Android 和 iOS 上表現(xiàn)略有差異且開發(fā)者工具模擬器上無法測試原生面板。應(yīng)對策略真機調(diào)試是必須的。不要依賴模擬器的表現(xiàn)。在bindfocus事件中可以統(tǒng)一給輸入框一個樣式反饋如邊框高亮提升體驗一致性。對于“跳過”或“取消”昵稱設(shè)置的情況bindblur事件中e.detail.value可能為空字符串要做好處理。問題2昵稱可能包含空格、特殊字符或表情Emoji。處理方案前端可以做基礎(chǔ)校驗如長度限制、去除首尾空格。對于表情和特殊字符除非業(yè)務(wù)明確禁止否則建議允許輸入并保存。關(guān)鍵在于后端接口要做好存儲兼容性處理如數(shù)據(jù)庫字符集使用utf8mb4。在顯示昵稱時確保前端頁面字體能正常渲染 Emoji。5.3 網(wǎng)絡(luò)請求與錯誤重試問題在弱網(wǎng)環(huán)境下頭像上傳容易失敗。優(yōu)化方案實現(xiàn)一個簡單的帶重試機制的上傳函數(shù)。async _uploadAvatarWithRetry(filePath, maxRetries 3) { let lastError; for (let i 0; i maxRetries; i) { try { const uploadResult await new Promise((resolve, reject) { wx.uploadFile({ url: https://your-server.com/api/upload, filePath, name: file, success: resolve, fail: reject }); }); const res JSON.parse(uploadResult.data); if (res.code 0) { return res.data.url; // 成功返回URL } else { throw new Error(res.msg); } } catch (error) { lastError error; console.warn(頭像上傳第 ${i 1} 次失敗:, error.message); if (i maxRetries - 1) { // 不是最后一次重試等待一段時間后繼續(xù) await new Promise(resolve setTimeout(resolve, 1000 * (i 1))); // 延遲遞增 } } } // 所有重試都失敗 throw lastError; }在onSubmit函數(shù)中調(diào)用this._uploadAvatarWithRetry(this.data.avatarUrl)替代直接的上傳調(diào)用。5.4 權(quán)限拒絕與降級處理場景用戶點擊了選擇頭像按鈕但隨后在系統(tǒng)相冊權(quán)限彈窗中拒絕了授權(quán)。處理監(jiān)聽chooseAvatar的fail回調(diào)。// 在WXML中按鈕可以綁定fail事件 button open-typechooseAvatar bindchooseavataronChooseAvatar bindfailonChooseAvatarFail選擇頭像/button // 在JS中 onChooseAvatarFail(e) { console.error(選擇頭像失敗:, e.detail); // e.detail.errMsg 可能包含失敗原因如 fail auth deny if (e.detail.errMsg.indexOf(auth deny) ! -1) { wx.showModal({ title: 提示, content: 您拒絕了相冊權(quán)限無法選擇頭像。可以在手機設(shè)置中為小程序打開相冊權(quán)限。, showCancel: false }); } }對于昵稱如果用戶完全不想設(shè)置我們應(yīng)提供“跳過”選項并將引導(dǎo)狀態(tài)記錄下來短期內(nèi)不再頻繁打擾用戶。6. 兼容性處理與未來展望6.1 基礎(chǔ)庫版本兼容chooseAvatar和input的typenickname都需要一定的基礎(chǔ)庫版本支持。chooseAvatar: 從基礎(chǔ)庫 2.21.2 開始支持。input typenickname: 從基礎(chǔ)庫 2.21.2 開始支持。兼容性方案在app.json中設(shè)置最低基礎(chǔ)庫版本miniprogram: { libVersion: 2.21.2 }。這會阻止舊版本微信用戶打開小程序最為徹底。運行時動態(tài)判斷與降級如果不希望拒絕低版本用戶可以在代碼中判斷。// 檢查是否支持 chooseAvatar const isChooseAvatarSupported () { if (wx.canIUse(button.open-type.chooseAvatar)) { return true; } // 或者通過判斷 wx.chooseAvatar 是否存在 (更底層API) // return typeof wx.chooseAvatar function; return false; }; // 在頁面或組件中 onLoad() { if (!isChooseAvatarSupported()) { // 降級方案使用舊的 wx.chooseImage然后提示用戶手動輸入昵稱 wx.showModal({ title: 版本提示, content: 您的微信版本較低請更新后使用完整功能。, confirmText: 使用舊版方式, success: (res) { if (res.confirm) { this._useLegacyMethod(); } } }); } }降級方案_useLegacyMethod可以調(diào)用wx.chooseImage選擇圖片并用一個普通輸入框讓用戶填寫昵稱但無法獲取微信昵稱只能讓用戶手動輸入。6.2 與微信開放數(shù)據(jù)結(jié)合有時我們的小程序可能需要使用用戶的微信頭像昵稱在社交場景如排行榜中展示這時會用到開放數(shù)據(jù)域open-data組件。但請注意open-data組件只能用于展示無法通過 JS API 獲取到其中的數(shù)據(jù)。新的chooseAvatar和nicknameinput 獲取到的數(shù)據(jù)是用戶可以控制和修改的與開放數(shù)據(jù)域中拉取的“當前微信資料”可能不同。兩者用途不同前者用于“用戶主動提供給本小程序的資料”后者用于“在特定場景如游戲展示用戶微信資料”。不要混淆。6.3 未來可能的調(diào)整微信的隱私政策在不斷收緊。我們需要持續(xù)關(guān)注官方公告和基礎(chǔ)庫更新日志。例如未來可能會對頭像選擇增加更細粒度的權(quán)限控制如“僅本次使用”。對昵稱輸入面板的樣式或交互進行微調(diào)。提供更便捷的、用戶同意后一鍵同步微信頭像昵稱的接口但必須保持主動觸發(fā)原則。作為開發(fā)者保持代碼的靈活性和可維護性緊跟官方動態(tài)是應(yīng)對變化的最好方法。目前這套基于chooseAvatar和nicknameinput 的方案是符合當前平臺規(guī)范的最佳實踐預(yù)計在相當長一段時間內(nèi)都會是主流。