首頁(yè)

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

seo達(dá)人

閱讀之前

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


什么是map()和forEach()?

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


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

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

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

}

// 1

// 2

// 3

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


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


map

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


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


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

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

// 數(shù)組的當(dāng)前值作為第一個(gè)參數(shù)

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

// 原始源數(shù)組作為第三個(gè)參數(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

你還可以將對(duì)象作為 thisArg 傳遞到map。


const obj = { name: 'Jane' };


[1].map(function() {

 // { name: 'Jane' }

 console.dir(this);

}, obj);


[1].map(() => {

 // window

 console.dir(this);

}, obj);

對(duì)象 obj 成為 map 的 thisArg。但是箭頭回調(diào)函數(shù)無(wú)法將 obj 作為其 thisArg。


這是因?yàn)榧^函數(shù)與正常函數(shù)不同。


forEach

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


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

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

// 數(shù)組的當(dāng)前值作為第一個(gè)參數(shù)

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

// 原始源數(shù)組作為第三個(gè)參數(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 卻沒(méi)有。但是它們都確保了原始對(duì)象的不變性。


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

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

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


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

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

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

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

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


這是一個(gè)簡(jiǎn)單的例子。


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 是非常常用的制作元素的方法,因?yàn)?map 在對(duì)原數(shù)組的數(shù)據(jù)進(jìn)行操作后,會(huì)創(chuàng)建并返回一個(gè)新的數(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

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


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

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


速度比較

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






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


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

藍(lán)藍(lán)設(shè)計(jì)sillybuy.com )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)




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

seo達(dá)人

序言

之前發(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ài)管理方案 recoil,雖然還處于實(shí)驗(yàn)狀態(tài),但是相必大家已經(jīng)私底下開(kāi)始欲欲躍試了,畢竟出生名門(mén),有fb背書(shū),一定會(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)是,如果有依賴,必需先書(shū)寫(xiě)好依賴在開(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 (

   <div>

     {num}<br/>

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

   </div>

 );

}

消費(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 (

   <div>

     numx10 :{numx10}<br/>

   </div>

 );

};

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

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


export default ()=>{

 return (

   <>

     <NumView />

     <NumValView />

   </>

 );

};

頂層節(jié)點(diǎn)包裹React.Suspense和RecoilRoot,前者用于配合異步計(jì)算函數(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初體驗(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í)更推薦的寫(xiě)法是


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 (

     <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>

 );

}

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


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

在線示例


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

ReactDOM.render(

 <React.StrictMode>

   <div>

     <ClsComp />

     <FnComp />

   </div>

 </React.StrictMode>,

 rootElement

);



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


組件調(diào)用reducer

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


mr 為 moduleReducer的簡(jiǎn)寫(xiě),直接書(shū)寫(xiě)為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 (

   <div>

     {state.num}

     <br />

     {counterCuStatus.done ? moduleComputed.numx10 : 'computing'}

     {/** 此處拿到錯(cuò)誤可以用于渲染,當(dāng)然也拋出去 */}

     {/** 讓ErrorBoundary之類的組件捕捉并渲染降級(jí)頁(yè)面 */}

     {counterCuStatus.err ? counterCuStatus.err.message : ''}

     <br />

     {moduleComputed.numx10_2}

     <br />

     {moduleComputed.numx10_3}

     <br />

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

   </div>

 );

}

![]https://raw.githubusercontent...


查看在線示例


更新

開(kāi)篇我說(shuō)對(duì)Recoli提到的更新保持了懷疑態(tài)度,有一些誤導(dǎo)的嫌疑,此處我們將揭開(kāi)疑團(tuán)


大家知道hook使用規(guī)則是不能寫(xiě)在條件控制語(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 ? <h1>{state.num}</h1> : '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 <h1>for setup<h1/>

}

更多關(guān)于effect與useEffect請(qǐng)查看此文


current mode

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


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


// bad

function Test(){

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

  return <h1>bad case</h1>

}


// good

function Test(){

  useEffect(()=>{

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

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

     track.upload('renderTrigger');

  })

  return <h1>bad case</h1>

}

