首頁

JavaScript中的map()和forEach()有什么區(qū)別?

seo達人

閱讀之前

基本上,在JavaScript中遍歷對象取決于對象是否可迭代。默認情況下,數(shù)組是可迭代的。map 和 forEach 包含在Array.prototype 中,因此我們無需考慮可迭代性。如果你想進一步學習,我推薦你看看什么是JavaScript中的可迭代對象!


什么是map()和forEach()?

map 和 forEach 是數(shù)組中的幫助器方法,可以輕松地在數(shù)組上循環(huán)。我們曾經(jīng)像下面這樣循環(huán)遍歷一個數(shù)組,沒有任何輔助函數(shù)。


var array = ['1', '2', '3'];

for (var i = 0; i < array.length; i += 1) {

 console.log(Number(array[i]));

}

// 1

// 2

// 3

自JavaScript時代開始以來,就一直存在 for 循環(huán)。它包含3個表達式:初始值,條件和最終表達式。


這是循環(huán)數(shù)組的經(jīng)典方法。從ECMAScript 5開始,新功能似乎使我們更加快樂。


map

map 的作用與 for 循環(huán)完全相同,只是 map 會創(chuàng)建一個新數(shù)組,其結(jié)果是在調(diào)用數(shù)組中的每個元素上調(diào)用提供的函數(shù)。


它需要兩個參數(shù):一個是稍后在調(diào)用 map 或 forEach 時調(diào)用的回調(diào)函數(shù),另一個是回調(diào)函數(shù)被調(diào)用時使用的名為 thisArg 的上下文變量。


const arr = ['1', '2', '3'];

// 回調(diào)函數(shù)接受3個參數(shù)

// 數(shù)組的當前值作為第一個參數(shù)

// 當前值在數(shù)組中的位置作為第二個參數(shù)

// 原始源數(shù)組作為第三個參數(shù)

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

回調(diào)函數(shù)可以如下使用。


arr.map((str) => { console.log(Number(str)); })

map 的結(jié)果不等于原始數(shù)組。


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。但是箭頭回調(diào)函數(shù)無法將 obj 作為其 thisArg。


這是因為箭頭函數(shù)與正常函數(shù)不同。


forEach

forEach 是數(shù)組的另一個循環(huán)函數(shù),但 map 和 forEach 在使用中有所不同。map 和 forEach 可以使用兩個參數(shù)——回調(diào)函數(shù)和 thisArg,它們用作其 this。


const arr = ['1', '2', '3'];

// 回調(diào)函數(shù)接受3個參數(shù)

// 數(shù)組的當前值作為第一個參數(shù)

// 當前值在數(shù)組中的位置作為第二個參數(shù)

// 原始源數(shù)組作為第三個參數(shù)

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 返回其原始數(shù)組的新數(shù)組,但是 forEach 卻沒有。但是它們都確保了原始對象的不變性。


[1,2,3].map(d => d + 1); // [2, 3, 4];

[1,2,3].forEach(d => d + 1); // undefined;

如果更改數(shù)組內(nèi)的值,forEach 不能確保數(shù)組的不變性。這個方法只有在你不接觸里面的任何值時,才能保證不變性。


[{a: 1, b: 2}, {a: 10, b: 20}].forEach((obj) => obj.a += 1);

// [{a: 2, b: 2}, {a: 11, b: 21}]

// 數(shù)組已更改!

何時使用map()和forEach()?

由于它們之間的主要區(qū)別在于是否有返回值,所以你會希望使用 map 來制作一個新的數(shù)組,而使用 forEach 只是為了映射到數(shù)組上。


這是一個簡單的例子。


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 在對原數(shù)組的數(shù)據(jù)進行操作后,會創(chuàng)建并返回一個新的數(shù)組。


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

另一方面,當你想對數(shù)據(jù)進行某些操作而不創(chuàng)建新數(shù)組時,forEach 很有用。順便說一句,可以使用 filter 重構(gòu)示例。


subjects.filter(subject => mySubjectId.includes(subject.id)).length;

綜上所述,我建議你在創(chuàng)建一個新的數(shù)組時使用map,當你不需要制作一個新的數(shù)組,而是要對數(shù)據(jù)做一些事情時,就使用forEach。


速度比較

有些帖子提到 map 比 forEach 快。所以,我很好奇這是不是真的。我找到了這個對比結(jié)果。






該代碼看起來非常相似,但結(jié)果卻相反。有些測試說 forEach 更快,有些說 map 更快。也許你在告訴自己 map/forEach 比其他的快,你可能是對的。老實說,我不確定。我認為在現(xiàn)代Web開發(fā)中,可讀性比 map 和 forEach 之間的速度重要得多。


但可以肯定的是——兩者都比JavaScript內(nèi)置的 for 循環(huán)慢。

藍藍設計www.yvirxh.cn )是一家專注而深入的界面設計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網(wǎng)站建設 、平面設計服務




細聊Concent & Recoil , 探索react數(shù)據(jù)流的新開發(fā)模式

seo達人

序言

之前發(fā)表了一篇文章 redux、mobx、concent特性大比拼, 看后生如何對局前輩,吸引了不少感興趣的小伙伴入群開始了解和使用 concent,并獲得了很多正向的反饋,實實在在的幫助他們提高了開發(fā)體驗,群里人數(shù)雖然還很少,但大家熱情高漲,技術討論氛圍濃厚,對很多新鮮技術都有保持一定的敏感度,如上個月開始逐漸被提及得越來越多的出自facebook的狀態(tài)管理方案 recoil,雖然還處于實驗狀態(tài),但是相必大家已經(jīng)私底下開始欲欲躍試了,畢竟出生名門,有fb背書,一定會大放異彩。


不過當我體驗完recoil后,我對其中標榜的更新保持了懷疑態(tài)度,有一些誤導的嫌疑,這一點下文會單獨分析,是否屬于誤導讀者在讀完本文后自然可以得出結(jié)論,總之本文主要是分析Concent與Recoil的代碼風格差異性,并探討它們對我們將來的開發(fā)模式有何新的影響,以及思維上需要做什么樣的轉(zhuǎn)變。


數(shù)據(jù)流方案之3大流派

目前主流的數(shù)據(jù)流方案按形態(tài)都可以劃分以下這三類


redux流派

redux、和基于redux衍生的其他作品,以及類似redux思路的作品,代表作有dva、rematch等等。


mobx流派

借助definePerperty和Proxy完成數(shù)據(jù)劫持,從而達到響應式編程目的的代表,類mobx的作品也有不少,如dob等。


Context流派

這里的Context指的是react自帶的Context api,基于Context api打造的數(shù)據(jù)流方案通常主打輕量、易用、概覽少,代表作品有unstated、constate等,大多數(shù)作品的核心代碼可能不超過500行。


到此我們看看Recoil應該屬于哪一類?很顯然按其特征屬于Context流派,那么我們上面說的主打輕量對

Recoil并不適用了,打開其源碼庫發(fā)現(xiàn)代碼并不是幾百行完事的,所以基于Context api做得好用且強大就未必輕量,由此看出facebook對Recoil是有野心并給予厚望的。


我們同時也看看Concent屬于哪一類呢?Concent在v2版本之后,重構(gòu)數(shù)據(jù)追蹤機制,啟用了defineProperty和Proxy特性,得以讓react應用既保留了不可變的追求,又享受到了運行時依賴收集和ui更新的性能提升福利,既然啟用了defineProperty和Proxy,那么看起來Concent應該屬于mobx流派?


