WeChat ?? ????? ????? ??? ???????? ?? ????? WeChat ?? ????? ?? ??? ??? ???? ??? ???????. ??? ?? ????!
WeChat ?? ???? ?? ???? ?? ????? ??? ? ?? ?? ??? ??? ?? ??? ????? ?? ???? ?? ? ????. weui, vant ?? ?? ?? ?? ?? ?????? ??? ? ??? ??? WeChat ???? ??? ?? ??? ???? ?? ??? ?? ?? ??? ????? ?? ????? ? ???? ????.
?? ???? ??? ?? ???? ?? ?? ??? ???? ? ????. ? ????? ?? ?? ? ?? ??? ?? ?????.
- ???? ?? ? ??
- ???? ??
???? ?? ? ??
WeChat ??? ???? ???? ??? ??? Exparser? ?? ?????. ??? ???? ????? ?? ????? ?? ??????? ?? ????, ??? ???? ? ?? ???? ?? ?? ????? Exparser? ?? ???? ?????.
??? ?? ?? ???? ??? ??? ????? ?? ??? ?????.
- index.json
- index.wxml
- index.wxss
- index.js
- index.wxs
?? ?? tab
?? ??? ?????:
??? ?? ?? ??? ??? ? json
???? comment
??? true
? ???? ???: json
文件中講 component
字段設(shè)為 true
:
{ "component": true }
在 js
文件中,基礎(chǔ)庫提供有 Page 和 Component 兩個(gè)構(gòu)造器,Page 對應(yīng)的頁面為頁面根組件,Component 則對應(yīng):
Component({ options: { // 組件配置 addGlobalClass: true, // 指定所有 _ 開頭的數(shù)據(jù)字段為純數(shù)據(jù)字段 // 純數(shù)據(jù)字段是一些不用于界面渲染的 data 字段,可以用于提升頁面更新性能 pureDataPattern: /^_/, multipleSlots: true // 在組件定義時(shí)的選項(xiàng)中啟用多slot支持 }, properties: { vtabs: {type: Array, value: []}, }, data: { currentView: 0, }, observers: { // 監(jiān)測 activeTab: function(activeTab) { this.scrollTabBar(activeTab); } }, relations: { // 關(guān)聯(lián)的子/父組件 '../vtabs-content/index': { type: 'child', // 關(guān)聯(lián)的目標(biāo)節(jié)點(diǎn)應(yīng)為子節(jié)點(diǎn) linked: function(target) { this.calcVtabsCotentHeight(target); }, unlinked: function(target) { delete this.data._contentHeight[target.data.tabIndex]; } } }, lifetimes: { // 組件聲明周期 created: function() { // 組件實(shí)例剛剛被創(chuàng)建好時(shí) }, attached: function() { // 在組件實(shí)例進(jìn)入頁面節(jié)點(diǎn)樹時(shí)執(zhí)行 }, detached: function() { // 在組件實(shí)例被從頁面節(jié)點(diǎn)樹移除時(shí)執(zhí)行 }, }, methods: { // 組件方法 calcVtabsCotentHeight(target) {} } });
如果有了解過 Vue2 的小伙伴,會(huì)發(fā)現(xiàn)這個(gè)聲明很熟悉。
在小程序啟動(dòng)時(shí),構(gòu)造器會(huì)將開發(fā)者設(shè)置的properties、data、methods等定義段,
寫入Exparser的組件注冊表中。這個(gè)組件在被其它組件引用時(shí),就可以根據(jù)這些注冊信息來創(chuàng)建自定義組件的實(shí)例。
模版文件 wxml:
<view class='vtabs'> <slot /> </view>
樣式文件:
.vtabs {}
外部頁面組件使用,只需要在頁面的 json
文件中引入
{ "navigationBarTitleText": "商品分類", "usingComponents": { "vtabs": "../../../components/vtabs", } }
在初始化頁面時(shí),Exparser 會(huì)創(chuàng)建出頁面根組件的一個(gè)實(shí)例,用到的其他組件也會(huì)響應(yīng)創(chuàng)建組件實(shí)例(這是一個(gè)遞歸的過程):
組件創(chuàng)建的過程大致有以下幾個(gè)要點(diǎn):
根據(jù)組件注冊信息,從組件原型上創(chuàng)建出組件節(jié)點(diǎn)的
JS
對象,即組件的this
;將組件注冊信息中的
data
復(fù)制一份,作為組件數(shù)據(jù),即this.data
;將這份數(shù)據(jù)結(jié)合組件
WXML
,據(jù)此創(chuàng)建出Shadow Tree
(組件的節(jié)點(diǎn)樹),由于Shadow Tree
中可能引用有其他組件,因而這會(huì)遞歸觸發(fā)其他組件創(chuàng)建過程;將
ShadowTree
拼接到Composed Tree
(最終拼接成的頁面節(jié)點(diǎn)樹)上,并生成一些緩存數(shù)據(jù)用于優(yōu)化組件更新性能;觸發(fā)組件的
created
生命周期函數(shù);如果不是頁面根組件,需要根據(jù)組件節(jié)點(diǎn)上的屬性定義,來設(shè)置組件的屬性值;
當(dāng)組件實(shí)例被展示在頁面上時(shí),觸發(fā)組件的
attached
生命周期函數(shù),如果Shadow Tree
Component({ properties: { vtabs: {type: Array, value: []}, // 數(shù)據(jù)項(xiàng)格式為 `{title}` } })
Injs< /code> ???? ?? ?????? Page? Component?? ? ?? ???? ?????. Page? ???? ???? ??? ?? ?? ???? ?? ??? ??? ????. </li><pre class='brush:php;toolbar:false;'> <vtabs vtabs="{{ vtabs }}"</vtabs></pre></ul> Vue2? ?? ??? ??? ?? ???? ?? ? ????. ?? ?????. <h2 id="strong-??-?????-????-????-????-???-??-???-???-?-??-??-???-Exparser?-??-??-??????-?????-?-??-???-??-??-????-????-???-??-???-????-???-??-??-???-?????-??-?-????-strong"><strong>?? ????? ???? ???? ???? ??? ??, ???, ??? ? ?? ?? ??? Exparser? ?? ?? ?????? ?????. ? ?? ??? ?? ?? ???? ???? ??? ?? ??? ???? ??? ?? ?? ??? ????? ?? ? ????. </strong></h2>??? ?? wxml: <p><pre class='brush:php;toolbar:false;'> <view bindtap="handleTabClick"></pre></p>??? ??: <p><pre class='brush:php;toolbar:false;'> handleClick(e) { this.triggerEvent( &#39;tabclick&#39;, { index }, { bubbles: false, // 事件是否冒泡 // 事件是否可以穿越組件邊界,為 false 時(shí),事件只在引用組件的節(jié)點(diǎn)樹上觸發(fā), // 不進(jìn)入其他任何組件的內(nèi)部 composed: false, capturePhase: false // 事件是否擁有捕獲階段 } ); }, handleChange(e) { this.triggerEvent(&#39;tabchange&#39;, { index }); },</pre></p>?? ??? ????? ????? ??? ??? ? ???? <code>json
???<vtabs vtabs="{{ vtabs }}" bindtabclick="handleTabClick" bindtabchange="handleTabChange" >
? ???? ???. ??? ??? ?????. ?? ??? ????? ??? ?? ?? ??? ?? ?? ???? ??? ?????(?? ?? ???????). ?? ?? ?? ?????? ?? ??? ?? ?? ??? ????.???? ?? ??? ?? ???? ??????? ???? ???
JS
??, ? ?????this
??? ?????. code>;???? ?? ?? ???
data
? ???? ???? ???? ?????. ?,this.data
? ???? ??? ?????.
WXML
???????? ??
(????? ?? ??)? ?????.??? ??</code?? ?? ????? ??? ? ?? ?????. >, ?? ?? ?? ??? ?? ????? ????? ??????. </span></strong></p><p> <code>ShadowTree
?Composed Tree
(????? ??? ????? ?? ??)? ???? ??? ?????. ?? ?? ???? ??? ????? ?? ???? ?????.?? ??
??
?? ?? ??? ??????.??? ?? ?? ??? ?? ?? ?? ??? ?? ?? ???? ???. ?? ?? ??? ?? ??? ?? ?? ?? ????? ???? ??? ? ?? ???
attached
?? ?? ??? ???? ?Shadow? ?? ?? ??? ?? ?? ??
, ?? ?? ?? ??? ??? ??????.selectComponent
???? ??
???? ???? ?? ? ???? ?? ?? ??? ???? ?? ??? ???, ?? ?? ?? ?? ??? ??? ?????. ?? ? ?? ?? ??? ?? ?? ?? ??? ??? ? ????. ???? ???? ??-?? ?? ?? ??? ?????. ?????????? 1 WXML ??? ??? ????????? ?? ?? ??? ???? ??? ???? ???? ? ?????. ?? ?? ??. ?????? ??? ?? ??? ?????.??<view> <vtabs-content="goods-content{{ index }}"></vtabs-content> </view>
???? ?? ?? ??: ??Page({ reCalcContentHeight(index) { const goodsContent = this.selectComponent(`#goods-content${index}`); }, })
???????? 2 ???????????? ?? ?? ??? ?? ?? ??? ???? ???? ? ???? ?? ???? ??? ? ????. ?????? ?? ???? ???? ????? ?? wxml ???? ?? ?? ??? ?? ???? ??????. ??export function jumpTo(url, options) { const baseUrl = url.split('?')[0]; // 如果 url 帶了參數(shù),需要把參數(shù)也掛載到 options 上 if (url.indexof('?') !== -1) { const { queries } = resolveUrl(url); Object.assign(options, queries, options); // options 的優(yōu)先級最高 } cosnt queryString = objectEntries(options) .filter(item => item[1] || item[0] === 0) // 除了數(shù)字 0 外,其他非值都過濾 .map( ([key, value]) => { if (typeof value === 'object') { // 對象轉(zhuǎn)字符串 value = JSON.stringify(value); } if (typeof value === 'string') { // 字符串 encode value = encodeURIComponent(value); } return `${key}=${value}`; } ).join('&'); if (queryString) { // 需要組裝參數(shù) url = `${baseUrl}?${queryString}`; } const pageCount = wx.getCurrentPages().length; if (jumpType === 'navigateTo' && pageCount < 5) { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } else { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } }
?? ?? ?? js ???? ???? ?????. ??? ??? ??? ??? ? ????. 2?? ????? ??? ??? ??? ? ???, 3?? ????? ??? ?????. ??export const resolveSearch = search => { const queries = {}; cosnt paramList = search.split('&'); paramList.forEach(param => { const [key, value = ''] = param.split('='); queries[key] = value; }); return queries; }; export const resolveUrl = (url) => { if (url.indexOf('?') === -1) { // 不帶參數(shù)的 url return { queries: {}, page: url } } const [page, search] = url.split('?'); const queries = resolveSearch(search); return { page, queries }; };
??????? ??? ???? ?? ?? ???? ?????. ??jumpTo({ url: 'pages/consignment/index', { sender: { name: 'naluduo233' } } });
????????? 3 selectComponent? ???? ?? ?? ???? ?? ???? ???????? ?? ???? ?? ?? ?? ??? ????? ??? ? ???? ?? ?? ??? ???? ??? ? ????. . ????Parent ????? wxml??const sender = JSON.parse(getParam('sender') || '{}');
??Parent ????? js??// 返回當(dāng)前頁面 export function getCurrentPage() { const pageStack = wx.getCurrentPages(); const lastIndex = pageStack.length - 1; const currentPage = pageStack[lastIndex]; return currentPage; } // 獲取頁面 url 參數(shù) export function getParams() { const currentPage = getCurrentPage() || {}; const allParams = {}; const { route, options } = currentPage; if (options) { const entries = objectEntries(options); entries.forEach( ([key, value]) => { allParams[key] = decodeURIComponent(value); } ); } return allParams; } // 按字段返回值 export function getParam(name) { const params = getParams() || {}; return params[name]; }
??selector? CSS selector? ????? ?? ??? ?????. ??- ID選擇器:
#the-id
(筆者只測試了這個(gè),其他讀者可自行測試) - class選擇器(可以連續(xù)指定多個(gè)):
.a-class.another-class
- 子元素選擇器:
.the-parent > .the-child
- 后代選擇器:
.the-ancestor .the-descendant
- 跨自定義組件的后代選擇器:
.the-ancestor >>> .the-descendant
- 多選擇器的并集:
#a-node
,.some-other-nodes
方法四 url 參數(shù)通信
在電商/物流等微信小程序中,會(huì)存在這樣的用戶故事,有一個(gè)「下單頁面A」和「貨物信息頁面B」
- 在「下單頁面 A」填寫基本信息,需要下鉆到「詳細(xì)頁面B」填寫詳細(xì)信息的情況。比如一個(gè)寄快遞下單頁面,需要下鉆到貨物信息頁面填寫更詳細(xì)的信息,然后返回上一個(gè)頁面。
- 在「下單頁面 A」下鉆到「貨物頁面B」,需要回顯「貨物頁面B」的數(shù)據(jù)。
微信小程序由一個(gè)
App()
實(shí)例和多個(gè)Page()
組成。小程序框架以棧的方式維護(hù)頁面(最多10個(gè)) 提供了以下 API 進(jìn)行頁面跳轉(zhuǎn),頁面路由如下wx.navigateTo(只能跳轉(zhuǎn)位于棧內(nèi)的頁面)
wx.redirectTo(可跳轉(zhuǎn)位于棧外的新頁面,并替代當(dāng)前頁面)
wx.navigateBack(返回上一層頁面,不能攜帶參數(shù))
wx.switchTab(切換 Tab 頁面,不支持 url 參數(shù))
wx.reLaunch(小程序重啟)
可以簡單封裝一個(gè) jumpTo 跳轉(zhuǎn)函數(shù),并傳遞參數(shù):
export function jumpTo(url, options) { const baseUrl = url.split('?')[0]; // 如果 url 帶了參數(shù),需要把參數(shù)也掛載到 options 上 if (url.indexof('?') !== -1) { const { queries } = resolveUrl(url); Object.assign(options, queries, options); // options 的優(yōu)先級最高 } cosnt queryString = objectEntries(options) .filter(item => item[1] || item[0] === 0) // 除了數(shù)字 0 外,其他非值都過濾 .map( ([key, value]) => { if (typeof value === 'object') { // 對象轉(zhuǎn)字符串 value = JSON.stringify(value); } if (typeof value === 'string') { // 字符串 encode value = encodeURIComponent(value); } return `${key}=${value}`; } ).join('&'); if (queryString) { // 需要組裝參數(shù) url = `${baseUrl}?${queryString}`; } const pageCount = wx.getCurrentPages().length; if (jumpType === 'navigateTo' && pageCount < 5) { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } else { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } }
jumpTo 輔助函數(shù):
export const resolveSearch = search => { const queries = {}; cosnt paramList = search.split('&'); paramList.forEach(param => { const [key, value = ''] = param.split('='); queries[key] = value; }); return queries; }; export const resolveUrl = (url) => { if (url.indexOf('?') === -1) { // 不帶參數(shù)的 url return { queries: {}, page: url } } const [page, search] = url.split('?'); const queries = resolveSearch(search); return { page, queries }; };
在「下單頁面A」傳遞數(shù)據(jù):
jumpTo({ url: 'pages/consignment/index', { sender: { name: 'naluduo233' } } });
在「貨物信息頁面B」獲得 URL 參數(shù):
const sender = JSON.parse(getParam('sender') || '{}');
url 參數(shù)獲取輔助函數(shù)
// 返回當(dāng)前頁面 export function getCurrentPage() { const pageStack = wx.getCurrentPages(); const lastIndex = pageStack.length - 1; const currentPage = pageStack[lastIndex]; return currentPage; } // 獲取頁面 url 參數(shù) export function getParams() { const currentPage = getCurrentPage() || {}; const allParams = {}; const { route, options } = currentPage; if (options) { const entries = objectEntries(options); entries.forEach( ([key, value]) => { allParams[key] = decodeURIComponent(value); } ); } return allParams; } // 按字段返回值 export function getParam(name) { const params = getParams() || {}; return params[name]; }
參數(shù)過長怎么辦?路由 api 不支持?jǐn)y帶參數(shù)呢?
雖然微信小程序官方文檔沒有說明可以頁面攜帶的參數(shù)有多長,但還是可能會(huì)有參數(shù)過長被截?cái)嗟娘L(fēng)險(xiǎn)。
我們可以使用全局?jǐn)?shù)據(jù)記錄參數(shù)值,同時(shí)解決 url 參數(shù)過長和路由 api 不支持?jǐn)y帶參數(shù)的問題。
// global-data.js // 由于 switchTab 不支持?jǐn)y帶參數(shù),所以需要考慮使用全局?jǐn)?shù)據(jù)存儲(chǔ) // 這里不管是不是 switchTab,先把數(shù)據(jù)掛載上去 const queryMap = { page: '', queries: {} };
更新跳轉(zhuǎn)函數(shù)
export function jumpTo(url, options) { // ... Object.assign(queryMap, { page: baseUrl, queries: options }); // ... if (jumpType === 'switchTab') { wx.switchTab({ url: baseUrl }); } else if (jumpType === 'navigateTo' && pageCount < 5) { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } else { wx.navigateTo({ url, fail: () => { wx.switch({ url: baseUrl }); } }); } }
url 參數(shù)獲取輔助函數(shù)
// 獲取頁面 url 參數(shù) export function getParams() { const currentPage = getCurrentPage() || {}; const allParams = {}; const { route, options } = currentPage; if (options) { const entries = objectEntries(options); entries.forEach( ([key, value]) => { allParams[key] = decodeURIComponent(value); } ); + if (isTabBar(route)) { + // 是 tab-bar 頁面,使用掛載到全局的參數(shù) + const { page, queries } = queryMap; + if (page === `${route}`) { + Object.assign(allParams, queries); + } + } } return allParams; }
輔助函數(shù)
// 判斷當(dāng)前路徑是否是 tabBar const { tabBar} = appConfig; export isTabBar = (route) => tabBar.list.some(({ pagePath })) => pagePath === route);
按照這樣的邏輯的話,是不是都不用區(qū)分是否是
isTabBar
頁面了,全部頁面都從 queryMap 中獲?。窟@個(gè)問題目前后續(xù)探究再下結(jié)論,因?yàn)槲夷壳斑€沒試過從頁面實(shí)例的options
中拿到的值是缺少的。所以可以先保留讀取getCurrentPages
的值。方法五 EventChannel 事件派發(fā)通信
前面我談到從「當(dāng)前頁面A」傳遞數(shù)據(jù)到被打開的「頁面B」可以通過 url 參數(shù)。那么想獲取被打開頁面?zhèn)魉偷疆?dāng)前頁面的數(shù)據(jù)要如何做呢?是否也可以通過 url 參數(shù)呢?
答案是可以的,前提是不需要保存「頁面A」的狀態(tài)。如果要保留「頁面 A」的狀態(tài),就需要使用
navigateBack
返回上一頁,而這個(gè) api 是不支持?jǐn)y帶 url 參數(shù)的。這樣時(shí)候可以使用 頁面間事件通信通道 EventChannel。
pageA 頁面
// wx.navigateTo({ url: 'pageB?id=1', events: { // 為指定事件添加一個(gè)監(jiān)聽器,獲取被打開頁面?zhèn)魉偷疆?dāng)前頁面的數(shù)據(jù) acceptDataFromOpenedPage: function(data) { console.log(data) }, }, success: function(res) { // 通過eventChannel向被打開頁面?zhèn)魉蛿?shù)據(jù) res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' }) } });
pageB 頁面
Page({ onLoad: function(option){ const eventChannel = this.getOpenerEventChannel() eventChannel.emit('acceptDataFromOpenedPage', {data: 'test'}); // 監(jiān)聽acceptDataFromOpenerPage事件,獲取上一頁面通過eventChannel傳送到當(dāng)前頁面的數(shù)據(jù) eventChannel.on('acceptDataFromOpenerPage', function(data) { console.log(data) }) } })
會(huì)出現(xiàn)數(shù)據(jù)無法監(jiān)聽的情況嗎?
小程序的棧不超過 10 層,如果當(dāng)前「頁面A」不是第 10 層,那么可以使用
navigateTo
跳轉(zhuǎn)保留當(dāng)前頁面,跳轉(zhuǎn)到「頁面B」,這個(gè)時(shí)候「頁面B」填寫完畢后傳遞數(shù)據(jù)給「頁面A」時(shí),「頁面A」是可以監(jiān)聽到數(shù)據(jù)的。如果當(dāng)前「頁面A」已經(jīng)是第10個(gè)頁面,只能使用
redirectTo
跳轉(zhuǎn)「PageB」頁面。結(jié)果是當(dāng)前「頁面A」出棧,新「頁面B」入棧。這個(gè)時(shí)候?qū)ⅰ疙撁鍮」傳遞數(shù)據(jù)給「頁面A」,調(diào)用navigateBack
是無法回到目標(biāo)「頁面A」的,因此數(shù)據(jù)是無法正常被監(jiān)聽到。不過我分析做過的小程序中,棧中很少有10層的情況,5 層的也很少。因?yàn)檎{(diào)用
wx.navigateBack
、wx.redirectTo
會(huì)關(guān)閉當(dāng)前頁面,調(diào)用wx.switchTab
會(huì)關(guān)閉其他所有非 tabBar 頁面。所以很少會(huì)出現(xiàn)這樣無法回到上一頁面以監(jiān)聽到數(shù)據(jù)的情況,如果真出現(xiàn)這種情況,首先要考慮的不是數(shù)據(jù)的監(jiān)聽問題了,而是要保證如何能夠返回上一頁面。
比如在「PageA」頁面中先調(diào)用
getCurrentPages
獲取頁面的數(shù)量,再把其他的頁面刪除,之后在跳轉(zhuǎn)「PageB」頁面,這樣就避免「PageA」調(diào)用wx.redirectTo
導(dǎo)致關(guān)閉「PageA」。但是官方是不推薦開發(fā)者手動(dòng)更改頁面棧的,需要慎重。如果有讀者遇到這種情況,并知道如何解決這種的話,麻煩告知下,感謝。
使用自定義的事件中心 EventBus
除了使用官方提供的 EventChannel 外,我們也可以自定義一個(gè)全局的 EventBus 事件中心。 因?yàn)檫@樣更加靈活,不需要在調(diào)用
wx.navigateTo
等APi里傳入?yún)?shù),多平臺的遷移性更強(qiáng)。export default class EventBus { private defineEvent = {}; // 注冊事件 public register(event: string, cb): void { if(!this.defineEvent[event]) { (this.defineEvent[event] = [cb]); } else { this.defineEvent[event].push(cb); } } // 派遣事件 public dispatch(event: string, arg?: any): void { if(this.defineEvent[event]) {{ for(let i=0, len = this.defineEvent[event].length; i<len; ++i) { this.defineEvent[event][i] && this.defineEvent[event][i](arg); } }} } // on 監(jiān)聽 public on(event: string, cb): void { return this.register(event, cb); } // off 方法 public off(event: string, cb?): void { if(this.defineEvent[event]) { if(typeof(cb) == "undefined") { delete this.defineEvent[event]; // 表示全部刪除 } else { // 遍歷查找 for(let i=0, len=this.defineEvent[event].length; i<len; ++i) { if(cb == this.defineEvent[event][i]) { this.defineEvent[event][i] = null; // 標(biāo)記為空 - 防止dispath 長度變化 // 延時(shí)刪除對應(yīng)事件 setTimeout(() => this.defineEvent[event].splice(i, 1), 0); break; } } } } } // once 方法,監(jiān)聽一次 public once(event: string, cb): void { let onceCb = arg => { cb && cb(arg); this.off(event, onceCb); } this.register(event, onceCb); } // 清空所有事件 public clean(): void { this.defineEvent = {}; } } export connst eventBus = new EventBus();
在 PageA 頁面監(jiān)聽:
eventBus.on('update', (data) => console.log(data));
在 PageB 頁面派發(fā)
eventBus.dispatch('someEvent', { name: 'naluduo233'});
小結(jié)
本文主要討論了微信小程序如何自定義組件,涉及兩個(gè)方面:
- 組件的聲明與使用
- 組件的通信
如果你使用的是 taro 的話,直接按照 react 的語法自定義組件就好。而其中的組件通信的話,因?yàn)?taro 最終也是會(huì)編譯為微信小程序,所以 url 和 eventbus 的頁面組件通信方式是適用的。后續(xù)會(huì)分析 vant-ui weapp 的一些組件源碼,看看有贊是如何實(shí)踐的。
感謝閱讀,如有錯(cuò)誤的地方請指出
【相關(guān)學(xué)習(xí)推薦:小程序開發(fā)教程】
? ??? WeChat ?? ????? ?? ??? ??? ???? ??? ?? ??? ??? ?? ?????. ??? ??? PHP ??? ????? ?? ?? ??? ?????!
- ID選擇器:

? AI ??

Undress AI Tool
??? ???? ??

Undresser.AI Undress
???? ?? ??? ??? ?? AI ?? ?

AI Clothes Remover
???? ?? ???? ??? AI ?????.

Clothoff.io
AI ? ???

Video Face Swap
??? ??? AI ?? ?? ??? ???? ?? ???? ??? ?? ????!

?? ??

??? ??

???++7.3.1
???? ?? ?? ?? ???

SublimeText3 ??? ??
??? ??, ???? ?? ????.

???? 13.0.1 ???
??? PHP ?? ?? ??

???? CS6
??? ? ?? ??

SublimeText3 Mac ??
? ??? ?? ?? ?????(SublimeText3)

Xianyu? ?? WeChat ?? ????? ??? ???????. ?? ??????? ??? ???? ???? ???/???? ????, ?? ?? ? ?? ??, ?? ?? ?? ? ? ????. ?????? Xianyu WeChat mini? ?????? ????? ?????? Xianyu WeChat ???? ??? ?????? ??: Xianyu, ?? ??, ?? ??, ???? ? ???. 1. ?? ??????? ?? ??? ??, ??? ???? ?? ???/????? ??????, ?? ?? ? ?? ??, ?? ?? ?? ?? ? ? ????. 2. ?? ???? ????? ??? ????? ????. ?? ??, ???, ? 5?? ?? 3. ????? ???? ?? WeChat ??? ????? ???.

WeChat ?? ?????? ?? ?? ?? ?? ?? ??? ??????? ??? ?? ???? ??? ??? ??? ??? ??? ?? ??? ?? ??? ???? ?? ?? ? ???? ????. WeChat ?? ??????? ?? ?? ??? ??? ? ?? ????? ?? ???? ???? ?? ?? ??? ?????. ? ????? WeChat ?? ?????? ??? ?? ??? ???? ??? ???? ???? ?? ??? ?????. ?? WeChat ???? ??? ?? ??? ???? ???? ???? ???? ???. ????? ??? ????? ??? ? ????.

WeChat ?? ?????? ???? ?? ??? ????? ???? ?? ??? ?????. ??? ???? ??? ?? WeChat ?? ????? ??? ??? ??? ??? ???? ?? ? ?? ???? ??? ?? ???? ??????. WeChat ?? ????? ?????. WeChat ?? ???? ??? ?? APP ???? ???? ???? ?? ?? ??? ???? ???. WeChat ?? ???? ???? ???? ??? ???? UI ?? ???, ? ?? ??? ??? ?????. ? ????? WeChat ????? ???? ?? ??? ???? ??? ??? ???? ???? ??? ?????.

Xianyu? ?? WeChat ?? ????? ????? ?? ??? ?? ???? ??? ? ?? ??? ???? ???? ?? ??? ???????. ?? ??????? ??? ???? ?? ??? ?? ???? ??? ? ???, ???? ? ?? ??, ??? ??? ??? ? ????. ???? WeChat ?? ?????? Xianyu? ??? ????? ????? ? ???? ?????? ?? ?? ??? ?????. ?? ?? ???? ? ??? ?? ?? ?????! Xianyu WeChat ???? ??? ?????? ??: Xianyu, ?? ??, ?? ??, ???? ? ???. 1. ?? ??????? ?? ??? ??, ??? ???? ?? ???/????? ??????, ?? ?? ? ?? ??, ?? ?? ?? ?? ? ? ????. 2. ?? ???? ????? ??? ????? ????. ?? ??, ??? ? 5?? ??.

WeChat ???? ?? ??? ??? ?????. ??? ???? ???? WeChat ???? ???? ?? ???? ?? ??? ?????. WeChat ?? ????? ??? ?????? ????? ??? ?? ??? ??? ??? ??? ??? ??? ?? ??? ?????. ? ????? WeChat ????? ??? ??? ??? ???? ??? ???? ???? ?? ??? ?????. 1. ?? ?? ?? ??? ???? ?? WeChat ??? ??? ?????? ???? WeChat ???? ???? ???. ??? WeChat? ???? ???.

WeChat ?? ?????? ??? ?? ??? ????? ???? ?? ??? ?????. WeChat ?? ????? ????? ??? ??? ?? ??? ??? ???? ?? ?????????. ?? ?????? ???? ??? ?? ??? API? ???? ??? ??? ?? ? ????. ? ? ?? ?? ??? ??????? ??? ?? ??? ?? ? ?? ???? ????? ????. WeChat ?? ?????? ??? ?? ??? ???? ?? ?????? ???? ????? API? ???? ???. ??? ??? ???? ?? ?? ?????.

WeChat ???? ???? ??? ?? ??? ????. WeChat ???? ?? ? ??? ???? ???? ?? ?????????. WeChat ?? ??????? ??? ?? ??? ???? ?? ???? ?? ?????. ? ????? WeChat ???? ???? ??? ?? ??? ?? ??? ???? ???? ?? ??? ?????. ?? WeChat ???? ??? ??? ??? ?? ??? ?????. ?? ?? <swiper> ??? ???? ???? ?? ??? ?? ? ????. ? ?? ????? b? ??? ? ????.

WeChat ?? ?????? ???? ?? ??? ????? ?? ?? ??? ?????. WeChat ?? ????? ??? ?? ???? ?? ???? ?? ?? ??? ?? ??? ???? ??? ????. ?? ???? ?? ??? ????? ???? ?? ?? ?????. ? ????? WeChat ????? ???? ?? ??? ???? ??? ??? ???? ???? ?? ??? ?????. 1. ?? ?? ?? WeChat ?? ?????? ???? ?? ??? ???? ?? ??? ?????. ?? ??: ???? ? ??? ? ?? ??? ????? ? ?? ??? ??? ????? ???.