我們首先要理解current mode原理是因?yàn)閒iber架構(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í)例(實(shí)例的狀態(tài)本身是一個(gè)個(gè)孤島,concent只負(fù)責(zé)同步建立起了依賴的store的數(shù)據(jù)),之后就是react自己的調(diào)度流程,修改狀態(tài)的函數(shù)并不會(huì)因?yàn)榻M件反復(fù)重入而多次執(zhí)行(這點(diǎn)需要我們遵循不該在渲染過(guò)程中書(shū)寫(xiě)包含有副作用的代碼原則),react僅僅是調(diào)度組件的渲染時(shí)機(jī),而組件的中斷和重入針對(duì)也是這個(gè)渲染過(guò)程。


所以同樣的,對(duì)于concent


const setup = (ctx)=>{

 ctx.effect(()=>{

    // effect是對(duì)useEffect的封裝,

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

     track.upload('renderTrigger');

 });

}


// good

function Test2(){

  useConcent({setup})

  return <h1>good case</h1>

}

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


// 假設(shè)這是一個(gè)渲染很耗時(shí)的組件,在current mode模式下可能會(huì)被中斷渲染

function HeavyComp(){

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


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

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

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

 return (

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

 );

}

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


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

react所有新特性其實(shí)都是被fiber激活了,有了fiber架構(gòu),衍生出了hook、time slicing、suspense以及將來(lái)的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其實(shí)為了引導(dǎo)用戶寫(xiě)能夠在Concurrent Mode里運(yùn)行的代碼而提供的輔助api,先讓用戶慢慢習(xí)慣這些限制,循序漸進(jìn)一步一步來(lái),最后再推出Concurrent Mode。


結(jié)語(yǔ)

Recoil推崇狀態(tài)和派生數(shù)據(jù)更細(xì)粒度控制,寫(xiě)法上demo看起來(lái)簡(jiǎn)單,實(shí)際上代碼規(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處消費(fèi)狀態(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)樹(shù)的本質(zhì),推崇模塊化管理數(shù)據(jù)以及派生數(shù)據(jù),同時(shí)依靠Proxy能力完成了運(yùn)行時(shí)依賴收集和追求不可變的完美整合。


run({

 counter: {// 聲明一個(gè)counter模塊

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

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

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

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

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

    }

 },

});


// ---> ui處消費(fèi)狀態(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;

所以你將獲得:


運(yùn)行時(shí)的依賴收集 ,同時(shí)也遵循react不可變的原則

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

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

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


藍(lán)藍(lán)設(shè)計(jì)sillybuy.com )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)



大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

資深UI設(shè)計(jì)者

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

什么是暗黑模式

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

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

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

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

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

為什么 Ant Design 要做暗黑模式

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

試想一下,我們?cè)陔娪霸嚎措娪皶r(shí),為什么要全場(chǎng)關(guān)燈?甚至有些 APP,在影片的下方也會(huì)又一個(gè)模擬關(guān)燈效果的操作,來(lái)讓整個(gè)手機(jī)屏幕變黑,只剩下視屏畫(huà)面的部分,這都幫助我們可以更專注、更沉浸在當(dāng)前的內(nèi)容下。

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

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

3. 大眾喜愛(ài)

黑色一直以來(lái)就可以給人以高級(jí)、神秘的語(yǔ)義象征,相比于淺色模式,暗色模式藏著更多可能性。

設(shè)計(jì)原則

在這次暗黑模式的設(shè)計(jì)中主要遵循以下兩大設(shè)計(jì)原則

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

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

2. 信息的一致性

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

如何設(shè)計(jì)

1. 界面層級(jí)

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

在經(jīng)過(guò)對(duì)螞蟻企業(yè)級(jí)頁(yè)面的典型布局結(jié)構(gòu)評(píng)估后,我們?cè)谥行陨性黾恿巳齻€(gè)梯度,將中性色擴(kuò)展至 13 個(gè)

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

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

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

2. 色彩

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

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

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

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

對(duì)比度極性

對(duì)比度極性分為正極性和負(fù)極性。

  • 對(duì)比度正極性:指在電子文本中文本為深色,背景色為淺色
  • 對(duì)比度負(fù)極性:指在電子文本中文本為淺色,背景色為深色

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