事實上Concent屬于一種全新的流派,不依賴react的Context api,不破壞react組件本身的形態(tài),保持追求不可變的哲學,僅在react自身的渲染調(diào)度機制之上建立一層邏輯層狀態(tài)分發(fā)調(diào)度機制,defineProperty和Proxy只是用于輔助收集實例和衍生數(shù)據(jù)對模塊數(shù)據(jù)的依賴,而修改數(shù)據(jù)入口還是setState(或基于setState封裝的dispatch, invoke, sync),讓Concent可以0入侵的接入react應用,真正的即插即用和無感知接入。


即插即用的核心原理是,Concent自建了一個平行于react運行時的全局上下文,精心維護這模塊與實例之間的歸屬關系,同時接管了組件實例的更新入口setState,保留原始的setState為reactSetState,所有當用戶調(diào)用setState時,concent除了調(diào)用reactSetState更新當前實例ui,同時智能判斷提交的狀態(tài)是否也還有別的實例關心其變化,然后一并拿出來依次執(zhí)行這些實例的reactSetState,進而達到了狀態(tài)全部同步的目的。




Recoil初體驗

我們以常用的counter來舉例,熟悉一下Recoil暴露的四個高頻使用的api


atom,定義狀態(tài)

selector, 定義派生數(shù)據(jù)

useRecoilState,消費狀態(tài)

useRecoilValue,消費派生數(shù)據(jù)

定義狀態(tài)

外部使用atom接口,定義一個key為num,初始值為0的狀態(tài)


const numState = atom({

 key: "num",

 default: 0

});

定義派生數(shù)據(jù)

外部使用selector接口,定義一個key為numx10,初始值是依賴numState再次計算而得到


const numx10Val = selector({

 key: "numx10",

 get: ({ get }) => {

   const num = get(numState);

   return num * 10;

 }

});

定義異步的派生數(shù)據(jù)

selector的get支持定義異步函數(shù)


需要注意的點是,如果有依賴,必需先書寫好依賴在開始執(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;

 }

});

消費狀態(tài)

組件里使用useRecoilState接口,傳入想要獲去的狀態(tài)(由atom創(chuàng)建而得)


const NumView = () => {

 const [num, setNum] = useRecoilState(numState);


 const add = ()=>setNum(num+1);


 return (

   <div>

     {num}<br/>

     <button onClick={add}>add</button>

   </div>

 );

}

消費派生數(shù)據(jù)

組件里使用useRecoilValue接口,傳入想要獲去的派生數(shù)據(jù)(由selector創(chuàng)建而得),同步派生數(shù)據(jù)和異步派生數(shù)據(jù),皆可通過此接口獲得


const NumValView = () => {

 const numx10 = useRecoilValue(numx10Val);

 const asyncNumx10 = useRecoilValue(asyncNumx10Val);


 return (

   <div>

     numx10 :{numx10}<br/>

   </div>

 );

};

渲染它們查看結(jié)果

暴露定義好的這兩個組件, 查看在線示例


export default ()=>{

 return (

   <>

     <NumView />

     <NumValView />

   </>

 );

};

頂層節(jié)點包裹React.Suspense和RecoilRoot,前者用于配合異步計算函數(shù)需要,后者用于注入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文檔(還在持續(xù)建設中...),可能部分人會認為api太多,難于記住,其實大部分都是可選的語法糖,我們以counter為例,只需要使用到以下兩個api即可


run,定義模塊狀態(tài)(必需)、模塊計算(可選)、模塊觀察(可選)

運行run接口后,會生成一份concent全局上下文

setState,修改狀態(tài)

定義狀態(tài)&修改狀態(tài)

以下示例我們先脫離ui,直接完成定義狀態(tài)&修改狀態(tài)的目的


import { run, setState, getState } from "concent";


run({

 counter: {// 聲明一個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很類似,需要定義一個單一的狀態(tài)樹,同時第一層key就引導用戶將數(shù)據(jù)模塊化管理起來.


引入reducer

上述示例中我們直接掉一個呢setState修改數(shù)據(jù),但是真實的情況是數(shù)據(jù)落地前有很多同步的或者異步的業(yè)務邏輯操作,所以我們對模塊填在reducer定義,用來聲明修改數(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來觸發(fā)修改狀態(tài)的方法


因dispatch會返回一個Promise,所以我們需要用一個async 包裹起來執(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)用時基于字符串匹配方式,之所以保留這樣的調(diào)用方式是為了照顧需要動態(tài)調(diào)用的場景,其實更推薦的寫法是


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),那么接下來我們需要用到以下兩個api來幫助react組件生成實例上下文(等同于與vue 3 setup里提到的渲染上下文),以及獲得消費concent模塊數(shù)據(jù)的能力


register, 注冊類組件為concent組件

useConcent, 注冊函數(shù)組件為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>

 );

}

注意到兩種寫法區(qū)別很小,除了組件的定義方式不一樣,其實渲染邏輯和數(shù)據(jù)來源都一模一樣。


渲染它們查看結(jié)果

在線示例


const rootElement = document.getElementById("root");

ReactDOM.render(

 <React.StrictMode>

   <div>

     <ClsComp />

     <FnComp />

   </div>

 </React.StrictMode>,

 rootElement

);



對比Recoil,我們發(fā)現(xiàn)沒有頂層并沒有Provider或者Root類似的組件包裹,react組件就已接入concent,做到真正的即插即用和無感知接入,同時api保留為與react一致的寫法。


組件調(diào)用reducer

concent為每一個組件實例都生成了實例上下文,方便用戶直接通過ctx.mr調(diào)用reducer方法


mr 為 moduleReducer的簡寫,直接書寫為ctx.moduleReducer也是合法的

//  --------- 對于類組件 -----------

changeNum = () => this.setState({ num: 10 })

// ===> 修改為

changeNum = () => this.ctx.mr.inc(10);// or this.ctx.mr.asynCtx()


//  --------- 對于函數(shù)組件 -----------

const { state, mr } = useConcent("counter");// useConcent 返回的就是ctx

const changeNum = () => mr.inc(20);// or ctx.mr.asynCtx()

異步計算函數(shù)

run接口里支持擴展computed屬性,即讓用戶定義一堆衍生數(shù)據(jù)的計算函數(shù)集合,它們可以是同步的也可以是異步的,同時支持一個函數(shù)用另一個函數(shù)的輸出作為輸入來做二次計算,計算的輸入依賴是自動收集到的。


const computed = {// 定義計算函數(shù)集合

 numx10({ num }) {

   return num * 10;

 },

 // n:newState, o:oldState, f:fnCtx

 // 結(jié)構(gòu)出num,表示當前計算依賴是num,僅當num發(fā)生變化時觸發(fā)此函數(shù)重計算

 async numx10_2({ num }, o, f) {

   // 必需調(diào)用setInitialVal給numx10_2一個初始值,

   // 該函數(shù)僅在初次computed觸發(fā)時執(zhí)行一次

   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 }

});

上述計算函數(shù)里,我們刻意讓numx10_3在某個時候報錯,對于此錯誤,我們可以在run接口的第二位options配置里定義errorHandler來捕捉。


run({/**storeConfig*/}, {

   errorHandler: (err)=>{

       alert(err.message);

   }

})

