Taro多端開發(fā)權(quán)威指南(小程序、H5與App高效開發(fā)實(shí)戰(zhàn))_第1頁
Taro多端開發(fā)權(quán)威指南(小程序、H5與App高效開發(fā)實(shí)戰(zhàn))_第2頁
Taro多端開發(fā)權(quán)威指南(小程序、H5與App高效開發(fā)實(shí)戰(zhàn))_第3頁
Taro多端開發(fā)權(quán)威指南(小程序、H5與App高效開發(fā)實(shí)戰(zhàn))_第4頁
Taro多端開發(fā)權(quán)威指南(小程序、H5與App高效開發(fā)實(shí)戰(zhàn))_第5頁
已閱讀5頁,還剩245頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

Taro多端開發(fā)權(quán)威指南小程序、H5與App高效開發(fā)實(shí)戰(zhàn)目錄TOC\h\h第1章初識(shí)Taro\h1.1Taro介紹\h1.1.1簡介\h1.1.2特性\h1.1.3TaroUI\h1.1.4其他\h1.2ES6常用語法簡介\h1.2.1變量定義新方式——let、const\h1.2.2告別字符串拼接——模板字符串\h1.2.3優(yōu)雅獲取數(shù)組或?qū)ο笾械闹怠鈽?gòu)賦值\h1.2.4二次元函數(shù)——箭頭函數(shù)\h1.2.5異步處理——Promise\h1.2.6面向?qū)ο缶幊獭猚lass\h1.2.7模塊化——import、export\h1.3開發(fā)環(huán)境及工具介紹\h1.3.1安裝Taro腳手架工具\(yùn)h1.3.2初始化項(xiàng)目\h1.3.3運(yùn)行項(xiàng)目\h1.3.4打包項(xiàng)目\h1.3.5Taro腳手架命令\h1.4規(guī)范約定\h1.4.1項(xiàng)目組織\h1.4.2JavaScript/TypeScript書寫規(guī)范\h1.4.3組件及JSX書寫規(guī)范\h1.5本章小結(jié)\h第2章Taro基礎(chǔ)\h2.1JSX\h2.1.1JSX簡介\h2.1.2JSX語法\h2.2組件化\h2.2.1初識(shí)組件\h2.2.2組件定義\h2.2.3props\h2.2.4state\h2.2.5樣式\h2.3組件生命周期\h2.3.1組件掛載\h2.3.2組件更新\h2.3.3組件卸載\h2.4事件處理\h2.5路由功能\h2.6實(shí)戰(zhàn)案例:受控與非受控Input組件\h2.7本章小結(jié)\h第3章Taro進(jìn)階\h3.1組件設(shè)計(jì)\h3.2組件通信\h3.2.1父子組件通信\h3.2.2兄弟組件通信\h3.2.3更復(fù)雜的組件通信\h3.3服務(wù)端通信\h3.3.1Taro.request\h3.3.2請(qǐng)求終止\h3.3.3請(qǐng)求攔截器\h3.4使用Ref\h3.5本章小結(jié)\h第4章集中狀態(tài)管理\h4.1Redux\h4.1.1Redux設(shè)計(jì)理念\h4.1.2在Taro中使用Redux\h4.1.3Redux案例\h4.2MobX\h4.2.1MobX設(shè)計(jì)理念\h4.2.2在Taro中使用MobX\h4.3本章小結(jié)\h第5章Hooks\h5.1Hooks簡介\h5.1.1class組件的不足\h5.1.2Hooks概覽\h5.1.3Hooks規(guī)則\h5.2Hooks基礎(chǔ)\h5.2.1useState\h5.2.2useEffect\h5.2.3useReducer\h5.2.4useCallback\h5.2.5useMemo\h5.2.6useRef\h5.2.7useContext\h5.2.8其他Hooks\h5.3自定義Hooks\h5.4本章小結(jié)\h第6章多端開發(fā)\h6.1編譯配置與約定\h6.1.1編譯配置\h6.1.2設(shè)計(jì)稿與尺寸單位約定\h6.2多端開發(fā)方案\h6.2.1內(nèi)置環(huán)境變量\h6.2.2統(tǒng)一接口的多端文件\h6.2.3指定解析node_modules包中的多端文件\h6.3多端同步調(diào)試與打包\h6.4本章小結(jié)\h第7章TaroUI\h7.1安裝及使用\h7.1.1快速上手\h7.1.2自定義主題\h7.2組件介紹\h7.3本章小結(jié)\h第8章插件機(jī)制\h8.1插件機(jī)制簡介\h8.2插件使用\h8.3自定義插件\h8.3.1插件結(jié)構(gòu)\h8.3.2插件使用場(chǎng)景\h8.3.3插件環(huán)境變量\h8.3.4插件方法\h8.4本章小結(jié)\h第9章性能優(yōu)化與原理剖析\h9.1性能優(yōu)化\h9.1.1Prerender\h9.1.2虛擬列表\h9.1.3組件更新條件\h9.2Taro框架原理\h9.2.1Taro框架結(jié)構(gòu)分析\h9.2.2Taro編譯原理\h9.2.3Taro運(yùn)行時(shí)\h9.3Taro3.x原理概述\h9.4本章小結(jié)\h第10章多端開發(fā)環(huán)境搭建\h10.1微信小程序開發(fā)環(huán)境搭建\h10.2支付寶小程序開發(fā)環(huán)境搭建\h10.3ReactNative開發(fā)環(huán)境搭建\h10.3.1在macOS系統(tǒng)下搭建iOS開發(fā)環(huán)境\h10.3.2在macOS系統(tǒng)下搭建Android開發(fā)環(huán)境\h10.3.3在Windows系統(tǒng)下搭建Android開發(fā)環(huán)境\h10.3.4使用Taro開發(fā)iOS應(yīng)用\h10.3.5使用Taro開發(fā)Android應(yīng)用\h10.4本章小結(jié)\h第11章閑置換App開發(fā)實(shí)踐\h11.1項(xiàng)目介紹\h11.1.1項(xiàng)目背景\h11.1.2項(xiàng)目需求\h11.1.3項(xiàng)目核心功能設(shè)計(jì)\h11.1.4項(xiàng)目架構(gòu)設(shè)計(jì)\h11.1.5項(xiàng)目接口mock\h11.2基礎(chǔ)功能開發(fā)\h11.2.1通用請(qǐng)求庫封裝\h11.2.2引入dva\h11.2.3定義請(qǐng)求服務(wù)\h11.2.4為H5配置請(qǐng)求代理\h11.3自定義導(dǎo)航器\h11.3.1需求分析\h11.3.2微信小程序端開發(fā)\h11.3.3H5端開發(fā)\h11.3.4ReactNative端開發(fā)\h11.4首頁開發(fā)\h11.4.1搜索組件\h11.4.2瀑布流圖片組件\h11.4.3輪播圖組件\h11.4.4數(shù)據(jù)聯(lián)調(diào)\h11.5消息頁開發(fā)\h11.5.1定義底部導(dǎo)航\h11.5.2消息列表頁開發(fā)\h11.5.3聊天頁面開發(fā)\h11.6商品詳情頁開發(fā)\h11.7項(xiàng)目優(yōu)化與發(fā)布\h11.7.1項(xiàng)目優(yōu)化\h11.7.2項(xiàng)目打包發(fā)布\h11.8本章小結(jié)\h第12章?lián)肀aro3\h12.1Taro演進(jìn)歷程\h12.1.1Taro1.x\h12.1.2Taro2.x\h12.1.3Taro3.x\h12.2使用Taro3\h12.2.1React模板\h12.2.2Vue模板\h12.3本章小結(jié)第1章初識(shí)Taro如今,小程序百花爭艷,好一派繁華;Web、原生應(yīng)用開發(fā)“踩雷”不斷,著實(shí)讓人焦慮。我們前端人不停探索,不停找尋多端開發(fā)方案,由此催生出了很多優(yōu)秀框架,其中就有Taro。使用Taro,你只需編寫一次代碼就可以編譯生成各端應(yīng)用,真正提高了小程序、Web開發(fā)效率。另外,由于成書時(shí),Taro3.x不支持ReactNative端開發(fā),所以書中的知識(shí)及實(shí)例以2.2.13版本為準(zhǔn)。如果你想了解更多關(guān)于Taro版本演進(jìn)與Taro3.x的知識(shí),可查看第12章的詳細(xì)介紹。1.1Taro介紹1.1.1簡介Taro是一套遵循React語法規(guī)范的多端開發(fā)解決方案,甚至在Taro3.0及以上版本可以選用Vue、React或Nerv作為開發(fā)規(guī)范。Taro遵循React語法,但和React并沒有直接關(guān)系。Taro底層使用了京東團(tuán)隊(duì)開發(fā)的Nerv框架,該框架語法接近React。面對(duì)微信小程序、京東小程序、百度小程序、支付寶小程序、字節(jié)跳動(dòng)小程序、快應(yīng)用、H5、ReactNative開發(fā),我們深感疲憊,假如只編寫一套代碼就能適配這里列舉的各種端,豈不快哉?不妨,先想象一下writeonce,runanywhere,是多么令人神往。1.1.2特性1.類似React的語法風(fēng)格Taro遵循React語法規(guī)范,它采用與React一致的組件化思想、組件生命周期、JSX語法等,如此,將開發(fā)學(xué)習(xí)的成本降到最低。只要你使用過React,就可以使用Taro來快速開發(fā)多端應(yīng)用,從而降低學(xué)習(xí)成本,提升開發(fā)體驗(yàn)。Taro基本用法的代碼示例如下:上面這段代碼展示了Taro構(gòu)建多端應(yīng)用的基本寫法,其中包括頁面元素、頁面數(shù)據(jù)、組件生命周期。遺憾的是,因?yàn)樵缙赥aro架構(gòu)限制,無法完全支持React所有的JSX語法。為了解決這一問題,Taro制定了對(duì)應(yīng)的語法規(guī)范,關(guān)于規(guī)范約定的詳細(xì)內(nèi)容請(qǐng)參閱1.4節(jié)的內(nèi)容。2.快速開發(fā)小程序Taro立足于微信小程序開發(fā)。眾所周知,微信小程序的開發(fā)體驗(yàn)不太友好,如經(jīng)常會(huì)被提及的這些問題:·小程序中無法使用npm來做第三方庫的管理?!o法使用新的ES規(guī)范。針對(duì)這些問題,Taro改良并提供了以下優(yōu)秀特性:·支持使用npm/yarn安裝管理第三方依賴?!ぶС质褂肊S7/ES8甚至更新的ES規(guī)范,一切都可以自行進(jìn)行配置。·支持使用CSS預(yù)編譯器,如Sass、Less等。·支持使用Redux、MobX等進(jìn)行狀態(tài)管理?!ば〕绦駻PI優(yōu)化,異步APIPromise化等。3.支持多端開發(fā)轉(zhuǎn)化Taro方案是在實(shí)踐中總結(jié)出的快速打造多端開發(fā)應(yīng)用的解決方案。目前通過Taro編寫的代碼能夠編譯為可以運(yùn)行在微信/京東/百度/支付寶/字節(jié)跳動(dòng)/QQ小程序的快應(yīng)用、H5及原生應(yīng)用(ReactNative)。1.1.3TaroUITaro解決了跨端開發(fā)規(guī)范的問題,但依然存在其他問題,如界面一致性。經(jīng)過社區(qū)不斷完善,催生出了TaroUI——提供多端界面風(fēng)格統(tǒng)一方案。其主要特性如下:·基于Taro開發(fā)的UI組件?!ひ惶捉M件可以在微信/支付寶/百度小程序、H5多端適配運(yùn)行(ReactNative端暫不支持)?!ぬ峁﹥?yōu)化的API,可靈活地使用組件。1.1.4其他學(xué)習(xí)是一個(gè)枯燥的過程,在學(xué)習(xí)Taro的過程中,無論你有任何問題或者建議,都可以訪問Taro官網(wǎng)查找資料或者提出相關(guān)建議。如果你經(jīng)常使用GitHub,也可搜索awesome-taro查看更多學(xué)習(xí)資源。1.2ES6常用語法簡介ECMAScript是JavaScript語言標(biāo)準(zhǔn),ECMAScript又有多個(gè)版本,目前我們使用最多的版本是ECMAScript6,簡稱ES6,使用最新語法能夠帶給我們更順暢、更高效的開發(fā)體驗(yàn)。正因如此,在學(xué)習(xí)Taro或者React之前,都應(yīng)該好好學(xué)習(xí)一下ES6甚至更新的語法規(guī)范,所以本節(jié)先整體介紹項(xiàng)目開發(fā)中使用最多的一些ES6語法。1.2.1變量定義新方式——let、const曾經(jīng),我們只知道var可以聲明一個(gè)變量,并且在項(xiàng)目中大量使用。不知道你是否遇到過類似以下的問題。1.聲明一個(gè)變量后,在下面的代碼中又聲明了一次,程序依然能夠運(yùn)行以上代碼在正常情況下的表現(xiàn)差強(qiáng)人意,但如果我們現(xiàn)在在做圓周長的計(jì)算,定義了一個(gè)變量表示圓周率π,不幸的是同事也使用了這個(gè)變量名。在理想情況下,我們這樣定義變量:如果同事在開發(fā)過程中,也定義了相同的變量名,則會(huì)出錯(cuò)。例如:在這個(gè)需求里,任何時(shí)候都不希望PI的值發(fā)生改變,這樣的值在程序中被稱為常量,在ES6中使用const聲明即可。如果在后面的代碼中,const聲明的變量值被修改,則會(huì)拋出錯(cuò)誤,從而提示開發(fā)者。2.定義變量前使用這個(gè)變量,不會(huì)報(bào)錯(cuò),而是會(huì)告訴你它是undefined這個(gè)問題在面試中也會(huì)經(jīng)常被問及,原因是var聲明的變量會(huì)被提升至作用域頂端,我們把這個(gè)特性叫作變量提升。這個(gè)特性會(huì)在開發(fā)過程中引入很多問題,因此不建議使用,我們可以使用let或const聲明變量來規(guī)避這個(gè)問題。3.作用域問題關(guān)于作用域問題,有一個(gè)很經(jīng)典的例子。我們通常使用setTimeout定時(shí)器來定義一個(gè)期望在未來執(zhí)行的操作,代碼如下:執(zhí)行以上代碼,我們期望在1s后,打印從0到4這5個(gè)數(shù)字,但最終輸出的結(jié)果卻是5個(gè)5,為什么呢?其實(shí)問題出在作用域上,我們可以使用let聲明變量,從而生成一個(gè)暫時(shí)性死區(qū),來解決這個(gè)問題。代碼示例如下:1.2.2告別字符串拼接——模板字符串模板字符串是對(duì)字符串的增強(qiáng),使用模板字符串替代普通字符串拼接能提高代碼可讀性。模板字符串使用反引號(hào)(`)標(biāo)識(shí),除了實(shí)現(xiàn)字符串拼接,還能在字符串中使用表達(dá)式或者已定義的變量,進(jìn)一步增強(qiáng)字符串能力。例如:1.2.3優(yōu)雅獲取數(shù)組或?qū)ο笾械闹怠鈽?gòu)賦值ES6獲取已定義數(shù)組或?qū)ο笾械膶傩愿憬?,以前在編寫代碼時(shí),獲取數(shù)組或?qū)ο笾械闹档姆绞饺缦拢菏褂媒鈽?gòu)賦值,可以提升代碼的可讀性。例如:深層結(jié)構(gòu)的數(shù)組或?qū)ο笠粯涌捎?。例如:rest元素或?qū)傩越鈽?gòu)賦值。例如:1.2.4二次元函數(shù)——箭頭函數(shù)箭頭函數(shù)可以更方便快捷地創(chuàng)建一個(gè)函數(shù),并且箭頭函數(shù)中的this指向函數(shù)定義時(shí)所在的上下文環(huán)境,規(guī)避this指向偏移發(fā)生的問題。值得一提的是,不能使用箭頭函數(shù)定義構(gòu)造器。例如:如果函數(shù)體包含多條語句,就需要使用大括號(hào)將代碼括起來,并使用return返回函數(shù)值。例如:1.2.5異步處理——Promise前端開發(fā)時(shí)常伴隨異步處理,在過去很長一段時(shí)間里,都是使用回調(diào)函數(shù)處理異步操作。假設(shè)我們現(xiàn)在停頓一秒計(jì)算1+2得到結(jié)果3,而后停頓一秒計(jì)算3+4,如果使用回調(diào)函數(shù),可能需要這樣編寫:回調(diào)函數(shù)嵌套問題如此可見一斑,這個(gè)問題叫作回調(diào)地獄。為了解決上述問題,ES6引入了可讀性更高的特性支持異步處理,那就是Promise。Promise允許使用鏈?zhǔn)秸{(diào)用方式處理異步隊(duì)列。使用Promise重寫上述操作,示例如下:這樣,嵌套的問題是不是解決了,代碼的可讀性是不是更高了?如果再使用async,就更易讀了,示例如下:有了這些語法特性,更方便處理異步操作,使用Taro開發(fā)項(xiàng)目時(shí)也會(huì)經(jīng)常用到這些語法。1.2.6面向?qū)ο缶幊獭猚lass以前的JavaScript面向?qū)ο缶幊滩患兇?,ES6引入了類的概念,是原型繼承的另一種書寫形式。代碼示例如下:有了類的概念,就可以輕松實(shí)現(xiàn)繼承,例如現(xiàn)在有一個(gè)類表示人,我們需要在這個(gè)類中派生出一個(gè)代表女性的類,使用extends關(guān)鍵字即可輕松實(shí)現(xiàn),代碼示例如下:1.2.7模塊化——import、exportES6實(shí)現(xiàn)了模塊化標(biāo)準(zhǔn),可以使用export導(dǎo)出模塊,import導(dǎo)入模塊。例如:以上介紹的相關(guān)內(nèi)容是我們?cè)谑褂肨aro開發(fā)小程序過程中經(jīng)常會(huì)使用的ES6語法,如果你想了解更多關(guān)于ES6的語法知識(shí),可自行查閱相關(guān)資料學(xué)習(xí)。1.3開發(fā)環(huán)境及工具介紹Taro項(xiàng)目開發(fā)依賴Node.js環(huán)境,并且要求Node.js版本高于8.0.0,Taro允許使用大多npm中的庫,支持更友好的第三方依賴管理。如果你剛接觸Taro,那么推薦使用Taro提供的腳手架工具創(chuàng)建項(xiàng)目,同時(shí)該工具提供了很多功能,譬如診斷依賴、創(chuàng)建模塊、更新包、打包構(gòu)建等。我們就從安裝Taro腳手架開始吧!1.3.1安裝Taro腳手架工具你可以選用npm或者Yarn全局安裝@tarojs/cli,或者使用npx。不過由于Node.js版本限制等問題,推薦使用nvm這一工具來管理Node.js版本。使用npm全局安裝Taro腳手架:使用Yarn全局安裝Taro腳手架:安裝過程可能會(huì)提示Sass相關(guān)依賴安裝錯(cuò)誤,這時(shí)請(qǐng)終止,然后手動(dòng)安裝mirror-config-china后重試。安裝命令如下:1.3.2初始化項(xiàng)目上一節(jié)已經(jīng)成功安裝Taro腳手架工具,現(xiàn)在只需一行命令就能創(chuàng)建出基礎(chǔ)Taro項(xiàng)目了,命令如下:如果你的npm版本大于5.2,則可以直接使用npx創(chuàng)建項(xiàng)目:項(xiàng)目模板及相關(guān)配置文件創(chuàng)建完成以后,Taro會(huì)自動(dòng)安裝項(xiàng)目中所需要的相關(guān)依賴。為了提升安裝速度,Taro內(nèi)部會(huì)為我們按照Yarn、cnpm、npm的順序檢測(cè)并選擇更快的方式去安裝依賴。如果在依賴安裝過程中出現(xiàn)錯(cuò)誤導(dǎo)致安裝終止,則可以進(jìn)入項(xiàng)目的根目錄嘗試手動(dòng)安裝。1.3.3運(yùn)行項(xiàng)目Taro開發(fā)環(huán)境的啟動(dòng)命令較多,分別對(duì)應(yīng)不同端的代碼編譯與調(diào)試,但是為了更方便記憶與語義化,Taro定義了相對(duì)一致的開發(fā)環(huán)境啟動(dòng)命令,以npm運(yùn)行命令為例,如下表所示。通過以上命令,可以將Taro項(xiàng)目編譯為不同端開發(fā)環(huán)境的代碼。這時(shí),只需要使用各端(除了H5)對(duì)應(yīng)的開發(fā)工具,打開編譯生成的項(xiàng)目文件,即可預(yù)覽調(diào)試。以微信小程序?yàn)槔海?)運(yùn)行針對(duì)微信小程序的編譯命令:npmrundev:weapp。(2)使用微信小程序開發(fā)工具,打開該項(xiàng)目目錄下的dist文件夾,即可在微信小程序開發(fā)者工具中進(jìn)行預(yù)覽與調(diào)試。如果你需要同時(shí)調(diào)試預(yù)覽多端應(yīng)用,則需要修改項(xiàng)目下的config/index.js文件,配置outputRoot參數(shù):注:Taro1.3.5+支持該配置,請(qǐng)確保項(xiàng)目中各端打包與編譯相關(guān)的依賴版本和@tarojs/cli版本一致。1.3.4打包項(xiàng)目Taro的打包命令同樣有多個(gè),也分別對(duì)應(yīng)不同端的線上環(huán)境代碼生成。為了方便記憶與語義化,Taro定義了相對(duì)一致的打包線上環(huán)境的代碼命令,以npm為例,如下表所示。通過以上命令,可以將Taro項(xiàng)目編譯為不同端線上環(huán)境的代碼,這時(shí)只需要使用各端(除了H5)對(duì)應(yīng)的開發(fā)工具發(fā)布項(xiàng)目即可。打包生成線上環(huán)境的代碼相較運(yùn)行本地開發(fā)環(huán)境的代碼,做了更多優(yōu)化相關(guān)的處理,例如JavaScript代碼壓縮丑化等。1.3.5Taro腳手架命令Taro腳手架提供了很多功能輔助我們開發(fā),可使用taro--help查看Taro腳手架工具的相關(guān)提示。這里給大家講解開發(fā)過程中常使用的幾個(gè)命令,更多命令可前往Taro官網(wǎng)查看學(xué)習(xí)。1.更新——update該命令用來更新項(xiàng)目中的Taro相關(guān)依賴或者更新自身的腳手架工具。更新項(xiàng)目依賴:如果用以上方法更新項(xiàng)目依賴失敗,則可嘗試修改package.json文件指定對(duì)應(yīng)的依賴版本,然后使用npm或Yarn手動(dòng)安裝。更新腳手架:注:以上[version]為選填項(xiàng),通過執(zhí)行對(duì)應(yīng)的版本號(hào),安裝或更新至對(duì)應(yīng)的版本。2.環(huán)境及依賴信息——info該命令用來檢測(cè)Taro環(huán)境及依賴的版本等信息,從而方便開發(fā)者查看項(xiàng)目環(huán)境及依賴,更便捷地排查因開發(fā)環(huán)境引起的問題。用法如下:命令執(zhí)行完畢,會(huì)打印出項(xiàng)目的相關(guān)信息,示例如下:這樣一來,我們可以發(fā)現(xiàn)當(dāng)前使用的Taro腳手架工具版本為2.2.4。但項(xiàng)目中的依賴版本卻是2.1.6,此時(shí)需要更新項(xiàng)目依賴,以保證與Taro腳手架工具版本一致,更新命令為:3.項(xiàng)目診斷——doctor該命令可以診斷項(xiàng)目的依賴、設(shè)置、結(jié)構(gòu)及代碼的規(guī)范是否存在問題,診斷結(jié)束后會(huì)嘗試給出對(duì)應(yīng)問題的解決方案。使用示例如下:1.4規(guī)范約定我們提到Taro和React并沒有直接聯(lián)系,Taro支持JSX等語法規(guī)范得益于Nerv框架,而Nerv與React支持的語法特性略有偏差,因此開發(fā)之前我們約法三章,以規(guī)避一些開發(fā)過程中可能遇到的問題。1.4.1項(xiàng)目組織項(xiàng)目組織有很多方案,以下所列建議為最佳實(shí)踐方案。1.文件組織所有項(xiàng)目的源碼都放在項(xiàng)目的根目錄src下,項(xiàng)目所需的最基本文件包括入口文件和頁面文件?!と肟谖募閍pp.js?!ろ撁嫖募ㄗh放置在src/pages目錄下。一個(gè)可靠的Taro項(xiàng)目可以按照如下方式進(jìn)行組織:2.文件命名與文件后綴名(1)在Taro項(xiàng)目中,普通JavaScript或TypeScript文件以小寫字母命名,多個(gè)單詞之間以下畫線連接,如util.js、util_helper.js。(2)在Taro項(xiàng)目中,組件文件命名遵循Pascal命名法,如ReservationCard.jsx。(3)在Taro項(xiàng)目中,普通JavaScript或TypeScript文件以.js或者.ts為文件后綴名。(4)在Taro項(xiàng)目中,組件以.jsx或者.tsx為文件后綴名。這不是強(qiáng)制約束,只是作為一個(gè)實(shí)踐的建議。如果你希望組件以.js或者.ts為文件后綴名,也依然可行。1.4.2JavaScript/TypeScript書寫規(guī)范關(guān)于JavaScript/TypeScript書寫規(guī)范,可以使用Eslint做代碼規(guī)范檢查。在項(xiàng)目創(chuàng)建時(shí),Taro就已經(jīng)創(chuàng)建了.eslintrc文件并安裝了Eslint,你在開發(fā)過程中遵循提示即可。若Taro創(chuàng)建的.eslintrc文件中定義的規(guī)范不能完全滿足你的需求,你也可以查閱Eslint配置文檔,根據(jù)團(tuán)隊(duì)制定的規(guī)范,配置規(guī)范約定。以下是.eslintrc配置片段:1.4.3組件及JSX書寫規(guī)范·使用兩個(gè)空格進(jìn)行縮進(jìn),不要混合使用空格與制表符作為縮進(jìn)。·JSX屬性均使用單引號(hào)?!ざ鄠€(gè)屬性,多行書寫,每個(gè)屬性占用一行,標(biāo)簽結(jié)束另起一行?!ぎ?dāng)標(biāo)簽沒有子元素時(shí),始終使用自閉合標(biāo)簽。·終始在自閉合標(biāo)簽前面添加一個(gè)空格?!傩悦Q始終使用駝峰式命名法?!び美ㄌ?hào)包裹多行JSX標(biāo)簽。·Taro組件中包含類靜態(tài)屬性、類屬性、生命周期等類成員,其書寫順序最好遵循以下約定(順序從上至下):?static靜態(tài)方法?constructor?componentWillMount?componentDidMount?componentWillReceiveProps?shouldComponentUpdate?componentWillUpdate?componentDidUpdate?componentWillUnmount?事件處理,如handleClick?render以上規(guī)范約定只是官方建議,開發(fā)者在具體使用過程中可根據(jù)公司團(tuán)隊(duì)或社區(qū)提供的規(guī)范來制定約束,由于內(nèi)容較多且屬于基本知識(shí),因此這里只進(jìn)行簡單介紹。如果想查看學(xué)習(xí)書寫規(guī)范的相關(guān)內(nèi)容,可參閱Taro官方文檔中關(guān)于書寫規(guī)范部分的內(nèi)容。1.5本章小結(jié)本章介紹了Taro的誕生背景、基本理念及主要特性,同時(shí)介紹了使用Taro進(jìn)行開發(fā)前需要掌握的ES6常用的語法知識(shí)、Taro腳手架及規(guī)范約定。相信大家已經(jīng)對(duì)使用Taro開發(fā)跨端應(yīng)用有了基本認(rèn)識(shí)。從第2章開始,我們正式進(jìn)入Taro開發(fā)基礎(chǔ)與最佳實(shí)踐的講解。第2章Taro基礎(chǔ)本章內(nèi)容圍繞Taro基礎(chǔ)知識(shí)展開,主要包括以下內(nèi)容:·JSX·組件化·組件生命周期·事件處理·路由功能·實(shí)戰(zhàn)案例:受控與非受控Input組件開發(fā)學(xué)習(xí)完本章內(nèi)容,你將基本掌握Taro的基礎(chǔ)核心知識(shí)點(diǎn)。實(shí)戰(zhàn)案例引導(dǎo)大家掌握組件狀態(tài)設(shè)計(jì)與用戶交互實(shí)現(xiàn)方法,從而深刻理解組件狀態(tài)的設(shè)計(jì)思想。2.1JSX2.1.1JSX簡介JSX是JavaScriptXML的縮寫,是一種用來描述UI的JavaScript語法糖(SyntacticSugar),Taro支持使用該語法來描述組件的UI表現(xiàn)。初學(xué)JSX,你可能會(huì)抱怨該語法的零散與隨意,無法理解在JavaScript代碼中直接書寫HTML代碼。如果你此前使用原生JavaScript或jQuery開發(fā)過大型應(yīng)用,則你一定會(huì)抱怨代碼耦合度過高、代碼可維護(hù)性差、團(tuán)隊(duì)協(xié)同效率低,而正確使用JSX能很好地解決這些問題。初學(xué)時(shí),你所謂的這些缺點(diǎn)正是它的優(yōu)點(diǎn),寫法隨意,可讀性更好,更有利于組件封裝與復(fù)用,特別是結(jié)合Hooks使用,可以輕松做到狀態(tài)與視圖解耦合,從而使組件復(fù)用“更上一層樓”。2.1.2JSX語法1.基礎(chǔ)語法JSX通俗理解就是支持在JavaScript代碼中書寫HTML代碼,將JavaScript交互操作與HTML界面定義完美結(jié)合。我們來看使用JSX的一段簡單的代碼:通過以上代碼,我們驚奇地發(fā)現(xiàn),可以將類似HTML代碼片段賦值給一個(gè)變量,這種語法就是JSX。注:此處列舉的View是Taro提供的視圖組件,暫且可以將該組件理解為HTML中的div元素。關(guān)于組件的具體內(nèi)容請(qǐng)查看下節(jié)內(nèi)容。2.值渲染與計(jì)算既然是JavaScript與HTML的完美結(jié)合,也就是說,我們還可以在類似HTML代碼片段中使用JavaScript變量或執(zhí)行運(yùn)算等,在HTML中使用JavaScript變量的示例如下:在HTML中執(zhí)行運(yùn)算:通過以上兩個(gè)實(shí)例不難發(fā)現(xiàn),在類似HTML代碼片段中使用JavaScript,只需使用{}操作符即可??梢岳斫鉃閧}中的內(nèi)容是需要交給JavaScript去計(jì)算并輸出最終結(jié)果的。3.屬性我們?cè)诰帉慔TML代碼時(shí),經(jīng)常會(huì)在標(biāo)簽中定義很多屬性,例如:·class,指定類名?!tyle,定義標(biāo)簽樣式?!ぁ贘SX語法中同樣可以定義屬性,但值得一提的是,class等眾多DOM屬性在JavaScript中是關(guān)鍵字或保留字,所以不能使用。例如,在書寫JSX時(shí)需要用className代替class,因?yàn)閏lass在JavaScript中為關(guān)鍵字:4.條件渲染有時(shí)組件UI需要根據(jù)特定條件渲染特定內(nèi)容,例如定義了一個(gè)變量flag,當(dāng)變量為true時(shí),頁面顯示“真”;當(dāng)變量為false時(shí),頁面顯示“假”:5.列表渲染使用諸如數(shù)組結(jié)構(gòu)數(shù)據(jù)時(shí),需要遍歷數(shù)據(jù)計(jì)算的最終UI結(jié)果。例如定義了一個(gè)數(shù)組變量fruits,數(shù)組中包含apple、peach、pear,遍歷該數(shù)組并返回結(jié)果:注:列表渲染一定要在生成的每一項(xiàng)中都添加唯一的key。2.2組件化長期以來,前端開發(fā)者都在探索如何更好地管理項(xiàng)目模塊,都在思考如何設(shè)計(jì)各模塊中類似的UI及邏輯以達(dá)到高效復(fù)用的目的。早期我們通過定義通用代碼文件,在項(xiàng)目中通過script標(biāo)簽引入方式完成復(fù)用,這種方式確實(shí)能在一定程度上實(shí)現(xiàn)通用代碼復(fù)用、對(duì)應(yīng)模塊版本管理等需求,但在大型項(xiàng)目中,這種方式會(huì)顯得很脆弱,模塊之間的依賴管理能力欠缺。后來,有了Bower、Grunt、Gulp等,解決了模塊文件或依賴間的控制問題。再后來,有了Webpack,有了各種模塊化規(guī)范,如AMD、CMD、Commonjs、ESmodule等,前端開發(fā)才進(jìn)入一個(gè)新的世紀(jì)。一路進(jìn)化,最終組件化、MVC、面向?qū)ο缶幊?、函?shù)式編程等思想才得以迸發(fā)。2.2.1初識(shí)組件首先來看一個(gè)例子,下圖是京東商城首頁,我們站在開發(fā)者的角度來分析一下這個(gè)網(wǎng)頁的頁面結(jié)構(gòu)。在使用Taro開發(fā)這個(gè)頁面時(shí),首先考慮將頁面內(nèi)容拆分為圖中標(biāo)注的6個(gè)模塊,設(shè)計(jì)好模塊間的數(shù)據(jù)與UI交互之后,便可以單獨(dú)開發(fā)每個(gè)模塊,最終組合各個(gè)模塊,完成開發(fā)。這里拆解的6個(gè)部分,正是6個(gè)獨(dú)立組件。2.2.2組件定義Taro中的組件分為兩種,一種是基于類創(chuàng)建的組件,被稱為類組件;一種是基于函數(shù)創(chuàng)建的組件,被稱為函數(shù)組件。1.類組件定義類組件是一件很容易的事情,你只需要定義一個(gè)類,這個(gè)類繼承自Taro.Component,且在組件中定義render方法并返回值即可,代碼示例如下:當(dāng)然,還有為了做優(yōu)化提供的另一種類組件,關(guān)于該組件的原理與用法,我們將在實(shí)戰(zhàn)優(yōu)化部分進(jìn)行詳細(xì)介紹,代碼示例如下:2.函數(shù)組件函數(shù)組件相較于類組件,定義更便捷,使用更靈活,尤其搭配Hooks使用能夠在某些場(chǎng)景下替代類組件。函數(shù)組件的定義如下:或者使用箭頭函數(shù),寫法如下:注:在組件中,無論是否使用Taro這個(gè)對(duì)象,都應(yīng)該將@tarojs/taro包引入。無論組件返回值多么簡單,都盡量使用@tarojs/components提供的組件包裹,而不應(yīng)該直接返回?cái)?shù)字或字符串等。定義好組件后,最終需要將最上層組件也就是根組件掛載到DOM節(jié)點(diǎn)上:為了方便講解,后續(xù)章節(jié)將統(tǒng)一使用類組件,當(dāng)然我們也會(huì)在Hooks章節(jié)詳細(xì)介紹函數(shù)組件的知識(shí)。2.2.3props很多時(shí)候,組件中使用的某些數(shù)據(jù)可能需要外部提供,就像我們使用HTML中的圖片標(biāo)簽時(shí),需要設(shè)置src屬性才能顯示對(duì)應(yīng)圖片。假如現(xiàn)在定義了一個(gè)名叫Timg的類似圖片img的組件,組件內(nèi)部應(yīng)該怎樣獲取外部傳入的屬性數(shù)據(jù)并使用呢?答案是使用props,代碼示例如下:效果如下圖所示。通過props,可以將數(shù)據(jù)傳遞給組件,組件內(nèi)部通過ps獲取對(duì)應(yīng)的屬性數(shù)據(jù),渲染即可。有時(shí),某些屬性數(shù)據(jù)并不一定是外部必須傳入的,因此我們?cè)诙x組件時(shí),可以設(shè)置默認(rèn)屬性數(shù)據(jù),如上例:若在使用Timg組件時(shí)不傳入src屬性,則Timg組件會(huì)使用我們通過defaultProps設(shè)置的src屬性的默認(rèn)數(shù)據(jù)渲染頁面。反之,Timg組件會(huì)使用外部傳入的src屬性數(shù)據(jù)進(jìn)行渲染。2.2.4state組件中還有一類數(shù)據(jù),它具備以下幾個(gè)特征:·數(shù)據(jù)私有,僅供組件內(nèi)部使用?!?shù)據(jù)需要根據(jù)某些操作發(fā)生更改,并觸發(fā)視圖更新。這些特征正是組件狀態(tài)state期望具備的,滿足這些特征的數(shù)據(jù)一般都要考慮放入組件狀態(tài)state中。我們現(xiàn)在想設(shè)計(jì)一個(gè)組件,組件中有一個(gè)狀態(tài)count,該值每過一秒增加1,并在增加后顯示在頁面中。代碼設(shè)計(jì)如下:通過這個(gè)示例,我們可以總結(jié)出state的用法:·類組件中有一個(gè)名叫state的預(yù)定義屬性,該屬性為對(duì)象,對(duì)象中記錄了關(guān)于該組件的所有狀態(tài)。如上例中,狀態(tài)count的初始值為1?!ぴ谛枰倪@個(gè)狀態(tài)時(shí),調(diào)用組件的setState方法,這個(gè)方法繼承自Taro.Component?!ぴ贘SX中,通過this.state獲取對(duì)應(yīng)狀態(tài)值并使用。注:任何時(shí)候都不要通過賦值的形式直接修改state,如上例中,this.state.count=this.state.count+1這種賦值方式是錯(cuò)誤的,正確的操作應(yīng)該是用this.setState更新指定狀態(tài)。2.2.5樣式看了以上與組件相關(guān)的例子,對(duì)于組件,你是否有種似曾相識(shí)的感覺?其實(shí)從某種角度來看,組件類似HTML中的標(biāo)簽,這樣類比后,關(guān)于組件的很多問題都能迎刃而解。組件中樣式的使用方法和HTML中一致,也分為兩種:內(nèi)聯(lián)樣式和外部樣式。1.內(nèi)聯(lián)樣式組件的內(nèi)聯(lián)樣式通過style屬性指定。與HTML標(biāo)簽的style屬性不同的是,組件的style屬性接收一個(gè)對(duì)象:使用內(nèi)聯(lián)樣式需要注意以下幾點(diǎn):·如果不指定尺寸單位,則會(huì)默認(rèn)解析為px,如前面代碼中的width:100,會(huì)被解析為width:'100px'?!傩悦臑轳劮迨矫?,如background-color改為backgroundColor。2.外部樣式外部樣式可以使用CSS、Less、Sass等文件定義樣式,然后在對(duì)應(yīng)的模塊文件中引入。我們以Less為例:注:我們前面就有提到,因?yàn)閏lass為JavaScript關(guān)鍵字,不能出現(xiàn)在JSX中,所以需要使用className替代class。2.3組件生命周期組件從創(chuàng)建到銷毀所經(jīng)歷的整個(gè)過程是組件的一生——生命周期。人類從出生到死亡會(huì)經(jīng)歷很多人生階段,Taro也為組件劃分了不同階段,方便開發(fā)者在組件的不同階段執(zhí)行不同操作。一般而言,組件生命周期大致分為3個(gè)階段:掛載、更新、卸載。與生命周期相關(guān)的方法如下:·static·constructor·componentWillMount·componentDidMount·componentWillReceiveProps·shouldComponentUpdate·componentWillUpdate·componentDidUpdate·componentWillUnmount·render2.3.1組件掛載初次渲染時(shí),需要將組件掛載至對(duì)應(yīng)的DOM節(jié)點(diǎn)上,這個(gè)階段主要經(jīng)歷了組件實(shí)例化、組件將要掛載、組件渲染、組件掛載完畢,對(duì)應(yīng)的生命周期方法如下表所示。2.3.2組件更新組件被掛載到DOM以后,組件的props或state發(fā)生更改時(shí)會(huì)引起組件的更新,通常props變化是因外部變化引起的,state變化是因組件內(nèi)部調(diào)用了setState引起的。這個(gè)階段主要經(jīng)歷了組件接收props、組件是否需要更新、組件將要更新、組件渲染、組件更新完畢。對(duì)應(yīng)的生命周期方法如下表所示。續(xù)表2.3.3組件卸載這個(gè)階段只有一個(gè)生命周期方法——componentWillUnmout,卻也是很多人會(huì)選擇忽略的一個(gè)方法。有時(shí)組件被卸載后,組件相關(guān)的內(nèi)容并沒有被清除“干凈”,例如組件中定義的定時(shí)器,需要在組件卸載時(shí)被清除。在2.2節(jié)關(guān)于組件狀態(tài)的講解中,定義了一個(gè)隨時(shí)間變化的數(shù)字顯示組件,定時(shí)器在組件掛載階段被定義,而組件卸載時(shí)并沒有清除這個(gè)定時(shí)器,我們對(duì)這部分代碼進(jìn)行優(yōu)化:對(duì)于初學(xué)者,類組件的生命周期概念晦澀難懂,甚至?xí)霈F(xiàn)錯(cuò)用、濫用的情況。慶幸的是,函數(shù)組件不存在上面列舉的煩瑣生命周期方法,函數(shù)組件的生命周期可使用Hooks實(shí)現(xiàn)。2.4事件處理1.基本使用Taro元素的事件處理和DOM元素的很相似。但是有一點(diǎn)語法上的不同,Taro的事件綁定屬性均以on開頭且為駝峰式命名,事件屬性的值為函數(shù)。下面做一個(gè)簡單對(duì)比。HTML為元素綁定事件的寫法,示例如下:Taro為組件綁定事件的寫法,示例如下:在Taro中,事件處理函數(shù)的參數(shù)中,同樣可以獲取事件對(duì)象,通過事件對(duì)象可以進(jìn)行事件操作,如阻止事件冒泡,代碼示例如下:2.為事件處理函數(shù)傳參假如在一個(gè)列表中,列表的每一項(xiàng)都有一個(gè)“刪除”按鈕,單擊“刪除”按鈕刪除對(duì)應(yīng)的數(shù)據(jù):本例使用函數(shù)的bind方法解決this指向問題,當(dāng)然我們還可以使用箭頭函數(shù):或者使用函數(shù)柯里化思想:上例中,單擊View組件會(huì)調(diào)用this.handleClick(current),該函數(shù)調(diào)用后會(huì)返回一個(gè)新的函數(shù),在這個(gè)函數(shù)中可以訪問current值,同時(shí)能保證this指向的是當(dāng)前Title組件。3.自定義事件有時(shí)存在這樣的需求,期望組件內(nèi)部的狀態(tài)變化或操作能夠傳達(dá)給上層,這種需求通常被稱為父子組件之間的通信,父組件期望子組件某個(gè)事件觸發(fā)時(shí),父組件執(zhí)行某些特定操作。需要注意的是,自定義事件的屬性名一定要以on開頭,并采用駝峰式命名法,示例如下:2.5路由功能路由的職責(zé)是通過給定路徑,匹配與之對(duì)應(yīng)的模塊視圖。在Taro中,路由的相關(guān)定義與微信小程序保持一致,路由功能是默認(rèn)提供的,不需要開發(fā)者進(jìn)行額外的路由配置。1.基本使用使用路由功能前,我們需要在入口文件的config配置中指定好pages,然后就可以在代碼中通過Taro提供的API來跳轉(zhuǎn)到目的頁面了,配置示例如下:這樣在Index頁面就可以使用Taro提供的API進(jìn)行路由跳轉(zhuǎn)了,示例如下:2.路由攜帶參數(shù)我們可以通過在所有跳轉(zhuǎn)的URL后面添加查詢字符串參數(shù),從而將參數(shù)攜帶至跳轉(zhuǎn)后的頁面,例如:跳轉(zhuǎn)至目標(biāo)頁面后,我們通過TaroComponent對(duì)象上已經(jīng)定義的$router獲取對(duì)應(yīng)的參數(shù),示例如下:Taro提供的與路由操作相關(guān)的方法如下表所示。續(xù)表2.6實(shí)戰(zhàn)案例:受控與非受控Input組件表單處理是項(xiàng)目中比較常見的功能,表單操作看似比較簡單,其實(shí)大有學(xué)問。本節(jié)以Input組件為例,簡單介紹狀態(tài)管理、表單數(shù)據(jù)存儲(chǔ)及事件處理之間的關(guān)聯(lián)設(shè)計(jì)。開始之前,我們先思考以下兩種場(chǎng)景:(1)Input框中的值在更改后期望被記錄,然后在提交時(shí)拿出記錄的值并傳輸給后端。(2)操作過程中不額外存儲(chǔ)Input框的值,而是在提交時(shí)直接獲取Input框的值并傳輸給后端。場(chǎng)景一為什么需要記錄輸入框的值呢?因?yàn)橛衅渌胤絿L試修改Input框的值,還不如頁面中有表單的一鍵清空操作;場(chǎng)景二是相對(duì)于場(chǎng)景一較為簡單的表單操作,事先我們知道Input框中的值不會(huì)在其他地方引起更改,所以我們無須存儲(chǔ)Input框中的值。場(chǎng)景一實(shí)現(xiàn)的Input組件是受控表單,場(chǎng)景二實(shí)現(xiàn)的是非受控表單。下面來看代碼示例。1.受控Input組件上例是比較經(jīng)典的受控表單處理問題,重點(diǎn)知識(shí)通過代碼注釋標(biāo)注。主要思路是:當(dāng)組件初始化時(shí),設(shè)置Input框的初始值為''。在Input框中鍵入數(shù)據(jù)后,失去焦點(diǎn),則通過事件對(duì)象獲取值,并將該數(shù)據(jù)設(shè)置到狀態(tài)中。當(dāng)提交事件被觸發(fā)時(shí),從狀態(tài)中獲取Input框的值并傳輸給后臺(tái),完成表單提交操作。2.非受控Input組件上例是比較經(jīng)典的非受控表單處理問題,重點(diǎn)知識(shí)通過代碼注釋標(biāo)注。主要思路是:當(dāng)組件初始化時(shí),創(chuàng)建用于引用Input組件實(shí)例的對(duì)象,該對(duì)象可以使用Input組件內(nèi)部的方法或獲取Input內(nèi)部的值(Value)。當(dāng)提交事件被觸發(fā)時(shí),通過inpRef獲取Input組件實(shí)例中的值并傳輸給后臺(tái),完成表單提交操作。2.7本章小結(jié)本章介紹了JSX語法基礎(chǔ),包括組件化開發(fā)基本思想、組件生命周期、組件中事件的處理,以及如何綁定事件、如何解決this指向問題等。了解了單個(gè)頁面開發(fā)以后,我們可以嘗試將多個(gè)頁面有機(jī)組合,這時(shí)就需要使用路由功能了。路由系統(tǒng)將各個(gè)模塊通過路徑和路徑參數(shù)編織成網(wǎng),路由操作允許你在網(wǎng)的節(jié)點(diǎn)之間穿梭。最后以表單控件串聯(lián)起了本章學(xué)習(xí)的重要內(nèi)容,舉一反三。下一章我們將更深入地學(xué)習(xí)組件開發(fā)與組件設(shè)計(jì)思想。第3章Taro進(jìn)階本章將深入探討Taro組件化開發(fā)內(nèi)容,組件化帶給我們友好開發(fā)體驗(yàn)的同時(shí),帶來了很多問題,諸如:·組件設(shè)計(jì)原則與分類問題?!ど顚咏M件狀態(tài)傳遞問題?!そM件通信問題?!ひ脝栴}。本章內(nèi)容將圍繞以上問題展開,學(xué)習(xí)完本章你會(huì)對(duì)組件設(shè)計(jì)有更深入的認(rèn)識(shí),為以后編寫出更易讀、可維護(hù)性更高的應(yīng)用奠定基礎(chǔ)。3.1組件設(shè)計(jì)你也許聽過這樣一句話:總體大于部分之和。以往我們對(duì)這句話都是不假思索地給予肯定,但在我們要講的組件設(shè)計(jì)這個(gè)場(chǎng)景下,總體帶來的收益并不一定大于部分之和,這里的收益包括開發(fā)效率、代碼可讀性、代碼可維護(hù)性等。初學(xué)者經(jīng)常會(huì)犯一個(gè)致命的錯(cuò)誤——過早設(shè)計(jì)與優(yōu)化。曾有剛?cè)肼毠镜膶?shí)習(xí)生問筆者,組件到底應(yīng)該怎么設(shè)計(jì),什么時(shí)候應(yīng)該進(jìn)一步拆分組件,拆分組件的時(shí)候應(yīng)該如何考慮狀態(tài)問題。其實(shí)針對(duì)這些問題并沒有絕對(duì)的答案,但是有幾個(gè)基本原則可以在組件設(shè)計(jì)時(shí)借鑒。1.單一職責(zé)相信學(xué)習(xí)過面向?qū)ο缶幊痰淖x者都知道面向?qū)ο缶幊痰脑O(shè)計(jì)原則,其中就有單一職責(zé),定義為:一個(gè)類發(fā)生變化的原因只應(yīng)該有一個(gè)。將這個(gè)定義放在函數(shù)式編程中就可以描述為:一個(gè)函數(shù)只應(yīng)該有一個(gè)自變量(參數(shù)),應(yīng)變量只隨這一個(gè)自變量變化。在Taro中,組件即模塊。單一職責(zé)要求在設(shè)計(jì)組件時(shí)做到“單一”,這一點(diǎn)我們大多時(shí)候都能滿足,而其中的難點(diǎn)在于組件單一的粒度:粒度太大不好復(fù)用,粒度太小會(huì)導(dǎo)致定義大量模塊,從而讓項(xiàng)目變得難以管理。所以需要把握好單一的度。遵循單一職責(zé)所設(shè)計(jì)出的組件具有以下特點(diǎn):·組件復(fù)雜度降低?!づc其他組件的耦合度降低?!た蓮?fù)用性提高。2.高內(nèi)聚,低耦合高內(nèi)聚要求一個(gè)組件有一個(gè)明確的組件邊界,組件將包容相關(guān)緊密聯(lián)系的內(nèi)容,實(shí)現(xiàn)“專一”功能。低耦合要求與其他組件的關(guān)聯(lián)性最小。當(dāng)然本條原則需要依賴第一條,只有考慮單一職責(zé)設(shè)計(jì)出的組件才能預(yù)知組件邊界,實(shí)現(xiàn)高內(nèi)聚,降低與其他組件的關(guān)聯(lián)性,實(shí)現(xiàn)低耦合。3.性能與優(yōu)化以上兩點(diǎn)主要從上層設(shè)計(jì)層面分析了組件設(shè)計(jì)需要遵循的基本原則,在完成以上兩步設(shè)計(jì)后,還需要考量以下幾點(diǎn):·基于性能選擇合適組件:無狀態(tài)組件>有狀態(tài)組件>class組件。·最小化props?!と绻皇且呀?jīng)確定的組件完整形態(tài),請(qǐng)不要過早優(yōu)化。3.2組件通信上一節(jié)我們了解了組件的設(shè)計(jì)思想,組件在被設(shè)計(jì)出來以后不是孤立的,也需要“交往”。例如下面的組件結(jié)構(gòu):MyContent與MySide的關(guān)系被稱為父子組件關(guān)系,MySide與MyList的關(guān)系被稱為兄弟組件關(guān)系。有時(shí)可能在MySide中的操作需要通知MyList,或者M(jìn)ySide中的操作需要通知MyContent,這一需求背后正是父子組件或兄弟組件之間的通信。3.2.1父子組件通信一般而言,父子組件之間的通信通過事件完成,即在父組件定義事件處理函數(shù),將這個(gè)函數(shù)作為props傳遞給子組件,就能實(shí)現(xiàn)指定子組件的操作通知到父組件。示例如下:上例說明了子組件操作觸發(fā)父組件的邏輯處理。那么父組件的操作觸發(fā)子組件的更新呢?便是通過props了,父組件數(shù)據(jù)更新后,通過props告知子組件,示例如下:這種方式其實(shí)是將MySide中的count狀態(tài)抽離出來存放到父組件,使得父組件或者父組件內(nèi)的其他組件更改這個(gè)值變得容易。如果父組件希望觸發(fā)子組件內(nèi)的操作,則可以結(jié)合引用Ref獲取子組件實(shí)例,然后通過引用使用子組件中的值或方法,具體使用方法我們將在3.4節(jié)介紹。3.2.2兄弟組件通信兄弟組件狀態(tài)同步只能通過父組件實(shí)現(xiàn),即將兄弟組件需要共用的狀態(tài)提取到父組件中,進(jìn)而在兄弟組件上使用自定義事件的方式監(jiān)聽組件內(nèi)部的操作。如果值發(fā)生更改,則需要通知其兄弟組件,代碼實(shí)現(xiàn)示例如下:回想一下,在討論組件設(shè)計(jì)需要遵循的原則時(shí),我們說組件的邊界要清晰,但是我們現(xiàn)在發(fā)現(xiàn)上例中MySide組件和MyList組件共同依賴了count這一狀態(tài)。這時(shí)我們需要思索,這個(gè)問題出現(xiàn)的原因是什么,是組件設(shè)計(jì)粒度不合理?還是組件之間確實(shí)需要共用這個(gè)狀態(tài)?如果是前者,則需要重新考慮一下組件的設(shè)計(jì);如果是后者,則可以用本例所示的解決方案去解決這一問題。3.2.3更復(fù)雜的組件通信大多數(shù)情況下,以上列舉的兩種方案可以很好地解決組件之間的狀態(tài)同步問題。可往往還有更復(fù)雜的場(chǎng)景、更復(fù)雜的狀態(tài)同步問題亟待解決。例如,更深層級(jí)的狀態(tài)傳遞與通信,且該狀態(tài)只被最內(nèi)層組件消費(fèi),以及狀態(tài)合并問題等。這里我們來看深層狀態(tài)傳遞問題。首先我們可以想到通過props逐層傳遞,例如:Two組件很顯然只是中介,count屬性傳遞給了它卻沒有被使用,只是透?jìng)鹘o了One組件。是否可以不經(jīng)過這樣層層傳遞,而直接由Three組件創(chuàng)建狀態(tài),One組件使用這個(gè)狀態(tài)呢?這就需要使用Taro提供的context實(shí)現(xiàn)。代碼示例如下:這樣一來,count這一狀態(tài)就只是Three組件創(chuàng)造與更新、One組件消費(fèi)了。這樣props更簡捷,沒有造成污染。如果以上方案還是無法解決應(yīng)用中的狀態(tài)管理問題,就可能需要使用全局狀態(tài)管理了,關(guān)于復(fù)雜組件的狀態(tài)管理我們將在下一章詳細(xì)探討。3.3服務(wù)端通信應(yīng)用中大多數(shù)據(jù)來自后端,掌握前后端數(shù)據(jù)請(qǐng)求至關(guān)重要。Taro提供了統(tǒng)一接口,用于網(wǎng)絡(luò)HTTP/HTTPS請(qǐng)求,該接口為Taro.request(options)。3.3.1Taro.request學(xué)習(xí)該接口之前,先整體介紹一下request常用的options參數(shù),如下表所示。請(qǐng)求得到數(shù)據(jù)以后,Taro會(huì)將返回的數(shù)據(jù)及狀態(tài)等內(nèi)容進(jìn)行封裝,封裝后得到的這個(gè)對(duì)象我們暫且稱為ResultObject,該對(duì)象主要包含四個(gè)屬性,如下表所示。Taro在大多數(shù)方面都考慮到了開發(fā)者的學(xué)習(xí)成本,我們從請(qǐng)求API也能夠看出,Taro.request的相關(guān)設(shè)計(jì)借鑒了fetch,你只要使用過fetch,就能很快上手Taro.request。假如現(xiàn)在需要使用Taro在組件中向后端發(fā)送一條登錄請(qǐng)求,我們會(huì)如下所示進(jìn)行代碼編寫:相信你能很輕松地理解以上代碼,但是我們發(fā)現(xiàn)以上請(qǐng)求的處理是放在回調(diào)函數(shù)中的,而回調(diào)函數(shù)的解耦合能力或者可讀性與可維護(hù)性較低,在Taro項(xiàng)目中建議使用Promise替代。改寫示例如下:或者:3.3.2請(qǐng)求終止在某些特殊情況下,可能在較短時(shí)間內(nèi)發(fā)出多個(gè)相同的請(qǐng)求,如此一來最終我們得到這個(gè)請(qǐng)求的響應(yīng)也可能會(huì)有多個(gè),這時(shí)就會(huì)面臨一個(gè)問題,如果只想保留最后發(fā)起的那一次請(qǐng)求所得到的數(shù)據(jù),該如何實(shí)現(xiàn)呢?這個(gè)問題是典型的時(shí)序控制問題,比較暴力的解決方案是每次發(fā)起請(qǐng)求前都將之前所發(fā)起的請(qǐng)求終止。在調(diào)用了Taro.request方法后會(huì)返回一個(gè)請(qǐng)求對(duì)象實(shí)例,該實(shí)例允許終止該次請(qǐng)求。示例如下:有了這個(gè)API,上述需求就能輕易實(shí)現(xiàn):上例代碼的主要思路是:在組件創(chuàng)建時(shí)創(chuàng)建一個(gè)引用對(duì)象,當(dāng)“登錄”按鈕被單擊時(shí),會(huì)先判斷此時(shí)引用是否已經(jīng)賦值為Taro請(qǐng)求對(duì)象,若該引用指向的Taro請(qǐng)求對(duì)象存在,則本次請(qǐng)求終止,然后創(chuàng)建一個(gè)最新的請(qǐng)求對(duì)象并賦值給引用,以此保證組件獲取的數(shù)據(jù)來自最后一次發(fā)起的請(qǐng)求。注:上例使用的引用會(huì)在下一節(jié)展開介紹。3.3.3請(qǐng)求攔截器攔截器能夠在請(qǐng)求發(fā)出前或發(fā)出后將請(qǐng)求攔截并對(duì)其options進(jìn)行一些額外處理,攔截器可以讓你更優(yōu)雅地去改變已有程序的表現(xiàn)。攔截器的處理過程類似洋蔥,所以我們常稱攔截器處理為洋蔥模型,如下圖所示。在請(qǐng)求發(fā)起之前,可使用Taro.addInterceptor為請(qǐng)求添加攔截器。Taro也有內(nèi)置攔截器供使用。1.內(nèi)置攔截器Taro提供了兩個(gè)內(nèi)置攔截器,分別是logInterceptor和timeoutInterceptor。顧名思義,前者是用于請(qǐng)求日志輸出的攔截器,后者是用于設(shè)置請(qǐng)求超時(shí)時(shí)間的攔截器。使用方法如下:添加logInterceptor攔截器以后,后面的每次請(qǐng)求都會(huì)輸出請(qǐng)求的相關(guān)信息,如請(qǐng)求路徑、參數(shù)、方法等。添加timeoutInterceptor攔截器以后,后面的請(qǐng)求如果超出預(yù)期時(shí)間依然沒有獲得返回結(jié)果,則這次請(qǐng)求將會(huì)被超時(shí)處理。2.自定義攔截器攔截器其實(shí)是一個(gè)特殊方法,該方法的參數(shù)攜帶了請(qǐng)求options,并且該方法體中的內(nèi)容處理完后必須返回一個(gè)Promise以進(jìn)行后續(xù)操作。假如我們現(xiàn)在想自定義一個(gè)日志攔截器,實(shí)現(xiàn)如下:當(dāng)請(qǐng)求發(fā)起時(shí),會(huì)打印請(qǐng)求方法、請(qǐng)求路徑及請(qǐng)求參數(shù);當(dāng)請(qǐng)求響應(yīng)到來時(shí),會(huì)打印路徑及響應(yīng)參數(shù)。以上兩處console.log是業(yè)務(wù)中需要自定義攔截器處理的邏輯,第一位置可用于處理請(qǐng)求內(nèi)容,第二位置可用于處理響應(yīng)內(nèi)容。使用攔截器在拓展處理請(qǐng)求響應(yīng)內(nèi)容的同時(shí)不會(huì)造成代碼侵入,這種思想是經(jīng)典的面向切面編程(AspectOrientedProgramming,AOP)思想。如果你對(duì)該思想感興趣,可以查閱相關(guān)資料進(jìn)一步學(xué)習(xí)。3.4使用Ref對(duì)于Ref,你可能已經(jīng)不陌生了,前面有兩處提到了Ref。第一處是第2章講解非受控表單時(shí),獲取Input組件對(duì)象進(jìn)而獲取該組件的Value;第二處是講解請(qǐng)求時(shí)序控制時(shí),確保在較短時(shí)間內(nèi)同一個(gè)請(qǐng)求發(fā)出多次,只須將最后一次請(qǐng)求獲得的數(shù)據(jù)渲染到頁面中。本節(jié)將全面梳理Taro中Ref的使用。1.引用組件在Web開發(fā)時(shí),我們可以使用document.getElementById等方法獲取指定節(jié)點(diǎn)元素,而在Taro中無法使用這些DOM操作方法,因?yàn)門aro是數(shù)據(jù)驅(qū)動(dòng)框架。幸運(yùn)的是,可以使用Ref獲取指定節(jié)點(diǎn),例如獲取一個(gè)表單元素的值,示例如下:在組件創(chuàng)建時(shí),創(chuàng)建了一個(gè)用于指代Input組件的引用。在組件掛載時(shí),該引用會(huì)指向Input實(shí)例,在后續(xù)的操作中就能通過該引用獲取Input組件的內(nèi)容了。還記得我們?cè)?.2節(jié)留下了一個(gè)伏筆,在父組件中如何使用子組件中的屬性或方法。這也是Ref的另一個(gè)用武之地,你可以將Ref運(yùn)用在自定義組件上來獲取組件實(shí)例,示例如下:其實(shí),如果你經(jīng)常在父組件中通過子組件引用來使用子組件中的屬性或方法,那么也許你需要思考片刻,是不是因?yàn)槟愕慕M件設(shè)計(jì)不合理迫使你這樣做?因?yàn)橐闷鋵?shí)也算打破了組件邊界,對(duì)組件外部暴露的內(nèi)容越多,說明組件封裝越需要優(yōu)化。2.請(qǐng)求時(shí)序控制時(shí)序控制需要確保在較短時(shí)間內(nèi)同一個(gè)請(qǐng)求發(fā)出多次,只須將最后一次請(qǐng)求獲得的數(shù)據(jù)渲染到頁面中。使用方法在上一節(jié)已做分析,此處不再贅述。對(duì)于Ref,我們可以將其理解為旁觀者,它以慵懶的姿態(tài)記錄你所給定的值,并且不會(huì)隨組件的更新而發(fā)生變化,Ref的變化只來自初始化、掛載及后續(xù)手動(dòng)賦值。3.5本章小結(jié)本章介紹了組件設(shè)計(jì)的基本原則。首先介紹了組件關(guān)系,組件之間的通信即狀態(tài)同步問題。然后介紹了組件與服務(wù)端數(shù)據(jù)交互和通信使用的API,同時(shí)介紹了如何使用攔截器在請(qǐng)求發(fā)出前或響應(yīng)到來后做一些特殊處理。最后介紹了Ref在開發(fā)過程中的使用方法。通過前面3章的學(xué)習(xí),我們已經(jīng)較深入地掌握了Taro開發(fā)基礎(chǔ)與組件設(shè)計(jì)等內(nèi)容。有一個(gè)亟待解決的問題,組件設(shè)計(jì)時(shí)遇到復(fù)雜狀態(tài)應(yīng)該如何處理,這時(shí)就可能需要將部分狀態(tài)進(jìn)行集中管理,下一章我們將圍繞這個(gè)問題給出不同的可選方案。第4章集中狀態(tài)管理大多數(shù)項(xiàng)目中的組件功能通過上一章所講的架構(gòu)就能實(shí)現(xiàn),但也存在更多復(fù)雜的場(chǎng)景。例如模塊之間需要共用某些狀態(tài),某模塊中需要更改另一模塊的數(shù)據(jù)或狀態(tài),這時(shí),無論通過提升狀態(tài)還是拆分組件,似乎都只會(huì)讓項(xiàng)目代碼的可讀性下降且不易維護(hù)。在這種場(chǎng)景下,也許需要其他狀態(tài)管理方案。4.1Redux在React中,比較流行的狀態(tài)管理方案中就有Redux。其實(shí)Redux本身和React或者Taro沒有直接聯(lián)系。換言之,只要是JavaScript應(yīng)用,都可以使用Redux作為數(shù)據(jù)管理工具。只是在Taro中,為了簡化Redux的使用,定義了與react-reduxAPI幾乎一致的包——@tarojs/redux。4.1.1Redux設(shè)計(jì)理念單頁應(yīng)用日趨復(fù)雜,應(yīng)用中的數(shù)據(jù)越來越難以管理。假設(shè)一個(gè)應(yīng)用中的狀態(tài)或者數(shù)據(jù)使用一個(gè)簡單對(duì)象來描述,可能會(huì)像這樣:這個(gè)對(duì)象定義了一個(gè)計(jì)劃應(yīng)用中的待辦事項(xiàng)列表。通常列表項(xiàng)的操作可能不止在一個(gè)模塊中。當(dāng)一個(gè)狀態(tài)需要在多個(gè)模塊中更新時(shí),直接修改對(duì)象的值是非常不明智的,有沒有更好的方案呢?有!單向數(shù)據(jù)流。關(guān)于單向數(shù)據(jù)流的概念我們不展開介紹,因?yàn)楦拍畋旧砭褪浅橄蟮?,我們暫且從?shí)際問題出發(fā)。我們繼續(xù)思考多個(gè)模塊修改一個(gè)狀態(tài)的問題,既然不能直接修改對(duì)象的值,那么我們不妨先描述一下需要對(duì)這個(gè)對(duì)象進(jìn)行的操作,例如添加待辦列表項(xiàng)、改變待辦列表項(xiàng)的狀態(tài)。在定義了這些操作以后,還要考慮在相同操作情況下處理的實(shí)際參數(shù)可能有所差異,因此每次操作都可以攜帶本次操作的參數(shù)(payload)。例如:這樣就定義了兩個(gè)操作,對(duì)應(yīng)添加和更改狀態(tài)。添加操作攜帶了text參數(shù)用于填充添加項(xiàng)的內(nèi)容,更改狀態(tài)操作攜帶了index參數(shù)用于指定需要更改項(xiàng)的索引。這樣就能清晰地知道更新后應(yīng)用中到底發(fā)生了什么。接下來應(yīng)用狀態(tài)的更新交給reducer。reducer用于處理不同操作,如ADD_TODO,該操作就是在原有列表的基礎(chǔ)上新增一項(xiàng),如果是數(shù)組,可以如下:如果是TOGGLE_TODO,則需要更新原有列表中的某項(xiàng)。這樣我們就可以將上述兩種操作定義在一個(gè)處理函數(shù)中,示例如下:需要注意的是,每個(gè)處理函數(shù)都應(yīng)該是純函數(shù)。最終只需要通過action與reducer創(chuàng)建store即可使用,使用方法如下:4.1.2在Taro中使用Redux首先安裝Redux、@tarojs/redux和@tarojs/redux-h5,以及一些需要用的Redux中間件,示例如下:然后可以在項(xiàng)目的src目錄下新增一個(gè)store目錄,在該目錄下創(chuàng)建index.js文件來配置store,同時(shí)可以選擇常用的Redux中間件并配置到項(xiàng)目中,例如使用redux-thunk和redux-logger中間件分別處理異步操作和記錄Redux操作日志,示例如下:接下來在項(xiàng)目入口文件app.js中使用@tarojs/redux中提供的Provider組件將已經(jīng)定義的store提供給項(xiàng)目組件使用,代碼示例如下:這樣就可以開始使用了。如Redux推薦的那樣,可以增加如下目錄:·constants目錄,用來放置所有的actiontype常量?!ctions目錄,用來放置所有的actions?!educers目錄,用來放置所有的reducers。4.1.3Redux案例我們使用Redux來開發(fā)一個(gè)簡單的加、減計(jì)數(shù)器功能。1.新增actiontype我們需要通過不同的action名稱枚舉出不同的action操作,如計(jì)數(shù)操作要設(shè)計(jì)增加和減少操作,所以需要定義兩個(gè)actiontype,分別對(duì)應(yīng)加和減,示例如下:2.新增reducer處理當(dāng)不同的操作發(fā)起請(qǐng)求時(shí),我們需要對(duì)不同的請(qǐng)求作出響應(yīng),并返回最新狀態(tài)。前面我們介紹了action存在加和減兩個(gè)類型,那么reducer中需要根據(jù)這兩個(gè)不同的類型返回不同的狀態(tài)。對(duì)于增加操作,首先獲取已存儲(chǔ)狀態(tài)中的num值,然后將該值加1。為了不影響狀態(tài)中的其他值,我們通過解構(gòu)賦值的方式將已存儲(chǔ)狀態(tài)解構(gòu)賦值到新的對(duì)象中,然后指定num值以覆蓋更新前的num值,最終返回這個(gè)對(duì)象,從而生成新的狀態(tài),代碼示例如下:通過redux提供的combineReducers方法,可以將定義的多個(gè)reducer合并,示例如下:3.新增action處理定義action函數(shù)的目的是進(jìn)一步約束可發(fā)起狀態(tài)變化的操作場(chǎng)景,本例中num的變化只能是通過ADD或MINUS操作引起的,因此在組件中可以預(yù)先定義好對(duì)應(yīng)的處理函數(shù),在需要使用的位置調(diào)用即可,代碼示例如下:通過以上3步,定義了action類型、action處理、reducer處理,接下來就可以在組件中使用了,示例如下:以上connect裝飾器接收參數(shù)mapStateToProps與mapDispatchToProps,分別如下:·mapStateToProps,函數(shù)類型,接收最新的state作為參數(shù),用于將state映射到組件的props?!apDispatchToProps,函數(shù)類型,接收dispatch()方法并返回期望注入展示組件的props中的回調(diào)方法。4.2MobX使用Redux做狀態(tài)管理是一個(gè)不錯(cuò)的選擇,但通過上一節(jié)的介紹,你似乎已經(jīng)發(fā)現(xiàn)Redux是有一定的學(xué)習(xí)成本的。什么是單向數(shù)據(jù)流?為什么需要定義actiontype?為什么需要定義reducer?這些問題在我們初學(xué)時(shí)比較難考慮清楚,并且有時(shí)希望選擇一個(gè)更自由、更簡單易用的狀態(tài)管理工具,也許MobX能夠滿足要求。4.2.1MobX設(shè)計(jì)理念MobX的設(shè)計(jì)比Redux小巧精簡,它不用定義reducer,也不需要通過純函數(shù)形式生成新的state。使用MobX只需要熟悉以下3個(gè)概念:·狀態(tài)state?!づ缮礵erivations?!げ僮鱝ction。MobX支持單向數(shù)據(jù)流,其中,action會(huì)更改state,同時(shí)會(huì)更新所有與該狀態(tài)相關(guān)的視圖,如下圖所示。使用MobX時(shí),狀態(tài)集合可以使用對(duì)象來描述,并且只要使用MobX提供的observable方法包裹對(duì)象,就能返回一個(gè)被檢測(cè)的對(duì)象。Taro提供了MobX實(shí)用工具庫——@tarojs/mobx。4.2.2在Taro中使用MobX安裝相關(guān)依賴,示例如下:我們一般將組件狀態(tài)封裝到一個(gè)對(duì)象中。如detail模塊,需要存儲(chǔ)公共狀態(tài),這時(shí)我們可以創(chuàng)建一個(gè)文件,命名為detail-store.js。這個(gè)文件默認(rèn)返回detail狀態(tài),示例如下:在本示例中,content、views參數(shù)被指定為MobX的觀測(cè)屬性,increateViews被指定為會(huì)引起觀測(cè)屬性變化的操作,這樣我們就可以在detail組件文件中使用該狀態(tài)了,示例如下:observer是一個(gè)高階組件,通過該注解,使得組件跟隨MobXstore的變化而更新。本例中當(dāng)“增加”按鈕被單擊時(shí),頁面的瀏覽數(shù)就能增加。但是這樣的store還是只能在本組件或者子組件中使用,如果期望跨組件都能訪問到這個(gè)store,就需要將store從應(yīng)用頂層注入,供所有組件消費(fèi)。這個(gè)操作在src/App.js中被定義,示例如下:這樣在需要使用公共store的組件上通過inject注解,將指定的store綁定到組件props上,即可在組件內(nèi)訪問使用,示例如下:需要注意以下兩件事情。·無論以何種方式使用inject,其后的observer均不能省略?!げ灰趇nject中引用可觀察對(duì)象,這將導(dǎo)致屬性改變后的頁面不更新,例如:4.3本章小結(jié)本章介紹了項(xiàng)目中常用的兩種集中狀態(tài)管理方案,不過也許你的項(xiàng)目并不需要。小型項(xiàng)目直接使用組件內(nèi)部state即可實(shí)現(xiàn)狀態(tài)管理,或者使用Hooks進(jìn)行狀態(tài)管理,下一章將展開講解關(guān)于Hooks的知識(shí)。大型項(xiàng)目考慮是否需要使用集中狀態(tài)管理,同時(shí)集中狀態(tài)管理的方案還有很多。如果你覺得Redux復(fù)雜但還是想使用基于Redux狀態(tài)管理的方案,那么可以選用dva;如果你的狀態(tài)流控制異常復(fù)雜,則可以考慮選用RxJs。Redux和MobX對(duì)于狀態(tài)管理的思想值得我們仔細(xì)研究學(xué)習(xí)。第5章Hooks前面的章節(jié),我們都是使用class定義組件的。class組件符合面向?qū)ο缶幊趟枷耄瑑?yōu)點(diǎn)很多,但缺點(diǎn)也很多。在Taro中,很多場(chǎng)景下函數(shù)式編程思想優(yōu)于面向?qū)ο缶幊趟枷?。正因如此,Taro引入了Hooks的特性。Hooks允許在函數(shù)式組件中管理狀態(tài)及其他特性。5.1Hooks簡介在此之前,有狀態(tài)組件只能使用class定義,但在某些場(chǎng)景下,class組件會(huì)引入一些問題,后來出現(xiàn)了函數(shù)組件。過去函數(shù)組件又被稱為stateless組件,即無狀態(tài)組件。但經(jīng)過嘗試發(fā)現(xiàn),函數(shù)組件在對(duì)組件UI與狀態(tài)的分離方面表現(xiàn)出色,由此Hooks應(yīng)運(yùn)而生。在介紹Hooks之前,我們先來細(xì)數(shù)class組件的不足。5.1.1class組件的不足1.組件之間難以復(fù)用狀態(tài)邏輯如果你使用過Taro一段時(shí)間,你也許會(huì)熟悉一些解決狀態(tài)復(fù)用問題的方案,如renderprops和高階組件。但這類方案需要重新組織你的組件結(jié)構(gòu),使你的代碼難以理解。并且你會(huì)發(fā)現(xiàn)由providers、consumers、高階組件、renderprops等其他抽象層組成的組件會(huì)形成“嵌套地獄”。因此Taro需要為共享狀態(tài)邏輯提供更好的原生途徑。2.復(fù)雜組件難以理解起初組件很簡單,但是逐漸會(huì)被狀態(tài)邏輯和副作用充斥。每個(gè)生命周期常常包含一些不相關(guān)的邏輯。例如,組件常在componentDidMount和componentDidUpdate中獲取數(shù)據(jù)。但是,同一個(gè)componentDidMount中可能包含很多其他邏輯,如設(shè)置事件監(jiān)聽,而后需在componentWillUnmount中清除事件。相互關(guān)聯(lián)且需要對(duì)照修改的代碼被拆分,而完全不相關(guān)的代碼卻在同一個(gè)方法中被組合。如此很容易產(chǎn)生Bug,導(dǎo)致邏輯不一致。3.難以理解的面向?qū)ο缶幊趟枷氤a復(fù)用和代碼管理會(huì)遇到困難外,面向?qū)ο缶幊趟枷胍彩鞘褂肨aro的一大屏障。你必須理解JavaScript中this的工作方式,不能忘記綁定事件處理器。也許我們很好理解props、state和自頂向下的數(shù)據(jù)流,但對(duì)面向?qū)ο缶幊趟枷雲(yún)s一籌莫展。為了解決以上問題,Taro提供了Hooks。Hooks可以使你在非class的情況下使用更多的Taro特性。Hooks充分擁抱函數(shù),同時(shí)沒有犧牲Taro的精神原則。Hooks提供了問題的解決方案,無須學(xué)習(xí)復(fù)雜的函數(shù)式或響應(yīng)式編程技術(shù)。Hooks是一系列可以讓你在函數(shù)組件中管理并使用state及生命周期等特性的函數(shù)。需要注意的是,Hooks不能在class組件中使用。5.1.2Hooks概覽我們以兩個(gè)最常用的Hooks作為示例,體驗(yàn)基于Hooks開發(fā)應(yīng)用。這兩個(gè)Hooks分別為useState和useEffect。1.useState首先來看一個(gè)計(jì)數(shù)器案例,需求很簡單。當(dāng)你單擊按鈕時(shí),計(jì)數(shù)器的值會(huì)增加1,并顯示在頁面中。代碼如下:在這里,useState就是一個(gè)Hook(后面我們?cè)敿?xì)介紹)。通過在函數(shù)組件里調(diào)用它,為組件添加一些內(nèi)部狀態(tài),Taro會(huì)在更新渲染時(shí)保留這個(gè)狀態(tài)。useState會(huì)返回一對(duì)值:當(dāng)前狀態(tài)和一個(gè)讓你更新它的函數(shù),你可以在事件處理函數(shù)中或其他一些地方調(diào)用這個(gè)函數(shù)。該函數(shù)類似class組件中的this.setState。在上面的例子中,計(jì)數(shù)器是從零開始的,所以初始state就是0。這里的state可以是你期望的任何類型數(shù)據(jù)。這個(gè)初始state參數(shù)只有在第一次渲染組件時(shí)被用到。如果組件中有多個(gè)狀態(tài)需要管理,則可以調(diào)用多個(gè)useState來生成狀態(tài)和該狀態(tài)的更新函數(shù),例如:上例中,我們?cè)谝粋€(gè)函數(shù)組件中定義了3個(gè)狀態(tài),分別為age、fruit、todos,同時(shí)對(duì)應(yīng)的更新函數(shù)是setAge、setFruit、setTodos。需要注意這里的命名,推薦將狀態(tài)對(duì)應(yīng)的更新函數(shù)命名為“set+狀態(tài)名”的形式。之后,只要在需要的時(shí)候調(diào)用狀態(tài)更新函數(shù),視圖就會(huì)根據(jù)新的狀態(tài)重新渲染。2.useEffect在了解這個(gè)函數(shù)之前,我們先來認(rèn)識(shí)一個(gè)術(shù)語——副作用。生活中我們常聽說這個(gè)詞,例如感冒了,需要喝藥,但喝藥是有副作用的,副作用就是嗜睡。在程序中,一個(gè)狀態(tài)改變的時(shí)候會(huì)引起其他視圖或狀態(tài)的更改,這也被稱為副作用。在計(jì)數(shù)器案例中,當(dāng)count狀態(tài)改變時(shí),我們期望更新標(biāo)題,這時(shí)就需要處理count狀態(tài)的副作用了,代碼如下:當(dāng)你在組件中使用了useEffect,那么組件在掛載和更新時(shí)就會(huì)嘗試處理這個(gè)副作用。useEffect傳入兩個(gè)參數(shù),第一個(gè)參數(shù)是副作用的處理函數(shù),第二個(gè)參數(shù)是與該副作用關(guān)聯(lián)的狀態(tài)或?qū)傩砸蕾嚁?shù)組,就像上例指明了count,說明本useEffect只在count變化時(shí)執(zhí)行副作用處理函數(shù)。這里發(fā)散一下思維,如果我們期望useEffect只在組件掛載時(shí)執(zhí)行,則該怎么做呢?可以如下使用:同時(shí)useEffect允許返回一個(gè)函數(shù),這個(gè)函數(shù)用于處理清除操作,類似componentWillUnmout,示例如下:與useState一樣,同一個(gè)組件中可以使用多個(gè)useEffect,例如:5.1.3Hooks規(guī)則Hooks是特殊的函數(shù)。準(zhǔn)確來說,Hooks的實(shí)現(xiàn)使用了函數(shù)閉包思想,因此在使用Hooks過程中,如果出現(xiàn)一些難以理解的問題,則可以猜測(cè)是否是閉包引起的問題。在使用Hooks時(shí),需要記住以下幾點(diǎn)規(guī)則:·只能在函數(shù)組件中使用Hooks?!ぶ荒茉诤瘮?shù)塊中使用Hooks。不能在循環(huán)、條件判斷或子函數(shù)中使用Hooks。5.2Hooks基礎(chǔ)通過上節(jié)中的案例,相信你已經(jīng)對(duì)Hooks有了一定認(rèn)識(shí),甚至你已迫切想要嘗試用Hooks來進(jìn)行開發(fā)。Taro提供了很多Hooks,如果這些Hooks無法滿足日常開發(fā)中的需求,也可以根據(jù)規(guī)則自定義Hooks。內(nèi)置Hooks都定義在@tarojs/taro中,所以可以這樣引入對(duì)應(yīng)的Hooks:5.2.1useState該方法接收一個(gè)參數(shù)用于初始化狀態(tài),返回值為一個(gè)數(shù)組,數(shù)組中第一項(xiàng)為狀態(tài),第二項(xiàng)為該狀態(tài)的更新函數(shù)。使用示例如下:組件初次渲染時(shí),count狀態(tài)會(huì)被賦值為0,后續(xù)在組件中調(diào)用setCount就可以使count狀態(tài)更新。但有時(shí)初始狀態(tài)可能需要經(jīng)過計(jì)算惰性初始化,這時(shí)可以傳入函數(shù)并返回對(duì)應(yīng)值來初始化狀態(tài),例如:假設(shè)在組件中有一個(gè)按鈕,當(dāng)單擊按鈕時(shí),將count加1,可以這樣實(shí)現(xiàn):或者使用回調(diào)函數(shù)形式實(shí)現(xiàn)count值的更新:5.2.2useEffect該方法接收兩個(gè)參數(shù),第一個(gè)參數(shù)為處理副作用的函數(shù),第二個(gè)參數(shù)為引起該副作用執(zhí)行的值數(shù)組。以前在使用函數(shù)組件時(shí),無法在組件中進(jìn)行DOM操作,因?yàn)楹瘮?shù)組件每次更新都會(huì)重新執(zhí)行函數(shù)中的邏輯。引入useEffect以后,將DOM、請(qǐng)求等操作放于其中,實(shí)現(xiàn)與class組件類似的效果。在函數(shù)組件中,使用useEffect可以實(shí)現(xiàn)類似class組件的componentDidMount生命周期函數(shù),示例如下:第二個(gè)參數(shù)指定為空數(shù)組時(shí),表示該副作用不依賴任何的值變化,只會(huì)在組件完成初次渲染后執(zhí)行一次。使用useEffect同樣可以實(shí)現(xiàn)類似class組件的componentWillUnmout生命周期函數(shù),示例如下:顯然,useEffect返回的函數(shù)即可定義該副作用的清除邏輯。使用useEffect還可以實(shí)現(xiàn)類似class組件的componentDidUpdate生命周期函數(shù)。值得一提的是,使用useEffect來處理更新優(yōu)于使用componentDidUpdate,因?yàn)閡seEffect在一個(gè)組件中可定義多個(gè)。也就是說,不同的值變化可以在不同的副作用中進(jìn)行處理,這樣就解決了class組件的componentDidUpdate函數(shù)中冗余太多不相關(guān)代碼的問題。對(duì)比如下:使用class組件使用函數(shù)組件+Hooks5.2.3useReducer通常使用useState和useEffect基本能滿足函數(shù)組件狀態(tài)管理的需求,但也難免會(huì)有更加復(fù)雜的場(chǎng)景。我們現(xiàn)在虛擬一個(gè)計(jì)算器組件,需要實(shí)現(xiàn)計(jì)算器的加減乘除等操作,顯然這些方法如果都分散定義,會(huì)導(dǎo)致狀態(tài)管理混亂。這時(shí)我們想到了Redux狀態(tài)管理的思想——單向數(shù)據(jù)流。現(xiàn)在Hooks也提供了這個(gè)功能。useReducer接收三個(gè)參數(shù),第一個(gè)參數(shù)是處理狀態(tài)更新的reducer,第二個(gè)參數(shù)是狀態(tài)初始值,第三個(gè)參數(shù)是狀態(tài)初始化函數(shù)。使用示例如下:第一個(gè)參數(shù)定義與Redux章節(jié)講解的reducer定義一樣,用于響應(yīng)不同action類型并返回新的狀態(tài)。例如我們現(xiàn)在定義count的加減1操作,示例如下:第二個(gè)參數(shù)用于定義初始狀態(tài)值,例如計(jì)算器組件在初始化時(shí),會(huì)將count初始化為0,那么我們定義的initialArg如下:一般情況下,定義了這兩個(gè)參數(shù)后就可以使用useReducer了,在組件中的使用方法如下:從這個(gè)例子可以發(fā)現(xiàn),使用useReducer來管理狀態(tài)比使用useState更符合封閉原則。并且如果有時(shí)外部傳入的p

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論