正負(fù)極性差值

顧名思義便是正負(fù)兩者的差值,這里取的是絕對(duì)值。

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

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

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

這點(diǎn)也可以從「正負(fù)對(duì)比度極性趨勢(shì)」兩者間的相對(duì)關(guān)系反應(yīng)出來(lái),從紅色到洋紅,綠線一開(kāi)始從逐漸在藍(lán)線的上方一點(diǎn),開(kāi)始逐漸上移,最后在峰值處開(kāi)始慢慢下移,在「極客藍(lán)」這個(gè)色相中接近重疊,在洋紅處又慢慢下移,說(shuō)明淺色下越深的顏色,在深色中越亮,反之則越暗。

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

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

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

最后可以看到顏色在調(diào)整過(guò)后實(shí)際應(yīng)用的效果

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

3. 文字

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

4. 陰影

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

5. 分割線

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

適應(yīng)性

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

大廠如何做好暗黑模式設(shè)計(jì)?來(lái)看 Ant Design 的規(guī)范文檔

結(jié)語(yǔ)

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

文章來(lái)源:優(yōu)設(shè)    作者:AlibabaDesign

藍(lán)藍(lán)設(shè)計(jì)sillybuy.com )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)

如何使JavaScript休眠或等待

seo達(dá)人

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


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


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


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


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


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


回到原始問(wèn)題,您嘗試調(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é)果完全沒(méi)有延遲,就像 setTimeout() 不存在一樣。


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


你重寫(xiě)代碼以將回調(diào)函數(shù)作為第一個(gè)參數(shù)并將必需的延遲作為第二個(gè)參數(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)

}

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


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


檢查setTimeout ()

你可能已經(jīng)注意到上面第二個(gè)代碼片段中使用了箭頭函數(shù)。這些是必需的,因?yàn)槟阈枰獙⒛涿卣{(diào)函數(shù)傳遞給 setTimeout(),該函數(shù)將在超時(shí)后運(yùn)行要執(zhí)行的代碼。


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


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

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

// 這等同于使用function關(guān)鍵字

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

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


// 應(yīng)該能用,但不能用

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

人們使用字符串解決此問(wèn)題,但是不建議這樣做。從字符串執(zhí)行JavaScript具有安全隱患,因?yàn)槿魏尾划?dāng)行為者都可以運(yùn)行作為字符串注入的任意代碼。


// 應(yīng)該沒(méi)用,但確實(shí)有用

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

那么,為什么在我們的第一組代碼示例中 setTimeout() 失???好像我們?cè)谡_使用它,每次都重復(fù)了1000ms的延遲。


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


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


如何編寫(xiě)sleep函數(shù)

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


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


這段代碼演示了如何編寫(xiě)一個(gè) 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ù)的功能與您預(yù)期的完全一樣,因?yàn)?await 導(dǎo)致代碼的同步執(zhí)行暫停,直到Promise被解決為止。


一個(gè)簡(jiǎn)單的選擇

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


以下代碼等效于上一個(gè)示例:


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

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

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

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


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

如你所料,以上兩種暫停JavaScript執(zhí)行的選項(xiàng)都可以在循環(huán)中正常工作。讓我們看兩個(gè)簡(jiǎ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()

這是一個(gè)簡(jiǎn)單的使用增加超時(shí)的代碼片段:


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

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

}

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


總結(jié)

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


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


采用不同的方法,可以將交錯(cuò)的(增加的)超時(shí)傳遞給 setTimeout() 來(lái)模擬 sleep() 函數(shù)。之所以可行,是因?yàn)樗袑?duì)setTimeout() 的調(diào)用都是同步執(zhí)行的,就像JavaScript通常一樣。


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


藍(lán)藍(lán)設(shè)計(jì)sillybuy.com )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 平面設(shè)計(jì)服務(wù)

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

seo達(dá)人

1. 隨機(jī)排列

在開(kāi)發(fā)者,有時(shí)候我們需要對(duì)數(shù)組的順序進(jìn)行重新的洗牌。 在 JS 中并沒(méi)有提供數(shù)組隨機(jī)排序的方法,這里提供一個(gè)隨機(jī)排序的方法:


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. 唯一值

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