當然更好的做法,利用concent-plugin-async-computed-status插件來完成對所有模塊計算函數(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], // 配置異步計算函數(shù)執(zhí)行狀態(tài)管理插件

 }

);

該插件會自動向concent配置一個cuStatus模塊,方便組件連接到它,消費相關計算函數(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模塊的計算函數(shù)執(zhí)行狀態(tài)

 const counterCuStatus = connectedState.cuStatus.counter;

 // 當然,可以更細粒度的獲得指定結(jié)算函數(shù)的執(zhí)行狀態(tài)

 // 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提到的更新保持了懷疑態(tài)度,有一些誤導的嫌疑,此處我們將揭開疑團


大家知道hook使用規(guī)則是不能寫在條件控制語句里的,這意味著下面語句是不允許的


const NumView = () => {

 const [show, setShow] = useState(true);

 if(show){// error

   const [num, setNum] = useRecoilState(numState);

 }

}

所以用戶如果ui渲染里如果某個狀態(tài)用不到此數(shù)據(jù)時,某處改變了num值依然會觸發(fā)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渲染完畢后,有發(fā)生改變時需要做其他事的需求,類似useEffect的效果,concent也支持用戶將其抽到setup里,定義effect來完成此場景訴求,相比useEffect,setup里的ctx.effect只需定義一次,同時只需傳遞key名稱,concent會自動對比前一刻和當前刻的值來決定是否要觸發(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 <h1>for setup<h1/>

}

更多關于effect與useEffect請查看此文


current mode

關于concent是否支持current mode這個疑問呢,這里先說答案,concent是100%完全支持的,或者進一步說,所有狀態(tài)管理工具,最終觸發(fā)的都是setState或forceUpdate,我們只要在渲染過程中不要寫具有任何副作用的代碼,讓相同的狀態(tài)輸入得到的渲染結(jié)果冪,即是在current mode下運行安全的代碼。


current mode只是對我們的代碼提出了更苛刻的要求。


// bad

function Test(){

  track.upload('renderTrigger');// 上報渲染觸發(fā)事件

  return <h1>bad case</h1>

}


// good

function Test(){

  useEffect(()=>{

     // 就算僅執(zhí)行了一次setState, current mode下該組件可能會重復渲染,

     // 但react內(nèi)部會保證該副作用只觸發(fā)一次

     track.upload('renderTrigger');

  })

  return <h1>bad case</h1>

}

我們首先要理解current mode原理是因為fiber架構(gòu)模擬出了和整個渲染堆棧(即fiber node上存儲的信息),得以有機會讓react自己以組件為單位調(diào)度組件的渲染過程,可以懸停并再次進入渲染,安排優(yōu)先級高的先渲染,重度渲染的組件會切片為多個時間段反復渲染,而concent的上下文本身是獨立于react存在的(接入concent不需要再頂層包裹任何Provider), 只負責處理業(yè)務生成新的數(shù)據(jù),然后按需派發(fā)給對應的實例(實例的狀態(tài)本身是一個個孤島,concent只負責同步建立起了依賴的store的數(shù)據(jù)),之后就是react自己的調(diào)度流程,修改狀態(tài)的函數(shù)并不會因為組件反復重入而多次執(zhí)行(這點需要我們遵循不該在渲染過程中書寫包含有副作用的代碼原則),react僅僅是調(diào)度組件的渲染時機,而組件的中斷和重入針對也是這個渲染過程。


所以同樣的,對于concent


const setup = (ctx)=>{

 ctx.effect(()=>{

    // effect是對useEffect的封裝,

    // 同樣在current mode下該副作用也只觸發(fā)一次(由react保證)

     track.upload('renderTrigger');

 });

}


// good

function Test2(){

  useConcent({setup})

  return <h1>good case</h1>

}

同樣的,依賴收集在current mode模式下,重復渲染僅僅是導致觸發(fā)了多次收集,只要狀態(tài)輸入一樣,渲染結(jié)果冪等,收集到的依賴結(jié)果也是冪等的。


// 假設這是一個渲染很耗時的組件,在current mode模式下可能會被中斷渲染

function HeavyComp(){

 const { state } = useConcent({module:'counter'});// 屬于counter模塊


// 這里讀取了num 和 numBig兩個值,收集到了依賴

// 即當僅當counter模塊的num、numBig的發(fā)生變化時,才觸發(fā)其重渲染(最終還是調(diào)用setState)

// 而counter模塊的其他值發(fā)生變化時,不會觸發(fā)該實例的setState

 return (

   <div>num: {state.num} numBig: {state.numBig}</div>

 );

}

最后我們可以梳理一下,hook本身是支持把邏輯剝離到用的自定義hook(無ui返回的函數(shù)),而其他狀態(tài)管理也只是多做了一層工作,引導用戶把邏輯剝離到它們的規(guī)則之下,最終還是把業(yè)務處理數(shù)據(jù)交回給react組件調(diào)用其setState或forceUpdate觸發(fā)重渲染,current mode的引入并不會對現(xiàn)有的狀態(tài)管理或者新生的狀態(tài)管理方案有任何影響,僅僅是對用戶的ui代碼提出了更高的要求,以免因為current mode引發(fā)難以排除的bug


為此react還特別提供了React.Strict組件來故意觸發(fā)雙調(diào)用機制, https://reactjs.org/docs/stri... 以引導用戶書寫更符合規(guī)范的react代碼,以便適配將來提供的current mode。

react所有新特性其實都是被fiber激活了,有了fiber架構(gòu),衍生出了hook、time slicing、suspense以及將來的Concurrent Mode,class組件和function組件都可以在Concurrent Mode下安全工作,只要遵循規(guī)范即可。


摘取自: 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。


結(jié)語

Recoil推崇狀態(tài)和派生數(shù)據(jù)更細粒度控制,寫法上demo看起來簡單,實際上代碼規(guī)模大之后依然很繁瑣。


// 定義狀態(tài)

const numState = atom({key:'num', default:0});

const numBigState = atom({key:'numBig', default:100});

// 定義衍生數(shù)據(jù)

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處消費狀態(tài)或衍生數(shù)據(jù)

const [num] = useRecoilState(numState);

const [numBig] = useRecoilState(numBigState);

const numx2 = useRecoilValue(numx2Val);

const numBigx2 = useRecoilValue(numBigx2Val);

const numSumBig = useRecoilValue(numSumBigVal);

Concent遵循redux單一狀態(tài)樹的本質(zhì),推崇模塊化管理數(shù)據(jù)以及派生數(shù)據(jù),同時依靠Proxy能力完成了運行時依賴收集和追求不可變的完美整合。


run({

 counter: {// 聲明一個counter模塊

   state: { num: 1, numBig: 100 }, // 定義狀態(tài)

   computed:{// 定義計算,參數(shù)列表里解構(gòu)具體的狀態(tài)時確定了依賴

      numx2: ({num})=> num * 2,

      numBigx2: ({numBig})=> numBig * 2,

      numSumBig: ({num, numBig})=> num + numBig,

    }

 },

});


// ---> ui處消費狀態(tài)或衍生數(shù)據(jù),在ui處結(jié)構(gòu)了才產(chǎn)生依賴

const { state, moduleComputed, setState } = useConcent('counter')

const { numx2, numBigx2, numSumBig} = moduleComputed;

const { num, numBig } = state;

所以你將獲得:


運行時的依賴收集 ,同時也遵循react不可變的原則

一切皆函數(shù)(state, reducer, computed, watch, event...),能獲得更友好的ts支持

支持中間件和插件機制,很容易兼容redux生態(tài)

同時支持集中與分形模塊配置,同步與異步模塊加載,對大型工程的彈性重構(gòu)過程更加友好


藍藍設計www.yvirxh.cn )是一家專注而深入的界面設計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網(wǎng)站建設 、平面設計服務



