首页

Vue 3.0 前瞻,体验 Vue Function API

seo达人

概述

Vue 2.x 及以前的高阶组件的组织形式或多或少都会面临一些问题,特别是在需要处理重复逻辑的项目中,一旦开发者组织项目结构组织得不好,组件代码极有可能被人诟病为“胶水代码”。而在 Vue 2.x 及之前的版本,解决此类问题的办法大致是下面的方案:



mixin

函数式组件

slots

笔者维护的项目也需要处理大量复用逻辑,在这之前,笔者一直尝试使用mixin的方式来实现组件的复用。有些问题也一直会对开发者和维护者造成困惑,如一个组件同时mixin多个组件,很难分清对应的属性或方法写在哪个mixin里。其次,mixin的命名空间冲突也可能造成问题。难以保证不同的mixin不用到同一个属性名。为此,官方团队提出函数式写法的意见征求稿,也就是RFC:Function-based component API。使用函数式的写法,可以做到更灵活地复用组件,开发者在组织高阶组件时,不必在组件组织上考虑复用,可以更好地把精力集中在功能本身的开发上。



注:本文只是笔者使用vue-function-api提前体验 Vue Function API ,而这个 API 只是 Vue 3.0 的 RFC,而并非与最终 Vue 3.x API 一致。发布后可能有不一致的地方。



在 Vue 2.x 中使用

要想提前在Vue 2.x中体验 Vue Function API ,需要引入vue-function-api,基本引入方式如下:



import Vue from 'vue';

import { plugin as VueFunctionApiPlugin } from 'vue-function-api';

 

Vue.use(VueFunctionApiPlugin);

基本组件示例

先来看一个基本的例子:



<template>

    <div>

        <span>count is {{ count }}</span>

        <span>plusOne is {{ plusOne }}</span>

        <button @click="increment">count++</button>

    </div>

</template>

 

<script>

import Vue from 'vue';

import { value, computed, watch, onMounted } from 'vue-function-api';

 