使用 Set 對(duì)象

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


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));


// 使用展開(kāi)方式

[...new Set(numArray)]

使用 Array.filter

使用 filter 方法,我們可以對(duì)元素是對(duì)象的進(jìn)行過(guò)濾。


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. 按屬性對(duì) 對(duì)象數(shù)組 進(jìn)行排序

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


在比較函數(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)成以指定符號(hào)分隔的字符串

JS 中有個(gè)方法可以做到這一點(diǎn),就是使用數(shù)組中的 .join() 方法,我們可以傳入指定的符號(hào)來(lái)做數(shù)組進(jìn)行分隔。


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


data.join(',');

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

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

對(duì)于此任務(wù),我們有多種方式,一種是使用 forEach 組合 if-else 的方式 ,另一種可以使用filter 方法,但是使用forEach 和filter的缺點(diǎn)是:


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

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

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


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'}

藍(lán)藍(lán)設(shè)計(jì)sillybuy.com )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)


上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

資深UI設(shè)計(jì)者

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

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

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

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

△ 凸出的部分,用來(lái)比喻新機(jī)型的賣(mài)點(diǎn)

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

什么是新擬物風(fēng)格?

想要了解新擬物的由來(lái),就必須知道擬物的概念。擬物又被稱為擬物化,或是現(xiàn)實(shí)主義(Realism),概括的說(shuō)其主要目標(biāo)是使用戶界面更有代入感,降低人們使用的學(xué)習(xí)成本,產(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維基百科上關(guān)于擬物化的釋義

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

△ 擬物化的精美 ICON

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

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

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

新擬物風(fēng)格在UI層面的設(shè)計(jì)表現(xiàn)及趨勢(shì)?

新擬物也經(jīng)常被拿來(lái)與扁平化比較,因?yàn)閿M物和扁平化是兩個(gè)相對(duì)的概念。其實(shí)在蘋(píng)果創(chuàng)造的設(shè)計(jì)系統(tǒng)的早期界面其實(shí)是非常擬物風(fēng)的,但系統(tǒng)從 IOS 7開(kāi)始,才轉(zhuǎn)向扁平的設(shè)計(jì)風(fēng)格。

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

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

在 Fluent Design System 提到的特點(diǎn)有:Lignt、Depth、Motion、Material、Scale。

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

1. Lignt

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

2. Depth

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

3. Motion

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

4. Material

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

6. Scale

縮放,在視覺(jué)上眼前的物體大,后面的物體小,所以縮放也是來(lái)營(yíng)造空間感、縱深感,尺度感的這樣一個(gè)設(shè)計(jì)特性。

新擬物風(fēng)格在設(shè)計(jì)上的應(yīng)用體驗(yàn)?

1. 界面設(shè)計(jì)上的特點(diǎn)

常應(yīng)用于圖標(biāo)、卡片或按鈕元素設(shè)計(jì)上,背景板多為干凈的純色;界面平滑,沒(méi)有明顯的顆粒感;

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

△ Filip Legierski 《 Banking App 》

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

△ Samson Vowles《 Neumorphic dark ui kit components 》

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

△ Emy Lascan《 Freebie Neumorphic UX UI Elements 》

2. 被吐糟的缺點(diǎn)

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

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

對(duì)比度和視覺(jué)限制

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

增加開(kāi)發(fā)難度

更為嚴(yán)重的是,不少設(shè)計(jì)者在使用 Neumorphism 進(jìn)行界面開(kāi)發(fā)過(guò)程中,也遭遇到了不少局限。要實(shí)現(xiàn)這個(gè)風(fēng)格,主要有兩個(gè)方式:

卡片、按鈕切圖,每個(gè)狀態(tài)(Normal、Hover、Pressed)都要裁切,導(dǎo)致資源庫(kù)圖片量過(guò)載;

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

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

完整梳理!上半年最熱門(mén)的新擬物設(shè)計(jì)趨勢(shì)是如何演變的?

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

文章來(lái)源:優(yōu)設(shè)    作者:UX辭典

日歷

鏈接

個(gè)人資料

存檔