大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

資深UI設計者

近年來暗黑模式的設計趨勢開始一點點明顯,Ant Design 在這次 4.0 的升級中也對這類暗黑場景化的設計開始進行初步的探索,接下來就讓我們一起來看下 Ant Design 這一針對企業(yè)級的設計體系是如何設計暗黑模式的。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

什么是暗黑模式

暗黑模式是指把所有 UI 換成黑色或者深色的一個模式。

需要說明的是,暗黑模式不只夜間模式:

暗黑模式更多的目的是希望使用者更加專注自己的操作任務,所以對于信息內(nèi)容的表達會更注重視覺性;

而夜間模式則更多是出于在夜間或暗光環(huán)境使用下的健康角度考慮,避免在黑暗環(huán)境中長時間注視高亮光源帶來的視覺刺激,所以在保證可讀性的前提下,會采用更弱的對比來減少屏幕光對眼睛的刺激。

同時,從使用場景上來說,暗黑模式既可以在黑暗環(huán)境,也可以在亮光環(huán)境下使用,可以理解為是對淺色主題的一種場景化補充,而夜間模式只建議在黑暗環(huán)境下使用,在亮光環(huán)境使用時很可能并不保證信息可讀性。

為什么 Ant Design 要做暗黑模式

1. 更加專注內(nèi)容

試想一下,我們在電影院看電影時,為什么要全場關燈?甚至有些 APP,在影片的下方也會又一個模擬關燈效果的操作,來讓整個手機屏幕變黑,只剩下視屏畫面的部分,這都幫助我們可以更專注、更沉浸在當前的內(nèi)容下。

色彩具有層級關系,深色會在視覺感官上自動后退,淺色部分則會向前延展,這樣對比強烈的層次關系可以讓用戶更注重被凸顯出來的內(nèi)容和交互操作;尤其在信息負責界面內(nèi)層級關系的合理拉開對操作效率都有明顯的促進作用。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

2. 在暗光環(huán)境下更加適用

如今社會我們身處黑夜使用手機、電腦、iPad等設備的次數(shù)越來越多,環(huán)境光與屏幕亮度的明暗差距在夜間會被放大 ,亮度對比帶來視覺刺激也更加明顯,使用暗色模式可以縮小屏幕顯示內(nèi)容與環(huán)境光強度的差距,同時也可以為設備的續(xù)航帶來積極影響,可以保證使用者在暗光環(huán)境下使用 OLED 設備的舒適度。

3. 大眾喜愛

黑色一直以來就可以給人以高級、神秘的語義象征,相比于淺色模式,暗色模式藏著更多可能性。

設計原則

在這次暗黑模式的設計中主要遵循以下兩大設計原則

1. 內(nèi)容的舒適性

不論是顏色還是文字或是組件本身,在暗色環(huán)境下的使用感受應當是舒適的,而不是十分費力的,如果一個顏色在淺色下使用正常,在暗色下卻亮的刺眼或根本看不見,那必然不夠舒適也不可讀;所以在顏色的處理上不建議直接使用,這樣會讓界面變得到處都是「亮點」,讓眼睛不適的同時,也會帶來許多誤操作。

2. 信息的一致性

暗黑模式下的信息內(nèi)容需要和淺色模式保持一致性,不應該打破原有的層級關系。舉個例子,在淺色模式下越深的顏色,與界面背景色對比度越大,也就越容易被人注意,視覺層級越高,比如 tooltip;在暗黑模式下我們同樣需要遵循這一規(guī)律,所以對應所使用的顏色也就越淺,反之則會越深。

如何設計

1. 界面層級

在大量的企業(yè)級產(chǎn)品界面中,我們通常用只用一個白色背景結(jié)合分割線就可以搞定所有界面的板塊層級,因為在淺色模式下有投影可以借助,然而暗黑模式中投影將不足以起到如此功效,我們需要通過顏色來區(qū)分層級關系。

在經(jīng)過對螞蟻企業(yè)級頁面的典型布局結(jié)構(gòu)評估后,我們在中性色中增加了三個梯度,將中性色擴展至 13 個

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

并定義出通用情況下頁面中的框架層次,主要分為三大塊:

  • 應用框架:也就是我們平時定義的導航欄,也是在大結(jié)構(gòu)中最上層的一部分
  • 內(nèi)容組件:指頁面中的具體內(nèi)容,通常情況下以區(qū)塊的形式存在,作為第二層級
  • 頁面容器:顧名思義,指頁面級別的容器盒子,可容納頁面中的所有內(nèi)容,可以理解為是一個背景板,也就是最末層

在目前的暗黑體系下,我們分別為這三大塊從淺到深定義了#1F1F1F、#141414、#000000 三個顏色,在實際應用中,你也可以根據(jù)自身業(yè)務的需求,從中性色板中直接選用或是依據(jù)透明度的思路自定義出合適的中性色彩。當定義出較為明確的框架層次和顏色后,也對后續(xù)系統(tǒng)中組件的顏色配置有著重要的指導意義。我們需要考慮組件出現(xiàn)在不同顏色背景下的可能性及其表現(xiàn),盡量保持一致性。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

2. 色彩

眾所周知,暗黑模式與淺色模式最大的不同就在色彩的處理上,在暗黑模式中,我們并不想打破淺色模式下基礎色板的配置規(guī)律及色值,當一個應用或站點深淺模式并存時,希望在色彩上有一定延續(xù)和關聯(lián),而不是毫不相關的兩套色板,這樣一是避免開發(fā)及后續(xù)的維護成本,二是實際切換和使用時,可以保證一致性,這意味著需要借助一定規(guī)則。

這里分享一下我們的處理思路:

基于 Ant Design 自然的設計價值觀,我們先從自然界中尋找靈感,如果說淺色模式如同初升時的朝陽,那暗黑模式就是落日下的晚霞,各有各的韻味,同一片天,唯一不同的是,受光線亮度的影響,晚霞整體會暗一些。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

所以我們大體的設計思路也是基于淺色的基礎色板,通過結(jié)合透明度的轉(zhuǎn)換,去得到一套暗黑模式下的色彩。這樣的好處是,深淺模式下的色彩根基是同一個,在這樣的基礎上經(jīng)過透明度的變換得到的結(jié)果也會相對和諧,同時也符合我們一致性的原則。

這里我們借助下面這兩個概念對透明度進行轉(zhuǎn)換:

對比度極性

對比度極性分為正極性和負極性。

  • 對比度正極性:指在電子文本中文本為深色,背景色為淺色
  • 對比度負極性:指在電子文本中文本為淺色,背景色為深色

這里可以給大家分享對比度查閱的一個工具:WebAIM,只要輸入色值就可以看到具體的值,十分方便。

正負極性差值

顧名思義便是正負兩者的差值,這里取的是絕對值。

根據(jù)一致性原則,我們嘗試通過對比一套顏色的正負極性變化趨勢來找到轉(zhuǎn)換規(guī)律。

