亚洲色成人网站www永久,亚洲欧美人成视频一区在线,亚洲国产成人高清在线观看,亚洲精品久久久久久动漫,亚洲国产精品久久电影欧美

數(shù)據(jù)專欄

智能大數(shù)據(jù)搬運(yùn)工,你想要的我們都有

科技資訊

科技學(xué)院

科技百科

科技書籍

網(wǎng)站大全

軟件大全

兩點(diǎn)前提: 函數(shù)式組件, 使用 react hooks 子組件導(dǎo)出方法, 供父組件在恰當(dāng)?shù)臅r(shí)機(jī)(想什么時(shí)候就什么時(shí)候)調(diào)用
我唯一想到的方法是使用 ref, 如下, 但是這也太 TM 丑了, 求助大佬們 type ChildFunc = () => number // eslint-disable-next-line react/display-name const Child = React.forwardRef((props, ref) => { // eslint-disable-next-line no-param-reassign (ref as React.MutableRefObject).current = () => 5 return <>I am child }) // Parent function Parent() { const funcRef = useRef() const theRightTime = new Promise((resolve) => { setTimeout(resolve, 1000) }) useEffect(() => { theRightTime.then(() => { if (funcRef.current) { console.log(funcRef.current()) } }) }, [funcRef, theRightTime]) return }
順便提一嘴, react 官網(wǎng)頂部有個(gè)"條幅" -- "黑人的命也是命。 支持公正司法倡議。" -- 但是各位可以切換語(yǔ)言看一下, 好像只有簡(jiǎn)體中文和英文有, 繁體中文、日文等都沒(méi)有(那么多語(yǔ)言, 我也沒(méi)一個(gè)一個(gè)翻, 但是我看了的好幾個(gè)都沒(méi)有)
前沿探索
2020-08-17 23:00:45
Firefox 79.0.2 Build #2015755339
下載某資源網(wǎng)站 app 時(shí)出現(xiàn)流氓軟件攔截,如下圖,這該如何禁用?
已在設(shè)置中禁用“增強(qiáng)型跟蹤保護(hù)”,刪除所有擴(kuò)展。
前沿探索
2020-08-17 22:59:52
去年大更新重點(diǎn)優(yōu)化了在 Mac 上的功耗問(wèn)題,之后一直在小改進(jìn),到現(xiàn)在又過(guò)了一年,WebRender 已經(jīng)在 Nightly 版本默認(rèn)開(kāi)啟了,使用 80.0b1 版本也可以手動(dòng)修改 gfx.webrender.all 開(kāi)啟,現(xiàn)在不看視頻的話功耗基本和 Chrome 持平,看視頻的話略熱,比 Chrome 高 5-10 度,但是 Bugzilla 里已經(jīng)有關(guān)于視頻播放部分的 issue 了,估計(jì)到年底就會(huì)解決。
FF 重構(gòu)之后雖然閹割了很多東西,但是界面還是基于 HTML 的,使用 userChrome 依然有很大的定制空間,而 Chrome 想改個(gè)標(biāo)簽頁(yè) Tab 樣式都要去編譯 Chromium 源碼
前沿探索
2020-08-17 22:59:49
一直用的 firefox 突然間無(wú)法上網(wǎng),顯示為一直在連接,但是就是無(wú)法連接上。 1 、檢查過(guò)網(wǎng)絡(luò)設(shè)置,沒(méi)有問(wèn)題,DNS 也是默認(rèn)的。檢查防火墻,沒(méi)有問(wèn)題,然后又手動(dòng)添加了規(guī)則,不行。殺毒軟件也沒(méi)有限制。 2 、在網(wǎng)上搜索相關(guān)的問(wèn)題,在 mozilla 的網(wǎng)站上照著指引設(shè)置,也是無(wú)效。卸載了,注冊(cè)表也刪除了項(xiàng)目,重裝后也不行。 3 、檢查路由設(shè)置,沒(méi)問(wèn)題。換了其它網(wǎng)絡(luò),同樣不行。 備機(jī)上的 firefox 則完全沒(méi)問(wèn)題。兩部電腦系統(tǒng)都是 WIN7 。 現(xiàn)在真的一籌莫展。暫時(shí)只能換其它瀏覽器使用。 想知道有沒(méi)人曾經(jīng)遇到過(guò)這個(gè)問(wèn)題?
前沿探索
2020-08-17 22:59:46
樓主 Linux Firefox 從源安裝 一直更新 最近幾天 CPU 使用很高 經(jīng)常性的很高 網(wǎng)頁(yè)和之前打開(kāi)的一樣(平時(shí)就看那幾個(gè))插件沒(méi)有新安裝 就一個(gè)翻譯升級(jí)了一下。發(fā)現(xiàn)打開(kāi) godoc 的時(shí)候 CPU 使用額外的高 持續(xù)時(shí)間大約兩三分鐘保持百分百.....
前沿探索
2020-08-17 22:59:39
忽然發(fā)覺(jué) Firefox(v77.0.1)啟動(dòng)速度有點(diǎn)讓人驚訝,個(gè)人體驗(yàn)來(lái)看,感覺(jué)不到延遲,同環(huán)境下,已經(jīng)超越 Chrome(v83.0.4103.106),完全不可同日而語(yǔ),想當(dāng)年,吐槽 Firefox 時(shí)候,啟動(dòng)絕對(duì)是一大詬病,沒(méi)想到如今真是讓人刮目相看,而且即使是其它的操作體驗(yàn),感覺(jué)都是非常不錯(cuò)。只能說(shuō)你,確實(shí)成長(zhǎng)了很多。
前沿探索
2020-08-17 22:59:35
好像更新完地址欄彈了個(gè)小窗,沒(méi)注意就點(diǎn)過(guò)去了
我暈了,淘寶、京東、B 站全跑國(guó)際站去了 = =
打開(kāi)首選項(xiàng) 常規(guī) 網(wǎng)絡(luò)設(shè)置 就可以關(guān)閉了……
前沿探索
2020-08-17 22:59:29
Firefox 瀏覽器在看虎牙火貓看直播時(shí),經(jīng)常大概率過(guò)一小段時(shí)間( 20 分鐘左右)直播網(wǎng)頁(yè)就會(huì)直接空白轉(zhuǎn)圈,刷新都沒(méi)用,只能關(guān)閉標(biāo)簽頁(yè)再重新打開(kāi),真的無(wú)語(yǔ),用 chrome 就不會(huì)有問(wèn)題,改 firefox 的 ua 照樣還是沒(méi)用,難道 Firefox 用戶在國(guó)內(nèi)已經(jīng)少到可以忽略了嗎?
前沿探索
2020-08-17 22:59:23
例如我想把設(shè)置頁(yè)中 "清除歷史記錄" 按鈕放在書簽工具欄上.https://tiebapic.baidu.com/forum/pic/item/d01373f082025aaf9b25d1a1ecedab64034f1a40.jpg
前沿探索
2020-08-17 22:59:17
https://github.com/ustclug/mirrorrequest/issues/251
如何投票
請(qǐng)?jiān)?Github Issue 頁(yè)面使用 Emoji ?? 表情來(lái)投票
前沿探索
2020-08-17 22:59:14
Chrome 和 Safari 上都有,Firefox 上有辦法添加這個(gè)菜單項(xiàng)嗎
前沿探索
2020-08-17 22:59:05
macOS 和 Firefox 都是最新版,不知道是哪個(gè)版本開(kāi)始出現(xiàn)這個(gè)問(wèn)題。
不管是用 Proxy SwitchyOmega 還是內(nèi)置的代理功能都會(huì)泄露本地 DNS。
network.proxy.socks_remote_dns 為 true
前沿探索
2020-08-17 22:59:01
我用的 firefox 最新版 每次搜索一個(gè)內(nèi)容后 搜索框上都會(huì)有個(gè)詢問(wèn) 您是要訪問(wèn) XX 嗎? 請(qǐng)問(wèn)這個(gè)怎么完全關(guān)閉,在設(shè)置找了半天也沒(méi)發(fā)現(xiàn)
前沿探索
2020-08-17 22:59:01
詳情請(qǐng)戳-> https://flutterweekly.dev/flutter-weekly-issue-64/
歡迎關(guān)注公眾號(hào):
前沿探索
2020-08-17 22:58:53
目前 似乎沒(méi)有看到 react-native 有關(guān)于性能優(yōu)化方面的新聞、 以及未來(lái)計(jì)劃
是否意味著 如果比較 在乎性能 , 就不能選 react-native 了?
想聽(tīng)聽(tīng)大家的看法
謝謝
前沿探索
2020-08-17 22:58:53
cronStock() async { _stockCode = _stockCodeController.value.text; _growRate = _growRateController.value.text; _reduceRate = _reduceRateController.value.text; …… saveData(); refreshStock(true); await AndroidAlarmManager.periodic(const Duration(seconds: 30), periodicAlarmID, refreshStock, wakeup: true); } saveData() async { SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.reload(); try { print('0001 saveData: '+DateTime.now().toString()); prefs.setString('stock_prefix', stockPrefix); prefs.setString('stock_code', _stockCodeController.value.text); prefs.setDouble('grow_rate', double.parse(_growRateController.value.text)); prefs.setDouble('reduce_rate', double.parse(_reduceRateController.value.text)); } on FormatException catch(e) { } catch (e) { // No specified type, handles all } } …… refreshStock([bool isManual = false]) { print('0002 refreshStock afterTimer: '+DateTime.now().toString()); Future stockShared = getShared(); stockShared.then((List shared) { print('isManual: $isManual'); print('shareData: $shared'); }); } Future getShared() async { print('getShared'); SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.reload(); return [ prefs.getString('stock_prefix'), prefs.getString('stock_name'), prefs.getString('stock_code'), prefs.getDouble('grow_rate'), prefs.getDouble('reduce_rate'), // 當(dāng)前值 prefs.getString('stock_price'), prefs.getDouble('stock_rate'), prefs.getInt('rate_color'), ]; }
AndroidAlarmManager 插件用到了 Isolate 環(huán)境,在 Isolate 環(huán)境下 refreshStock 函數(shù)中的 getShared()拿到的 SharedPreferences 數(shù)和上面 saveData()存儲(chǔ)的數(shù)據(jù)不同步,要多次間隔調(diào)用的后才會(huì)同步到。
前沿探索
2020-08-17 22:58:44
原文地址: https://blog.codemagic.io/releasing-your-flutter-desktop-application/
原文作者: https://medium.com/@rody.davis.jr
發(fā)布時(shí)間:2020 年 6 月 16 日
所以你建立了你的第一個(gè) Flutter 應(yīng)用,并在 iOS 設(shè)備的 AppStore 和 Android 設(shè)備的 Google Play 上發(fā)布。然后,你想接觸更多的受眾和目標(biāo)網(wǎng)絡(luò),所以你用靜態(tài)主機(jī)發(fā)布了它。但你仍然想要更多。如果你想要移動(dòng)設(shè)備的性能,但又想要 web 的響應(yīng)速度,那么桌面版就是答案。
目前的選擇
對(duì)于桌面,你有幾個(gè)選擇來(lái)發(fā)布--但它們都有一定的權(quán)衡。在這里,我們將討論為什么你可能想選擇一個(gè)而不是另一個(gè)。
Electron
你可能以前聽(tīng)說(shuō)過(guò),因?yàn)槟壳?MacOS 和 Windows 上的大部分第三方應(yīng)用都是用它發(fā)布的。Electron 是一個(gè)由 chromium 驅(qū)動(dòng)的瀏覽器,它使用 Node.js 將網(wǎng)絡(luò)瀏覽器與文件系統(tǒng)粘合在一起。你可以得到為網(wǎng)絡(luò)開(kāi)發(fā)的好處,但又有桌面的靈活性。它使用大量的 JS 來(lái)做到這一點(diǎn),所以你失去了 AOT (提前編譯) ,這意味著沒(méi)有剪枝,優(yōu)化或一般性能。
Flutter 在移動(dòng)端工作得很好的原因是由于發(fā)布構(gòu)建的 AOT 。你可以通過(guò)在根目錄下添加一個(gè) manifest 文件和一些額外的模板腳本,用 Electron 發(fā)布一個(gè) Flutter 應(yīng)用程序。您打包您的 Flutter Web 應(yīng)用程序,就像部署到靜態(tài)托管時(shí)一樣。如果您想使用 FLUTTER_WEB_USE_SKIA 標(biāo)志,您可以從 Web 版本中獲得更好的性能。
單機(jī)版
無(wú)論你是用 Electron 、桌面嵌入還是自定義嵌入器來(lái)構(gòu)建你的 Flutter 桌面應(yīng)用,你都需要一種方法來(lái)向世界發(fā)布應(yīng)用。如果你用 Codemagic 、Github Actions 或手動(dòng)創(chuàng)建一個(gè)發(fā)布構(gòu)建,你就把構(gòu)建上傳到 Amazon S3 或類似的 CDN,并把鏈接提供給客戶。這種方法很好,因?yàn)槟悴槐氐却龑彶檫^(guò)程,也不必處理每個(gè)平臺(tái)的非常具體的規(guī)則。然后,你可以把這個(gè)鏈接放在你的 Flutter Web 應(yīng)用或 PWA 清單上,只要用戶覺(jué)得合適,就可以提供原生體驗(yàn)。
這種方法的一個(gè)主要缺點(diǎn)是你如何處理更新。您需要使用一個(gè)庫(kù)或自定義構(gòu)建的解決方案,用于在后臺(tái)下載和安裝更新或通知用戶新的更新。你給用戶的步驟越多,他們完成所有步驟的可能性就越小。你以用戶為代價(jià)換取了靈活性和可用性。
有一些不錯(cuò)的工具,比如 MacOS 和 Windows 的 Sparkle,它為你每天使用的很多應(yīng)用程序提供了動(dòng)力。每當(dāng)你看到一個(gè)彈出窗口說(shuō)一個(gè)更新已經(jīng)準(zhǔn)備好安裝時(shí),很有可能是在使用這個(gè)庫(kù)。該庫(kù)通過(guò)托管的 RSS 源運(yùn)行,你可以通過(guò)解析來(lái)獲取發(fā)布說(shuō)明、版本和安裝鏈接。該應(yīng)用程序?qū)L試在后臺(tái)為你自動(dòng)安裝它們,并在你下次啟動(dòng)時(shí)重新加載。
官方商店
現(xiàn)在,你可能會(huì)推遲的選擇是發(fā)布到官方商店。在應(yīng)用商店之外,可能有合法的理由,因?yàn)槟憧赡軟](méi)有遵循所有的指導(dǎo)方針,有一個(gè)自定義的部署和發(fā)布后臺(tái),企業(yè)應(yīng)用只用于內(nèi)部或有限的使用,或者你只是想在你的網(wǎng)站上有一個(gè)鏈接來(lái)下載應(yīng)用。許多應(yīng)用程序甚至在商店中提供應(yīng)用程序,但也有一個(gè)在線版本,可能是測(cè)試頻道或特殊構(gòu)建。我發(fā)現(xiàn)這是一個(gè)很好的方法,因?yàn)槔?Mac AppStore 仍然沒(méi)有像 iOS 那樣為 MacOS 應(yīng)用提供 TestFlight 。
部署到商店可能具有挑戰(zhàn)性,但我相信最終是值得的,因?yàn)槟惬@得了安全性和自動(dòng)更新。他們還將處理付款和退款。在未來(lái)的文章中,我將會(huì)介紹向 Mac AppStore 發(fā)布 Flutter 應(yīng)用程序,就像我在 iPadOS 和 MacOS 上發(fā)布新的 Widget Studio 一樣。順便說(shuō)一下,Widget Studio 也可以作為一個(gè) PWA 。我建議在 MacOS 上,你應(yīng)該只包括你正在積極使用的權(quán)限,如果你想分享 Mac 和 iPad 應(yīng)用的購(gòu)買,你需要有相同的捆綁 ID 。你不會(huì)用 Catalyst,而是用當(dāng)前的桌面嵌入來(lái)做這件事。
結(jié)束語(yǔ)
這是一個(gè)激動(dòng)人心的時(shí)刻,以一種原生的方式將移動(dòng)應(yīng)用帶到桌面,這在以前是不可能的。Flutter 很厲害,可以讓你針對(duì) MacOS 、Windows 和 Linux,現(xiàn)在由你來(lái)決定如何發(fā)布。Codemagic 支持 Mac 和 Linux ,這比 2 個(gè)復(fù)選框還要簡(jiǎn)單。如果你有任何問(wèn)題,請(qǐng)告訴我,我期待著看到你的 Flutter 桌面應(yīng)用程序!
Rody Davis Jr 是一名專業(yè)的全棧開(kāi)發(fā)者,在企業(yè)和個(gè)人應(yīng)用方面都有豐富的經(jīng)驗(yàn)。他使用最新的框架為 App Store 、Google Play 、Web 和桌面創(chuàng)建應(yīng)用程序。Rody 熱愛(ài) Flutter 、Web 和所有有創(chuàng)意的東西,并在 Medium 上寫 Flutter 文章。他希望通過(guò)他的應(yīng)用接觸到盡可能多的人,并展示最新科技的可能性。
通過(guò) www.DeepL.com/Translator (免費(fèi)版)翻譯
前沿探索
2020-08-17 22:58:41
需要定時(shí)運(yùn)行一個(gè)函數(shù)獲取 API 數(shù)據(jù),用的 Timer.periodic 做間隔執(zhí)行,但是測(cè)試發(fā)現(xiàn)正式包手機(jī)息屏后 Timer.periodic 就暫停了,真機(jī)調(diào)試模式下是沒(méi)有問(wèn)題的。后來(lái)?yè)Q android_alarm_manager 插件實(shí)現(xiàn)間隔調(diào)用還是有同樣的問(wèn)題。
前沿探索
2020-08-17 22:58:39
我這邊沿襲了 web 的笨辦法,在一個(gè) webservice.dart 下面維護(hù)所有的接口 url,然后每一個(gè)功能模塊 import 一次,各位有啥更先進(jìn)的辦法嗎,比如作為一個(gè)環(huán)境變量來(lái)維護(hù),剛上手對(duì) dart 特性還不太熟悉,先謝謝了
前沿探索
2020-08-17 22:58:34
詳情請(qǐng)戳-> https://flutterweekly.dev/flutter-weekly-issue-63/
歡迎關(guān)注公眾號(hào):
前沿探索
2020-08-17 22:58:22
今天在 Boss 直聘查詢了一下跟移動(dòng)端開(kāi)發(fā)的崗位信息,發(fā)現(xiàn)目前對(duì) flutter 的崗位需求越來(lái)越大了。
Boss 直聘崗位數(shù)量,以下統(tǒng)計(jì)頁(yè)數(shù),每頁(yè) 30 條 android 12 頁(yè) 安卓 10 頁(yè) ios 14 頁(yè) flutter 12 頁(yè) uniapp 4 頁(yè) uni-app 5 頁(yè) taro 1 條
前沿探索
2020-08-17 22:58:24
前言
前幾天寫了一個(gè) Fluter 插件 tcard ,用來(lái)實(shí)現(xiàn)類似于探探卡片的布局。效果如下,本文講解如何使用 Stack 控件實(shí)現(xiàn)這個(gè)布局。
在線查看
初識(shí) Stack
Stack 是一個(gè)有多子項(xiàng)的控件,它會(huì)將自己的子項(xiàng)相對(duì)于自身邊緣進(jìn)行定位,后面的子項(xiàng)會(huì)覆蓋前面的子項(xiàng)。通常用來(lái)實(shí)現(xiàn)將一個(gè)控件覆蓋于另一個(gè)控件之上的布局,比如在一張圖片上顯示一些文字。子項(xiàng)的默認(rèn)位置在 Stack 左上角,也可以用 Align 或者 Positioned 控件分別進(jìn)行定位。 Stack( children: [ Container( width: 100, height: 100, color: Colors.red, ), Container( width: 90, height: 90, color: Colors.green, ), Container( width: 80, height: 80, color: Colors.blue, ), ], )
Stack (Flutter Widget of the Week)
布局思路
要使用 Stack 實(shí)現(xiàn)這個(gè)卡片布局的大致思路如下 首先需要前,中,后三個(gè)子控件,使用 Align 控件定位在容器中。 需要一個(gè)手勢(shì)監(jiān)聽(tīng)器 GestureDetector 監(jiān)聽(tīng)手指滑動(dòng)。 監(jiān)聽(tīng)手指在屏幕上滑動(dòng)同時(shí)更新最前面卡片的位置。 判斷移動(dòng)的橫軸距離進(jìn)行卡片位置變換動(dòng)畫或者卡片回彈動(dòng)畫。 如果運(yùn)行了卡片位置變換動(dòng)畫在動(dòng)畫結(jié)束后更新卡片的索引值。
卡片布局 創(chuàng)建 Stack 容器以及前,中,后三個(gè)子控件 class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { // 前面的卡片,使用 Align 定位 Widget _frontCard() { return Align( child: Container( color: Colors.blue, ), ); } // 中間的卡片,使用 Align 定位 Widget _middleCard() { return Align( child: Container( color: Colors.red, ), ); } // 后面的卡片,使用 Align 定位 Widget _backCard() { return Align( child: Container( color: Colors.green, ), ); } @override Widget build(BuildContext context) { return MaterialApp( title: 'TCards demo', debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: SizedBox( width: 300, height: 400, child: Stack( children: [ // 后面的子項(xiàng)會(huì)顯示在上面,所以前面的卡片放在最后 _backCard(), _middleCard(), _frontCard(), ], ), ), ), ), ); } } 對(duì)子控件分別定位并設(shè)置其尺寸
定位需要設(shè)置 Align 控件的 alignment 屬性,傳入一個(gè) Alignment(x, y) 進(jìn)行設(shè)置。設(shè)置尺寸需要使用 LayoutBuilder 獲取當(dāng)前父容器的尺寸,然后根據(jù)容器尺寸進(jìn)行計(jì)算。 class _MyAppState extends State { // 前面的卡片,使用 Align 定位 Widget _frontCard(BoxConstraints constraints) { return Align( alignment: Alignment(0.0, -0.5), // 使用 SizedBox 確定卡片尺寸 child: SizedBox.fromSize( // 計(jì)算卡片尺寸,相對(duì)于父容器 size: Size(constraints.maxWidth * 0.9, constraints.maxHeight * 0.9), child: Container( color: Colors.blue, ), ), ); } // 中間的卡片,使用 Align 定位 Widget _middleCard(BoxConstraints constraints) { return Align( alignment: Alignment(0.0, 0.0), child: SizedBox.fromSize( // 計(jì)算卡片尺寸,相對(duì)于父容器 size: Size(constraints.maxWidth * 0.85, constraints.maxHeight * 0.9), child: Container( color: Colors.red, ), ), ); } // 后面的卡片,使用 Align 定位 Widget _backCard(BoxConstraints constraints) { return Align( alignment: Alignment(0.0, 0.5), child: SizedBox.fromSize( // 計(jì)算卡片尺寸,相對(duì)于父容器 size: Size(constraints.maxWidth * 0.8, constraints.maxHeight * .9), child: Container( color: Colors.green, ), ), ); } @override Widget build(BuildContext context) { return MaterialApp( title: 'TCards demo', debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: SizedBox( width: 300, height: 400, child: LayoutBuilder( builder: (context, constraints) { // 使用 LayoutBuilder 獲取容器的尺寸,傳個(gè)子項(xiàng)計(jì)算卡片尺寸 return Stack( children: [ // 后面的子項(xiàng)會(huì)顯示在上面,所以前面的卡片放在最后 _backCard(constraints), _middleCard(constraints), _frontCard(constraints), ], ); }, ), ), ), ), ); } } 更新最前面卡片位置
向 Stack 容器添加一個(gè) GestureDetector ,手指在屏幕上移動(dòng)時(shí)更新最前面卡片的位置。 class _MyAppState extends State { // 保存最前面卡片的定位 Alignment _frontCardAlignment = Alignment(0.0, -0.5); // 保存最前面卡片的旋轉(zhuǎn)角度 double _frontCardRotation = 0.0; // 前面的卡片,使用 Align 定位 Widget _frontCard(BoxConstraints constraints) { return Align( alignment: _frontCardAlignment, // 使用 Transform.rotate 旋轉(zhuǎn)卡片 child: Transform.rotate( angle: (pi / 180.0) * _frontCardRotation, // 使用 SizedBox 確定卡片尺寸 child: SizedBox.fromSize( size: Size(constraints.maxWidth * 0.9, constraints.maxHeight * 0.9), child: Container( color: Colors.blue, ), ), ), ); } // 省略...... @override Widget build(BuildContext context) { return MaterialApp( title: 'TCards demo', debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: SizedBox( width: 300, height: 400, child: LayoutBuilder( builder: (context, constraints) { // 使用 LayoutBuilder 獲取容器的尺寸,傳個(gè)子項(xiàng)計(jì)算卡片尺寸 Size size = MediaQuery.of(context).size; double speed = 10.0; return Stack( children: [ // 后面的子項(xiàng)會(huì)顯示在上面,所以前面的卡片放在最后 _backCard(constraints), _middleCard(constraints), _frontCard(constraints), // 使用一個(gè)占滿父元素的 GestureDetector 監(jiān)聽(tīng)手指移動(dòng) SizedBox.expand( child: GestureDetector( onPanDown: (DragDownDetails details) {}, onPanUpdate: (DragUpdateDetails details) { // 手指移動(dòng)就更新最前面卡片的 alignment 屬性 _frontCardAlignment += Alignment( details.delta.dx / (size.width / 2) * speed, details.delta.dy / (size.height / 2) * speed, ); // 設(shè)置最前面卡片的旋轉(zhuǎn)角度 _frontCardRotation = _frontCardAlignment.x; // setState 更新界面 setState(() {}); }, onPanEnd: (DragEndDetails details) {}, ), ), ], ); }, ), ), ), ), ); } }
卡片動(dòng)畫
這個(gè)布局有三種動(dòng)畫,最前面卡片移開(kāi)的動(dòng)畫;后面兩張卡片位置和尺寸變化的動(dòng)畫;最前面卡片回到原位的動(dòng)畫。 判斷卡片橫軸移動(dòng)距離
在手指離開(kāi)屏幕時(shí)判斷卡片橫軸的移動(dòng)距離,如果最前面的卡片橫軸移動(dòng)距離超過(guò)限制就運(yùn)行換位動(dòng)畫,否則運(yùn)行回彈動(dòng)畫。 // 改變位置的動(dòng)畫 void _runChangeOrderAnimation() {} // 卡片回彈的動(dòng)畫 void _runReboundAnimation(Offset pixelsPerSecond, Size size) {} // 省略... // 卡片橫軸距離限制 final double limit = 10.0; SizedBox.expand( child: GestureDetector( // 省略... onPanEnd: (DragEndDetails details) { // 如果最前面的卡片橫軸移動(dòng)距離超過(guò)限制就運(yùn)行換位動(dòng)畫,否則運(yùn)行回彈動(dòng)畫 if (_frontCardAlignment.x > limit || _frontCardAlignment.x < -limit) { _runChangeOrderAnimation(); } else { _runReboundAnimation( details.velocity.pixelsPerSecond, size, ); } }, ), ), 卡片回彈動(dòng)畫
首先實(shí)現(xiàn)卡片回彈的動(dòng)畫,使用 AnimationController 控制動(dòng)畫,在 initState 初始化動(dòng)畫控制器。創(chuàng)建一個(gè) AlignmentTween 設(shè)置動(dòng)畫運(yùn)動(dòng)值,起始值是卡片當(dāng)前位置,最終值是卡片的默認(rèn)位置。然后將一個(gè)彈簧模擬 SpringSimulation 傳遞給動(dòng)畫控制器,讓動(dòng)畫模擬運(yùn)行。 class _MyAppState extends State with TickerProviderStateMixin { // 省略... // 卡片回彈動(dòng)畫 Animation _reboundAnimation; // 卡片回彈動(dòng)畫控制器 AnimationController _reboundController; // 省略... // 卡片回彈的動(dòng)畫 void _runReboundAnimation(Offset pixelsPerSecond, Size size) { // 創(chuàng)建動(dòng)畫值 _reboundAnimation = _reboundController.drive( AlignmentTween( // 起始值是卡片當(dāng)前位置,最終值是卡片的默認(rèn)位置 begin: _frontCardAlignment, end: Alignment(0.0, -0.5), ), ); // 計(jì)算卡片運(yùn)動(dòng)速度 final double unitsPerSecondX = pixelsPerSecond.dx / size.width; final double unitsPerSecondY = pixelsPerSecond.dy / size.height; final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY); final unitVelocity = unitsPerSecond.distance; // 創(chuàng)建彈簧模擬的定義 const spring = SpringDescription(mass: 30, stiffness: 1, damping: 1); // 創(chuàng)建彈簧模擬 final simulation = SpringSimulation(spring, 0, 1, -unitVelocity); // 根據(jù)給定的模擬運(yùn)行動(dòng)畫 _reboundController.animateWith(simulation); // 重置旋轉(zhuǎn)值 _frontCardRotation = 0.0; setState(() {}); } @override void initState() { super.initState(); // 初始化回彈的動(dòng)畫控制器 _reboundController = AnimationController(vsync: this) ..addListener(() { setState(() { // 動(dòng)畫運(yùn)行時(shí)更新最前面卡片的 alignment 屬性 _frontCardAlignment = _reboundAnimation.value; }); }); } // 省略... } 卡片換位動(dòng)畫
卡片換位動(dòng)畫就是將最前面的卡片移除可視區(qū),將中間的卡片移動(dòng)到最前面,將最后的卡片移動(dòng)到中間,然后新建一個(gè)最后面的卡片。在卡片更換位置的同時(shí)需要改變卡片的尺寸,位置動(dòng)畫和尺寸動(dòng)畫同時(shí)進(jìn)行。首先定義每個(gè)卡片運(yùn)動(dòng)時(shí)的動(dòng)畫值 /// 卡片尺寸 class CardSizes { static Size front(BoxConstraints constraints) { return Size(constraints.maxWidth * 0.9, constraints.maxHeight * 0.9); } static Size middle(BoxConstraints constraints) { return Size(constraints.maxWidth * 0.85, constraints.maxHeight * 0.9); } static Size back(BoxConstraints constraints) { return Size(constraints.maxWidth * 0.8, constraints.maxHeight * .9); } } /// 卡片位置 class CardAlignments { static Alignment front = Alignment(0.0, -0.5); static Alignment middle = Alignment(0.0, 0.0); static Alignment back = Alignment(0.0, 0.5); } /// 卡片運(yùn)動(dòng)動(dòng)畫 class CardAnimations { /// 最前面卡片的消失動(dòng)畫值 static Animation frontCardDisappearAnimation( AnimationController parent, Alignment beginAlignment, ) { return AlignmentTween( begin: beginAlignment, end: Alignment( beginAlignment.x > 0 ? beginAlignment.x + 30.0 : beginAlignment.x - 30.0, 0.0, ), ).animate( CurvedAnimation( parent: parent, curve: Interval(0.0, 0.5, curve: Curves.easeIn), ), ); } /// 中間卡片位置變換動(dòng)畫值 static Animation middleCardAlignmentAnimation( AnimationController parent, ) { return AlignmentTween( begin: CardAlignments.middle, end: CardAlignments.front, ).animate( CurvedAnimation( parent: parent, curve: Interval(0.2, 0.5, curve: Curves.easeIn), ), ); } /// 中間卡片尺寸變換動(dòng)畫值 static Animation middleCardSizeAnimation( AnimationController parent, BoxConstraints constraints, ) { return SizeTween( begin: CardSizes.middle(constraints), end: CardSizes.front(constraints), ).animate( CurvedAnimation( parent: parent, curve: Interval(0.2, 0.5, curve: Curves.easeIn), ), ); } /// 最后面卡片位置變換動(dòng)畫值 static Animation backCardAlignmentAnimation( AnimationController parent, ) { return AlignmentTween( begin: CardAlignments.back, end: CardAlignments.middle, ).animate( CurvedAnimation( parent: parent, curve: Interval(0.4, 0.7, curve: Curves.easeIn), ), ); } /// 最后面卡片尺寸變換動(dòng)畫值 static Animation backCardSizeAnimation( AnimationController parent, BoxConstraints constraints, ) { return SizeTween( begin: CardSizes.back(constraints), end: CardSizes.middle(constraints), ).animate( CurvedAnimation( parent: parent, curve: Interval(0.4, 0.7, curve: Curves.easeIn), ), ); } }
使用一個(gè) AnimationController 控制動(dòng)畫運(yùn)行,動(dòng)畫運(yùn)行時(shí)在卡片上應(yīng)用以上的動(dòng)畫值,否則使用卡片默認(rèn)的位置和尺寸。 class _MyAppState extends State with TickerProviderStateMixin { // 省略... // 卡片位置變換動(dòng)畫控制器 AnimationController _cardChangeController; // 前面的卡片,使用 Align 定位 Widget _frontCard(BoxConstraints constraints) { // 判斷動(dòng)畫是否在運(yùn)行 bool forward = _cardChangeController.status == AnimationStatus.forward; // 使用 Transform.rotate 旋轉(zhuǎn)卡片 Widget rotate = Transform.rotate( angle: (pi / 180.0) * _frontCardRotation, // 使用 SizedBox 確定卡片尺寸 child: SizedBox.fromSize( size: CardSizes.front(constraints), child: Container( color: Colors.blue, ), ), ); // 在動(dòng)畫運(yùn)行時(shí)使用動(dòng)畫值 if (forward) { return Align( alignment: CardAnimations.frontCardDisappearAnimation( _cardChangeController, _frontCardAlignment, ).value, child: rotate, ); } // 否則使用默認(rèn)值 return Align( alignment: _frontCardAlignment, child: rotate, ); } // 中間的卡片,使用 Align 定位 Widget _middleCard(BoxConstraints constraints) { // 判斷動(dòng)畫是否在運(yùn)行 bool forward = _cardChangeController.status == AnimationStatus.forward; Widget child = Container(color: Colors.red); // 在動(dòng)畫運(yùn)行時(shí)使用動(dòng)畫值 if (forward) { return Align( alignment: CardAnimations.middleCardAlignmentAnimation( _cardChangeController, ).value, child: SizedBox.fromSize( size: CardAnimations.middleCardSizeAnimation( _cardChangeController, constraints, ).value, child: child, ), ); } // 否則使用默認(rèn)值 return Align( alignment: CardAlignments.middle, child: SizedBox.fromSize( size: CardSizes.middle(constraints), child: child, ), ); } // 后面的卡片,使用 Align 定位 Widget _backCard(BoxConstraints constraints) { // 判斷動(dòng)畫是否在運(yùn)行 bool forward = _cardChangeController.status == AnimationStatus.forward; Widget child = Container(color: Colors.green); // 在動(dòng)畫運(yùn)行時(shí)使用動(dòng)畫值 if (forward) { return Align( alignment: CardAnimations.backCardAlignmentAnimation( _cardChangeController, ).value, child: SizedBox.fromSize( size: CardAnimations.backCardSizeAnimation( _cardChangeController, constraints, ).value, child: child, ), ); } // 否則使用默認(rèn)值 return Align( alignment: CardAlignments.back, child: SizedBox.fromSize( size: CardSizes.back(constraints), child: child, ), ); } // 改變位置的動(dòng)畫 void _runChangeOrderAnimation() { _cardChangeController.reset(); _cardChangeController.forward(); } // 省略... @override void initState() { super.initState(); // 省略... // 初始化卡片換位動(dòng)畫控制器 _cardChangeController = AnimationController( duration: Duration(milliseconds: 1000), vsync: this, ) ..addListener(() => setState(() {})) ..addStatusListener((status) { if (status == AnimationStatus.completed) { // 動(dòng)畫運(yùn)行結(jié)束后重置位置和旋轉(zhuǎn) _frontCardRotation = 0.0; _frontCardAlignment = CardAlignments.front; setState(() {}); } }); } // 省略... }
數(shù)據(jù)更新
主題內(nèi)容長(zhǎng)度不能超過(guò) 20000 個(gè)字符。。。超長(zhǎng)限制,全文地址在此 用 Flutter 實(shí)現(xiàn)探探卡片布局
前沿探索
2020-08-17 22:58:17
鑒于 Flutter 高性能渲染和跨平臺(tái)的優(yōu)勢(shì),閃點(diǎn)清單在移動(dòng)端 APP 上,使用了完整的 Flutter 框架來(lái)開(kāi)發(fā)。既然是完整 APP,架構(gòu)搭建完全不受歷史 Native APP 的影響,沒(méi)有歷史包袱的沉淀,設(shè)計(jì)也能更靈活和健壯。
國(guó)際化語(yǔ)言的支持,是很多 APP 都有的一個(gè)強(qiáng)需求,APP 無(wú)論大小,只要還不想放棄國(guó)外的客戶,一般就需要支持國(guó)際化。
官方支持
Flutter 官方方案提供了國(guó)際化的基礎(chǔ)支持,如 Flutter 內(nèi)置組件的國(guó)際化、語(yǔ)言代理、Widget 使用語(yǔ)言包、語(yǔ)言設(shè)置回調(diào)等,并支持自定義第三方類來(lái)擴(kuò)展,可以參考 Flutter 國(guó)際化文檔。 官方支持代碼示例: class DemoLocalizations { DemoLocalizations(this.locale); final Locale locale; static DemoLocalizations of(BuildContext context) { return Localizations.of(context, DemoLocalizations); } static Map> _localizedValues = { 'en': { 'title': 'Hello World', }, 'es': { 'title': 'Hola Mundo', }, }; String get title { return _localizedValues[locale.languageCode]['title']; } }
官方方案的缺陷
官方的支持有幾個(gè)缺陷: 依賴于 BuildContext 對(duì)象,在非 Widget 中調(diào)用時(shí),需要層層傳遞 BuildContext 對(duì)象,或存儲(chǔ)全局 BuildContext 對(duì)象。 在 MaterialApp 初始化前無(wú)法使用國(guó)際化(原因也是依賴于 BuildContext 對(duì)象)。 語(yǔ)言包定義推薦使用 Map 方式,無(wú)法利用靜態(tài)語(yǔ)言的優(yōu)勢(shì)(語(yǔ)法提示、錯(cuò)誤檢查等);而為語(yǔ)言包每個(gè)屬性自定義類和類字段,成本較高、使用和更新靈活性差。
i18n 介紹
鑒于 Flutter 官方支持的缺陷,我們調(diào)研了很多第三方庫(kù),最終發(fā)現(xiàn)了 i18n,并在此基礎(chǔ)上、結(jié)合 Flutter 官方支持和自身封裝,實(shí)現(xiàn)了更靈活易用的方案。
基礎(chǔ)使用
i18n 使用 yaml 格式來(lái)定義語(yǔ)言包,同時(shí)提供構(gòu)建腳本一鍵生成 Dart 語(yǔ)言包 Class 。如下: lib/messages.i18n.yaml button: save: Save load: Load users: welcome(String name): "Hello $name!" logout: Logout
該配置會(huì)生成幾個(gè) Class:Messages 、ButtonMessages 、UserMessages,生成后的 Dart 文件使用方式如下: Messages m = Messages(); debugPrint(m.users.logout); debugPrint(m.users.welcome('World'));
生成的 Dart 文件預(yù)覽(開(kāi)發(fā)時(shí)無(wú)需關(guān)心): class Messages { const Messages(); ButtonMessages get button => ButtonExampleMessages(this); UsersMessages get users => UsersExampleMessages(this); } class ButtonMessages { final Messages _parent; const ButtonMessages(this._parent); String get save => "Save"; String get load => "Load"; } class UsersMessages { final Messages _parent; const UsersMessages(this._parent); String get logout => "Logout"; String welcome(String name) => "Hello $name!"; }
進(jìn)階功能
下面講解一些進(jìn)階用法。
函數(shù)定義
i18n 支持函數(shù)定義,并支持傳參,如上述的 welcome 函數(shù): debugPrint(m.users.welcome('World'));
參數(shù)定義基本沒(méi)有限制,可以隨意定義參數(shù)個(gè)數(shù)和類型。
內(nèi)置函數(shù)
i18n 支持了一些內(nèi)置函數(shù),用于做不同語(yǔ)言解析的體驗(yàn)優(yōu)化,如:plural 、cardinal 、ordinal 。具體規(guī)則和使用,可以參考這里: http://cldr.unicode.org/index/cldr-spec/plural-rules
使用 Dart 字符串模板
Dart 字符串模板是非常強(qiáng)大的,而在 i18n 中,你可以使用字符串模板(這點(diǎn)非常贊),如: count(int cnt): "You have created $cnt ${_plural(cnt, one:'invoice', many:'invoices')}."
前置編譯
i18n 依然依賴了 Dart 官方提供的 builder_runner 工具,來(lái)從 yaml 文件生成 Dart 文件,使用方式: flutter pub run build_runner build 。
語(yǔ)言包使用
前置編譯后,每個(gè)語(yǔ)言包會(huì)生成 N 個(gè) Class (語(yǔ)言包的每一個(gè)分類或組合會(huì)生成一個(gè) Class 文件),然后會(huì)生成一個(gè)根 Class,我們可以直接使用根 Class (當(dāng)然也可以使用任何一個(gè)分類層級(jí)的 Class )。
比如兩個(gè)語(yǔ)言包文件: AppMessages.i18n.yaml 和 AppMessages_en.i18n.yaml (未加語(yǔ)言后綴的,會(huì)認(rèn)為是默認(rèn)語(yǔ)言包,因此 AppMessages.i18n.yaml 是默認(rèn)語(yǔ)言包),會(huì)生成 2 個(gè)根 Dart Class: class AppMessages 和 class AppMessages_en extends AppMessages 。
AppMessages_en 自動(dòng)繼承自 AppMessages ,因此我們可以直接使用 AppMessages 類型來(lái)存儲(chǔ)語(yǔ)言包,并在語(yǔ)言切換時(shí)重新為其實(shí)例化對(duì)應(yīng)的子類: AppMessages appMessages = new AppMessages(); resetLocalLang(String localeName) { switch (localeName) { case 'en': appMessages = AppMessages_en(); break; case 'zh': default: appMessages = AppMessages(); break; } }
然后你可以在任意地方使用語(yǔ)言包: debugPrint('Load Button: ${appMessages.button.load}'); FlatButton( child: Text(appMessages.button.save), onPressed: () { /// 干點(diǎn)什么 }, )
Flutter 集成
集成到 Flutter,依然要依賴于官方的支持,在 MaterialApp 中設(shè)置和監(jiān)聽(tīng)本地語(yǔ)言包: @override Widget build(BuildContext context) { return MaterialApp( localeResolutionCallback: (Locale locale, Iterable supportedLocales) { /// Local changed }, localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: ['zh', 'en'] .map((loc) => new Locale(loc)) .toList(growable: false), /// ... ); }
結(jié)尾
國(guó)際化支持,是一個(gè)移動(dòng)端 APP 框架層的基礎(chǔ)能力,設(shè)計(jì)原則應(yīng)該是使用無(wú)感知、靈活易擴(kuò)展;但維護(hù)成本是難免有增加的,比如每次改文案要所有語(yǔ)言包同時(shí)更改。
講到這里,還并沒(méi)有完成基礎(chǔ)框架的搭建,后面我們會(huì)講解更多的 Flutter 架構(gòu)設(shè)計(jì)內(nèi)容,比如:通知、分享、UI 設(shè)計(jì)等等。
持續(xù)分享閃點(diǎn)清單在 Flutter 上的開(kāi)發(fā)經(jīng)驗(yàn)。 閃點(diǎn)清單 ,一款懸浮清單軟件:
前沿探索
2020-08-17 22:58:10
用 React 做了一個(gè)二維碼生成器,歡迎來(lái)玩!
Github 倉(cāng)庫(kù)
github.com/ciaochaos/qrbtf
訪問(wèn)網(wǎng)站
qrbtf.com
圖文介紹 如何制作一個(gè)漂亮的二維碼
urlify.cn/qUfaMb QRBTF 開(kāi)源啦!來(lái)寫個(gè)二維碼樣式吧~
urlify.cn/VBNVVb
前沿探索
2020-08-17 22:57:47
現(xiàn)在公司大部分前端項(xiàng)目都切換到 React 了,社區(qū)、論壇類,電商商品詳情頁(yè)都有 SEO 需求,然鵝 SPA 應(yīng)用 SEO 確實(shí)是個(gè)麻煩事,經(jīng)過(guò)前期實(shí)踐后總結(jié)出我們的 SEO 方案??梢钥纯聪挛?歡迎感興趣的小伙伴討論下~
SPA 的 SEO 方案對(duì)比、最終實(shí)踐
PS:安利下公司的一個(gè)任務(wù)管理神器, TaskHub 文件式任務(wù)管理
前沿探索
2020-08-17 22:57:35
請(qǐng)教一個(gè) react router 問(wèn)題 ,我想獲取當(dāng)前路徑匹配的 path ( user/:id )而不是 pathname(user/111),用的 umi,我記得 react 之前好像是區(qū)分 path 和 url,我又找不到資料了的先謝謝了
前沿探索
2020-08-17 22:57:33
我最近心血來(lái)潮學(xué)前端, 覺(jué)得還是 React 社區(qū)力量大所以準(zhǔn)備投入這個(gè)陣營(yíng).但是跟著官方的那個(gè)下棋的教程走完之后發(fā)現(xiàn)如果想做個(gè)稍微復(fù)雜一點(diǎn)的頁(yè)面,比如百度首頁(yè)(盡管很簡(jiǎn)潔但是對(duì)于初學(xué)者還是復(fù)雜了點(diǎn)) 那么就要找到合適的布局工具或者技術(shù)。 但是翻了一下 React 的官方文檔,似乎沒(méi)有談到如何布局的文章。 哪位 React 大佬可以發(fā)幾篇文章鏈接看看么?
前沿探索
2020-08-17 22:57:30
分享一個(gè) RN 快速開(kāi)發(fā)庫(kù): react-native-easy-app 。一款為 React Native App 開(kāi)發(fā)提供基礎(chǔ)服務(wù)的純 JS 庫(kù)( 支持 IOS & Android ),可以為開(kāi)發(fā)者開(kāi)發(fā)項(xiàng)目提供強(qiáng)有力的支持,可以大幅度提高編碼的效率,特別是在項(xiàng)目搭建初期,至少可以為開(kāi)發(fā)者減少 30%的工作量。
由于前面的文章已經(jīng)做過(guò)介紹,在這里就不詳細(xì)介紹了,通過(guò)本開(kāi)源庫(kù),你可以有以下“高級(jí)的操作”: 可以像訪問(wèn)內(nèi)存對(duì)象一樣訪問(wèn) AsyncStorage
相關(guān)文章:
一分鐘實(shí)現(xiàn),一個(gè) RN 持久數(shù)據(jù)管理器 ;
react-native-easy-app 詳解與使用之(一) AsyncStorage 只需要幾十行代碼就能實(shí)現(xiàn),一個(gè)完整的 app 與服務(wù)器的 Http 請(qǐng)求交互
相關(guān)文章:
二十分鐘封裝,一個(gè) App 前后臺(tái) Http 交互的實(shí)現(xiàn) ; react-native-easy-app 詳解與使用之(二) fetch 一行配置 + 基礎(chǔ)組件的使用就即可以實(shí)現(xiàn),UI 自動(dòng)屏幕適配
相關(guān)文章:
詳解與使用之(三) View,Text,Image,Flatlist ; react-native-easy-app 詳解與使用之(四)屏幕適配
另附有多個(gè)不同版本的 Demo 供大家參考用法:
Sample
Sample_Mobx
Sample_Redux
以下為 Sample_Redux 示例程序的 UI 部分截圖:
開(kāi)源庫(kù)中也有詳細(xì)的 README 說(shuō)明文檔,如下圖:歡迎大家使用,感謝 Star ! 是不是想進(jìn)一步了解一下啦?那趕緊點(diǎn)擊鏈接 react-native-easy-app 進(jìn)去看看吧?
前沿探索
2020-08-17 22:57:21
舉個(gè)例子
https://styled-components.com/docs/basics#adapting-based-on-props
在很多的地方看到過(guò)這種效果,但是不曉得如何實(shí)現(xiàn)
前沿探索
2020-08-17 22:57:19
在 render 里面實(shí)現(xiàn)了一部分渲染的工作,但沒(méi)有把對(duì)應(yīng) Component 的內(nèi)容全部寫入 render,因?yàn)樵?initial load 時(shí),某些 dom 節(jié)點(diǎn)還沒(méi)被創(chuàng)建, 直到 render() 之后跑到 componentDidMount() 時(shí),這些 dom 節(jié)點(diǎn)才出現(xiàn), 所以在這里手動(dòng) render 另一部分的內(nèi)容。 請(qǐng)教一下這樣做是不是一開(kāi)始的方向就錯(cuò)了?
前沿探索
2020-08-17 22:57:09
Java web 開(kāi)發(fā)想了解下前端。最近在跟 fullstackopen.com 的課程?,F(xiàn)在沒(méi)法在 windows 上安裝 react-app 。
試過(guò)的指令有:
1 、set HTTP_PROXY=http://127.0.0.1:58591, npx create-react-app my-app ;
2 、npm 設(shè)置代理,npm init react-app my-app ;
3 、在 linux 下載再傳 windows,但是運(yùn)行 npm start 報(bào)錯(cuò)。
想請(qǐng)教下大家是如何解決這個(gè)問(wèn)題的。
前沿探索
2020-08-17 22:57:06
比如 vue2.0 是因?yàn)?ie8 以下不支持 defineProperty,react 的原因是什么?
前沿探索
2020-08-17 22:57:02
開(kāi)源不易,感謝你的支持, ? star me if you like concent ^_^
序言
之前發(fā)表了一篇文章 redux 、mobx 、concent 特性大比拼, 看后生如何對(duì)局前輩 ,吸引了不少感興趣的小伙伴入群開(kāi)始了解和使用 concent ,并獲得了很多正向的反饋,實(shí)實(shí)在在的幫助他們提高了開(kāi)發(fā)體驗(yàn),群里人數(shù)雖然還很少,但大家熱情高漲,技術(shù)討論氛圍濃厚,對(duì)很多新鮮技術(shù)都有保持一定的敏感度,如上個(gè)月開(kāi)始逐漸被提及得越來(lái)越多的出自 facebook 的最新?tīng)顟B(tài)管理方案 recoil ,雖然還處于實(shí)驗(yàn)狀態(tài),但是相必大家已經(jīng)私底下開(kāi)始欲欲躍試了,畢竟出生名門,有 fb 背書,一定會(huì)大放異彩。
不過(guò)當(dāng)我體驗(yàn)完 recoil 后,我對(duì)其中標(biāo)榜的 精確更新 保持了懷疑態(tài)度,有一些誤導(dǎo)的嫌疑,這一點(diǎn)下文會(huì)單獨(dú)分析,是否屬于誤導(dǎo)讀者在讀完本文后自然可以得出結(jié)論,總之本文主要是分析 Concent 與 Recoil 的代碼風(fēng)格差異性,并探討它們對(duì)我們將來(lái)的開(kāi)發(fā)模式有何新的影響,以及思維上需要做什么樣的轉(zhuǎn)變。
數(shù)據(jù)流方案之 3 大流派
目前主流的數(shù)據(jù)流方案按形態(tài)都可以劃分以下這三類 redux 流派
redux 、和基于 redux 衍生的其他作品,以及類似 redux 思路的作品,代表作有 dva 、rematch 等等。 mobx 流派
借助 definePerperty 和 Proxy 完成數(shù)據(jù)劫持,從而達(dá)到響應(yīng)式編程目的的代表,類 mobx 的作品也有不少,如 dob 等。 Context 流派
這里的 Context 指的是 react 自帶的 Context api,基于 Context api 打造的數(shù)據(jù)流方案通常主打輕量、易用、概覽少,代表作品有 unstated 、constate 等,大多數(shù)作品的核心代碼可能不超過(guò) 500 行。
到此我們看看 Recoil 應(yīng)該屬于哪一類?很顯然按其特征屬于 Context 流派,那么我們上面說(shuō)的主打輕量對(duì) Recoil 并不適用了,打開(kāi)其源碼庫(kù)發(fā)現(xiàn)代碼并不是幾百行完事的,所以基于 Context api 做得好用且強(qiáng)大就未必輕量,由此看出 facebook 對(duì) Recoil 是有野心并給予厚望的。
我們同時(shí)也看看 Concent 屬于哪一類呢? Concent 在 v2 版本之后,重構(gòu)數(shù)據(jù)追蹤機(jī)制,啟用了 defineProperty 和 Proxy 特性,得以讓 react 應(yīng)用既保留了不可變的追求,又享受到了運(yùn)行時(shí)依賴收集和 ui 精確更新的性能提升福利,既然啟用了 defineProperty 和 Proxy,那么看起來(lái) Concent 應(yīng)該屬于 mobx 流派?
事實(shí)上 Concent 屬于一種全新的流派,不依賴 react 的 Context api,不破壞 react 組件本身的形態(tài),保持追求不可變的哲學(xué),僅在 react 自身的渲染調(diào)度機(jī)制之上建立一層邏輯層狀態(tài)分發(fā)調(diào)度機(jī)制,defineProperty 和 Proxy 只是用于輔助收集實(shí)例和衍生數(shù)據(jù)對(duì)模塊數(shù)據(jù)的依賴,而修改數(shù)據(jù)入口還是 setState(或基于 setState 封裝的 dispatch, invoke, sync),讓 Concent 可以 0 入侵的接入 react 應(yīng)用,真正的即插即用和無(wú)感知接入。
即插即用 的核心原理是, Concent 自建了一個(gè)平行于 react 運(yùn)行時(shí)的全局上下文,精心維護(hù)這模塊與實(shí)例之間的歸屬關(guān)系,同時(shí)接管了組件實(shí)例的更新入口 setState,保留原始的 setState 為 reactSetState,所有當(dāng)用戶調(diào)用 setState 時(shí),concent 除了調(diào)用 reactSetState 更新當(dāng)前實(shí)例 ui,同時(shí)智能判斷提交的狀態(tài)是否也還有別的實(shí)例關(guān)心其變化,然后一并拿出來(lái)依次執(zhí)行這些實(shí)例的 reactSetState,進(jìn)而達(dá)到了狀態(tài)全部同步的目的。
Recoil 初體驗(yàn)
我們以常用的 counter 來(lái)舉例,熟悉一下 Recoil 暴露的四個(gè)高頻使用的 api atom,定義狀態(tài) selector, 定義派生數(shù)據(jù) useRecoilState,消費(fèi)狀態(tài) useRecoilValue,消費(fèi)派生數(shù)據(jù)
定義狀態(tài)
外部使用 atom 接口,定義一個(gè) key 為 num ,初始值為 0 的狀態(tài) const numState = atom({ key: "num", default: 0 });
定義派生數(shù)據(jù)
外部使用 selector 接口,定義一個(gè) key 為 numx10 ,初始值是依賴 numState 再次計(jì)算而得到 const numx10Val = selector({ key: "numx10", get: ({ get }) => { const num = get(numState); return num * 10; } });
定義異步的派生數(shù)據(jù)
selector 的 get 支持定義異步函數(shù) 需要注意的點(diǎn)是,如果有依賴,必需先書寫好依賴在開(kāi)始執(zhí)行異步邏輯 const delay = () => new Promise(r => setTimeout(r, 1000)); const asyncNumx10Val = selector({ key: "asyncNumx10", get: async ({ get }) => { // !!!這句話不能放在 delay 之下, selector 需要同步的確定依賴 const num = get(numState); await delay(); return num * 10; } });
消費(fèi)狀態(tài)
組件里使用 useRecoilState 接口,傳入想要獲去的狀態(tài)(由 atom 創(chuàng)建而得) const NumView = () => { const [num, setNum] = useRecoilState(numState); const add = ()=>setNum(num+1); return (
{num}
); }
消費(fèi)派生數(shù)據(jù)
組件里使用 useRecoilValue 接口,傳入想要獲去的派生數(shù)據(jù)(由 selector 創(chuàng)建而得),同步派生數(shù)據(jù)和異步派生數(shù)據(jù),皆可通過(guò)此接口獲得 const NumValView = () => { const numx10 = useRecoilValue(numx10Val); const asyncNumx10 = useRecoilValue(asyncNumx10Val); return (
numx10 :{numx10}
); };
渲染它們查看結(jié)果
暴露定義好的這兩個(gè)組件, 查看在線示例 export default ()=>{ return ( <> ); };
頂層節(jié)點(diǎn)包裹 React.Suspense 和 RecoilRoot ,前者用于配合異步計(jì)算函數(shù)需要,后者用于注入 Recoil 上下文 const rootElement = document.getElementById("root"); ReactDOM.render( Loading...
}> , rootElement );
Concent 初體驗(yàn)
如果讀過(guò) concent 文檔(還在持續(xù)建設(shè)中...),可能部分人會(huì)認(rèn)為 api 太多,難于記住,其實(shí)大部分都是可選的語(yǔ)法糖,我們以 counter 為例,只需要使用到以下兩個(gè) api 即可 run,定義模塊狀態(tài)(必需)、模塊計(jì)算(可選)、模塊觀察(可選) 運(yùn)行 run 接口后,會(huì)生成一份 concent 全局上下文 setState,修改狀態(tài)
定義狀態(tài)&修改狀態(tài)
以下示例我們先脫離 ui,直接完成定義狀態(tài)&修改狀態(tài)的目的 import { run, setState, getState } from "concent"; run({ counter: {// 聲明一個(gè) counter 模塊 state: { num: 1 }, // 定義狀態(tài) } }); console.log(getState('counter').num);// log: 1 setState('counter', {num:10});// 修改 counter 模塊的 num 值為 10 console.log(getState('counter').num);// log: 10
我們可以看到,此處和 redux 很類似,需要定義一個(gè)單一的狀態(tài)樹(shù),同時(shí)第一層 key 就引導(dǎo)用戶將數(shù)據(jù)模塊化管理起來(lái).
引入 reducer
上述示例中我們直接掉一個(gè)呢 setState 修改數(shù)據(jù),但是真實(shí)的情況是數(shù)據(jù)落地前有很多同步的或者異步的業(yè)務(wù)邏輯操作,所以我們對(duì)模塊填在 reducer 定義,用來(lái)聲明修改數(shù)據(jù)的方法集合。 import { run, dispatch, getState } from "concent"; const delay = () => new Promise(r => setTimeout(r, 1000)); const state = () => ({ num: 1 });// 狀態(tài)聲明 const reducer = {// reducer 聲明 inc(payload, moduleState) { return { num: moduleState.num + 1 }; }, async asyncInc(payload, moduleState) { await delay(); return { num: moduleState.num + 1 }; } }; run({ counter: { state, reducer } });
然后我們用 dispatch 來(lái)觸發(fā)修改狀態(tài)的方法 因 dispatch 會(huì)返回一個(gè) Promise,所以我們需要用一個(gè) async 包裹起來(lái)執(zhí)行代碼 import { dispatch } from "concent"; (async ()=>{ console.log(getState("counter").num);// log 1 await dispatch("counter/inc");// 同步修改 console.log(getState("counter").num);// log 2 await dispatch("counter/asyncInc");// 異步修改 console.log(getState("counter").num);// log 3 })()
注意 dispatch 調(diào)用時(shí)基于字符串匹配方式,之所以保留這樣的調(diào)用方式是為了照顧需要?jiǎng)討B(tài)調(diào)用的場(chǎng)景,其實(shí)更推薦的寫法是 import { dispatch } from "concent"; (async ()=>{ console.log(getState("counter").num);// log 1 await dispatch(reducer.inc);// 同步修改 console.log(getState("counter").num);// log 2 await dispatch(reducer.asyncInc);// 異步修改 console.log(getState("counter").num);// log 3 })()
接入 react
上述示例主要演示了如何定義狀態(tài)和修改狀態(tài),那么接下來(lái)我們需要用到以下兩個(gè) api 來(lái)幫助 react 組件生成實(shí)例上下文(等同于與 vue 3 setup 里提到的渲染上下文),以及獲得消費(fèi) concent 模塊數(shù)據(jù)的能力 register, 注冊(cè)類組件為 concent 組件 useConcent, 注冊(cè)函數(shù)組件為 concent 組件 import { register, useConcent } from "concent"; @register("counter") class ClsComp extends React.Component { changeNum = () => this.setState({ num: 10 }) render() { return (

class comp: {this.state.num}

); } } function FnComp() { const { state, setState } = useConcent("counter"); const changeNum = () => setState({ num: 20 }); return (

fn comp: {state.num}

); }
注意到兩種寫法區(qū)別很小,除了組件的定義方式不一樣,其實(shí)渲染邏輯和數(shù)據(jù)來(lái)源都一模一樣。
渲染它們查看結(jié)果
在線示例 const rootElement = document.getElementById("root"); ReactDOM.render(
, rootElement );
對(duì)比 Recoil ,我們發(fā)現(xiàn)沒(méi)有頂層并沒(méi)有 Provider 或者 Root 類似的組件包裹,react 組件就已接入 concent,做到真正的即插即用和無(wú)感知接入,同時(shí) api 保留為與 react 一致的寫法。
組件調(diào)用 reducer
concent 為每一個(gè)組件實(shí)例都生成了實(shí)例上下文,方便用戶直接通過(guò) ctx.mr 調(diào)用 reducer 方法 mr 為 moduleReducer 的簡(jiǎn)寫,直接書寫為 ctx.moduleReducer 也是合法的 // --------- 對(duì)于類組件 ----------- changeNum = () => this.setState({ num: 10 }) // ===> 修改為 changeNum = () => this.ctx.mr.inc(10);// or this.ctx.mr.asynCtx() // --------- 對(duì)于函數(shù)組件 ----------- const { state, mr } = useConcent("counter");// useConcent 返回的就是 ctx const changeNum = () => mr.inc(20);// or ctx.mr.asynCtx()
異步計(jì)算函數(shù)
run 接口里支持?jǐn)U展 computed 屬性,即讓用戶定義一堆衍生數(shù)據(jù)的計(jì)算函數(shù)集合,它們可以是同步的也可以是異步的,同時(shí)支持一個(gè)函數(shù)用另一個(gè)函數(shù)的輸出作為輸入來(lái)做二次計(jì)算,計(jì)算的輸入依賴是自動(dòng)收集到的。 const computed = {// 定義計(jì)算函數(shù)集合 numx10({ num }) { return num * 10; }, // n:newState, o:oldState, f:fnCtx // 結(jié)構(gòu)出 num,表示當(dāng)前計(jì)算依賴是 num,僅當(dāng) num 發(fā)生變化時(shí)觸發(fā)此函數(shù)重計(jì)算 async numx10_2({ num }, o, f) { // 必需調(diào)用 setInitialVal 給 numx10_2 一個(gè)初始值, // 該函數(shù)僅在初次 computed 觸發(fā)時(shí)執(zhí)行一次 f.setInitialVal(num * 55); await delay(); return num * 100; }, async numx10_3({ num }, o, f) { f.setInitialVal(num * 1); await delay(); // 使用 numx10_2 再次計(jì)算 const ret = num * f.cuVal.numx10_2; if (ret % 40000 === 0) throw new Error("-->mock error"); return ret; } } // 配置到 counter 模塊 run({ counter: { state, reducer, computed } });
上述計(jì)算函數(shù)里,我們刻意讓 numx10_3 在某個(gè)時(shí)候報(bào)錯(cuò),對(duì)于此錯(cuò)誤,我們可以在 run 接口的第二位 options 配置里定義 errorHandler 來(lái)捕捉。 run({/**storeConfig*/}, { errorHandler: (err)=>{ alert(err.message); } })
當(dāng)然更好的做法,利用 concent-plugin-async-computed-status 插件來(lái)完成對(duì)所有模塊計(jì)算函數(shù)執(zhí)行狀態(tài)的統(tǒng)一管理。 import cuStatusPlugin from "concent-plugin-async-computed-status"; run( {/**storeConfig*/}, { errorHandler: err => { console.error('errorHandler ', err); // alert(err.message); }, plugins: [cuStatusPlugin], // 配置異步計(jì)算函數(shù)執(zhí)行狀態(tài)管理插件 } );
該插件會(huì)自動(dòng)向 concent 配置一個(gè) cuStatus 模塊,方便組件連接到它,消費(fèi)相關(guān)計(jì)算函數(shù)的執(zhí)行狀態(tài)數(shù)據(jù) function Test() { const { moduleComputed, connectedState, setState, state, ccUniqueKey } = useConcent({ module: "counter",// 屬于 counter 模塊,狀態(tài)直接從 state 獲得 connect: ["cuStatus"],// 連接到 cuStatus 模塊,狀態(tài)從 connectedState.{$moduleName}獲得 }); const changeNum = () => setState({ num: state.num + 1 }); // 獲得 counter 模塊的計(jì)算函數(shù)執(zhí)行狀態(tài) const counterCuStatus = connectedState.cuStatus.counter; // 當(dāng)然,可以更細(xì)粒度的獲得指定結(jié)算函數(shù)的執(zhí)行狀態(tài) // const {['counter/numx10_2']:num1Status, ['counter/numx10_3']: num2Status} = connectedState.cuStatus; return (
{state.num}
{counterCuStatus.done ? moduleComputed.numx10 : 'computing'} {/** 此處拿到錯(cuò)誤可以用于渲染,當(dāng)然也拋出去 */} {/** 讓 ErrorBoundary 之類的組件捕捉并渲染降級(jí)頁(yè)面 */} {counterCuStatus.err ? counterCuStatus.err.message : ''}
{moduleComputed.numx10_2}
{moduleComputed.numx10_3}
); }
![] https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/recoil-vs-concent/r4.gif )
查看在線示例
精確更新
開(kāi)篇我說(shuō)對(duì) Recoli 提到的 精確更新 保持了懷疑態(tài)度,有一些誤導(dǎo)的嫌疑,此處我們將揭開(kāi)疑團(tuán)
大家知道 hook 使用規(guī)則是不能寫在條件控制語(yǔ)句里的,這意味著下面語(yǔ)句是不允許的 const NumView = () => { const [show, setShow] = useState(true); if(show){// error const [num, setNum] = useRecoilState(numState); } }
所以用戶如果 ui 渲染里如果某個(gè)狀態(tài)用不到此數(shù)據(jù)時(shí),某處改變了 num 值依然會(huì)觸發(fā) NumView 重渲染,但是 concent 的實(shí)例上下文里取出來(lái)的 state 和 moduleComputed 是一個(gè) Proxy 對(duì)象,是在實(shí)時(shí)的收集每一輪渲染所需要的依賴,這才是真正意義上的按需渲染和精確更新。 const NumView = () => { const [show, setShow] = useState(true); const {state} = useConcent('counter'); // show 為 true 時(shí),當(dāng)前實(shí)例的渲染對(duì) state.num 的渲染有依賴 return {show ?

{state.num}

: 'nothing'} }
點(diǎn)我查看代碼示例
當(dāng)然如果用戶對(duì) num 值有 ui 渲染完畢后,有發(fā)生改變時(shí)需要做其他事的需求,類似 useEffect 的效果,concent 也支持用戶將其抽到 setup 里,定義 effect 來(lái)完成此場(chǎng)景訴求,相比 useEffect ,setup 里的 ctx.effect 只需定義一次,同時(shí)只需傳遞 key 名稱,concent 會(huì)自動(dòng)對(duì)比前一刻和當(dāng)前刻的值來(lái)決定是否要觸發(fā)副作用函數(shù)。 conset setup = (ctx)=>{ ctx.effect(()=>{ console.log('do something when num changed'); return ()=>console.log('clear up'); }, ['num']) } function Test1(){ useConcent({module:'cunter', setup}); return

for setup

}
更多關(guān)于 effect 與 useEffect 請(qǐng)查看此文
結(jié)語(yǔ)
Recoil 推崇狀態(tài)和派生數(shù)據(jù)更細(xì)粒度控制,寫法上 demo 看起來(lái)簡(jiǎn)單,實(shí)際上代碼規(guī)模大之后依然很繁瑣。
Concent 遵循 redux 單一狀態(tài)樹(shù)的本質(zhì),推崇模塊化管理數(shù)據(jù)以及派生數(shù)據(jù),同時(shí)依靠 Proxy 能力完成了 運(yùn)行時(shí)依賴收集 和 追求不可變 的完美整合。
所以你將獲得: 運(yùn)行時(shí)的依賴收集 ,同時(shí)也遵循 react 不可變的原則 一切皆函數(shù)(state, reducer, computed, watch, event...),能獲得更友好的 ts 支持 支持中間件和插件機(jī)制,很容易兼容 redux 生態(tài) 同時(shí)支持集中與分形模塊配置,同步與異步模塊加載,對(duì)大型工程的彈性重構(gòu)過(guò)程更加友好
最后解答一下關(guān)于 concent 是否支持 current mode 的疑惑,先上結(jié)論,100%支持。
我們首先要理解 current mode 原理是因?yàn)?fiber 架構(gòu)模擬出了和整個(gè)渲染堆棧(即 fiber node 上存儲(chǔ)的信息),得以有機(jī)會(huì)讓 react 自己以組件為單位調(diào)度組件的渲染過(guò)程,可以懸停并再次進(jìn)入渲染,安排優(yōu)先級(jí)高的先渲染,重度渲染的組件會(huì)切片為多個(gè)時(shí)間段反復(fù)渲染,而 concent 的上下文本身是獨(dú)立于 react 存在的(接入 concent 不需要再頂層包裹任何 Provider ), 只負(fù)責(zé)處理業(yè)務(wù)生成新的數(shù)據(jù),然后按需派發(fā)給對(duì)應(yīng)的實(shí)例,之后就是 react 自己的調(diào)度流程,修改狀態(tài)的函數(shù)并不會(huì)因?yàn)榻M件反復(fù) 重入 而多次執(zhí)行(這點(diǎn)需要我們遵循不該在渲染過(guò)程中書寫包含有副作用的代碼原則),react 僅僅是調(diào)度組件的渲染時(shí)機(jī)。
? star me if you like concent ^_^
Edit on CodeSandbox
Edit on StackBlitz
如果有關(guān)于 concent 的疑問(wèn),可以掃碼加群咨詢,會(huì)盡力答疑解惑,幫助你了解更多,里面的不少小伙伴都變成老司機(jī)了,用過(guò)之后都表示非常 happy,客官上船試試便知??。

前沿探索
2020-08-17 22:56:54
React 的 spa 可能 SEO 不友好如果只是希望 seo 友好的展示頁(yè)面 同時(shí)需要遠(yuǎn)程 api 更新幾個(gè)小的數(shù)據(jù)點(diǎn) React 是不是就不太適合了? Nextjs 之類的框架貌似可以 但是靜態(tài)服務(wù)器又用不了了? 有點(diǎn)迷茫
前沿探索
2020-08-17 22:56:51
目前公司主流是 vue,想找個(gè) ng 論壇抱團(tuán)取暖
前沿探索
2020-08-17 22:56:48
想嘗試 angular , 但是感覺(jué) angular 的模板 "不夠 typescript" , 例如官網(wǎng)上的一段 template :

Products

{{ product.name }}


單純看這個(gè)模板, 完全看不出來(lái) product 的類型, 同時(shí) typescript 和 eslint 也沒(méi)能力管控這個(gè)模板中的變量。相對(duì)的來(lái)說(shuō), 在 react 的 tsx 中, typescript 和 eslint 是能夠?qū)ψ兞窟M(jìn)行充分的靜態(tài)分析的。
無(wú)意引戰(zhàn), 確實(shí)希望試試 angular , 可是, 是我使用的姿勢(shì)不對(duì)嗎?
前沿探索
2020-08-17 22:56:44
在接觸 Angular 后,這些年工作業(yè)余也用 Angular 做了若干實(shí)際項(xiàng)目。不過(guò),由于 Angular 在國(guó)內(nèi)的流行度不高,各種原創(chuàng)內(nèi)容和參考資料也相對(duì)較少。雖然網(wǎng)絡(luò)上也能找到各種各樣的技術(shù)文章,官方的文檔也很全面,但總的來(lái)講,信息還是有些分散。
很慚愧,幾年里沒(méi)有做什么特別有技術(shù)含量的工作。 只是提煉出一個(gè)很基礎(chǔ)的模板,幫助開(kāi)發(fā)者快速創(chuàng)建一個(gè) SPA ( Single Page Application )站點(diǎn),或是供對(duì) Android 感興趣的讀者了解 Android 的語(yǔ)法和基本的框架機(jī)制。該模板先分享于此 —— AngularGo on GitHub 。
該模板基于目前最新的 Angular 8,且會(huì)持續(xù)跟進(jìn)更新。在 Angular CLI 自動(dòng)創(chuàng)建的范例項(xiàng)目的基礎(chǔ)上,目前版本的 AngularGo 還包含以下內(nèi)容: Angular 8 以及相關(guān)依賴的最新版本配置 一種可能的源文件結(jié)構(gòu)示例 一種與 Cordova 共享代碼庫(kù)的可能方式 基礎(chǔ) Angular 組件及依賴注入的使用范例 基礎(chǔ) Angular Material 控件的使用范例 支持桌面和移動(dòng)設(shè)備的抽屜式側(cè)滑菜單 支持 lazy loading 的 app-routing 全局路由 支持 authentication guard 的模塊路由 支持 bearer token 驗(yàn)證的 Restangular 初始化及 service 用例 基于 HttpClient 的用戶注冊(cè)與登錄 API 調(diào)用 基于 scss 的 Angular Material 樣式(尚未采用 BEM 命名規(guī)則) Azure Application Insights 集成 適用于 Windows server/ Azure App Service 的 web.config 配置
AngularGo 還很初步,很多細(xì)節(jié)因水平有限和時(shí)間限制寫得也比較粗糙,想必會(huì)有其他優(yōu)秀的開(kāi)源模板提供了更好的實(shí)現(xiàn)。從某種意義上來(lái)講,該模板一方面是對(duì)自己經(jīng)驗(yàn)得一個(gè)整理,同時(shí)期望能起到一個(gè)拋磚引玉的作用。希望對(duì)讀者有幫助,也歡迎批評(píng)指教。
via
前沿探索
2020-08-17 22:56:38
https://material.angular.io/guide/theming
Material 主題可以自定義,主要是默認(rèn)主題顏色太激進(jìn)了,有沒(méi)有好看的主題包推薦?
前沿探索
2020-08-17 22:56:25
有誰(shuí)搶先體驗(yàn)了一下新特性嗎?來(lái)分享一下唄。
還有,啥時(shí)候出 Angular 板塊?不帶 JS 的。
前沿探索
2020-08-17 22:56:21
217218219220221222223224225226227