export default {

    setup(props, context) {

        // reactive state

        const count = value(0);

        // computed state

        const plusOne = computed(() => count.value + 1);

        // method

        const increment = () => {

            count.value++;

        };

        // watch

        watch(

            () => count.value 2,

            val => {

                console.log(`count
2 is ${val});<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br /> &nbsp; &nbsp; &nbsp; &nbsp; );<br /> &nbsp; &nbsp; &nbsp; &nbsp; // lifecycle<br /> &nbsp; &nbsp; &nbsp; &nbsp; onMounted(() =&gt; {<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log(mounted);<br /> &nbsp; &nbsp; &nbsp; &nbsp; });<br /> &nbsp; &nbsp; &nbsp; &nbsp; // expose bindings on render context<br /> &nbsp; &nbsp; &nbsp; &nbsp; return {<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; count,<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; plusOne,<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; increment,<br /> &nbsp; &nbsp; &nbsp; &nbsp; };<br /> &nbsp; &nbsp; },<br /> };<br /> &lt;/script&gt;<br /> 详解<br /> setup<br /> setup函数是Vue Function API 构建的函数式写法的主逻辑,当组件被创建时,就会被调用,函数接受两个参数,分别是父级组件传入的props和当前组件的上下文context。看下面这个例子,可以知道在context中可以获取到下列属性值<br /> <br /> const MyComponent = {<br /> &nbsp; &nbsp; props: {<br /> &nbsp; &nbsp; &nbsp; &nbsp; name: String<br /> &nbsp; &nbsp; },<br /> &nbsp; &nbsp; setup(props, context) {<br /> &nbsp; &nbsp; &nbsp; &nbsp; console.log(props.name);<br /> &nbsp; &nbsp; &nbsp; &nbsp; // context.attrs<br /> &nbsp; &nbsp; &nbsp; &nbsp; // context.slots<br /> &nbsp; &nbsp; &nbsp; &nbsp; // context.refs<br /> &nbsp; &nbsp; &nbsp; &nbsp; // context.emit<br /> &nbsp; &nbsp; &nbsp; &nbsp; // context.parent<br /> &nbsp; &nbsp; &nbsp; &nbsp; // context.root<br /> &nbsp; &nbsp; }<br /> }<br /> value &amp; state<br /> value函数创建一个包装对象,它包含一个响应式属性value:<br /> <br /> <br /> <br /> 那么为何要使用value呢,因为在JavaScript中,基本类型并没有引用,为了保证属性是响应式的,只能借助包装对象来实现,这样做的好处是组件状态会以引用的方式保存下来,从而可以被在setup中调用的不同的模块的函数以参数的形式传递,既能复用逻辑,又能方便地实现响应式。<br /> <br /> 直接获取包装对象的值必须使用.value,但是,如果包装对象作为另一个响应式对象的属性,Vue内部会通过proxy来自动展开包装对象。同时,在模板渲染的上下文中,也会被自动展开。<br /> <br /> import { state, value } from 'vue-function-api';<br /> const MyComponent = {<br /> &nbsp; &nbsp; setup() {<br /> &nbsp; &nbsp; &nbsp; &nbsp; const count = value(0);<br /> &nbsp; &nbsp; &nbsp; &nbsp; const obj = state({<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; count,<br /> &nbsp; &nbsp; &nbsp; &nbsp; });<br /> &nbsp; &nbsp; &nbsp; &nbsp; console.log(obj.count) // 作为另一个响应式对象的属性,会被自动展开<br /> &nbsp;<br /> &nbsp; &nbsp; &nbsp; &nbsp; obj.count++ // 作为另一个响应式对象的属性,会被自动展开<br /> &nbsp; &nbsp; &nbsp; &nbsp; count.value++ // 直接获取响应式对象,必须使用.value<br /> &nbsp;<br /> &nbsp; &nbsp; &nbsp; &nbsp; return {<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; count,<br /> &nbsp; &nbsp; &nbsp; &nbsp; };<br /> &nbsp; &nbsp; },<br /> &nbsp; &nbsp; template:<button @click="count++">{{ count }}</button>,<br /> };<br /> 如果某一个状态不需要在不同函数中被响应式修改,可以通过state创建响应式对象,这个state创建的响应式对象并不是包装对象,不需要使用.value来取值。<br /> <br /> watch &amp; computed<br /> watch和computed的基本概念与 Vue 2.x 的watch和computed一致,watch可以用于追踪状态变化来执行一些后续操作,computed用于计算属性,用于依赖属性发生变化进行重新计算。<br /> <br /> computed返回一个只读的包装对象,和普通包装对象一样可以被setup函数返回,这样就可以在模板上下文中使用computed属性。可以接受两个参数,第一个参数返回当前的计算属性值,当传递第二个参数时,computed是可写的。<br /> <br /> import { value, computed } from 'vue-function-api';<br /> &nbsp;<br /> const count = value(0);<br /> const countPlusOne = computed(() =&gt; count.value + 1);<br /> &nbsp;<br /> console.log(countPlusOne.value); // 1<br /> &nbsp;<br /> count.value++;<br /> console.log(countPlusOne.value); // 2<br /> &nbsp;<br /> // 可写的计算属性值<br /> const writableComputed = computed(<br /> &nbsp; &nbsp; // read<br /> &nbsp; &nbsp; () =&gt; count.value + 1,<br /> &nbsp; &nbsp; // write<br /> &nbsp; &nbsp; val =&gt; {<br /> &nbsp; &nbsp; &nbsp; &nbsp; count.value = val - 1;<br /> &nbsp; &nbsp; },<br /> );<br /> watch第一个参数和computed类似,返回被监听的包装对象属性值,不过另外需要传递两个参数:第二个参数是回调函数,当数据源发生变化时触发回调函数,第三个参数是options。其默认行为与 Vue 2.x 有所不同:<br /> <br /> lazy:是否会在组件创建时就调用一次回调函数,与 Vue 2.x 相反,lazy默认是false,默认会在组件创建时调用一次。<br /> deep:与 Vue 2.x 的 deep 一致<br /> flush:有三个可选值,分别为 'post'(在渲染后,即nextTick后才调用回调函数),'pre'(在渲染前,即nextTick前调用回调函数),'sync'(同步触发)。默认值为'post'。<br /> // double 是一个计算包装对象<br /> const double = computed(() =&gt; count.value * 2);<br /> &nbsp;<br /> watch(double, value =&gt; {<br /> &nbsp; &nbsp; console.log('double the count is: ', value);<br /> }); // -&gt; double the count is: 0<br /> &nbsp;<br /> count.value++; // -&gt; double the count is: 2<br /> 当watch多个被包装对象属性时,参数均可以通过数组的方式进行传递,同时,与 Vue 2.x 的vm.$watch一样,watch返回取消监听的函数:<br /> <br /> const stop = watch(<br /> &nbsp; &nbsp; [valueA, () =&gt; valueB.value],<br /> &nbsp; &nbsp; ([a, b], [prevA, prevB]) =&gt; {<br /> &nbsp; &nbsp; &nbsp; &nbsp; console.log(a is: ${a});<br /> &nbsp; &nbsp; &nbsp; &nbsp; console.log(b is: ${b});<br /> &nbsp; &nbsp; }<br /> );<br /> &nbsp;<br /> stop();<br /> 注意:在RFC:Function-based component API初稿中,有提到effect-cleanup,是用于清理一些特殊情况的副作用的,目前已经在提案中被取消了。<br /> <br /> 生命周期<br /> 所有现有的生命周期都有对应的钩子函数,通过onXXX的形式创建,但有一点不同的是,destoryed钩子函数需要使用unmounted代替:<br /> <br /> import { onMounted, onUpdated, onUnmounted } from 'vue-function-api';<br /> &nbsp;<br /> const MyComponent = {<br /> &nbsp; &nbsp; setup() {<br /> &nbsp; &nbsp; &nbsp; &nbsp; onMounted(() =&gt; {<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log('mounted!');<br /> &nbsp; &nbsp; &nbsp; &nbsp; });<br /> &nbsp; &nbsp; &nbsp; &nbsp; onUpdated(() =&gt; {<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log('updated!');<br /> &nbsp; &nbsp; &nbsp; &nbsp; });<br /> &nbsp; &nbsp; &nbsp; &nbsp; // destroyed 调整为 unmounted<br /> &nbsp; &nbsp; &nbsp; &nbsp; onUnmounted(() =&gt; {<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log('unmounted!');<br /> &nbsp; &nbsp; &nbsp; &nbsp; });<br /> &nbsp; &nbsp; },<br /> };<br /> 一些思考<br /> 上面的详解部分,主要抽取的是 Vue Function API 的常见部分,并非RFC:Function-based component API的全部,例如其中的依赖注入,TypeScript类型推导等优势,在这里,由于篇幅有限,想要了解更多的朋友,可以点开RFC:Function-based component API查看。个人也在Function-based component API讨论区看到了更多地一些意见:<br /> <br /> 由于底层设计,在setup取不到组件实例this的问题,这个问题在笔者尝试体验时也遇到了,期待正式发布的 Vue 3.x 能够改进这个问题。<br /> <br /> 对于基本类型的值必须使用包装对象的问题:在 RFC 讨论区,为了同时保证TypeScript类型推导、复用性和保留Vue的数据监听,包装属性必须使用.value来取值是讨论最激烈的<br /> <br /> 关于包装对象value和state方法命名不清晰可能导致开发者误导等问题,已经在Amendment proposal to Function-based Component API这个提议中展开了讨论:<br /> <br /> setup() {<br /> &nbsp; &nbsp; const state = reactive({<br /> &nbsp; &nbsp; &nbsp; &nbsp; count: 0,<br /> &nbsp; &nbsp; });<br /> &nbsp;<br /> &nbsp; &nbsp; const double = computed(() =&gt; state.count * 2);<br /> &nbsp;<br /> &nbsp; &nbsp; function increment() {<br /> &nbsp; &nbsp; &nbsp; &nbsp; state.count++;<br /> &nbsp; &nbsp; }<br /> &nbsp;<br /> &nbsp; &nbsp; return {<br /> &nbsp; &nbsp; &nbsp; &nbsp; ...toBindings(state), // retains reactivity on mutations made tostate`

        double,

        increment,

    };

}

 



引入reactive API 和 binding API,其中reactive API 类似于 state API , binding API 类似于 value API。

之前使用的方法名state在 Vue 2.x 中可能被用作组件状态对象,导致变量命名空间的冲突问题,团队认为将state API 更名为 reactive 更为优雅。开发者能够写出const state = ... ,然后通过state.xxxx这种方式来获取组件状态,这样也相对而言自然一些。

value方法用于封装基本类型时,确实会出现不够优雅的.value的情况,开发者可能会在直接对包装对象取值时忘记使用.value,修正方案提出的 reactive API,其含义是创建响应式对象,初始化状态state就使用reactive创建,可保留每项属性的getter和setter,这么做既满足类型推导,也可以保留响应式引用,从而可在不同模块中共享状态值的引用。

但reactive可能导致下面的问题,需要引入binding API。 解决,如使用reactive创建的响应式对象,对其使用拓展运算符...时,则会丢失对象的getter和setter,提供toBindings方法能够保留状态的响应式。

当然,目前 Vue Function API 还处在讨论阶段,Vue 3.0 还处在开发阶段,还是期待下半年 Vue 3.0 的初版问世吧,希望能给我们带来更多的惊喜。


汽车UI界面怎么做?来看特斯拉和Apple Carplay等高手的案例!

用心设计

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里


现在市面上的汽车大部分都是过时的,没有吸引力的用户界面,正因为如此,很多设计师都在思考未来的汽车用户界面将会如何改善我们的驾驶体验。今天这篇译文,一起来学习特斯拉和Apple Carplay 是怎么设计的!

汽车行业似乎每天都有着新的发展。很多证据都表明在接下来的20年里我们将会看到比上个世纪都要剧烈的变革。因此,顶尖的数字设计师们应该把重点放在对这个行业的关注。那么,到底什么才是车辆用户界面的未来?它能从根本上改变我们和汽车之间的关系么?

下面是我收集的一些由全世界不同的设计师设计的美丽和未来的汽车抬头显示器、用户界面交互、第三方app控制器。这些设计想法,有一些是真实存在的,有一些还正在发展中。

特斯拉移动控制中心原型

uisdc-car-201610133

它为什么会让人震惊?

当你想确定你是否已经锁好车,关上灯,或者把你的车钥匙交给刚考完驾照的小屁孩的时候,你是否觉得自己是个偏执狂?

这个移动app可以让你在不离开座位的时候检查这一切,有一个柴油混合动力车?你甚至可以在你吃早餐的时候启动引擎。直观的动画会反馈你这个动作是否已经完成。

车载控制面板用户界面

uisdc-car-201610134

它为什么会让人震惊?

这个用户界面看起来是一个用肌肉记忆和手势驱动的通用控制模型,不同数目的手指可以触发不同事件,你可以通过iPad面板,无线鼠标等控制它。你可以通过动作来控制数值的大小等等。

这个用户界面去掉了所有需要用户记忆的小控件和视觉元素,从而你可以使用相同的手势或者它的变体,来完成多个不同种类的任务。

你可以在这里查看整个案例研究:A New Car UI

特斯拉仪表界面概念版

uisdc-car-201610135

它为什么会让人震惊?

汽车将会变得更加的智能,为我们的公共场所腾出更多的空间,并且汽车的功能也将变得越来越互相关联起来。随着这些发展,数据在我们面前将会变得势不可挡,试想一下不久之前,我们的手机和其它东西一样还是一个单一功能的设备。我们在我们看到什么和如何看上有我们的控制权,它应该变得更加自然和方便使用。人工智能和机器学习将会采取直觉控制。这个由Bureau Oberhaeuser制作的原型让我们提前看到了这一切的到来。

你可以在这里查看整个案例研究:Behance

远程车辆健康测试与控制

uisdc-car-201610139

它为什么会让人震惊?

当汽车所有的零部件都变成电子系统时,你想拿起扳手就能发现问题变得越来越不可能了。这个移动app原型试图用一种你能理解的语言去描述汽车当前的健康状况,让你知道你是否有必要在开启你的海岸线之旅前修一下它。

轮胎压力和气候的用户界面

uisdc-car-201610131

它为什么会让人震惊?

当进行轮胎检查时,这个界面将会告诉你每个轮胎的压力范围并且告诉你它们的极限在哪里。这个用户界面提供了如此重要的反馈,我觉得它应该被纳入常用标准中去。

Hudway增强现实显示器

uisdc-car-201610138

它为什么会让人震惊?

不用说大家都知道司机应该保持视线在路上,但是现在的手机导航都需要司机把注意力来回切换在手机屏幕和路面之间,这个叫Hudway的app把你的第二块屏幕跟挡风玻璃很好的结合了起来。你面前的那块挡风玻璃很有可能就是未来的显示界面。

你可以在这里查看整个案例研究:Augmented Reality Projections Turn Windshield Into A Navigation Screen [Video]

城市导览汽车应用

uisdc-car-201610132

它为什么会让人震惊?

这个用户界面对于勇敢的探险家来说非常完美,如果你想快速的知道你周围有什么,这个用户界面可以让你在有限的路线里来一段自发的旅行。

Apple Carplay

uisdc-car-2016101310

它为什么会让人震惊?

每个人都在关注它的到来,想都不要想,如果你已经有了一堆苹果的产品,你也会在你的汽车上装上它的。Apple Carplay将会是下一代的第二块屏幕。

数字仪表板集群显示器

uisdc-car-201610136

它为什么会让人震惊?

没有什么能和发动引擎点亮仪表,汽车轰鸣犹如巡游乐队组成的和旋一样。身边的一切,声音和延迟的视觉互动,都给与你感官的反馈。仪表会通过动画直观的告诉你,合适讲切入下个转弯,油量是否变低,以及胎压是否过低。

然而,用数字型号在方方面面代替模拟型号也会开始令人担忧,因为无法人工手动介入账款车子,当保险丝熔断,或者电子仪器故障时,汽车很快会失控。

特斯拉 iWatch UI 原型

uisdc-car-201610137

它为什么会让人震惊?

当可穿戴设备成为我们设备的一部分时,它很明显会帮助我们的手持设备分担掉一部分的操作功能。这个原型很好的说明了它是如何将这一切展示到手腕上来的。



蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

在移动应用上故事怎么讲才有逼格?

博博

 故事也许是人类最早的叙事方法。大多数人在童年时,故事用来引导他们的行为,了解历史文化和培养社会认同感。”-Form Wikipedia

1.什么是故事(WHAT)

在讨论如何打造故事感体验之前,我们先聊聊什么是故事。

故事(Storytelling)是通过文字,声音或图像来传播的事件。它在各种文化扮演着娱乐,教育,传播文化和价值观的作用。讲故事或许是人类最早的叙事方法。大多数人在童年时,成人都会用故事来引导他们的行为,传授历史文化和培养社会认同感。注1

讲故事是一种解释和分享的方法。这在我们生活中非常普遍。它们可以弥合文化,语言和年龄之间的分歧。例如在教育中,可以通过故事让人更加容易理解。又如远古社会,人类使用纪录和传播故事,这些壁画即使过了上千年,依旧能理解其中部分含义。

故事怎么讲才有逼格?

古埃及金字塔中的。图片来自 forum.china.com.cn

甚至在工作中,通过讲故事的形式,也可以让信息在人与人之间更加的传输。比如说在讨论用户需求的会议上,通过讲故事的手法,产品设计可以将用户需求转化为一个个场景故事,便可以准确的让别人理解。 人类从古至今一直深受故事影响。从圣经到现代小说无不是故事。因此当人的大脑在听到故事时,会产生特别的反应。

在论文《A Theory of Narrative Empathy》中描述到,当人类听到故事时,大脑中的”镜像神经细胞“(一种神经细胞,使人们学会从简单模仿到复杂模仿 -Wikipedia)会异常活跃,促进人类引发共鸣并理解别人的行为,所以故事很容易让人类沉浸其中。注2

在移动应用上故事怎么讲才有逼格?

用心设计

“ 故事也许是人类最早的叙事方法。大多数人在童年时,故事用来引导他们的行为,了解历史文化和培养社会认同感。”-Form Wikipedia

bannner

交互案例实战!三个按钮背后由小见大的交互思考

用心设计

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供有效的UI界面设计BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

来源:莫贝网

初入交互设计工 作,最常见的需求就是——放按钮。放按钮看起来是超级简单的事,无非就是设计按钮的样式、摆放按钮的位置、确定按钮的状态与反馈等,但殊不知其『麻雀虽 小,五脏俱全』,小小的按钮设计中也蕴含了很多交互设计的思维,当然除了设计本身之外,还能看到如何平衡开发、产品以及用户各方的需求。

所以,本文章会通过三个真实但抽象的例子说明工作中『放按钮』是怎么做的。真实说的是案例来源于实战,抽象就是提炼出思维但讲的场景都是虚拟的,免得透露机密。有趣的是,三个按理都和时间、时效有关。

案例一

虚拟场景:某订单页面,商品订单以卡片的形式罗列,每个卡片下方有若干操作按钮,如支付、取消、投诉等。根据规则,投诉按钮必须在订单产生2天后才可使用。

现状:目前产品线上的设计方案是,几个按钮同时呈现可点击状态,当投诉按钮处于可用时间段内时,点击就会去往一个投诉页面填写投诉申请;当目前处于不可投诉时间段内,投诉按钮点击后弹窗提醒用户:还有 XXX 时间才可发起投诉。

目标:提出这个案例时,我们希望让这个操作过程更加自然,不要让用户点击了之后才告诉用户这个功能不可用,而不要脱离用户的交互期望(如果按钮可用,交互期望就应该是实现相应的功能)。

设计过程:

最开始考虑这个案例时,我的第一反应就是如果一个按钮当前是不可用的,就应该隐藏起来,这个场景按钮还是挺挤的,明明不能用的东西为什么要放在界面中吸引视线呢?

但是很明显,这个第一感觉有严重的硬伤。首先,对于一个按钮或功能,告诉用户这个东西不可用,和不告诉用户有这个功能,是完全两码事。尤其是一个存在可用/不可用两种状态的按钮,在特定时间将其隐藏的风险非常大——你不知道这个功能的被需要度多高。

于是,我们去查阅了一下数据和之前的设计方案。首先得出的结论是,这个按钮的点击率不低(超过10%的用户,而且这个页面的总 pv 非常高),其次,以前有尝试过把这个按钮直接在不可用时隐藏的设计,但是立刻引来了大量的客服投诉——用户找不到投诉入口在哪里了。

所以,似乎数据和历史方案帮助我们排除了第一种做法,也就是直接隐藏按钮。那在不可用期间把按钮置灰怎么样?

置灰的好处在于,用户一眼就能看到这个功能的存在,同时又能知道这个功能暂时是不可用的。但是问题也来了,怎么知道这个按钮什么时候可点什么时候不可点?一个解决方案是在置灰的按钮中加上文案提醒,另一个是点击置灰的按钮后给出文案提示,但是前者似乎没有足够的空间进行展示了,而后者看起来不错……但是好像又回到了原点?(后来还讨论过一些更复杂的方案,比如最后半天置灰,更早的时候隐藏等,但我们并无法判断用户对时间点的真实感受和期待。)

所以最终的结论就是,由于当前的线上形式并没有什么致命的问题,也没有收到太多关于体验方面的投诉,而考虑到开发成本、设计成本和迭代周期,还是选择保持线上的样式不变。

感觉走了一圈走回原地?在实际工作中经常遇到这样的情况,设计师通过本能和直觉判断某些内容的体验可能不是最佳的,但经过资料搜集、自我批判、成本评估后发现,似乎保持现状是最好的做法。

总结:这个案例告诉我们,设计时不要总想着找茬,第一反应往往可能漏过了诸多细节,思考清楚再做行动。

案例二

虚拟场景:对商家或商品进行投诉之后,需要用户选择投诉原因,如态度不好、价格变动、缺货等。其中,在某些特殊时间段内,如该商品本身就是秒杀商品,用户发起投诉时则不能选择『缺货』原因。

现状:线上目前没有这个功能,即所有的投诉原因都是可选的,选择组件使用的是 picker,大概如下图的样式(最常见的就是选择时间啦)。

目标:对于『缺货』这个投诉原因进行交互处理,令其在特定时间段内(商品秒杀阶段)无法选择,并给用户相关提醒。

设计过程:

乍一看和案例一非常相似,但其实差别还是挺大的。案例一属于设计师自 发尝试对体验改进,而案例二确实明确的业务需求,需要把没有的功能融入到现有方案中。拿到这个需求时,首先看到大概会涉及两个页面可以用来设计,分别是投 诉页面和投诉原因选择页(这两个通常不会直接在一起,但是后者有可能以弹层的形式出现,当然也可能新开页面),于是闪过几种可能的处理方案。

方案一,在投诉页面提前进行文案披露,告诉用户『缺货』原因不可选,同时进入原因选择页时将『缺货』选项置灰;

方案二,原因选择页的『缺货』按钮正常可选,选择后通过弹窗提醒用户该原因不可用,并恢复默认待选原因状态;

方案三,仅设计原因选择页,在『缺货』选项内提供文案如『暂不可选』,并且置灰该选项。

详细了解需求背景之后,方案三首先被排除了。原因很简单,本身置灰就已经表示不可选了,『暂不可选』文案所表达的内容太少,既没有展示出不可选的原因,也没有告诉用户什么时候恢复该投诉选项。同时,我们得知反馈的文案会比较长,大约有十几个汉字,按钮本身的空间有限,并且直接在原因字段内拼接『暂不可选』也会有点技术成本。

方案二在流程上是顺畅的,但是和案例一中提到的类似,我们一般希望用户在操作之前就能预期到交互的结果,而不是让可选的按钮点击后出现提醒再告诉用户不可选。

同时考虑方案一,对于按钮置灰有一个问题需要考虑,可以看到无线端使用的是这种滚轮式的选择组件,其本身置灰能不能做是要打个问号的,事实上和开发沟通之后发现确实无法实现。最后,综合考虑下,还是采用了方案二。

总结:通过案例二可以看到,设计中常常会有一个最优解,但又总会因为技术成本、其他业务原因的影响去妥协,最后得到的方案也许不是交互上最佳的,但确实整体效率最高的。这也说明了,新人不要轻易对其他产品做体验分析,因为你并没有这些真实限制,所得出的结论也是空中楼阁。

案例三

虚拟场景:某些操作是有时效的,比如某个商品限量销售,如果还没有到付款的时间节点,付款功能是不可用的,那么付款按钮能否优化处理。

现状:当商品不在支付时间段内,付款按钮隐藏。

目标:不要让用户找不到付款按钮,令操作更加顺畅,可见性更强。

设计过程:

和案例一一样,这也是一个设计师自发想要改善体验的过程,正好有其他业务需要对这些页面进行改造,能否把这个按钮隐藏的过程优化呢?原因相信大家都能理解,把不可用的功能藏起来,总归不是最好的做法。

仔细查看线上的样式我们发现,这个案例和案例一还不太一样。最重要的一点是,付款的时间是有倒计时的,也就是说即便付款按钮本身是隐藏起来的,但其附近依然有文案提醒现在不在支付时间内,所以用户不太容易迷惑。

比较简单的解决办法就是,把支付按钮暴露出来,并且置灰。看来今天三个案例都和置灰杠上了。经过交互小组和产品小组的讨论,都一致觉得把不可用的支付按钮置灰更加合适,并且线上已有文案提醒说明,用户也更容易理解。

但是这个案例最后的结论有些出乎意料,首先,因为线上所有类似业务都采用了隐藏不可支付按钮的做法,所以如果这类交易要改,其他全都要改(时间成本);其次,这个按钮组件在早期开发过程中,并没有设计、开发 disable 的状态,也就是没有置灰的样式,如果要做,需要重新设计和开发(开发成本);最后,线上这个隐藏的样式已经存在很长时间了,用户大部分都其已经习惯,并且没有发生普遍的反馈表示认知困难(用户理解成本)。所以,结论还是保持线上的样式。

总结:有时候设计是对的,所有人都认可,但是依然会有各种因素前来制约。尤其在较大的产品团队,业务节奏快、需求多、上线紧,这类优化体验的工作常 常优先级不够高。与此同时,很多朋友可能会说一个按钮而已,加一个 disable 的样式又有何难?然而很多团队的流程规则导致业务工作是一条线,组件优化新增样式又是另一条线,想要一步把两件事都做了并没有想象中的容易。

怎么样,简单的三个按钮,是不是让你有些晕了?交互设计的难点常常就在于体验、产品、开发各个内容的协调,相互妥协,最后达到一个平衡的状态。由小见大,小按钮我们能应付了,慢慢就可以面对大问题啦。

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档