首先可以看下,如果一個顏色在不做任何修改的前提下直接使用,它的正負極性趨勢以及差值趨勢的走勢和關系是怎么樣的,我們嘗試描繪出這樣的曲線,他們的變化規(guī)律也將作為我們規(guī)則轉(zhuǎn)換的參考標準。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

經(jīng)過對比,可以發(fā)現(xiàn)一些變化規(guī)律:

首先來說下「差值趨勢」,橫向?qū)Ρ瓤梢园l(fā)現(xiàn),不同顏色的正負極性走勢是很不一樣的,可以看到在黃綠色段差值曲線達到一個變化峰值,這是由于黃綠色本身由于明度、飽和度值相比其他顏色偏高,所以總是有種刺眼的感覺,生活中也會用它來作為警示、提醒的作用,所以在深淺背景下的對比度有一個比較大的差異,可以說這個變化是正常的。

這點也可以從「正負對比度極性趨勢」兩者間的相對關系反應出來,從紅色到洋紅,綠線一開始從逐漸在藍線的上方一點,開始逐漸上移,最后在峰值處開始慢慢下移,在「極客藍」這個色相中接近重疊,在洋紅處又慢慢下移,說明淺色下越深的顏色,在深色中越亮,反之則越暗。

縱向比對單個色板,可以發(fā)現(xiàn),其斜率變化主要受顏色的明度、飽和度影響,可以反應一個顏色的不同梯度在黑白背景下的變化規(guī)律。

有了這個大的變化規(guī)律,我們便可做到心中有數(shù)。首先以 Ant Design 的品牌色「破曉藍」為例,經(jīng)過在多個業(yè)務、場景中不斷嘗試與調(diào)整,我們得到一個透明度轉(zhuǎn)換規(guī)則:

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

并將這個規(guī)則應用在其他 11 套色板中,驗證其可用性。這個過程確實沒有什么快捷通道,唯一的辦法就是不斷嘗試。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

最后,我們將經(jīng)過規(guī)則轉(zhuǎn)換的實色與常規(guī)顏色的變化趨勢做對比:

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

可以看到在大趨勢走向上左右兩側(cè)圖基本一致,這代表兩個色板在變化規(guī)律接近一致,基本可以證明規(guī)則的合理性。區(qū)別在于,對比度負極性和差值相對于右側(cè)未經(jīng)處理的值明顯有所下降。這是由于透明度的處理讓暗色下的顏色在明度、飽和度上有所下降導致。

再仔細觀察可以發(fā)現(xiàn),在左側(cè)的常規(guī)顏色中,從破曉藍-洋紅這段偏冷色系幾個顏色中,差值趨勢變化最平緩的是「極客藍」這顏色,這說明該顏色在深淺背景下的表現(xiàn)較為穩(wěn)定,起伏不大,當基于一個統(tǒng)一的透明度規(guī)則前提下,會讓它在暗色下的對比度減弱,所以反而會導致差值趨勢變大,所以我們會發(fā)現(xiàn)差值趨勢變化較小的顏色轉(zhuǎn)移到了「破曉藍」、「洋紅」中,也是一個正?,F(xiàn)象。

最后可以看到顏色在調(diào)整過后實際應用的效果

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

在官網(wǎng)中點擊「切換」即可預覽:

如果上述 12 個色板不滿足你的業(yè)務需求,你也可以在官網(wǎng)上自己選擇顏色,我們會根據(jù)規(guī)則幫你生成一個暗色色板。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

另外,如果在實際應用過程中,你選了色相在 225~325 間的深冷色系作為主色或強調(diào)色使用,建議適當提高透明度的值,避免在暗色界面上引起閱讀障礙。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

3. 文字

暗黑模式中,文字的使用與淺色模式基本一致,依舊從 85%-65%-45%,唯一不同的地方在 disable 的狀態(tài),其透明度值提升為 30%,避免顏色過淡真的「不可見」。另外,對于 #FFFFFF 的純白色文字,盡量避免大面積使用,尤其對于表格、列表這類偏閱讀瀏覽的場景,如有需要,做小范圍強調(diào)即可。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

4. 陰影

暗黑模式中的陰影規(guī)則與淺色模式基本保持一致,但由于本身的界面背景較深,在陰影色值上也有所加深,幫助層次更好的體現(xiàn),整體將色值擴大到原先的 4 倍,但在陰影的位移、擴展上均保持不變。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

5. 分割線

分割線在暗黑模式中建議根據(jù)界面中常用的背景色,通過透明度換算成實色使用,Ant Design 中分割線主要有 #434343 和 #303030 兩種顏色,分別對應淺色模式下的 #D9D9D 和 #F0F0F0,這樣做的目的主要是為了避免帶來額外的交錯疊加,尤其對于表格類以及很多帶有 border 屬性的組件,實色會更為穩(wěn)妥便于記憶。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

適應性

適應性方面,Ant Design 的暗黑模式可以與海兔及可視化資產(chǎn)進行無縫銜接,使用時可以任意組合拖拽,你可以下載 Kitchen 插件,獲取海量資產(chǎn)。

大廠如何做好暗黑模式設計?來看 Ant Design 的規(guī)范文檔

結(jié)語

暗黑模式最近越來越受到人們的關注,與某一特定產(chǎn)品的暗黑設計不同,Ant Design 的暗黑模式更多以設計體系的角度考慮企業(yè)級這個大場景下的內(nèi)容,在易用性、擴展度、復用度等方面還有許多需要完善和繼續(xù)研究探索的地方,我們會在未來的迭代中逐步進行,先完成再完善。希望上述內(nèi)容能對大家在暗黑模式的設計上有所幫助。

文章來源:優(yōu)設    作者:AlibabaDesign

藍藍設計www.yvirxh.cn )是一家專注而深入的界面設計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網(wǎng)站建設 、平面設計服務

如何使JavaScript休眠或等待

seo達人

JavaScript不具有 sleep() 函數(shù),該函數(shù)會導致代碼在恢復執(zhí)行之前等待指定的時間段。如果需要JavaScript等待,該怎么做呢?


假設您想將三則消息記錄到Javascript控制臺,每條消息之間要延遲一秒鐘。JavaScript中沒有 sleep() 方法,所以你可以嘗試使用下一個最好的方法 setTimeout()。


不幸的是,setTimeout() 不能像你期望的那樣正常工作,這取決于你如何使用它。你可能已經(jīng)在JavaScript循環(huán)中的某個點上試過了,看到 setTimeout() 似乎根本不起作用。


問題的產(chǎn)生是由于將 setTimeout() 誤解為 sleep() 函數(shù),而實際上它是按照自己的一套規(guī)則工作的。


在本文中,我將解釋如何使用 setTimeout(),包括如何使用它來制作一個睡眠函數(shù),使JavaScript暫停執(zhí)行并在連續(xù)的代碼行之間等待。


瀏覽一下 setTimeout() 的文檔,它似乎需要一個 "延遲 "參數(shù),以毫秒為單位。


回到原始問題,您嘗試調(diào)用 setTimeout(1000) 在兩次調(diào)用 console.log() 函數(shù)之間等待1秒。


不幸的是 setTimeout() 不能這樣工作:


setTimeout(1000)

console.log(1)

setTimeout(1000)

console.log(2)

setTimeout(1000)

console.log(3)


for (let i = 0; i <= 3; i++) {

 setTimeout(1000)

 console.log(`#${i}`)

}

這段代碼的結(jié)果完全沒有延遲,就像 setTimeout() 不存在一樣。


