最近在項目中遇到這樣一個問題
當頁面加載完畢后由于選項卡的另外兩張屬于display:none;狀態 所以另外兩張選項卡內echarts的寬高都會變成默認100*100
查閱了很多網上的案例,得出一下一些解決方案:
1:
原因很簡單,在tab頁中,圖表的父容器div是隱藏的(display:none),圖表在執行js初始化的時候找不到這個元素,所以自動將“100%”轉成了“100”,最后計算出來的圖表就成了100px
解決辦法:
找一個在tab頁的切換操作中不會隱藏的父容器,把它的寬度的具體值取出后在初始化圖表之前直接賦給圖表
1 $("#chartMain").css('width',$("#TabContent").width());//獲取父容器的寬度具體數值直接賦值給圖表以達到寬度100%的效果 2 var Chart = echarts.init(document.getElementById('chartMain')); 3 4 // 指定圖表的配置項和數據 5 option = { ...配置項和數據 }; 6 7 // 使用剛指定的配置項和數據顯示圖表。 8 Chart.setOption(option);
2:mychart.resize() 重新渲染高度
3: 后來我想到了問題所在,既然高度是因為display:none;導致的 那大可不必設置這個屬性,但是在頁面渲染完畢后加上即可
所以取消了選項卡的display:none; 但在頁面加載完畢后
window.οnlοad=function(){
根基id在添加css display:none;
}
即可解決,
分割線
---------------------------------------------------------------------
接下來解決一下ifram內外通訊 互相通訊賦值ifram src 和高度問題
統一資源定位符,縮寫為URL,是對網絡資源(網頁、圖像、文件)的引用。URL指定資源位置和檢索資源的機制(http、ftp、mailto)。
舉個例子,這里是這篇文章的 URL 地址:
https://dmitripavlutin.com/parse-url-javascript
很多時候你需要獲取到一段 URL 的某個組成部分。它們可能是 hostname(例如 dmitripavlutin.com),或者 pathname(例如 /parse-url-javascript)。
一個方便的用于獲取 URL 組成部分的辦法是通過 URL() 構造函數。
在這篇文章中,我將給大家展示一段 URL 的結構,以及它的主要組成部分。
接著,我會告訴你如何使用 URL() 構造函數來輕松獲取 URL 的組成部分,比如 hostname,pathname,query 或者 hash。
1. URL 結構
一圖勝千言。不需要過多的文字描述,通過下面的圖片你就可以理解一段 URL 的各個組成部分:
image
2. URL() 構造函數
URL() 構造函數允許我們用它來解析一段 URL:
const url = new URL(relativeOrAbsolute [, absoluteBase]);
參數 relativeOrAbsolute 既可以是絕對路徑,也可以是相對路徑。如果第一個參數是相對路徑的話,那么第二個參數 absoluteBase 則必傳,且必須為第一個參數的絕對路徑。
舉個例子,讓我們用一個絕對路徑的 URL 來初始化 URL() 函數:
const url = new URL('http://example.com/path/index.html');
url.href; // => 'http://example.com/path/index.html'
或者我們可以使用相對路徑和絕對路徑:
const url = new URL('/path/index.html', 'http://example.com');
url.href; // => 'http://example.com/path/index.html'
URL() 實例中的 href 屬性返回了完整的 URL 字符串。
在新建了 URL() 的實例以后,你可以用它來訪問前文圖片中的任意 URL 組成部分。作為參考,下面是 URL() 實例的接口列表:
interface URL {
href: USVString;
protocol: USVString;
username: USVString;
password: USVString;
host: USVString;
hostname: USVString;
port: USVString;
pathname: USVString;
search: USVString;
hash: USVString;
readonly origin: USVString;
readonly searchParams: URLSearchParams;
toJSON(): USVString;
}
上述的 USVString 參數在 JavaScript 中會映射成字符串。
3. Query 字符串
url.search 可以獲取到 URL 當中 ? 后面的 query 字符串:
const url = new URL(
'http://example.com/path/index.html?message=hello&who=world'
);
url.search; // => '?message=hello&who=world'
如果 query 參數不存在,url.search 默認會返回一個空字符串 '':
const url1 = new URL('http://example.com/path/index.html');
const url2 = new URL('http://example.com/path/index.html?');
url1.search; // => ''
url2.search; // => ''
3.1 解析 query 字符串
相比于獲得原生的 query 字符串,更實用的場景是獲取到具體的 query 參數。
獲取具體 query 參數的一個簡單的方法是利用 url.searchParams 屬性。這個屬性是 URLSearchParams 的實例。
URLSearchParams 對象提供了許多用于獲取 query 參數的方法,如get(param),has(param)等。
下面來看個例子:
const url = new URL(
'http://example.com/path/index.html?message=hello&who=world'
);
url.searchParams.get('message'); // => 'hello'
url.searchParams.get('missing'); // => null
url.searchParams.get('message') 返回了 message 這個 query 參數的值——hello。
如果使用 url.searchParams.get('missing') 來獲取一個不存在的參數,則得到一個 null。
4. hostname
url.hostname 屬性返回一段 URL 的 hostname 部分:
const url = new URL('http://example.com/path/index.html');
url.hostname; // => 'example.com'
5. pathname
url. pathname 屬性返回一段 URL 的 pathname 部分:
const url = new URL('http://example.com/path/index.html?param=value');
url.pathname; // => '/path/index.html'
如果這段 URL 不含 path,則該屬性返回一個斜杠 /:
const url = new URL('http://example.com/');
url.pathname; // => '/'
6. hash
最后,我們可以通過 url.hash 屬性來獲取 URL 中的 hash 值:
const url = new URL('http://example.com/path/index.html#bottom');
url.hash; // => '#bottom'
當 URL 中的 hash 不存在時,url.hash 屬性會返回一個空字符串 '':
const url = new URL('http://example.com/path/index.html');
url.hash; // => ''
7. URL 校驗
當使用 new URL() 構造函數來新建實例的時候,作為一種副作用,它同時也會對 URL 進行校驗。如果 URL 不合法,則會拋出一個 TypeError。
舉個例子,http ://example.com 是一段非法 URL,因為它在 http 后面多寫了一個空格。
讓我們用這個非法 URL 來初始化 URL() 構造函數:
try {
const url = new URL('http ://example.com');
} catch (error) {
error; // => TypeError, "Failed to construct URL: Invalid URL"
}
因為 http ://example.com 是一段非法 URL,跟我們想的一樣,new URL() 拋出了一個 TypeError。
8. 修改 URL
除了獲取 URL 的組成部分以外,像 search,hostname,pathname 和 hash 這些屬性都是可寫的——這也意味著你可以修改 URL。
舉個例子,讓我們把一段 URL 從 red.com 修改成 blue.io:
const url = new URL('http://red.com/path/index.html');
url.href; // => 'http://red.com/path/index.html'
url.hostname = 'blue.io';
url.href; // => 'http://blue.io/path/index.html'
注意,在 URL() 實例中只有 origin 和 searchParams 屬性是只讀的,其他所有的屬性都是可寫的,并且會修改原來的 URL。
9. 總結
URL() 構造函數是 JavaScript 中的一個能夠很方便地用于解析(或者校驗)URL 的工具。
new URL(relativeOrAbsolute [, absoluteBase]) 中的第一個參數接收 URL 的絕對路徑或者相對路徑。當第一個參數是相對路徑時,第二個參數必傳且必須為第一個參數的基路徑。
在新建 URL() 的實例以后,你就能很輕易地獲得 URL 當中的大部分組成部分了,比如:
url.search 獲取原生的 query 字符串
url.searchParams 通過 URLSearchParams 的實例去獲取具體的 query 參數
url.hostname獲取 hostname
url.pathname 獲取 pathname
url.hash 獲取 hash 值
那么你最愛用的解析 URL 的 JavaScript 工具又是什么呢?
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
為什么需要額外的類型檢查?
TypeScript 只在編譯期執行靜態類型檢查!實際運行的是從 TypeScript 編譯的 JavaScript,這些生成的 JavaScript 對類型一無所知。編譯期靜態類型檢查在代碼庫內部能發揮很大作用,但對不合規范的輸入(比如,從 API 處接收的輸入)無能為力。
運行時檢查的嚴格性
至少需要和編譯期檢查一樣嚴格,否則就失去了編譯期檢查提供的保證。
如有必要,可以比編譯期檢查更嚴格,例如,年齡需要大于等于 0。
運行時類型檢查策略
定制代碼手動檢查
靈活
可能比較枯燥,容易出錯
容易和實際代碼脫節
使用校驗庫手動檢查
比如使用 joi:
import Joi from "@hapi/joi"const schema = Joi.object({ firstName: Joi.string().required(), lastName: Joi.string().required(), age: Joi.number().integer().min(0).required()});
靈活
容易編寫
容易和實際代碼脫節
手動創建 JSON Schema
例如:
{ "$schema": "http://json-schema.org/draft-07/schema#", "required": [ "firstName", "lastName", "age" ], "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "age": { "type": "integer", "minimum": 0 } }}
使用標準格式,有大量庫可以校驗。
JSON 很容易存儲和復用。
可能會很冗長,手寫 JSON Schema 可能會很枯燥。
需要確保 Schema 和代碼同步更新。
自動創建 JSON Schema
基于 TypeScript 代碼生成 JSON Schema
-- 比如 typescript-json-schema 這個工具就可以做到這一點(同時支持作為命令行工具使用和通過代碼調用)。
-- 需要確保 Schema 和代碼同步更新。
基于 JSON 輸入示例生成
-- 沒有使用已經在 TypeScript 代碼中定義的類型信息。
-- 如果提供的 JSON 輸入示例和實際輸入不一致,可能導致錯誤。
-- 仍然需要確保 Schema 和代碼同步更新。
轉譯
例如使用 ts-runtime。
這種方式會將代碼轉譯成功能上等價但內置運行時類型檢查的代碼。
比如,下面的代碼:
interface Person { firstName: string; lastName: string; age: number;}const test: Person = { firstName: "Foo", lastName: "Bar", age: 55}
會被轉譯為:
import t from "ts-runtime/lib";const Person = t.type( "Person", t.object( t.property("firstName", t.string()), t.property("lastName", t.string()), t.property("age", t.number()) ));const test = t.ref(Person).assert({ firstName: "Foo", lastName: "Bar", age: 55});
這一方式的缺陷是無法控制在何處進行運行時檢查(我們只需在輸入輸出的邊界處進行運行時類型檢查)。
順便提一下,這是一個實驗性的庫,不建議在生產環境使用。
運行時類型派生靜態類型
比如使用 io-ts 這個庫。
這一方式下,我們定義運行時類型,TypeScript 會根據我們定義的運行時類型推斷出靜態類型。
運行時類型示例:
import t from "io-ts";const PersonType = t.type({ firstName: t.string, lastName: t.string, age: t.refinement(t.number, n => n >= 0, 'Positive')})
從中提取相應的靜態類型:
interface Person extends t.TypeOf<typeof PersonType> {}
以上類型等價于:
interface Person { firstName: string; lastName: string; age: number;}
類型總是同步的。
io-ts 很強大,比如支持遞歸類型。
需要將類型定義為 io-ts 運行時類型,這在定義類時不適用:
-- 有一種變通的辦法是使用 io-ts 定義一個接口,然后讓類實現這個接口。然而,這意味著每次給類增加屬性的時候都要更新 io-ts 類型。
不容易復用接口(比如前后端之間使用同一接口),因為這些接口是 io-ts 類型而不是普通的 TypeScript 類型。
基于裝飾器的類校驗
比如使用 class-validator 這個庫。
基于類屬性的裝飾器。
和 Java 的 JSR-380 Bean Validation 2.0 (比如 Hibernate Validator 就實現了這一標準)很像。
-- 此類 Java EE 風格的庫還有 typeorm (ORM 庫,類似 Java 的 JPA)和 routing-controllers (用于定義 API,類似 Java 的 JAX-RS)。
代碼示例:
import { plainToClass } from "class-transformer";import { validate, IsString, IsInt, Min } from "class-validator";class Person { @IsString() firstName: string; @IsString() lastName: string; @IsInt() @Min(0) age: number;}const input: any = { firstName: "Foo", age: -1};const inputAsClassInstance = plainToClass( Person, input as Person);validate(inputAsClassInstance).then(errors => { // 錯誤處理代碼});
類型總是同步的。
需要對類進行檢查時很有用。
可以用來檢查接口(定義一個實現接口的類)。
注意:class-validator 用于具體的類實例。在上面的代碼中,我們使用它的姊妹庫 class-transformer 將普通輸入轉換為 Person 實例。轉換過程本身不進行任何類型檢查。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
如今,Page Speed(頁面速度)的意義非凡。
自從Google改變Googlebot's的算法以高度支持快速,適合移動設備的網站以來,擁有快速網站變得越來越重要。如果這還不夠好,用戶通常會花更少的時間,轉化率也會更低,你的網站體驗越慢,用戶的轉化率就越低。
什么是Page Speed
Page Speed是將內容完全加載到網頁上所花費的時間。
對于任何給定的用戶來說,頁面緩慢的原因可能有很多,你的用戶可能正在火車上,通過信號弱的隧道,或者他們的互聯網速度很慢。
通過遵循最佳實踐,我們至少可以通過確保我們已經做了最好的工作來緩解問題。
現在你知道它是什么了,下面我就來教你如何提高頁面速度。
注意:這些是按難度順序列出的。在某個時候,你將需要開發人員來幫助優化你的網站。
1.使用CDN
CDN是內容傳輸網絡的縮寫。使用CDN可以讓你有效地訪問全球數百臺小服務器,這些服務器為你提供網站的副本,大大減少了你的網站獲取時間。如果你沒有使用CDN,你的網站的每一個請求(包括圖片、CSS和JavaScript)都會被緩慢地傳送到你的服務器上。
根據HTTPArchive中的4.68億個請求,48%的請求不是來自CDN。那是超過2.24億的請求,如果他們花幾分鐘的時間給自己的網站添加一個CDN,速度可能會超過50%。
一定要檢查你的CDN配置是否正確——在你的CDN中緩存丟失意味著CDN必須向你的源服務器請求資源,這就違背了使用CDN的初衷!所以,你的CDN必須要有一個正確的配置。
2.啟用GZIP壓縮
在一些CDN上,GZIP壓縮只是一個標有 "啟用壓縮 "的復選框。這大概會減少一半的文件大小,你的用戶需要下載文件才能使用你的網站,你的用戶會因此而喜歡你。
3.使用較小的圖像
這意味著既要降低分辨率(例如,攝像頭的輸出從4000x3000像素減少到網絡的1000x750),又要通過壓縮文件來減小尺寸。
如果你的網站使用WordPress,則有一些插件會在你上傳圖片時自動為你執行此操作。
在撰寫博客文章時,我個人使用TinyJPG壓縮圖像。
https://tinyjpg.com/
4.減少頁面發出的請求數
目標是減少加載頁面頂部部分所需的請求數量。
這里有兩種思維方式,你可以:
通過刪除花哨的動畫或不能改善網站體驗的圖像,減少整個頁面上的請求數量。
或者,你可以通過使用延遲加載來推遲優先級不高的加載內容。
5.盡可能避免重定向
重定向會大大降低網站速度。使用響應式CSS并從一個域為你的網站提供服務,而不是為移動用戶提供特殊的子域。
有些重定向是不可避免的,比如 www-> 根域 或 根域 ->www,但你的大部分流量不應該經歷重定向來查看你的網站。
6.減少到第一個字節的時間
到第一個字節的時間是指你的瀏覽器在發出資源請求后,從服務器接收到第一個字節的數據所花費的時間。
有兩個部分:
在服務器上花費的時間
發送數據所花費的時間
你可以通過優化你的服務器端渲染、數據庫查詢、API調用、負載平衡、你的應用程序的實際代碼以及服務器的負載本身(特別是如果你使用的是廉價的虛擬主機——這將影響你的網站的性能),來改善你在服務器上花費的時間。
你可以使用CDN大大減少發送數據所花費的時間。
7.減少并刪除阻止渲染的JavaScript
外部腳本(特別是那些用于營銷的外部腳本)往往會寫得很差,會阻止你的頁面加載,直到它運行完畢。
你可以通過將外部腳本標記為異步來減少這種影響:
<script async src="https://example.com/external.js"></script>
你還可以延遲加載市場營銷腳本,直到用戶開始滾動為止:
window.addEventListener(
'scroll',
() =>
setTimeout(() => {
// 在此插入營銷片段
}, 1000),
{ once: true }
);
8.縮小CSS和JS
Minifying是指使用工具來刪除空格、換行符和縮短變量名。通常情況下,這將作為構建過程的一部分自動完成。
要縮小JavaScript,請查看UglifyJS。
http://lisperator.net/uglifyjs/
要縮小CSS,請查看cssnano。
9.刪除未使用的CSS
自Chrome 59(2017年4月發布)以來,在Chrome DevTools中可以看到未使用的JS和CSS。
要看這個,打開DevTools,顯示控制臺抽屜(就是點擊Esc時出現的那個煩人的東西),點擊左下角的三個小點,打開 "Coverage",就可以看到。
點擊帶有重新加載圖標的按鈕將刷新頁面,并審核CSS和JS的使用情況。
在Google Chrome瀏覽器中審核初始頁面時,外觀如下所示:
10.定期跟蹤網站速度
在你的網站速度變慢的瞬間,修復網站速度問題就會容易得多。除此之外,如果你把檢查網站速度作為一種習慣,那么修復網站速度慢的問題就會變成一件小得多的事情。
有免費的工具可以監視你網站的速度,其中的兩個是WebPageTest和Google Lighthouse。這些工具的缺點是你需要記住在進行更改之前和之后都必須運行它們。
PerfBeacon是一項服務(由本文的作者創建),該服務定期運行Google Lighthouse,并讓你隨時跟蹤網站的速度。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
了解如何使用JavaScript中的Cache API緩存資源。
Cache API允許服務工作者對要緩存的資源(HTML頁面、CSS、JavaScript文件、圖片、JSON等)進行控制。通過Cache API,服務工作者可以緩存資源以供脫機使用,并在以后檢索它們。
檢測Cache支持
檢查 caches 對象在 window 中是否可用。
let isCacheSupported = 'caches' in window;
caches 是 CacheStorage 的一個實例。
創建/初始化Cache
我們可以使用 open 方法創建一個具有 name 的緩存,這將返回 promise。如果緩存已經存在,則不會創建新的緩存。
caches.open('cacheName').then( cache => {
});
你不能訪問為其他源(域)設置的緩存。
你正在創建的緩存將為你的域創建。
你可以為同一個域添加多個緩存,可以通過 caches.keys() 訪問。
將項目添加到緩存
可以使用三種方法 add,addAll,set 來緩存資源。 add() 和 addAll() 方法自動獲取資源并對其進行緩存,而在 set 方法中,我們將獲取數據并設置緩存。
add
let cacheName = 'userSettings';
let url = '/api/get/usersettings';
caches.open(cacheName).then( cache => {
cache.add(url).then( () => {
console.log("Data cached ")
});
});
在上面的代碼中,內部對 /api/get/usersettings url的請求已發送到服務器,一旦接收到數據,響應將被緩存。
addAll
addAll 接受URL數組,并在緩存所有資源時返回Promise。
let urls = ['/get/userSettings?userId=1', '/get/userDetails'];
caches.open(cacheName).then( cache => {
cache.addAll(urls).then( () => {
console.log("Data cached ")
});
});
Cache.add/Cache.addAll 不緩存 Response.status 值不在200范圍內的響應,Cache.put 可以讓你存儲任何請求/響應對。
put
put 為當前的 Cache 對象添加一個key/value對,在 put 中,我們需要手動獲取請求并設置值。
注意:put() 將覆蓋先前存儲在高速緩存中與請求匹配的任何鍵/值對。
let cacheName = 'userSettings';
let url = '/api/get/userSettings';
fetch(url).then(res => {
return caches.open(cacheName).then(cache => {
return cache.put(url, res);
})
})
從緩存中檢索
使用 cache.match() 可以得到存儲到URL的 Response。
const cacheName = 'userSettings'
const url = '/api/get/userSettings'
caches.open(cacheName).then(cache => {
cache.match(url).then(settings => {
console.log(settings);
}
});
settings 是一個響應對象,它看起來像
Response {
body: (...),
bodyUsed: false,
headers: Headers,
ok: true,
status: 200,
statusText: "OK",
type: "basic",
url: "https://test.com/api/get/userSettings"
}
檢索緩存中的所有項目
cache 對象包含 keys 方法,這些方法將擁有當前緩存對象的所有url。
caches.open(cacheName).then( (cache) => {
cache.keys().then((arrayOfRequest) => {
console.log(arrayOfRequest); // [Request, Request]
});
});
arrayOfRequest是一個Request對象數組,其中包含有關請求的所有詳細信息。
檢索所有緩存
caches.keys().then(keys => {
// keys是一個數組,其中包含鍵的列表
})
從緩存中刪除項目
可以對 cache 對象使用 delete 方法來刪除特定的緩存請求。
let cacheName = userSettings;
let urlToDelete = '/api/get/userSettings';
caches.open(cacheName).then(cache => {
cache.delete(urlToDelete)
})
完全刪除緩存
caches.delete(cacheName).then(() => {
console.log('Cache successfully deleted!');
})
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
作為一個曾經的Java coder, 當我第一次看到js里面的裝飾器(Decorator)的時候,就馬上想到了Java中的注解,當然在實際原理和功能上面,Java的注解和js的裝飾器還是有很大差別的。本文題目是Vue中使用裝飾器,我是認真的,但本文將從裝飾器的概念開發聊起,一起來看看吧。
通過本文內容,你將學到以下內容:
了解什么是裝飾器
在方法使用裝飾器
在class中使用裝飾器
在Vue中使用裝飾器
本文首發于公眾號【前端有的玩】,不想當咸魚,想要換工作,關注公眾號,帶你每日一起刷大廠面試題,關注 === 大廠offer。
什么是裝飾器
裝飾器是ES2016提出來的一個提案,當前處于Stage 2階段,關于裝飾器的體驗,可以點擊 https://github.com/tc39/proposal-decorators查看詳情。裝飾器是一種與類相關的語法糖,用來包裝或者修改類或者類的方法的行為,其實裝飾器就是設計模式中裝飾者模式的一種實現方式。不過前面說的這些概念太干了,我們用人話來翻譯一下,舉一個例子。
在日常開發寫bug過程中,我們經常會用到防抖和節流,比如像下面這樣
class MyClass {
follow = debounce(function() {
console.log('我是子君,關注我哦')
}, 100)
}
const myClass = new MyClass()
// 多次調用只會輸出一次
myClass.follow()
myClass.follow()
上面是一個防抖的例子,我們通過debounce函數將另一個函數包起來,實現了防抖的功能,這時候再有另一個需求,比如希望在調用follow函數前后各打印一段日志,這時候我們還可以再開發一個log函數,然后繼續將follow包裝起來
/**
* 最外層是防抖,否則log會被調用多次
*/
class MyClass {
follow = debounce(
log(function() {
console.log('我是子君,關注我哦')
}),
100
)
}
上面代碼中的debounce和log兩個函數,本質上是兩個包裝函數,通過這兩個函數對原函數的包裝,使原函數的行為發生了變化,而js中的裝飾器的原理就是這樣的,我們使用裝飾器對上面的代碼進行改造
class MyClass {
@debounce(100)
@log
follow() {
console.log('我是子君,關注我哦')
}
}
裝飾器的形式就是 @ + 函數名,如果有參數的話,后面的括號里面可以傳參
在方法上使用裝飾器
裝飾器可以應用到class上或者class里面的屬性上面,但一般情況下,應用到class屬性上面的場景會比較多一些,比如像上面我們說的log,debounce等等,都一般會應用到類屬性上面,接下來我們一起來具體看一下如何實現一個裝飾器,并應用到類上面。在實現裝飾器之前,我們需要先了解一下屬性描述符
了解一下屬性描述符
在我們定義一個對象里面的屬性的時候,其實這個屬性上面是有許多屬性描述符的,這些描述符標明了這個屬性能不能修改,能不能枚舉,能不能刪除等等,同時ECMAScript將這些屬性描述符分為兩類,分別是數據屬性和訪問器屬性,并且數據屬性與訪問器屬性是不能共存的。
數據屬性
數據屬性包含一個數據值的位置,在這個位置可以讀取和寫入值。數據屬性包含了四個描述符,分別是
configurable
表示能不能通過delete刪除屬性,能否修改屬性的其他描述符特性,或者能否將數據屬性修改為訪問器屬性。當我們通過let obj = {name: ''}聲明一個對象的時候,這個對象里面所有的屬性的configurable描述符的值都是true
enumerable
表示能不能通過for in或者Object.keys等方式獲取到屬性,我們一般聲明的對象里面這個描述符的值是true,但是對于class類里面的屬性來說,這個值是false
writable
表示能否修改屬性的數據值,通過將這個修改為false,可以實現屬性只讀的效果。
value
表示當前屬性的數據值,讀取屬性值的時候,從這里讀?。粚懭雽傩灾档臅r候,會寫到這個位置。
訪問器屬性
訪問器屬性不包含數據值,他們包含了getter與setter兩個函數,同時configurable與enumerable是數據屬性與訪問器屬性共有的兩個描述符。
getter
在讀取屬性的時候調用這個函數,默認這個函數為undefined
setter
在寫入屬性值的時候調用這個函數,默認這個函數為undefined
了解了這六個描述符之后,你可能會有幾個疑問: 我如何去定義修改這些屬性描述符?這些屬性描述符與今天的文章主題有什么關系?接下來是揭曉答案的時候了。
使用Object.defineProperty
了解過vue2.0雙向綁定原理的同學一定知道,Vue的雙向綁定就是通過使用Object.defineProperty去定義數據屬性的getter與setter方法來實現的,比如下面有一個對象
let obj = {
name: '子君',
officialAccounts: '前端有的玩'
}
我希望這個對象里面的用戶名是不能被修改的,用Object.defineProperty該如何定義呢?
Object.defineProperty(obj,'name', {
// 設置writable 是 false, 這個屬性將不能被修改
writable: false
})
// 修改obj.name
obj.name = "君子"
// 打印依然是子君
console.log(obj.name)
通過Object.defineProperty可以去定義或者修改對象屬性的屬性描述符,但是因為數據屬性與訪問器屬性是互斥的,所以一次只能修改其中的一類,這一點需要注意。
定義一個防抖裝飾器
裝飾器本質上依然是一個函數,不過這個函數的參數是固定的,如下是防抖裝飾器的代碼
/**
*@param wait 延遲時長
*/
function debounce(wait) {
return function(target, name, descriptor) {
descriptor.value = debounce(descriptor.value, wait)
}
}
// 使用方式
class MyClass {
@debounce(100)
follow() {
console.log('我是子君,我的公眾號是 【前端有的玩】,關注有驚喜哦')
}
}
我們逐行去分析一下代碼
首先我們定義了一個 debounce函數,同時有一個參數wait,這個函數對應的就是在下面調用裝飾器時使用的@debounce(100)
debounce函數返回了一個新的函數,這個函數即裝飾器的核心,這個函數有三個參數,下面逐一分析
target: 這個類屬性函數是在誰上面掛載的,如上例對應的是MyClass類
name: 這個類屬性函數的名稱,對應上面的follow
descriptor: 這個就是我們前面說的屬性描述符,通過直接descriptor上面的屬性,即可實現屬性只讀,數據重寫等功能
然后第三行 descriptor.value = debounce(descriptor.value, wait), 前面我們已經了解到,屬性描述符上面的value對應的是這個屬性的值,所以我們通過重寫這個屬性,將其用debounce函數包裝起來,這樣在函數調用follow時實際調用的是包裝后的函數
通過上面的三步,我們就實現了類屬性上面可使用的裝飾器,同時將其應用到了類屬性上面
在class上使用裝飾器
裝飾器不僅可以應用到類屬性上面,還可以直接應用到類上面,比如我希望可以實現一個類似Vue混入那樣的功能,給一個類混入一些方法屬性,應該如何去做呢?
// 這個是要混入的對象
const methods = {
logger() {
console.log('記錄日志')
}
}
// 這個是一個登陸登出類
class Login{
login() {}
logout() {}
}
如何將上面的methods混入到Login中,首先我們先實現一個類裝飾器
function mixins(obj) {
return function (target) {
Object.assign(target.prototype, obj)
}
}
// 然后通過裝飾器混入
@mixins(methods)
class Login{
login() {}
logout() {}
}
這樣就實現了類裝飾器。對于類裝飾器,只有一個參數,即target,對應的就是這個類本身。
了解完裝飾器,我們接下來看一下如何在Vue中使用裝飾器。
在Vue中使用裝飾器
使用ts開發Vue的同學一定對vue-property-decorator不會感到陌生,這個插件提供了許多裝飾器,方便大家開發的時候使用,當然本文的中點不是這個插件。其實如果我們的項目沒有使用ts,也是可以使用裝飾器的,怎么用呢?
配置基礎環境
除了一些老的項目,我們現在一般新建Vue項目的時候,都會選擇使用腳手架vue-cli3/4來新建,這時候新建的項目已經默認支持了裝飾器,不需要再配置太多額外的東西,如果你的項目使用了eslint,那么需要給eslint配置以下內容。
parserOptions: {
ecmaFeatures:{
// 支持裝飾器
legacyDecorators: true
}
}
使用裝飾器
雖然Vue的組件,我們一般書寫的時候export出去的是一個對象,但是這個并不影響我們直接在組件中使用裝飾器,比如就拿上例中的log舉例。
function log() {
/**
* @param target 對應 methods 這個對象
* @param name 對應屬性方法的名稱
* @param descriptor 對應屬性方法的修飾符
*/
return function(target, name, descriptor) {
console.log(target, name, descriptor)
const fn = descriptor.value
descriptor.value = function(...rest) {
console.log(`這是調用方法【${name}】前打印的日志`)
fn.call(this, ...rest)
console.log(`這是調用方法【${name}】后打印的日志`)
}
}
}
export default {
created() {
this.getData()
},
methods: {
@log()
getData() {
console.log('獲取數據')
}
}
}
看了上面的代碼,是不是發現在Vue中使用裝飾器還是很簡單的,和在class的屬性上面使用的方式一模一樣,但有一點需要注意,在methods里面的方法上面使用裝飾器,這時候裝飾器的target對應的是methods。
除了在methods上面可以使用裝飾器之外,你也可以在生命周期鉤子函數上面使用裝飾器,這時候target對應的是整個組件對象。
一些常用的裝飾器
下面小編羅列了幾個小編在項目中常用的幾個裝飾器,方便大家使用
1. 函數節流與防抖
函數節流與防抖應用場景是比較廣的,一般使用時候會通過throttle或debounce方法對要調用的函數進行包裝,現在就可以使用上文說的內容將這兩個函數封裝成裝飾器, 防抖節流使用的是lodash提供的方法,大家也可以自行實現節流防抖函數哦
import { throttle, debounce } from 'lodash'
/**
* 函數節流裝飾器
* @param {number} wait 節流的毫秒
* @param {Object} options 節流選項對象
* [options.leading=true] (boolean): 指定調用在節流開始前。
* [options.trailing=true] (boolean): 指定調用在節流結束后。
*/
export const throttle = function(wait, options = {}) {
return function(target, name, descriptor) {
descriptor.value = throttle(descriptor.value, wait, options)
}
}
/**
* 函數防抖裝飾器
* @param {number} wait 需要延遲的毫秒數。
* @param {Object} options 選項對象
* [options.leading=false] (boolean): 指定在延遲開始前調用。
* [options.maxWait] (number): 設置 func 允許被延遲的最大值。
* [options.trailing=true] (boolean): 指定在延遲結束后調用。
*/
export const debounce = function(wait, options = {}) {
return function(target, name, descriptor) {
descriptor.value = debounce(descriptor.value, wait, options)
}
}
封裝完之后,在組件中使用
import {debounce} from '@/decorator'
export default {
methods:{
@debounce(100)
resize(){}
}
}
2. loading
在加載數據的時候,為了個用戶一個友好的提示,同時防止用戶繼續操作,一般會在請求前顯示一個loading,然后在請求結束之后關掉loading,一般寫法如下
export default {
methods:{
async getData() {
const loading = Toast.loading()
try{
const data = await loadData()
// 其他操作
}catch(error){
// 異常處理
Toast.fail('加載失敗');
}finally{
loading.clear()
}
}
}
}
我們可以把上面的loading的邏輯使用裝飾器重新封裝,如下代碼
import { Toast } from 'vant'
/**
* loading 裝飾器
* @param {*} message 提示信息
* @param {function} errorFn 異常處理邏輯
*/
export const loading = function(message = '加載中...', errorFn = function() {}) {
return function(target, name, descriptor) {
const fn = descriptor.value
descriptor.value = async function(...rest) {
const loading = Toast.loading({
message: message,
forbidClick: true
})
try {
return await fn.call(this, ...rest)
} catch (error) {
// 在調用失敗,且用戶自定義失敗的回調函數時,則執行
errorFn && errorFn.call(this, error, ...rest)
console.error(error)
} finally {
loading.clear()
}
}
}
}
然后改造上面的組件代碼
export default {
methods:{
@loading('加載中')
async getData() {
try{
const data = await loadData()
// 其他操作
}catch(error){
// 異常處理
Toast.fail('加載失敗');
}
}
}
}
3. 確認框
當你點擊刪除按鈕的時候,一般都需要彈出一個提示框讓用戶確認是否刪除,這時候常規寫法可能是這樣的
import { Dialog } from 'vant'
export default {
methods: {
deleteData() {
Dialog.confirm({
title: '提示',
message: '確定要刪除數據,此操作不可回退。'
}).then(() => {
console.log('在這里做刪除操作')
})
}
}
}
我們可以把上面確認的過程提出來做成裝飾器,如下代碼
import { Dialog } from 'vant'
/**
* 確認提示框裝飾器
* @param {*} message 提示信息
* @param {*} title 標題
* @param {*} cancelFn 取消回調函數
*/
export function confirm(
message = '確定要刪除數據,此操作不可回退。',
title = '提示',
cancelFn = function() {}
) {
return function(target, name, descriptor) {
const originFn = descriptor.value
descriptor.value = async function(...rest) {
try {
await Dialog.confirm({
message,
title: title
})
originFn.apply(this, rest)
} catch (error) {
cancelFn && cancelFn(error)
}
}
}
}
然后再使用確認框的時候,就可以這樣使用了
export default {
methods: {
// 可以不傳參,使用默認參數
@confirm()
deleteData() {
console.log('在這里做刪除操作')
}
}
}
是不是瞬間簡單多了,當然還可以繼續封裝很多很多的裝飾器,因為文章內容有限,暫時提供這三個。
裝飾器組合使用
在上面我們將類屬性上面使用裝飾器的時候,說道裝飾器可以組合使用,在Vue組件上面使用也是一樣的,比如我們希望在確認刪除之后,調用接口時候出現loading,就可以這樣寫(一定要注意順序)
export default {
methods: {
@confirm()
@loading()
async deleteData() {
await delete()
}
}
}
本節定義的裝飾器,均已應用到這個項目中 https://github.com/snowzijun/vue-vant-base, 這是一個基于Vant開發的開箱即用移動端框架,你只需要fork下來,無需做任何配置就可以直接進行業務開發,歡迎使用,喜歡麻煩給一個star。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
目錄
1. Animista
2. Animate CSS
3. Vivify
4. Magic Animations CSS3
5. cssanimation.io
6. Angrytools
7. Hover.css
8. WickedCSS
9. Three Dots
10. CSShake
1.Animista
網站地址:http://animista.net/
網站描述:在線生成 css 動畫
Animista是一個在線動畫生成器,同時也是一個動畫庫,它為我們提供了以下功能
1. 選擇不同的動畫
我們可以選擇想要的動畫類型(例如entrance/exist),除了可以選擇某個動畫(例如,scale-in)外,甚至還可以為該動畫選擇不同的展示效果(例如: scale-in-right)。
2. 定制
Animista還提供了一個功能,允許我們定制動畫的某些部分,比如
duration
delay
direction
更好的是,可以選擇要設置動畫的對象:
3. 生成CSS代碼
選擇適合自己需要的動畫后,我們可以直接從網站上獲取代碼,甚者可以進行壓縮:
4. 下載代碼
另一個好用的功能是,可以把自己收藏自己喜歡的動畫,然后一起下載下來, 或者,我們也可以選擇將這些動畫的代碼復制到一起。
2. Animate CSS
網站地址:http://daneden.github.io/anim...
網站描述:齊全的CSS3動畫庫
想必這個不用介紹,大部分人都知道了。Animate CSS 可能是最著名的動畫庫之一。這里簡要介紹一下它的用法:
1. 用法
首先,必須在總需要動畫元素上添加類animated ,然后是動畫的名字。
<div class="animated slideInLeft"></div>
如果我們想讓動畫一直持續,可以添加infinite類。
通過 JS 來添加動畫:
document.querySelector('.my-element').classList.add('animated', 'slideInLeft')
通過 JQ 來添加動畫:
$(".my-element").addClass("animated slideInLeft")
2. 其它功能
Animate CSS提供了一些基本的類來控制動畫的延遲和速度。
delay
可以添加 delay 類來延遲動畫的播放。
<div class="animated slideInLeft delay-{1-5}"><div>
speed
我們還可以通過添加如下列出的類之一來控制動畫速度。
類名 速度時間
show 2s
slower 3s
fast 800ms
faster 500ms
<div class="animated slideInLeft slow|slower|fast|faster"><div>
3. Vivify
網站地址: http://vivify.mkcreative.cz/
網站描述: 一個更加豐富css動畫庫
Vivify 是一個動畫庫,可以看作是Animate CSS的增強版。它們的工作方式完全相同,有Animate CSS的大多數類且還擴展了一些。
<div class="vivify slideInLeft"></div>
使用 JS 方式:
document.querySelector('.my-element').classList.add('vivify', 'slideInLeft')
使用 JQ 方式:
$(".my-element").addClass("vivify slideInLeft")
與Animate CSS一樣,Vivify 還提供了一些類來控制動畫的持續時間和延遲。
延遲和持續時間類在以下間隔中可用:
<div class="delay|duration-{100|150|200|250...1000|1250|1500|1750...10750}"></div>
4. Magic Animations CSS3
網站地址: https://www.minimamente.com/p...
網站描述: Magic CSS3 Animations 是 CSS3 動畫的包,伴有特殊的效果,用戶可以自由的在 web 項目中使用。
這個動畫庫有一些非常漂亮和流暢的動畫,特別是3D的。沒什么好說的,自己去嘗試。
<div class="magictime fadeIn"></div>
使用 JS 方式:
document.querySelector('.my-element').classList.add('magictime', 'fadeIn')
使用 JQ 方式:
$(".my-element").addClass("magictime fadeIn")
5. cssanimation.io
網站地址: http://cssanimation.io/index....
cssanimation.io是一大堆不同動畫的集合,總共大概有200個,這很強大。如果你連在這里都沒有找到你所需的動畫,那么在其它也將很難找到。
它的工作原理與 Animista 類似。例如,可以選擇一個動畫并直接從站點獲取代碼,或者也可以下載整個庫。
用法
將cssanimation {animation_name}添加到指定的元素上。
<div class="cssanimation fadeIn"></div>
使用 JS
document.querySelector('.my-element').classList.add('cssanimation','fadeIn')
使用 JQ
$(".my-element").addClass("cssanimation fadeIn")
還可以添加 infinite 類,這樣動畫就可以循環播放。
<div class="cssanimation fadeIn infinite"></div>
此外,cssanimation.io還為我們提供了動漫字母的功能。使用這個需要引入letteranimation.js文件,然后將le {animation_name}添加到我們的文本元素中。
<div class="cssanimation leSnake"></div>
要使字母按順序產生動畫,添加sequence類,要使它們隨機產生動畫,添加random類。
<div class="cssanimation leSnake {sequence|random}"></div>
Sequence
Random
6.Angrytools
網站地址: https://angrytools.com/css/an...
如果使用不同的生成器,Angrytools實際上是一個集合,其中還包括CSS動畫生成器。
它可能不像Animista那么復雜,但我覺得這個也很不錯。這個站點還提供了一些自定義動畫的特性,比如動畫的持續時間或延遲。
但是我喜歡的是,我們可以在其展示時間軸上添加自定義的keyframes,然后可以直接在其中編寫代碼。 另外,也可以編輯現有的。
當我們完成的時候,可以得到完整的動畫代碼,也可以下載它。
7.Hover.css
網站地址: http://ianlunn.github.io/Hover/
網站描述: 純CSS3鼠標滑過效果動畫庫
Hover.css是許多CSS動畫的集合,與上面的動畫不同,每次將元素懸停時都會觸發。
一組CSS3支持的懸停效果,可應用于鏈接、按鈕、徽標、SVG和特色圖像等。
** 用法
它非常簡單:只需將類的名稱添加到元素中,比如
<button class="hvr-fade">Hover me!</button>
8.WickedCSS
網站地址: http://kristofferandreasen.gi...
WickedCSS是一個小的CSS動畫庫,它沒有太多的動畫變體,但至少有很大的變化。 其中大多數是我們已經熟悉的基礎知識,但它們確實很干凈。
它的用法很簡單,只需將動畫的名稱添加到元素中即可。
<div class="bounceIn"></div>
** 使用 JS
document.querySelector('.my-element').classList.add('bounceIn')
** 使用 JQ
$(".my-element").addClass("bounceIn")
9.Three Dots
網站地址: https://nzbin.github.io/three...
Three Dots是一組CSS加載動畫,它由三個點組成,而這些點僅由單個元素組成。
** 用法
只需創建一個div元素,并添加動畫的名稱
<div class="dot-elastic"></div>
10.CSShake
網站地址: https://elrumordelaluz.github...
顧名思義,CSShake是一個CSS動畫庫,其中包含不同類型的震動動畫。
** 用法
將shake {animation name}添加到元素中。
<div class="shake shake-hard"></div>
使用 JS
document.querySelector('.my-element').classList.add('shake','shake-hard')
使用 JQ
$(".my-element").addClass("shake shake-hard")
人才們的 【三連】 就是小智不斷分享的最大動力,如果本篇博客有任何錯誤和建議,歡迎人才們留言,最后,謝謝大家的觀看。
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
原文:https://dev.to/weeb/10-of-the...
交流
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
毫無疑問,JavaScript是Web開發中最流行的編程語言之一。無論您使用的是React,Vue還是Angular,都只是JavaScript。圍繞JS展開了廣泛而重要的生態系統,提供了無數的框架和庫,可幫助你更快地開發應用程序。
但是有時候最好退一步,嘗試了解如何在沒有庫的情況下做事。看看下面的代碼片段,以優雅的方式解決簡單的問題,并在日常項目情況下使用這些知識或為編碼面試做準備。
1.反轉字符串
在此示例中,我們使用擴展運算符(…),Array的reverse方法和String的join方法來反轉給定的字符串。
const reverseString = string => [...string].reverse().join('');
// 例子
reverseString('javascript'); // 'tpircsavaj'
reverseString('good'); // 'doog'
2.計算數字的階乘
要計算給定數字的階乘,我們使用箭頭函數和嵌套三元運算符。
const factoriaOfNumber = number => number < 0 ? (() => {
throw new TypeError('No negative numbers please');
})()
: number <=1
? 1
: number * factoriaOfNumber(number -1);
// 例子
factoriaOfNumber(4); // 24
factoriaOfNumber(8); // 40320
3.將數字轉換為數字數組
在此示例中,我們使用擴展運算符(…),Array的map方法和 parseInt 函數將給定的數字轉換為一個單數的數組。
const convertToArray = number => [...`${number}`].map(el => parseInt(el));
// 例子
convertToArray(5678); // [5, 6, 7, 8]
convertToArray(123456789); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
4.檢查數字是否為2的冪
這很簡單。我們檢查該數字不是偽造的,并使用按位AND運算符(&)來確定數字是否為2的冪。
const isNumberPowerOfTwo = number => !!number && (number & (number - 1)) == 0;
// 例子
isNumberPowerOfTwo(100); // false
isNumberPowerOfTwo(128); // true
5.從對象創建鍵值對數組
在此示例中,我們使用Object中的keys方法和Array中的map方法來映射Object的鍵并創建鍵/值對數組。
const keyValuePairsToArray = object => Object.keys(object).map(el => [el, object[el]]);
// 例子
keyValuePairsToArray({ Better: 4, Programming: 2 });
// [ ['Better', 4], ['Programming', 2] ]
keyValuePairsToArray({ x: 1, y: 2, z: 3 });
// [ ['x', 1], ['y', 2], ['z', 3] ]
6.返回數組中的[Number]個最大元素
為了從數組中返回最大元素,我們使用了一個箭頭函數,該函數獲取數組和我們希望函數返回的元素數。我們使用擴展運算符(…)以及Array中的sort和slice方法。請注意,如果我們不提供第二個參數,則 number 的默認值為 1,因此僅返回一個最大元素。
const maxElementsFromArray = (array, number = 1) => [...array].sort((x, y) => y - x).slice(0, number);
// 例子
maxElementsFromArray([1,2,3,4,5]); // [5]
maxElementsFromArray([7,8,9,10,10],2); // [10, 10]
7.檢查數組中的所有元素是否相等
在這個簡短的示例中,我們使用Array中的every方法檢查數組中的所有元素是否相等。我們基本上檢查每個元素是否等于數組中的第一個元素。
const elementsAreEqual = array => array.every(el => el === array[0]);
// 例子
elementsAreEqual([9,8,7,6,5]); // false
elementsAreEqual([4,4,4,4,4]); // true
8.返回兩個數的平均值
在此示例中,我們使用了擴展運算符(…)和Array中的reduce方法來返回兩個給定數字或一個數組的平均值。
const averageOfTwoNumbers = (...numbers) => numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0) / numbers.length;
// 例子
averageOfTwoNumbers(...[6,7,8]); // 7
averageOfTwoNumbers(6,7,8,9); // 7.5
9.返回兩個或多個數字的總和
要返回兩個或多個給定數字或一個數組的總和,我們再次使用擴展運算符(…)和Array中的reduce方法。
const sumOfNumbers = (...array) => [...array].reduce((accumulator, currentValue) => accumulator + currentValue, 0);
// 例子
sumOfNumbers(5,6,7,8,9.10); // 45
sumOfNumbers(...[1,2,3,4,5,6,7,8,9,10]); // 50
10.返回數字數組的冪集
在最后一個示例中,我們要返回數字數組的冪集。因此,我們使用Array中的reduce,map和concat方法。
const powersetOfArray = array => array.reduce((accumulator, currentValue) => accumulator.concat(accumulator.map(el => [currentValue].concat(el))), [[]]);
// 例子
powersetOfArray([4, 2]); // [[], [4], [2], [2, 4]]
powersetOfArray([1, 2, 3]); /
// [[], [1], [2], [2, 1], [3], [3, 1], [3, 2], [3, 2, 1]]
如你所見,使用JavaScript和一些ES6魔術來解決這些任務并不總是困難的。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
閱讀之前
基本上,在JavaScript中遍歷對象取決于對象是否可迭代。默認情況下,數組是可迭代的。map 和 forEach 包含在Array.prototype 中,因此我們無需考慮可迭代性。如果你想進一步學習,我推薦你看看什么是JavaScript中的可迭代對象!
什么是map()和forEach()?
map 和 forEach 是數組中的幫助器方法,可以輕松地在數組上循環。我們曾經像下面這樣循環遍歷一個數組,沒有任何輔助函數。
var array = ['1', '2', '3'];
for (var i = 0; i < array.length; i += 1) {
console.log(Number(array[i]));
}
// 1
// 2
// 3
自JavaScript時代開始以來,就一直存在 for 循環。它包含3個表達式:初始值,條件和最終表達式。
這是循環數組的經典方法。從ECMAScript 5開始,新功能似乎使我們更加快樂。
map
map 的作用與 for 循環完全相同,只是 map 會創建一個新數組,其結果是在調用數組中的每個元素上調用提供的函數。
它需要兩個參數:一個是稍后在調用 map 或 forEach 時調用的回調函數,另一個是回調函數被調用時使用的名為 thisArg 的上下文變量。
const arr = ['1', '2', '3'];
// 回調函數接受3個參數
// 數組的當前值作為第一個參數
// 當前值在數組中的位置作為第二個參數
// 原始源數組作為第三個參數
const cb = (str, i, origin) => {
console.log(`${i}: ${Number(str)} / ${origin}`);
};
arr.map(cb);
// 0: 1 / 1,2,3
// 1: 2 / 1,2,3
// 2: 3 / 1,2,3
回調函數可以如下使用。
arr.map((str) => { console.log(Number(str)); })
map 的結果不等于原始數組。
const arr = [1];
const new_arr = arr.map(d => d);
arr === new_arr; // false
你還可以將對象作為 thisArg 傳遞到map。
const obj = { name: 'Jane' };
[1].map(function() {
// { name: 'Jane' }
console.dir(this);
}, obj);
[1].map(() => {
// window
console.dir(this);
}, obj);
對象 obj 成為 map 的 thisArg。但是箭頭回調函數無法將 obj 作為其 thisArg。
這是因為箭頭函數與正常函數不同。
forEach
forEach 是數組的另一個循環函數,但 map 和 forEach 在使用中有所不同。map 和 forEach 可以使用兩個參數——回調函數和 thisArg,它們用作其 this。
const arr = ['1', '2', '3'];
// 回調函數接受3個參數
// 數組的當前值作為第一個參數
// 當前值在數組中的位置作為第二個參數
// 原始源數組作為第三個參數
const cb = (str, i, origin) => {
console.log(`${i}: ${Number(str)} / ${origin}`);
};
arr.forEach(cb);
// 0: 1 / 1,2,3
// 1: 2 / 1,2,3
// 2: 3 / 1,2,3
那有什么不同?
map 返回其原始數組的新數組,但是 forEach 卻沒有。但是它們都確保了原始對象的不變性。
[1,2,3].map(d => d + 1); // [2, 3, 4];
[1,2,3].forEach(d => d + 1); // undefined;
如果更改數組內的值,forEach 不能確保數組的不變性。這個方法只有在你不接觸里面的任何值時,才能保證不變性。
[{a: 1, b: 2}, {a: 10, b: 20}].forEach((obj) => obj.a += 1);
// [{a: 2, b: 2}, {a: 11, b: 21}]
// 數組已更改!
何時使用map()和forEach()?
由于它們之間的主要區別在于是否有返回值,所以你會希望使用 map 來制作一個新的數組,而使用 forEach 只是為了映射到數組上。
這是一個簡單的例子。
const people = [
{ name: 'Josh', whatCanDo: 'painting' },
{ name: 'Lay', whatCanDo: 'security' },
{ name: 'Ralph', whatCanDo: 'cleaning' }
];
function makeWorkers(people) {
return people.map((person) => {
const { name, whatCanDo } = person;
return <li key={name}>My name is {name}, I can do {whatCanDo}</li>
});
}
<ul>makeWorkers(people)</ul>
比如在React中,map 是非常常用的制作元素的方法,因為 map 在對原數組的數據進行操作后,會創建并返回一個新的數組。
const mySubjectId = ['154', '773', '245'];
function countSubjects(subjects) {
let cnt = 0;
subjects.forEach(subject => {
if (mySubjectId.includes(subject.id)) {
cnt += 1;
}
});
return cnt;
}
countSubjects([
{ id: '223', teacher: 'Mark' },
{ id: '154', teacher: 'Linda' }
]);
// 1
另一方面,當你想對數據進行某些操作而不創建新數組時,forEach 很有用。順便說一句,可以使用 filter 重構示例。
subjects.filter(subject => mySubjectId.includes(subject.id)).length;
綜上所述,我建議你在創建一個新的數組時使用map,當你不需要制作一個新的數組,而是要對數據做一些事情時,就使用forEach。
速度比較
有些帖子提到 map 比 forEach 快。所以,我很好奇這是不是真的。我找到了這個對比結果。
該代碼看起來非常相似,但結果卻相反。有些測試說 forEach 更快,有些說 map 更快。也許你在告訴自己 map/forEach 比其他的快,你可能是對的。老實說,我不確定。我認為在現代Web開發中,可讀性比 map 和 forEach 之間的速度重要得多。
但可以肯定的是——兩者都比JavaScript內置的 for 循環慢。
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
序言
之前發表了一篇文章 redux、mobx、concent特性大比拼, 看后生如何對局前輩,吸引了不少感興趣的小伙伴入群開始了解和使用 concent,并獲得了很多正向的反饋,實實在在的幫助他們提高了開發體驗,群里人數雖然還很少,但大家熱情高漲,技術討論氛圍濃厚,對很多新鮮技術都有保持一定的敏感度,如上個月開始逐漸被提及得越來越多的出自facebook的狀態管理方案 recoil,雖然還處于實驗狀態,但是相必大家已經私底下開始欲欲躍試了,畢竟出生名門,有fb背書,一定會大放異彩。
不過當我體驗完recoil后,我對其中標榜的更新保持了懷疑態度,有一些誤導的嫌疑,這一點下文會單獨分析,是否屬于誤導讀者在讀完本文后自然可以得出結論,總之本文主要是分析Concent與Recoil的代碼風格差異性,并探討它們對我們將來的開發模式有何新的影響,以及思維上需要做什么樣的轉變。
數據流方案之3大流派
目前主流的數據流方案按形態都可以劃分以下這三類
redux流派
redux、和基于redux衍生的其他作品,以及類似redux思路的作品,代表作有dva、rematch等等。
mobx流派
借助definePerperty和Proxy完成數據劫持,從而達到響應式編程目的的代表,類mobx的作品也有不少,如dob等。
Context流派
這里的Context指的是react自帶的Context api,基于Context api打造的數據流方案通常主打輕量、易用、概覽少,代表作品有unstated、constate等,大多數作品的核心代碼可能不超過500行。
到此我們看看Recoil應該屬于哪一類?很顯然按其特征屬于Context流派,那么我們上面說的主打輕量對
Recoil并不適用了,打開其源碼庫發現代碼并不是幾百行完事的,所以基于Context api做得好用且強大就未必輕量,由此看出facebook對Recoil是有野心并給予厚望的。
我們同時也看看Concent屬于哪一類呢?Concent在v2版本之后,重構數據追蹤機制,啟用了defineProperty和Proxy特性,得以讓react應用既保留了不可變的追求,又享受到了運行時依賴收集和ui更新的性能提升福利,既然啟用了defineProperty和Proxy,那么看起來Concent應該屬于mobx流派?
事實上Concent屬于一種全新的流派,不依賴react的Context api,不破壞react組件本身的形態,保持追求不可變的哲學,僅在react自身的渲染調度機制之上建立一層邏輯層狀態分發調度機制,defineProperty和Proxy只是用于輔助收集實例和衍生數據對模塊數據的依賴,而修改數據入口還是setState(或基于setState封裝的dispatch, invoke, sync),讓Concent可以0入侵的接入react應用,真正的即插即用和無感知接入。
即插即用的核心原理是,Concent自建了一個平行于react運行時的全局上下文,精心維護這模塊與實例之間的歸屬關系,同時接管了組件實例的更新入口setState,保留原始的setState為reactSetState,所有當用戶調用setState時,concent除了調用reactSetState更新當前實例ui,同時智能判斷提交的狀態是否也還有別的實例關心其變化,然后一并拿出來依次執行這些實例的reactSetState,進而達到了狀態全部同步的目的。
Recoil初體驗
我們以常用的counter來舉例,熟悉一下Recoil暴露的四個高頻使用的api
atom,定義狀態
selector, 定義派生數據
useRecoilState,消費狀態
useRecoilValue,消費派生數據
定義狀態
外部使用atom接口,定義一個key為num,初始值為0的狀態
const numState = atom({
key: "num",
default: 0
});
定義派生數據
外部使用selector接口,定義一個key為numx10,初始值是依賴numState再次計算而得到
const numx10Val = selector({
key: "numx10",
get: ({ get }) => {
const num = get(numState);
return num * 10;
}
});
定義異步的派生數據
selector的get支持定義異步函數
需要注意的點是,如果有依賴,必需先書寫好依賴在開始執行異步邏輯
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;
}
});
消費狀態
組件里使用useRecoilState接口,傳入想要獲去的狀態(由atom創建而得)
const NumView = () => {
const [num, setNum] = useRecoilState(numState);
const add = ()=>setNum(num+1);
return (
<div>
{num}<br/>
<button onClick={add}>add</button>
</div>
);
}
消費派生數據
組件里使用useRecoilValue接口,傳入想要獲去的派生數據(由selector創建而得),同步派生數據和異步派生數據,皆可通過此接口獲得
const NumValView = () => {
const numx10 = useRecoilValue(numx10Val);
const asyncNumx10 = useRecoilValue(asyncNumx10Val);
return (
<div>
numx10 :{numx10}<br/>
</div>
);
};
渲染它們查看結果
暴露定義好的這兩個組件, 查看在線示例
export default ()=>{
return (
<>
<NumView />
<NumValView />
</>
);
};
頂層節點包裹React.Suspense和RecoilRoot,前者用于配合異步計算函數需要,后者用于注入Recoil上下文
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<React.Suspense fallback={<div>Loading...</div>}>
<RecoilRoot>
<Demo />
</RecoilRoot>
</React.Suspense>
</React.StrictMode>,
rootElement
);
Concent初體驗
如果讀過concent文檔(還在持續建設中...),可能部分人會認為api太多,難于記住,其實大部分都是可選的語法糖,我們以counter為例,只需要使用到以下兩個api即可
run,定義模塊狀態(必需)、模塊計算(可選)、模塊觀察(可選)
運行run接口后,會生成一份concent全局上下文
setState,修改狀態
定義狀態&修改狀態
以下示例我們先脫離ui,直接完成定義狀態&修改狀態的目的
import { run, setState, getState } from "concent";
run({
counter: {// 聲明一個counter模塊
state: { num: 1 }, // 定義狀態
}
});
console.log(getState('counter').num);// log: 1
setState('counter', {num:10});// 修改counter模塊的num值為10
console.log(getState('counter').num);// log: 10
我們可以看到,此處和redux很類似,需要定義一個單一的狀態樹,同時第一層key就引導用戶將數據模塊化管理起來.
引入reducer
上述示例中我們直接掉一個呢setState修改數據,但是真實的情況是數據落地前有很多同步的或者異步的業務邏輯操作,所以我們對模塊填在reducer定義,用來聲明修改數據的方法集合。
import { run, dispatch, getState } from "concent";
const delay = () => new Promise(r => setTimeout(r, 1000));
const state = () => ({ num: 1 });// 狀態聲明
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來觸發修改狀態的方法
因dispatch會返回一個Promise,所以我們需要用一個async 包裹起來執行代碼
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調用時基于字符串匹配方式,之所以保留這樣的調用方式是為了照顧需要動態調用的場景,其實更推薦的寫法是
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
上述示例主要演示了如何定義狀態和修改狀態,那么接下來我們需要用到以下兩個api來幫助react組件生成實例上下文(等同于與vue 3 setup里提到的渲染上下文),以及獲得消費concent模塊數據的能力
register, 注冊類組件為concent組件
useConcent, 注冊函數組件為concent組件
import { register, useConcent } from "concent";
@register("counter")
class ClsComp extends React.Component {
changeNum = () => this.setState({ num: 10 })
render() {
return (
<div>
<h1>class comp: {this.state.num}</h1>
<button onClick={this.changeNum}>changeNum</button>
</div>
);
}
}
function FnComp() {
const { state, setState } = useConcent("counter");
const changeNum = () => setState({ num: 20 });
return (
<div>
<h1>fn comp: {state.num}</h1>
<button onClick={changeNum}>changeNum</button>
</div>
);
}
注意到兩種寫法區別很小,除了組件的定義方式不一樣,其實渲染邏輯和數據來源都一模一樣。
渲染它們查看結果
在線示例
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<div>
<ClsComp />
<FnComp />
</div>
</React.StrictMode>,
rootElement
);
對比Recoil,我們發現沒有頂層并沒有Provider或者Root類似的組件包裹,react組件就已接入concent,做到真正的即插即用和無感知接入,同時api保留為與react一致的寫法。
組件調用reducer
concent為每一個組件實例都生成了實例上下文,方便用戶直接通過ctx.mr調用reducer方法
mr 為 moduleReducer的簡寫,直接書寫為ctx.moduleReducer也是合法的
// --------- 對于類組件 -----------
changeNum = () => this.setState({ num: 10 })
// ===> 修改為
changeNum = () => this.ctx.mr.inc(10);// or this.ctx.mr.asynCtx()
// --------- 對于函數組件 -----------
const { state, mr } = useConcent("counter");// useConcent 返回的就是ctx
const changeNum = () => mr.inc(20);// or ctx.mr.asynCtx()
異步計算函數
run接口里支持擴展computed屬性,即讓用戶定義一堆衍生數據的計算函數集合,它們可以是同步的也可以是異步的,同時支持一個函數用另一個函數的輸出作為輸入來做二次計算,計算的輸入依賴是自動收集到的。
const computed = {// 定義計算函數集合
numx10({ num }) {
return num * 10;
},
// n:newState, o:oldState, f:fnCtx
// 結構出num,表示當前計算依賴是num,僅當num發生變化時觸發此函數重計算
async numx10_2({ num }, o, f) {
// 必需調用setInitialVal給numx10_2一個初始值,
// 該函數僅在初次computed觸發時執行一次
f.setInitialVal(num * 55);
await delay();
return num * 100;
},
async numx10_3({ num }, o, f) {
f.setInitialVal(num * 1);
await delay();
// 使用numx10_2再次計算
const ret = num * f.cuVal.numx10_2;
if (ret % 40000 === 0) throw new Error("-->mock error");
return ret;
}
}
// 配置到counter模塊
run({
counter: { state, reducer, computed }
});
上述計算函數里,我們刻意讓numx10_3在某個時候報錯,對于此錯誤,我們可以在run接口的第二位options配置里定義errorHandler來捕捉。
run({/**storeConfig*/}, {
errorHandler: (err)=>{
alert(err.message);
}
})
當然更好的做法,利用concent-plugin-async-computed-status插件來完成對所有模塊計算函數執行狀態的統一管理。
import cuStatusPlugin from "concent-plugin-async-computed-status";
run(
{/**storeConfig*/},
{
errorHandler: err => {
console.error('errorHandler ', err);
// alert(err.message);
},
plugins: [cuStatusPlugin], // 配置異步計算函數執行狀態管理插件
}
);
該插件會自動向concent配置一個cuStatus模塊,方便組件連接到它,消費相關計算函數的執行狀態數據
function Test() {
const { moduleComputed, connectedState, setState, state, ccUniqueKey } = useConcent({
module: "counter",// 屬于counter模塊,狀態直接從state獲得
connect: ["cuStatus"],// 連接到cuStatus模塊,狀態從connectedState.{$moduleName}獲得
});
const changeNum = () => setState({ num: state.num + 1 });
// 獲得counter模塊的計算函數執行狀態
const counterCuStatus = connectedState.cuStatus.counter;
// 當然,可以更細粒度的獲得指定結算函數的執行狀態
// const {['counter/numx10_2']:num1Status, ['counter/numx10_3']: num2Status} = connectedState.cuStatus;
return (
<div>
{state.num}
<br />
{counterCuStatus.done ? moduleComputed.numx10 : 'computing'}
{/** 此處拿到錯誤可以用于渲染,當然也拋出去 */}
{/** 讓ErrorBoundary之類的組件捕捉并渲染降級頁面 */}
{counterCuStatus.err ? counterCuStatus.err.message : ''}
<br />
{moduleComputed.numx10_2}
<br />
{moduleComputed.numx10_3}
<br />
<button onClick={changeNum}>changeNum</button>
</div>
);
}
![]https://raw.githubusercontent...
查看在線示例
更新
開篇我說對Recoli提到的更新保持了懷疑態度,有一些誤導的嫌疑,此處我們將揭開疑團
大家知道hook使用規則是不能寫在條件控制語句里的,這意味著下面語句是不允許的
const NumView = () => {
const [show, setShow] = useState(true);
if(show){// error
const [num, setNum] = useRecoilState(numState);
}
}
所以用戶如果ui渲染里如果某個狀態用不到此數據時,某處改變了num值依然會觸發NumView重渲染,但是concent的實例上下文里取出來的state和moduleComputed是一個Proxy對象,是在實時的收集每一輪渲染所需要的依賴,這才是真正意義上的按需渲染和更新。
const NumView = () => {
const [show, setShow] = useState(true);
const {state} = useConcent('counter');
// show為true時,當前實例的渲染對state.num的渲染有依賴
return {show ? <h1>{state.num}</h1> : 'nothing'}
}
點我查看代碼示例
當然如果用戶對num值有ui渲染完畢后,有發生改變時需要做其他事的需求,類似useEffect的效果,concent也支持用戶將其抽到setup里,定義effect來完成此場景訴求,相比useEffect,setup里的ctx.effect只需定義一次,同時只需傳遞key名稱,concent會自動對比前一刻和當前刻的值來決定是否要觸發副作用函數。
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 <h1>for setup<h1/>
}
更多關于effect與useEffect請查看此文
current mode
關于concent是否支持current mode這個疑問呢,這里先說答案,concent是100%完全支持的,或者進一步說,所有狀態管理工具,最終觸發的都是setState或forceUpdate,我們只要在渲染過程中不要寫具有任何副作用的代碼,讓相同的狀態輸入得到的渲染結果冪,即是在current mode下運行安全的代碼。
current mode只是對我們的代碼提出了更苛刻的要求。
// bad
function Test(){
track.upload('renderTrigger');// 上報渲染觸發事件
return <h1>bad case</h1>
}
// good
function Test(){
useEffect(()=>{
// 就算僅執行了一次setState, current mode下該組件可能會重復渲染,
// 但react內部會保證該副作用只觸發一次
track.upload('renderTrigger');
})
return <h1>bad case</h1>
}
我們首先要理解current mode原理是因為fiber架構模擬出了和整個渲染堆棧(即fiber node上存儲的信息),得以有機會讓react自己以組件為單位調度組件的渲染過程,可以懸停并再次進入渲染,安排優先級高的先渲染,重度渲染的組件會切片為多個時間段反復渲染,而concent的上下文本身是獨立于react存在的(接入concent不需要再頂層包裹任何Provider), 只負責處理業務生成新的數據,然后按需派發給對應的實例(實例的狀態本身是一個個孤島,concent只負責同步建立起了依賴的store的數據),之后就是react自己的調度流程,修改狀態的函數并不會因為組件反復重入而多次執行(這點需要我們遵循不該在渲染過程中書寫包含有副作用的代碼原則),react僅僅是調度組件的渲染時機,而組件的中斷和重入針對也是這個渲染過程。
所以同樣的,對于concent
const setup = (ctx)=>{
ctx.effect(()=>{
// effect是對useEffect的封裝,
// 同樣在current mode下該副作用也只觸發一次(由react保證)
track.upload('renderTrigger');
});
}
// good
function Test2(){
useConcent({setup})
return <h1>good case</h1>
}
同樣的,依賴收集在current mode模式下,重復渲染僅僅是導致觸發了多次收集,只要狀態輸入一樣,渲染結果冪等,收集到的依賴結果也是冪等的。
// 假設這是一個渲染很耗時的組件,在current mode模式下可能會被中斷渲染
function HeavyComp(){
const { state } = useConcent({module:'counter'});// 屬于counter模塊
// 這里讀取了num 和 numBig兩個值,收集到了依賴
// 即當僅當counter模塊的num、numBig的發生變化時,才觸發其重渲染(最終還是調用setState)
// 而counter模塊的其他值發生變化時,不會觸發該實例的setState
return (
<div>num: {state.num} numBig: {state.numBig}</div>
);
}
最后我們可以梳理一下,hook本身是支持把邏輯剝離到用的自定義hook(無ui返回的函數),而其他狀態管理也只是多做了一層工作,引導用戶把邏輯剝離到它們的規則之下,最終還是把業務處理數據交回給react組件調用其setState或forceUpdate觸發重渲染,current mode的引入并不會對現有的狀態管理或者新生的狀態管理方案有任何影響,僅僅是對用戶的ui代碼提出了更高的要求,以免因為current mode引發難以排除的bug
為此react還特別提供了React.Strict組件來故意觸發雙調用機制, https://reactjs.org/docs/stri... 以引導用戶書寫更符合規范的react代碼,以便適配將來提供的current mode。
react所有新特性其實都是被fiber激活了,有了fiber架構,衍生出了hook、time slicing、suspense以及將來的Concurrent Mode,class組件和function組件都可以在Concurrent Mode下安全工作,只要遵循規范即可。
摘取自: https://reactjs.org/docs/stri...
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
所以呢,React.Strict其實為了引導用戶寫能夠在Concurrent Mode里運行的代碼而提供的輔助api,先讓用戶慢慢習慣這些限制,循序漸進一步一步來,最后再推出Concurrent Mode。
結語
Recoil推崇狀態和派生數據更細粒度控制,寫法上demo看起來簡單,實際上代碼規模大之后依然很繁瑣。
// 定義狀態
const numState = atom({key:'num', default:0});
const numBigState = atom({key:'numBig', default:100});
// 定義衍生數據
const numx2Val = selector({
key: "numx2",
get: ({ get }) => get(numState) * 2,
});
const numBigx2Val = selector({
key: "numBigx2",
get: ({ get }) => get(numBigState) * 2,
});
const numSumBigVal = selector({
key: "numSumBig",
get: ({ get }) => get(numState) + get(numBigState),
});
// ---> ui處消費狀態或衍生數據
const [num] = useRecoilState(numState);
const [numBig] = useRecoilState(numBigState);
const numx2 = useRecoilValue(numx2Val);
const numBigx2 = useRecoilValue(numBigx2Val);
const numSumBig = useRecoilValue(numSumBigVal);
Concent遵循redux單一狀態樹的本質,推崇模塊化管理數據以及派生數據,同時依靠Proxy能力完成了運行時依賴收集和追求不可變的完美整合。
run({
counter: {// 聲明一個counter模塊
state: { num: 1, numBig: 100 }, // 定義狀態
computed:{// 定義計算,參數列表里解構具體的狀態時確定了依賴
numx2: ({num})=> num * 2,
numBigx2: ({numBig})=> numBig * 2,
numSumBig: ({num, numBig})=> num + numBig,
}
},
});
// ---> ui處消費狀態或衍生數據,在ui處結構了才產生依賴
const { state, moduleComputed, setState } = useConcent('counter')
const { numx2, numBigx2, numSumBig} = moduleComputed;
const { num, numBig } = state;
所以你將獲得:
運行時的依賴收集 ,同時也遵循react不可變的原則
一切皆函數(state, reducer, computed, watch, event...),能獲得更友好的ts支持
支持中間件和插件機制,很容易兼容redux生態
同時支持集中與分形模塊配置,同步與異步模塊加載,對大型工程的彈性重構過程更加友好
藍藍設計( www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務
藍藍設計的小編 http://www.syprn.cn