回顧文檔,你會發(fā)現(xiàn)問題在于實際上第一個參數(shù)應該是函數(shù)調(diào)用,而不是延遲。畢竟,setTimeout() 實際上不是 sleep() 方法。


你重寫代碼以將回調(diào)函數(shù)作為第一個參數(shù)并將必需的延遲作為第二個參數(shù):


setTimeout(() => console.log(1), 1000)

setTimeout(() => console.log(2), 1000)

setTimeout(() => console.log(3), 1000)


for (let i = 0; i <= 3; i++) {

 setTimeout(() => console.log(`#${i}`), 1000)

}

這樣一來,三個console.log的日志信息在經(jīng)過1000ms(1秒)的單次延時后,會一起顯示,而不是每次重復調(diào)用之間延時1秒的理想效果。


在討論如何解決此問題之前,讓我們更詳細地研究一下 setTimeout() 函數(shù)。


檢查setTimeout ()

你可能已經(jīng)注意到上面第二個代碼片段中使用了箭頭函數(shù)。這些是必需的,因為你需要將匿名回調(diào)函數(shù)傳遞給 setTimeout(),該函數(shù)將在超時后運行要執(zhí)行的代碼。


在匿名函數(shù)中,你可以指定在超時時間后執(zhí)行的任意代碼:


// 使用箭頭語法的匿名回調(diào)函數(shù)。

setTimeout(() => console.log("你好!"), 1000)

// 這等同于使用function關鍵字

setTimeout(function() { console.log("你好!") }, 1000)

理論上,你可以只傳遞函數(shù)作為第一個參數(shù),回調(diào)函數(shù)的參數(shù)作為剩余的參數(shù),但對我來說,這似乎從來沒有正確的工作:


// 應該能用,但不能用

setTimeout(console.log, 1000, "你好")

人們使用字符串解決此問題,但是不建議這樣做。從字符串執(zhí)行JavaScript具有安全隱患,因為任何不當行為者都可以運行作為字符串注入的任意代碼。


// 應該沒用,但確實有用

setTimeout(`console.log("你好")`, 1000)

那么,為什么在我們的第一組代碼示例中 setTimeout() 失敗?好像我們在正確使用它,每次都重復了1000ms的延遲。


原因是 setTimeout() 作為同步代碼執(zhí)行,并且對 setTimeout() 的多次調(diào)用均同時運行。每次調(diào)用 setTimeout() 都會創(chuàng)建異步代碼,該代碼將在給定延遲后稍后執(zhí)行。由于代碼段中的每個延遲都是相同的(1000毫秒),因此所有排隊的代碼將在1秒鐘的單個延遲后同時運行。


如前所述,setTimeout() 實際上不是 sleep() 函數(shù),取而代之的是,它只是將異步代碼排入隊列以供以后執(zhí)行。幸運的是,可以使用 setTimeout() 在JavaScript中創(chuàng)建自己的 sleep() 函數(shù)。


如何編寫sleep函數(shù)

通過Promises,async 和 await 的功能,您可以編寫一個 sleep() 函數(shù),該函數(shù)將按預期運行。


但是,你只能從 async 函數(shù)中調(diào)用此自定義 sleep() 函數(shù),并且需要將其與 await 關鍵字一起使用。


這段代碼演示了如何編寫一個 sleep() 函數(shù):


const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))


const repeatedGreetings = async () => {

 await sleep(1000)

 console.log(1)

 await sleep(1000)

 console.log(2)

 await sleep(1000)

 console.log(3)

}

repeatedGreetings()

此JavaScript sleep() 函數(shù)的功能與您預期的完全一樣,因為 await 導致代碼的同步執(zhí)行暫停,直到Promise被解決為止。


一個簡單的選擇

另外,你可以在第一次調(diào)用 setTimeout() 時指定增加的超時時間。


以下代碼等效于上一個示例:


setTimeout(() => console.log(1), 1000)

setTimeout(() => console.log(2), 2000)

setTimeout(() => console.log(3), 3000)

使用增加超時是可行的,因為代碼是同時執(zhí)行的,所以指定的回調(diào)函數(shù)將在同步代碼執(zhí)行的1、2和3秒后執(zhí)行。


它會循環(huán)運行嗎?

如你所料,以上兩種暫停JavaScript執(zhí)行的選項都可以在循環(huán)中正常工作。讓我們看兩個簡單的例子。


這是使用自定義 sleep() 函數(shù)的代碼段:


const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))


async function repeatGreetingsLoop() {

 for (let i = 0; i <= 5; i++) {

     await sleep(1000)

   console.log(`Hello #${i}`)

   }

}

repeatGreetingsLoop()

這是一個簡單的使用增加超時的代碼片段:


for (let i = 0; i <= 5; i++) {

 setTimeout(() => console.log(`Hello #${i}`), 1000 * i)

}

我更喜歡后一種語法,特別是在循環(huán)中使用。


總結(jié)

JavaScript可能沒有 sleep() 或 wait() 函數(shù),但是使用內(nèi)置的 setTimeout() 函數(shù)很容易創(chuàng)建一個JavaScript,只要你謹慎使用它即可。


就其本身而言,setTimeout() 不能用作 sleep() 函數(shù),但是你可以使用 async 和 await 創(chuàng)建自定義JavaScript sleep() 函數(shù)。


采用不同的方法,可以將交錯的(增加的)超時傳遞給 setTimeout() 來模擬 sleep() 函數(shù)。之所以可行,是因為所有對setTimeout() 的調(diào)用都是同步執(zhí)行的,就像JavaScript通常一樣。


希望這可以幫助你在代碼中引入一些延遲——僅使用原始JavaScript,而無需外部庫或框架。


藍藍設計www.yvirxh.cn )是一家專注而深入的界面設計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網(wǎng)站建設 、平面設計服務

5 個 JS 數(shù)組技巧可提高你的開發(fā)技能

seo達人

1. 隨機排列

在開發(fā)者,有時候我們需要對數(shù)組的順序進行重新的洗牌。 在 JS 中并沒有提供數(shù)組隨機排序的方法,這里提供一個隨機排序的方法:


function shuffle(arr) {

 var i, j, temp;

 for (i = arr.length - 1; i > 0; i--) {

   j = Math.floor(Math.random() * (i + 1));

   temp = arr[i];

   arr[i] = arr[j];

   arr[j] = temp;

 }

 return arr;

}

2. 唯一值

在開發(fā)者,我們經(jīng)常需要過濾重復的值,這里提供幾種方式來過濾數(shù)組的重復值。


使用 Set 對象

使用 Set() 函數(shù),此函數(shù)可與單個值數(shù)組一起使用。對于數(shù)組中嵌套的對象值而言,不是一個好的選擇。


const numArray = [1,2,3,4,2,3,4,5,1,1,2,3,3,4,5,6,7,8,2,4,6];


// 使用 Array.from 方法

Array.from(new Set(numArray));


// 使用展開方式

[...new Set(numArray)]

使用 Array.filter

使用 filter 方法,我們可以對元素是對象的進行過濾。


const data = [

 {id: 1, name: 'Lemon'},

 {id: 2, name: 'Mint'},

 {id: 3, name: 'Mango'},

 {id: 4, name: 'Apple'},

 {id: 5, name: 'Lemon'},

 {id: 6, name: 'Mint'},

 {id: 7, name: 'Mango'},

 {id: 8, name: 'Apple'},

]


function findUnique(data) {

 return data.filter((value, index, array) => {

   if (array.findIndex(item => item.name === value.name) === index) {

     return value;

   }

 })

}

3. 使用 loadsh 的 lodash 方法

import {uniqBy} from 'lodash'


const data = [

 {id: 1, name: 'Lemon'},

 {id: 2, name: 'Mint'},

 {id: 3, name: 'Mango'},

 {id: 4, name: 'Apple'},

 {id: 5, name: 'Lemon'},

 {id: 6, name: 'Mint'},

 {id: 7, name: 'Mango'},

 {id: 8, name: 'Apple'},

]


function findUnique(data) {

 return uniqBy(data, e => {

       return e.name

   })

}

3. 按屬性對 對象數(shù)組 進行排序

我們知道 JS 數(shù)組中的 sort 方法是按字典順序進行排序的,所以對于字符串類, 該方法是可以很好的正常工作,但對于數(shù)據(jù)元素是對象類型,就不太好使了,這里我們需要自定義一個排序方法。


在比較函數(shù)中,我們將根據(jù)以下條件返回值:


小于0:A 在 B 之前

大于0 :B 在 A 之前

等于0 :A 和 B 彼此保持不變

const data = [

 {id: 1, name: 'Lemon', type: 'fruit'},

 {id: 2, name: 'Mint', type: 'vegetable'},

 {id: 3, name: 'Mango', type: 'grain'},

 {id: 4, name: 'Apple', type: 'fruit'},

 {id: 5, name: 'Lemon', type: 'vegetable'},

 {id: 6, name: 'Mint', type: 'fruit'},

 {id: 7, name: 'Mango', type: 'fruit'},

 {id: 8, name: 'Apple', type: 'grain'},

]


function compare(a, b) {

 // Use toLowerCase() to ignore character casing

 const typeA = a.type.toLowerCase();

 const typeB = b.type.toLowerCase();


 let comparison = 0;

 if (typeA > typeB) {

   comparison = 1;

 } else if (typeA < typeB) {

   comparison = -1;

 }

 return comparison;

}


data.sort(compare)

4. 把數(shù)組轉(zhuǎn)成以指定符號分隔的字符串

JS 中有個方法可以做到這一點,就是使用數(shù)組中的 .join() 方法,我們可以傳入指定的符號來做數(shù)組進行分隔。


const data = ['Mango', 'Apple', 'Banana', 'Peach']


data.join(',');

// return "Mango,Apple,Banana,Peach"

5. 從數(shù)組中選擇一個元素

對于此任務,我們有多種方式,一種是使用 forEach 組合 if-else 的方式 ,另一種可以使用filter 方法,但是使用forEach 和filter的缺點是:


在forEach中,我們要額外的遍歷其它不需要元素,并且還要使用 if 語句來提取所需的值。

在filter 方法中,我們有一個簡單的比較操作,但是它將返回的是一個數(shù)組,而是我們想要是根據(jù)給定條件從數(shù)組中獲得單個對象。

為了解決這個問題,我們可以使用 find函數(shù)從數(shù)組中找到確切的元素并返回該對象,這里我們不需要使用if-else語句來檢查元素是否滿足條件。


const data = [

 {id: 1, name: 'Lemon'},

 {id: 2, name: 'Mint'},

 {id: 3, name: 'Mango'},

 {id: 4, name: 'Apple'}

]


const value = data.find(item => item.name === 'Apple')

// value = {id: 4, name: 'Apple'}

藍藍設計www.yvirxh.cn )是一家專注而深入的界面設計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網(wǎng)站建設 、平面設計服務


上半年最熱門的新擬物設計趨勢是如何演變的?

資深UI設計者

新擬物化——Neumorphism ,這么說可能不容易理解,但如果說「新擬物風格」,想必 UI 界的設計師們就知道這股「風頭」,在2020年刮的多么兇猛了。

烏克蘭設計師亞歷山大·普盧托 (Alexander Plyuto) 在 Dribble 平臺發(fā)布了一張 UI 作品《Skeuomorph Mobile Banking》,由于該作品使用了擬物化的設計風格,令人耳目一新,導致了作品的熱度持續(xù)飆升,并登上了平臺 Popular 榜首。Dribble 的評論區(qū)直接炸開了鍋,大家紛紛討論。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ 普盧托的《Skeuomorph Mobile Banking》,獲得了3000多次喜歡

隨后一位評論者杰森·凱利(Jason Kelley)在評論中將 New Skeuomorphism 「新擬物化」組合得到的 Neuomorphism 稱為「新擬物」 ,并決定去掉「 o 」,于是新設計詞匯「 Neumorphism 」便產(chǎn)生了。之后大家便用此做 Tag ,為自己的新擬物化設計作品打標簽上傳。

此風格的出現(xiàn)也給一直流行的扁平化設計添加了新的展現(xiàn)形式。今年2月初,三星召開 Galaxy Unpacked 活動,為宣傳新設備而發(fā)出的邀請函,便應用了新擬物化。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ 凸出的部分,用來比喻新機型的賣點

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

什么是新擬物風格?

想要了解新擬物的由來,就必須知道擬物的概念。擬物又被稱為擬物化,或是現(xiàn)實主義(Realism),概括的說其主要目標是使用戶界面更有代入感,降低人們使用的學習成本,產(chǎn)生熟悉親和的情感聯(lián)系。

A skeuomorph, or skeuomorphism is a design element of a product that imitates design elements functionally necessary in the original product design, but which have become ornamental in the new design. Skeuomorphs may be deliberately employed to make the new look comfortablyold and familiar.

via:en.wikipedia.org/wiki/Skeuomorph維基百科上關于擬物化的釋義

Apple 蘋果公司最早提出了擬物化的設計概念,通過模擬現(xiàn)實物體的紋理、材質(zhì)來進行界面設計,當時的 UI 設計師們都為擬物化設計「癡狂」。蘋果創(chuàng)始人喬布斯也非常推崇擬物化,他認為:「通過擬物化,用這種更加自然的認知體驗方式,可以減少用戶對電腦操作產(chǎn)生的恐懼感」。不妨來回憶下曾經(jīng)擬物化的 IOS 界面:

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ IOS 5系統(tǒng)中的相機展開狀態(tài)(擬物化的鏡頭)

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ 擬物化的精美 ICON

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ IOS 6系統(tǒng)中,被精細刻畫的錄音機(底部指針也很惟妙惟肖)

而新擬物則是擬物的變體,在擬物的基礎上改變了圖形的樣式,讓設計元素看起來更有真實感,不再是精細的模擬,更像是從界面中「生長」出來。設計師 Michal Malewicz 以卡片的形式,將新擬物和質(zhì)感設計(Material Design)對比,闡述了二者在實現(xiàn)時的差別。

新擬物卡片給人呈現(xiàn)的是一種無縫隙的「閉合」感,由界面中凸起;而質(zhì)感設計卡片,則是漂浮狀,陰影向四周發(fā)散,沒有邊界限制;二者的光影效果也非常明顯,新擬物偏柔和,質(zhì)感設計則相反,非常凸顯物體本體。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

Michal Malewicz 還標注了新擬物卡片的背景、陰影和高光的色值,整體色調(diào)比較接近。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

擬物化風格的結(jié)構(gòu)由背景色+高光色+陰影組成,掌握了基本規(guī)律,就可以通過改變按鈕、卡片的參數(shù)進行調(diào)整變換。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ 形狀、陰影參數(shù)的不同,實際效果也有區(qū)別

新擬物風格在UI層面的設計表現(xiàn)及趨勢?

新擬物也經(jīng)常被拿來與扁平化比較,因為擬物和扁平化是兩個相對的概念。其實在蘋果創(chuàng)造的設計系統(tǒng)的早期界面其實是非常擬物風的,但系統(tǒng)從 IOS 7開始,才轉(zhuǎn)向扁平的設計風格。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

隨著 AR、VR 技術的進步,其實對于真實物理環(huán)境,或者說對顯示效率的提升之后,我們對接近物理環(huán)境的設計更熱衷了。比較有代表性的就是 Google 推出的 Material Design System,它基于人們?nèi)ツM真實的物理世界的樣子,進而在數(shù)字世界里展現(xiàn)我們對于真實世界的一個反饋后,這樣的設計流程和邏輯,也讓我們的設計更真實,更具有感染力。當然也不止 Google 一家發(fā)布了這樣偏擬物化的設計風格。

從美學角度來看,其實新擬物化拋棄了之前很多擬物化里不必要的冗余,比如一些陰影、細節(jié)的繁瑣設計,更偏近現(xiàn)在先進科技發(fā)展的設計風格。比如 Windows 推出的 Fluent Design System ,正迎合了未來的 AR、VR 技術廣泛普及后的設計環(huán)境,希望打造一個先趨的設計系統(tǒng)。

在 Fluent Design System 提到的特點有:Lignt、Depth、Motion、Material、Scale。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

1. Lignt

光照,它指的是點擊或 Hover 的動作上面加入光照的效果,或是像柔和的光源灑落在空間中,可以去反應物體本身的反光質(zhì)感,它和 Material Design 強調(diào)的光影的擴散的光影效果是不同的概念。

2. Depth

深度,其實它的概念從 Material Design 開始就已經(jīng)被強調(diào)了,但是 Fluent Design System 希望是用更多的方式去呈現(xiàn),比如井深的模糊效果,視差滾動的動態(tài)效果,物件彼此的大小與位置等等。

3. Motion

動效,其實它想強調(diào)的動態(tài)效果更接近真實的世界,更強調(diào)細膩的變化,比如李安的電影「比利·林恩的中場戰(zhàn)事」,這個電影拍攝的幀數(shù)與以往傳統(tǒng)電影不一樣,看起來的感覺會更加的流暢自然,你體驗過之后會很難回去之后那種電影呈現(xiàn)效果了。而 Windows 強調(diào)的 Motion 也是一樣的,比起這種單調(diào)的動作,它也會去強調(diào)每個設計對象彼此之間的動態(tài)效果的時間差,看起來會更加的流暢自然。而且與真實空間中前景后景的物理概念一樣,不同的時間差會更容易凸顯出想要凸顯的主題效果,也會更加的聚焦。

4. Material

材質(zhì),其實在 Windows 提出的 Fluent Design System 里面,它會出現(xiàn)大量的模糊,透明的背景。也就是模擬毛玻璃的材質(zhì)感。通常也會代入一些微光源的效果。除了能夠吸睛,吸引你的視覺之外呢,其實在 AR、VR 的界面上面感知空間中的物件是很重要的,所以模糊的背景的利用可以在不影響觀看內(nèi)容情況下,還能起到背景暗示的作用。其實毛玻璃效果在 Windows 系統(tǒng)中已經(jīng)被運用到了,但是由于當時的效能以及干擾視線的問題僅僅運用在了一些小區(qū)域,而這次 Fluent Design System 的就成為了最強烈的視覺焦點,其實同樣的 iOS 和 Mac iOS 系統(tǒng)里面在最近的更新也大量使用了模糊效果。

6. Scale

縮放,在視覺上眼前的物體大,后面的物體小,所以縮放也是來營造空間感、縱深感,尺度感的這樣一個設計特性。

新擬物風格在設計上的應用體驗?

1. 界面設計上的特點

常應用于圖標、卡片或按鈕元素設計上,背景板多為干凈的純色;界面平滑,沒有明顯的顆粒感;

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ HYPE4《 Neumorphic Bank Redesign in Dark and Light mode 》

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ Filip Legierski 《 Banking App 》

按鈕的外邊框均設置了陰影、漸變效果,突出立體感;

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ Samson Vowles《 Neumorphic dark ui kit components 》

在視覺處理上,凸出的按鈕為可點擊的狀態(tài),凹進去則代表已選中。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ Emy Lascan《 Freebie Neumorphic UX UI Elements 》

2. 被吐糟的缺點

層次結(jié)構(gòu)弱

Whale Lab 觀察發(fā)現(xiàn),新擬物弱化顏色區(qū)分而強調(diào)近遠景陰影布局,所以整體色彩都相近,除了在個別的位置加入其它顏色點綴,用戶識別起來也會迷茫;而卡片、按鈕都使用了陰影,高光效果,層次結(jié)構(gòu)不明確,也很難帶來流暢的體驗;

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

△ 新擬物風格,F(xiàn)ilip Legierski《 Neumorphism UI Elements 》

對比度和視覺限制

明顯的對比是界面設計的重要原則。由于新擬物風格具有各種陰影色調(diào)和角度,可單擊的內(nèi)容與不可單擊的內(nèi)容區(qū)域在哪里不是很好區(qū)分。根據(jù)產(chǎn)品的功能和要求,每個應用神經(jīng)同構(gòu)的產(chǎn)品都可以具有自己的UI階段規(guī)則;但是由于陰影,角度和浮動水平的不同,由于缺乏一致性,迷失方向的可操作項,「神經(jīng)變形」會給用戶帶來混亂,最終為殘疾用戶造成使用障礙。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

如同下面這個例子,按鈕狀態(tài)已點擊和未點擊的一個效果,由于受壓狀態(tài)的反差太小,則看起來的效果也沒有什么不同。

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

增加開發(fā)難度

更為嚴重的是,不少設計者在使用 Neumorphism 進行界面開發(fā)過程中,也遭遇到了不少局限。要實現(xiàn)這個風格,主要有兩個方式:

卡片、按鈕切圖,每個狀態(tài)(Normal、Hover、Pressed)都要裁切,導致資源庫圖片量過載;

代碼實現(xiàn),這個風格的實現(xiàn)效果是對元素增加兩個不同方向的投影,但需要開發(fā)對每個元素添加投影,樣式代碼增多,工作量浩大。

網(wǎng)站neumorphism.io,可以快速生成 Neumorphism UI 。設置按鈕的參數(shù)值,就能看到多樣的新擬物化效果,非常神奇!

完整梳理!上半年最熱門的新擬物設計趨勢是如何演變的?

新技術、事物、趨勢的出現(xiàn),起初都會給人們帶來焦慮甚至是恐慌。不管是擬物還是扁平,Whale Lab 覺得若是絕對化的去推崇某一種,都是錯誤的,盡管蘋果放棄了擬物進入扁平化,也不一定代表扁平就是最好,畢竟二者始終相輔相成。不敢否認,新擬物風格在今后是否變得「真香」,但對于設計師來說,從用戶體驗、產(chǎn)品出發(fā)的優(yōu)秀設計,都值得被認可與尊敬。

文章來源:優(yōu)設    作者:UX辭典

日歷

鏈接

個人資料

藍藍設計的小編 http://www.yvirxh.cn

存檔