前端及开发文章及欣赏

帮助你优化网站,提高页面速度的10种基础方法

seo达人

如今,Page Speed(页面速度)的意义非凡。


自从Google改变Googlebot's的算法以高度支持快速,适合移动设备的网站以来,拥有快速网站变得越来越重要。如果这还不够好,用户通常会花更少的时间,转化率也会更低,你的网站体验越慢,用户的转化率就越低。


什么是Page Speed

Page Speed是将内容完全加载到网页上所花费的时间。


对于任何给定的用户来说,页面缓慢的原因可能有很多,你的用户可能正在火车上,通过信号弱的隧道,或者他们的互联网速度很慢。


通过遵循最佳实践,我们至少可以通过确保我们已经做了最好的工作来缓解问题。


现在你知道它是什么了,下面我就来教你如何提高页面速度。


注意:这些是按难度顺序列出的。在某个时候,你将需要开发人员来帮助优化你的网站。


1.使用CDN



CDN是内容传输网络的缩写。使用CDN可以让你有效地访问全球数百台小服务器,这些服务器为你提供网站的副本,大大减少了你的网站获取时间。如果你没有使用CDN,你的网站的每一个请求(包括图片、CSS和JavaScript)都会被缓慢地传送到你的服务器上。


根据HTTPArchive中的4.68亿个请求,48%的请求不是来自CDN。那是超过2.24亿的请求,如果他们花几分钟的时间给自己的网站添加一个CDN,速度可能会超过50%。


一定要检查你的CDN配置是否正确——在你的CDN中缓存丢失意味着CDN必须向你的源服务器请求资源,这就违背了使用CDN的初衷!所以,你的CDN必须要有一个正确的配置。


2.启用GZIP压缩



在一些CDN上,GZIP压缩只是一个标有 "启用压缩 "的复选框。这大概会减少一半的文件大小,你的用户需要下载文件才能使用你的网站,你的用户会因此而喜欢你。


3.使用较小的图像

这意味着既要降低分辨率(例如,摄像头的输出从4000x3000像素减少到网络的1000x750),又要通过压缩文件来减小尺寸。


如果你的网站使用WordPress,则有一些插件会在你上传图片时自动为你执行此操作。


在撰写博客文章时,我个人使用TinyJPG压缩图像。


https://tinyjpg.com/


4.减少页面发出的请求数

目标是减少加载页面顶部部分所需的请求数量。


这里有两种思维方式,你可以:


通过删除花哨的动画或不能改善网站体验的图像,减少整个页面上的请求数量。

或者,你可以通过使用延迟加载来推迟优先级不高的加载内容。

5.尽可能避免重定向



重定向会大大降低网站速度。使用响应式CSS并从一个域为你的网站提供服务,而不是为移动用户提供特殊的子域。


有些重定向是不可避免的,比如 www-> 根域 或 根域 ->www,但你的大部分流量不应该经历重定向来查看你的网站。


6.减少到第一个字节的时间

到第一个字节的时间是指你的浏览器在发出资源请求后,从服务器接收到第一个字节的数据所花费的时间。


有两个部分:


在服务器上花费的时间

发送数据所花费的时间

你可以通过优化你的服务器端渲染、数据库查询、API调用、负载平衡、你的应用程序的实际代码以及服务器的负载本身(特别是如果你使用的是廉价的虚拟主机——这将影响你的网站的性能),来改善你在服务器上花费的时间。


你可以使用CDN大大减少发送数据所花费的时间。


7.减少并删除阻止渲染的JavaScript

外部脚本(特别是那些用于营销的外部脚本)往往会写得很差,会阻止你的页面加载,直到它运行完毕。


你可以通过将外部脚本标记为异步来减少这种影响:


<script async src="https://example.com/external.js"></script>

你还可以延迟加载市场营销脚本,直到用户开始滚动为止:


window.addEventListener(

 'scroll',

 () =>

   setTimeout(() => {

     // 在此插入营销片段

   }, 1000),

 { once: true }

);

8.缩小CSS和JS

Minifying是指使用工具来删除空格、换行符和缩短变量名。通常情况下,这将作为构建过程的一部分自动完成。


要缩小JavaScript,请查看UglifyJS。


http://lisperator.net/uglifyjs/


要缩小CSS,请查看cssnano。


9.删除未使用的CSS

自Chrome 59(2017年4月发布)以来,在Chrome DevTools中可以看到未使用的JS和CSS。


要看这个,打开DevTools,显示控制台抽屉(就是点击Esc时出现的那个烦人的东西),点击左下角的三个小点,打开 "Coverage",就可以看到。


点击带有重新加载图标的按钮将刷新页面,并审核CSS和JS的使用情况。


在Google Chrome浏览器中审核初始页面时,外观如下所示:




10.定期跟踪网站速度

在你的网站速度变慢的瞬间,修复网站速度问题就会容易得多。除此之外,如果你把检查网站速度作为一种习惯,那么修复网站速度慢的问题就会变成一件小得多的事情。


有免费的工具可以监视你网站的速度,其中的两个是WebPageTest和Google Lighthouse。这些工具的缺点是你需要记住在进行更改之前和之后都必须运行它们。


PerfBeacon是一项服务(由本文的作者创建),该服务定期运行Google Lighthouse,并让你随时跟踪网站的速度。


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

JavaScript中的缓存API

seo达人

了解如何使用JavaScript中的Cache API缓存资源。


Cache API允许服务工作者对要缓存的资源(HTML页面、CSS、JavaScript文件、图片、JSON等)进行控制。通过Cache API,服务工作者可以缓存资源以供脱机使用,并在以后检索它们。


检测Cache支持

检查 caches 对象在 window 中是否可用。


let isCacheSupported = 'caches' in window;

caches 是 CacheStorage 的一个实例。


创建/初始化Cache

我们可以使用 open 方法创建一个具有 name 的缓存,这将返回 promise。如果缓存已经存在,则不会创建新的缓存。


caches.open('cacheName').then( cache => {

});

你不能访问为其他源(域)设置的缓存。

你正在创建的缓存将为你的域创建。

你可以为同一个域添加多个缓存,可以通过 caches.keys() 访问。

将项目添加到缓存

可以使用三种方法 add,addAll,set 来缓存资源。 add() 和 addAll() 方法自动获取资源并对其进行缓存,而在 set 方法中,我们将获取数据并设置缓存。


add

let cacheName = 'userSettings';

let url = '/api/get/usersettings';

caches.open(cacheName).then( cache => {

  cache.add(url).then( () => {

      console.log("Data cached ")

   });

});

在上面的代码中,内部对 /api/get/usersettings url的请求已发送到服务器,一旦接收到数据,响应将被缓存。


addAll

addAll 接受URL数组,并在缓存所有资源时返回Promise。


let urls = ['/get/userSettings?userId=1', '/get/userDetails'];

caches.open(cacheName).then( cache => {

cache.addAll(urls).then( () => {

      console.log("Data cached ")

   });

});

Cache.add/Cache.addAll 不缓存 Response.status 值不在200范围内的响应,Cache.put 可以让你存储任何请求/响应对。


put

put 为当前的 Cache 对象添加一个key/value对,在 put 中,我们需要手动获取请求并设置值。


注意:put() 将覆盖先前存储在高速缓存中与请求匹配的任何键/值对。


let cacheName = 'userSettings';

let url = '/api/get/userSettings';

fetch(url).then(res => {

 return caches.open(cacheName).then(cache => {

   return cache.put(url, res);

 })

})

从缓存中检索

使用 cache.match() 可以得到存储到URL的 Response。


const cacheName = 'userSettings'

const url = '/api/get/userSettings'

caches.open(cacheName).then(cache => {

 cache.match(url).then(settings => {

   console.log(settings);

 }

});

settings 是一个响应对象,它看起来像


Response {

 body: (...),

 bodyUsed: false,

 headers: Headers,

 ok: true,

 status: 200,

 statusText: "OK",

 type: "basic",

 url: "https://test.com/api/get/userSettings"

}

检索缓存中的所有项目

cache 对象包含 keys 方法,这些方法将拥有当前缓存对象的所有url。


caches.open(cacheName).then( (cache) => {

 cache.keys().then((arrayOfRequest) => {

     console.log(arrayOfRequest); // [Request,  Request]

 });

});

arrayOfRequest是一个Request对象数组,其中包含有关请求的所有详细信息。


检索所有缓存

caches.keys().then(keys => {

 // keys是一个数组,其中包含键的列表

})

从缓存中删除项目

可以对 cache 对象使用 delete 方法来删除特定的缓存请求。


let cacheName = userSettings;

let urlToDelete = '/api/get/userSettings';

caches.open(cacheName).then(cache => {

 cache.delete(urlToDelete)

})

完全删除缓存

caches.delete(cacheName).then(() => {

  console.log('Cache successfully deleted!');

})

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

Vue中使用装饰器,我是认真的

seo达人

作为一个曾经的Java coder, 当我第一次看到js里面的装饰器(Decorator)的时候,就马上想到了Java中的注解,当然在实际原理和功能上面,Java的注解和js的装饰器还是有很大差别的。本文题目是Vue中使用装饰器,我是认真的,但本文将从装饰器的概念开发聊起,一起来看看吧。


通过本文内容,你将学到以下内容:


了解什么是装饰器

在方法使用装饰器

在class中使用装饰器

在Vue中使用装饰器

本文首发于公众号【前端有的玩】,不想当咸鱼,想要换工作,关注公众号,带你每日一起刷大厂面试题,关注 === 大厂offer。

什么是装饰器

装饰器是ES2016提出来的一个提案,当前处于Stage 2阶段,关于装饰器的体验,可以点击 https://github.com/tc39/proposal-decorators查看详情。装饰器是一种与类相关的语法糖,用来包装或者修改类或者类的方法的行为,其实装饰器就是设计模式中装饰者模式的一种实现方式。不过前面说的这些概念太干了,我们用人话来翻译一下,举一个例子。


在日常开发写bug过程中,我们经常会用到防抖和节流,比如像下面这样


class MyClass {

 follow = debounce(function() {

   console.log('我是子君,关注我哦')

 }, 100)

}


const myClass = new MyClass()

// 多次调用只会输出一次

myClass.follow()

myClass.follow()

上面是一个防抖的例子,我们通过debounce函数将另一个函数包起来,实现了防抖的功能,这时候再有另一个需求,比如希望在调用follow函数前后各打印一段日志,这时候我们还可以再开发一个log函数,然后继续将follow包装起来


/**

* 最外层是防抖,否则log会被调用多次

*/

class MyClass {

 follow = debounce(

   log(function() {

     console.log('我是子君,关注我哦')

   }),

   100

 )

}

上面代码中的debounce和log两个函数,本质上是两个包装函数,通过这两个函数对原函数的包装,使原函数的行为发生了变化,而js中的装饰器的原理就是这样的,我们使用装饰器对上面的代码进行改造


class MyClass {

 @debounce(100)

 @log

 follow() {

   console.log('我是子君,关注我哦')

 }

}

装饰器的形式就是 @ + 函数名,如果有参数的话,后面的括号里面可以传参


在方法上使用装饰器

装饰器可以应用到class上或者class里面的属性上面,但一般情况下,应用到class属性上面的场景会比较多一些,比如像上面我们说的log,debounce等等,都一般会应用到类属性上面,接下来我们一起来具体看一下如何实现一个装饰器,并应用到类上面。在实现装饰器之前,我们需要先了解一下属性描述符


了解一下属性描述符

在我们定义一个对象里面的属性的时候,其实这个属性上面是有许多属性描述符的,这些描述符标明了这个属性能不能修改,能不能枚举,能不能删除等等,同时ECMAScript将这些属性描述符分为两类,分别是数据属性和访问器属性,并且数据属性与访问器属性是不能共存的。


数据属性

数据属性包含一个数据值的位置,在这个位置可以读取和写入值。数据属性包含了四个描述符,分别是


configurable

表示能不能通过delete删除属性,能否修改属性的其他描述符特性,或者能否将数据属性修改为访问器属性。当我们通过let obj = {name: ''}声明一个对象的时候,这个对象里面所有的属性的configurable描述符的值都是true


enumerable

表示能不能通过for in或者Object.keys等方式获取到属性,我们一般声明的对象里面这个描述符的值是true,但是对于class类里面的属性来说,这个值是false


writable

表示能否修改属性的数据值,通过将这个修改为false,可以实现属性只读的效果。


value

表示当前属性的数据值,读取属性值的时候,从这里读取;写入属性值的时候,会写到这个位置。


访问器属性

访问器属性不包含数据值,他们包含了getter与setter两个函数,同时configurable与enumerable是数据属性与访问器属性共有的两个描述符。


getter

在读取属性的时候调用这个函数,默认这个函数为undefined


setter

在写入属性值的时候调用这个函数,默认这个函数为undefined


了解了这六个描述符之后,你可能会有几个疑问: 我如何去定义修改这些属性描述符?这些属性描述符与今天的文章主题有什么关系?接下来是揭晓答案的时候了。


使用Object.defineProperty

了解过vue2.0双向绑定原理的同学一定知道,Vue的双向绑定就是通过使用Object.defineProperty去定义数据属性的getter与setter方法来实现的,比如下面有一个对象


let obj = {

 name: '子君',

 officialAccounts: '前端有的玩'

}

我希望这个对象里面的用户名是不能被修改的,用Object.defineProperty该如何定义呢?


Object.defineProperty(obj,'name', {

 // 设置writable 是 false, 这个属性将不能被修改

 writable: false

})

// 修改obj.name

obj.name = "君子"

// 打印依然是子君

console.log(obj.name)

通过Object.defineProperty可以去定义或者修改对象属性的属性描述符,但是因为数据属性与访问器属性是互斥的,所以一次只能修改其中的一类,这一点需要注意。


定义一个防抖装饰器

装饰器本质上依然是一个函数,不过这个函数的参数是固定的,如下是防抖装饰器的代码


/**

*@param wait 延迟时长

*/

function debounce(wait) {

 return function(target, name, descriptor) {

   descriptor.value = debounce(descriptor.value, wait)

 }

}

// 使用方式

class MyClass {

 @debounce(100)

 follow() {

   console.log('我是子君,我的公众号是 【前端有的玩】,关注有惊喜哦')

 }

}

我们逐行去分析一下代码


首先我们定义了一个 debounce函数,同时有一个参数wait,这个函数对应的就是在下面调用装饰器时使用的@debounce(100)

debounce函数返回了一个新的函数,这个函数即装饰器的核心,这个函数有三个参数,下面逐一分析


target: 这个类属性函数是在谁上面挂载的,如上例对应的是MyClass类

name: 这个类属性函数的名称,对应上面的follow

descriptor: 这个就是我们前面说的属性描述符,通过直接descriptor上面的属性,即可实现属性只读,数据重写等功能

然后第三行 descriptor.value = debounce(descriptor.value, wait), 前面我们已经了解到,属性描述符上面的value对应的是这个属性的值,所以我们通过重写这个属性,将其用debounce函数包装起来,这样在函数调用follow时实际调用的是包装后的函数

通过上面的三步,我们就实现了类属性上面可使用的装饰器,同时将其应用到了类属性上面


在class上使用装饰器

装饰器不仅可以应用到类属性上面,还可以直接应用到类上面,比如我希望可以实现一个类似Vue混入那样的功能,给一个类混入一些方法属性,应该如何去做呢?


// 这个是要混入的对象

const methods = {

 logger() {

   console.log('记录日志')

 }

}


// 这个是一个登陆登出类

class Login{

 login() {}

 logout() {}

}

如何将上面的methods混入到Login中,首先我们先实现一个类装饰器


function mixins(obj) {

 return function (target) {

   Object.assign(target.prototype, obj)  

 }

}


// 然后通过装饰器混入

@mixins(methods)

class Login{

 login() {}

 logout() {}

}

这样就实现了类装饰器。对于类装饰器,只有一个参数,即target,对应的就是这个类本身。


了解完装饰器,我们接下来看一下如何在Vue中使用装饰器。


在Vue中使用装饰器

使用ts开发Vue的同学一定对vue-property-decorator不会感到陌生,这个插件提供了许多装饰器,方便大家开发的时候使用,当然本文的中点不是这个插件。其实如果我们的项目没有使用ts,也是可以使用装饰器的,怎么用呢?


配置基础环境

除了一些老的项目,我们现在一般新建Vue项目的时候,都会选择使用脚手架vue-cli3/4来新建,这时候新建的项目已经默认支持了装饰器,不需要再配置太多额外的东西,如果你的项目使用了eslint,那么需要给eslint配置以下内容。


 parserOptions: {

   ecmaFeatures:{

     // 支持装饰器

     legacyDecorators: true

   }

 }

使用装饰器

虽然Vue的组件,我们一般书写的时候export出去的是一个对象,但是这个并不影响我们直接在组件中使用装饰器,比如就拿上例中的log举例。


function log() {

 /**

  * @param target 对应 methods 这个对象

  * @param name 对应属性方法的名称

  * @param descriptor 对应属性方法的修饰符

  */

 return function(target, name, descriptor) {

   console.log(target, name, descriptor)

   const fn = descriptor.value

   descriptor.value = function(...rest) {

     console.log(`这是调用方法【${name}】前打印的日志`)

     fn.call(this, ...rest)

     console.log(`这是调用方法【${name}】后打印的日志`)

   }

 }

}


export default {

 created() {

   this.getData()

 },

 methods: {

   @log()

   getData() {

     console.log('获取数据')

   }

 }

}

看了上面的代码,是不是发现在Vue中使用装饰器还是很简单的,和在class的属性上面使用的方式一模一样,但有一点需要注意,在methods里面的方法上面使用装饰器,这时候装饰器的target对应的是methods。


除了在methods上面可以使用装饰器之外,你也可以在生命周期钩子函数上面使用装饰器,这时候target对应的是整个组件对象。


一些常用的装饰器

下面小编罗列了几个小编在项目中常用的几个装饰器,方便大家使用


1. 函数节流与防抖

函数节流与防抖应用场景是比较广的,一般使用时候会通过throttle或debounce方法对要调用的函数进行包装,现在就可以使用上文说的内容将这两个函数封装成装饰器, 防抖节流使用的是lodash提供的方法,大家也可以自行实现节流防抖函数哦


import { throttle, debounce } from 'lodash'

/**

* 函数节流装饰器

* @param {number} wait 节流的毫秒

* @param {Object} options 节流选项对象

* [options.leading=true] (boolean): 指定调用在节流开始前。

* [options.trailing=true] (boolean): 指定调用在节流结束后。

*/

export const throttle =  function(wait, options = {}) {

 return function(target, name, descriptor) {

   descriptor.value = throttle(descriptor.value, wait, options)

 }

}


/**

* 函数防抖装饰器

* @param {number} wait 需要延迟的毫秒数。

* @param {Object} options 选项对象

* [options.leading=false] (boolean): 指定在延迟开始前调用。

* [options.maxWait] (number): 设置 func 允许被延迟的最大值。

* [options.trailing=true] (boolean): 指定在延迟结束后调用。

*/

export const debounce = function(wait, options = {}) {

 return function(target, name, descriptor) {

   descriptor.value = debounce(descriptor.value, wait, options)

 }

}

封装完之后,在组件中使用


import {debounce} from '@/decorator'


export default {

 methods:{

   @debounce(100)

   resize(){}

 }

}

2. loading

在加载数据的时候,为了个用户一个友好的提示,同时防止用户继续操作,一般会在请求前显示一个loading,然后在请求结束之后关掉loading,一般写法如下


export default {

 methods:{

   async getData() {

     const loading = Toast.loading()

     try{

       const data = await loadData()

       // 其他操作

     }catch(error){

       // 异常处理

       Toast.fail('加载失败');

     }finally{

       loading.clear()

     }  

   }

 }

}

我们可以把上面的loading的逻辑使用装饰器重新封装,如下代码


import { Toast } from 'vant'


/**

* loading 装饰器

* @param {*} message 提示信息

* @param {function} errorFn 异常处理逻辑

*/

export const loading =  function(message = '加载中...', errorFn = function() {}) {

 return function(target, name, descriptor) {

   const fn = descriptor.value

   descriptor.value = async function(...rest) {

     const loading = Toast.loading({

       message: message,

       forbidClick: true

     })

     try {

       return await fn.call(this, ...rest)

     } catch (error) {

       // 在调用失败,且用户自定义失败的回调函数时,则执行

       errorFn && errorFn.call(this, error, ...rest)

       console.error(error)

     } finally {

       loading.clear()

     }

   }

 }

}

然后改造上面的组件代码


export default {

 methods:{

   @loading('加载中')

   async getData() {

     try{

       const data = await loadData()

       // 其他操作

     }catch(error){

       // 异常处理

       Toast.fail('加载失败');

     }  

   }

 }

}

3. 确认框

当你点击删除按钮的时候,一般都需要弹出一个提示框让用户确认是否删除,这时候常规写法可能是这样的


import { Dialog } from 'vant'


export default {

 methods: {

   deleteData() {

     Dialog.confirm({

       title: '提示',

       message: '确定要删除数据,此操作不可回退。'

     }).then(() => {

       console.log('在这里做删除操作')

     })

   }

 }

}

我们可以把上面确认的过程提出来做成装饰器,如下代码


import { Dialog } from 'vant'


/**

* 确认提示框装饰器

* @param {*} message 提示信息

* @param {*} title 标题

* @param {*} cancelFn 取消回调函数

*/

export function confirm(

 message = '确定要删除数据,此操作不可回退。',

 title = '提示',

 cancelFn = function() {}

) {

 return function(target, name, descriptor) {

   const originFn = descriptor.value

   descriptor.value = async function(...rest) {

     try {

       await Dialog.confirm({

         message,

         title: title

       })

       originFn.apply(this, rest)

     } catch (error) {

       cancelFn && cancelFn(error)

     }

   }

 }

}

然后再使用确认框的时候,就可以这样使用了


export default {

 methods: {

   // 可以不传参,使用默认参数

   @confirm()

   deleteData() {

     console.log('在这里做删除操作')

   }

 }

}

是不是瞬间简单多了,当然还可以继续封装很多很多的装饰器,因为文章内容有限,暂时提供这三个。


装饰器组合使用

在上面我们将类属性上面使用装饰器的时候,说道装饰器可以组合使用,在Vue组件上面使用也是一样的,比如我们希望在确认删除之后,调用接口时候出现loading,就可以这样写(一定要注意顺序)


export default {

 methods: {

   @confirm()

   @loading()

   async deleteData() {

     await delete()

   }

 }

}

本节定义的装饰器,均已应用到这个项目中 https://github.com/snowzijun/vue-vant-base, 这是一个基于Vant开发的开箱即用移动端框架,你只需要fork下来,无需做任何配置就可以直接进行业务开发,欢迎使用,喜欢麻烦给一个star。

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




10 个最佳 CSS 动画库

seo达人

目录

1. Animista

2. Animate CSS

3. Vivify

4. Magic Animations CSS3

5. cssanimation.io

6. Angrytools

7. Hover.css

8. WickedCSS

9. Three Dots

10. CSShake


1.Animista

网站地址:http://animista.net/

网站描述:在线生成 css 动画




Animista是一个在线动画生成器,同时也是一个动画库,它为我们提供了以下功能


1. 选择不同的动画

我们可以选择想要的动画类型(例如entrance/exist),除了可以选择某个动画(例如,scale-in)外,甚至还可以为该动画选择不同的展示效果(例如: scale-in-right)。




2. 定制

Animista还提供了一个功能,允许我们定制动画的某些部分,比如


duration

delay

direction

更好的是,可以选择要设置动画的对象:




3. 生成CSS代码

选择适合自己需要的动画后,我们可以直接从网站上获取代码,甚者可以进行压缩:




4. 下载代码

另一个好用的功能是,可以把自己收藏自己喜欢的动画,然后一起下载下来, 或者,我们也可以选择将这些动画的代码复制到一起。




2. Animate CSS

网站地址:http://daneden.github.io/anim...


网站描述:齐全的CSS3动画库




想必这个不用介绍,大部分人都知道了。Animate CSS 可能是最著名的动画库之一。这里简要介绍一下它的用法:


1. 用法

首先,必须在总需要动画元素上添加类animated ,然后是动画的名字。


<div class="animated slideInLeft"></div>

如果我们想让动画一直持续,可以添加infinite类。


通过 JS 来添加动画:


document.querySelector('.my-element').classList.add('animated', 'slideInLeft')

通过 JQ 来添加动画:


$(".my-element").addClass("animated slideInLeft")

2. 其它功能

Animate CSS提供了一些基本的类来控制动画的延迟和速度。


delay


可以添加 delay 类来延迟动画的播放。


<div class="animated slideInLeft delay-{1-5}"><div>

speed


我们还可以通过添加如下列出的类之一来控制动画速度。


类名 速度时间

show 2s

slower 3s

fast 800ms

faster 500ms

<div class="animated slideInLeft slow|slower|fast|faster"><div>


3. Vivify

网站地址: http://vivify.mkcreative.cz/


网站描述: 一个更加丰富css动画库




Vivify 是一个动画库,可以看作是Animate CSS的增强版。它们的工作方式完全相同,有Animate CSS的大多数类且还扩展了一些。


<div class="vivify slideInLeft"></div>

使用 JS 方式:


document.querySelector('.my-element').classList.add('vivify', 'slideInLeft')

使用 JQ 方式:


$(".my-element").addClass("vivify slideInLeft")

与Animate CSS一样,Vivify 还提供了一些类来控制动画的持续时间和延迟。


延迟和持续时间类在以下间隔中可用:


<div class="delay|duration-{100|150|200|250...1000|1250|1500|1750...10750}"></div>

4. Magic Animations CSS3

网站地址: https://www.minimamente.com/p...


网站描述: Magic CSS3 Animations 是 CSS3 动画的包,伴有特殊的效果,用户可以自由的在 web 项目中使用。




这个动画库有一些非常漂亮和流畅的动画,特别是3D的。没什么好说的,自己去尝试。


<div class="magictime fadeIn"></div>

使用 JS 方式:


document.querySelector('.my-element').classList.add('magictime', 'fadeIn')

使用 JQ 方式:


$(".my-element").addClass("magictime fadeIn")

5. cssanimation.io



网站地址: http://cssanimation.io/index....


cssanimation.io是一大堆不同动画的集合,总共大概有200个,这很强大。如果你连在这里都没有找到你所需的动画,那么在其它也将很难找到。


它的工作原理与 Animista 类似。例如,可以选择一个动画并直接从站点获取代码,或者也可以下载整个库。




用法


将cssanimation {animation_name}添加到指定的元素上。


<div class="cssanimation fadeIn"></div>

使用 JS


document.querySelector('.my-element').classList.add('cssanimation','fadeIn')

使用 JQ


$(".my-element").addClass("cssanimation fadeIn")

还可以添加 infinite 类,这样动画就可以循环播放。


<div class="cssanimation fadeIn infinite"></div>

   

此外,cssanimation.io还为我们提供了动漫字母的功能。使用这个需要引入letteranimation.js文件,然后将le {animation_name}添加到我们的文本元素中。


<div class="cssanimation leSnake"></div>

要使字母按顺序产生动画,添加sequence类,要使它们随机产生动画,添加random类。


<div class="cssanimation leSnake {sequence|random}"></div>

Sequence



Random




6.Angrytools

网站地址: https://angrytools.com/css/an...


如果使用不同的生成器,Angrytools实际上是一个集合,其中还包括CSS动画生成器。


它可能不像Animista那么复杂,但我觉得这个也很不错。这个站点还提供了一些自定义动画的特性,比如动画的持续时间或延迟。


但是我喜欢的是,我们可以在其展示时间轴上添加自定义的keyframes,然后可以直接在其中编写代码。 另外,也可以编辑现有的。




当我们完成的时候,可以得到完整的动画代码,也可以下载它。


7.Hover.css

网站地址: http://ianlunn.github.io/Hover/

网站描述: 纯CSS3鼠标滑过效果动画库


Hover.css是许多CSS动画的集合,与上面的动画不同,每次将元素悬停时都会触发。


一组CSS3支持的悬停效果,可应用于链接、按钮、徽标、SVG和特色图像等。

** 用法


它非常简单:只需将类的名称添加到元素中,比如


<button class="hvr-fade">Hover me!</button>

8.WickedCSS

网站地址: http://kristofferandreasen.gi...


WickedCSS是一个小的CSS动画库,它没有太多的动画变体,但至少有很大的变化。 其中大多数是我们已经熟悉的基础知识,但它们确实很干净。


它的用法很简单,只需将动画的名称添加到元素中即可。


<div class="bounceIn"></div>

** 使用 JS


document.querySelector('.my-element').classList.add('bounceIn')

** 使用 JQ


$(".my-element").addClass("bounceIn")






9.Three Dots

网站地址: https://nzbin.github.io/three...




Three Dots是一组CSS加载动画,它由三个点组成,而这些点仅由单个元素组成。


** 用法


只需创建一个div元素,并添加动画的名称


<div class="dot-elastic"></div>


10.CSShake

网站地址: https://elrumordelaluz.github...




顾名思义,CSShake是一个CSS动画库,其中包含不同类型的震动动画。


** 用法


将shake {animation name}添加到元素中。


<div class="shake shake-hard"></div>

使用 JS


document.querySelector('.my-element').classList.add('shake','shake-hard')

使用 JQ


$(".my-element").addClass("shake shake-hard")

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。


原文:https://dev.to/weeb/10-of-the...


交流

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




你现在可以使用的10个JavaScript代码段

seo达人

毫无疑问,JavaScript是Web开发中最流行的编程语言之一。无论您使用的是React,Vue还是Angular,都只是JavaScript。围绕JS展开了广泛而重要的生态系统,提供了无数的框架和库,可帮助你更快地开发应用程序。


但是有时候最好退一步,尝试了解如何在没有库的情况下做事。看看下面的代码片段,以优雅的方式解决简单的问题,并在日常项目情况下使用这些知识或为编码面试做准备。


1.反转字符串

在此示例中,我们使用扩展运算符(…),Array的reverse方法和String的join方法来反转给定的字符串。


const reverseString = string => [...string].reverse().join('');


// 例子

reverseString('javascript'); // 'tpircsavaj'

reverseString('good'); // 'doog'

2.计算数字的阶乘

要计算给定数字的阶乘,我们使用箭头函数和嵌套三元运算符。


const factoriaOfNumber = number => number < 0 ? (() => {

 throw new TypeError('No negative numbers please');

})()

: number <=1

? 1

: number * factoriaOfNumber(number -1);


// 例子

factoriaOfNumber(4); // 24

factoriaOfNumber(8); // 40320

3.将数字转换为数字数组

在此示例中,我们使用扩展运算符(…),Array的map方法和 parseInt 函数将给定的数字转换为一个单数的数组。


const convertToArray = number => [...`${number}`].map(el => parseInt(el));


// 例子

convertToArray(5678); // [5, 6, 7, 8]

convertToArray(123456789); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

4.检查数字是否为2的幂

这很简单。我们检查该数字不是伪造的,并使用按位AND运算符(&)来确定数字是否为2的幂。


const isNumberPowerOfTwo = number => !!number && (number & (number - 1)) == 0;


// 例子

isNumberPowerOfTwo(100); // false

isNumberPowerOfTwo(128); // true

5.从对象创建键值对数组

在此示例中,我们使用Object中的keys方法和Array中的map方法来映射Object的键并创建键/值对数组。


const keyValuePairsToArray = object => Object.keys(object).map(el => [el, object[el]]);


// 例子

keyValuePairsToArray({ Better: 4, Programming: 2 });

// [ ['Better', 4], ['Programming', 2] ]

keyValuePairsToArray({ x: 1, y: 2, z: 3 });

// [ ['x', 1], ['y', 2], ['z', 3] ]

6.返回数组中的[Number]个最大元素

为了从数组中返回最大元素,我们使用了一个箭头函数,该函数获取数组和我们希望函数返回的元素数。我们使用扩展运算符(…)以及Array中的sort和slice方法。请注意,如果我们不提供第二个参数,则 number 的默认值为 1,因此仅返回一个最大元素。


const maxElementsFromArray = (array, number = 1) => [...array].sort((x, y) => y - x).slice(0, number);


// 例子

maxElementsFromArray([1,2,3,4,5]); // [5]

maxElementsFromArray([7,8,9,10,10],2); // [10, 10]

7.检查数组中的所有元素是否相等

在这个简短的示例中,我们使用Array中的every方法检查数组中的所有元素是否相等。我们基本上检查每个元素是否等于数组中的第一个元素。


const elementsAreEqual = array => array.every(el => el === array[0]);


// 例子

elementsAreEqual([9,8,7,6,5]); // false

elementsAreEqual([4,4,4,4,4]); // true

8.返回两个数的平均值

在此示例中,我们使用了扩展运算符(…)和Array中的reduce方法来返回两个给定数字或一个数组的平均值。


const averageOfTwoNumbers = (...numbers) => numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0) / numbers.length;


// 例子

averageOfTwoNumbers(...[6,7,8]); // 7

averageOfTwoNumbers(6,7,8,9); // 7.5

9.返回两个或多个数字的总和

要返回两个或多个给定数字或一个数组的总和,我们再次使用扩展运算符(…)和Array中的reduce方法。


const sumOfNumbers = (...array) => [...array].reduce((accumulator, currentValue) => accumulator + currentValue, 0);


// 例子

sumOfNumbers(5,6,7,8,9.10); // 45

sumOfNumbers(...[1,2,3,4,5,6,7,8,9,10]); // 50

10.返回数字数组的幂集

在最后一个示例中,我们要返回数字数组的幂集。因此,我们使用Array中的reduce,map和concat方法。


const powersetOfArray = array => array.reduce((accumulator, currentValue) => accumulator.concat(accumulator.map(el => [currentValue].concat(el))), [[]]);


// 例子

powersetOfArray([4, 2]); // [[], [4], [2], [2, 4]]

powersetOfArray([1, 2, 3]); /

// [[], [1], [2], [2, 1], [3], [3, 1], [3, 2], [3, 2, 1]]

如你所见,使用JavaScript和一些ES6魔术来解决这些任务并不总是困难的。

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



JavaScript中的map()和forEach()有什么区别?

seo达人

阅读之前

基本上,在JavaScript中遍历对象取决于对象是否可迭代。默认情况下,数组是可迭代的。map 和 forEach 包含在Array.prototype 中,因此我们无需考虑可迭代性。如果你想进一步学习,我推荐你看看什么是JavaScript中的可迭代对象!


什么是map()和forEach()?

map 和 forEach 是数组中的帮助器方法,可以轻松地在数组上循环。我们曾经像下面这样循环遍历一个数组,没有任何辅助函数。


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

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

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

}

// 1

// 2

// 3

自JavaScript时代开始以来,就一直存在 for 循环。它包含3个表达式:初始值,条件和最终表达式。


这是循环数组的经典方法。从ECMAScript 5开始,新功能似乎使我们更加快乐。


map

map 的作用与 for 循环完全相同,只是 map 会创建一个新数组,其结果是在调用数组中的每个元素上调用提供的函数。


它需要两个参数:一个是稍后在调用 map 或 forEach 时调用的回调函数,另一个是回调函数被调用时使用的名为 thisArg 的上下文变量。


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

// 回调函数接受3个参数

// 数组的当前值作为第一个参数

// 当前值在数组中的位置作为第二个参数

// 原始源数组作为第三个参数

const cb = (str, i, origin) => {

 console.log(`${i}: ${Number(str)} / ${origin}`);

};

arr.map(cb);

// 0: 1 / 1,2,3

// 1: 2 / 1,2,3

// 2: 3 / 1,2,3

回调函数可以如下使用。


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

map 的结果不等于原始数组。


const arr = [1];

const new_arr = arr.map(d => d);


arr === new_arr; // false

你还可以将对象作为 thisArg 传递到map。


const obj = { name: 'Jane' };


[1].map(function() {

 // { name: 'Jane' }

 console.dir(this);

}, obj);


[1].map(() => {

 // window

 console.dir(this);

}, obj);

对象 obj 成为 map 的 thisArg。但是箭头回调函数无法将 obj 作为其 thisArg。


这是因为箭头函数与正常函数不同。


forEach

forEach 是数组的另一个循环函数,但 map 和 forEach 在使用中有所不同。map 和 forEach 可以使用两个参数——回调函数和 thisArg,它们用作其 this。


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

// 回调函数接受3个参数

// 数组的当前值作为第一个参数

// 当前值在数组中的位置作为第二个参数

// 原始源数组作为第三个参数

const cb = (str, i, origin) => {

 console.log(`${i}: ${Number(str)} / ${origin}`);

};

arr.forEach(cb);

// 0: 1 / 1,2,3

// 1: 2 / 1,2,3

// 2: 3 / 1,2,3

那有什么不同?


map 返回其原始数组的新数组,但是 forEach 却没有。但是它们都确保了原始对象的不变性。


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

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

如果更改数组内的值,forEach 不能确保数组的不变性。这个方法只有在你不接触里面的任何值时,才能保证不变性。


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

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

// 数组已更改!

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

由于它们之间的主要区别在于是否有返回值,所以你会希望使用 map 来制作一个新的数组,而使用 forEach 只是为了映射到数组上。


这是一个简单的例子。


const people = [

 { name: 'Josh', whatCanDo: 'painting' },

 { name: 'Lay', whatCanDo: 'security' },

 { name: 'Ralph', whatCanDo: 'cleaning' }

];


function makeWorkers(people) {

 return people.map((person) => {

   const { name, whatCanDo } = person;

   return <li key={name}>My name is {name}, I can do {whatCanDo}</li>

 });

}


<ul>makeWorkers(people)</ul>

比如在React中,map 是非常常用的制作元素的方法,因为 map 在对原数组的数据进行操作后,会创建并返回一个新的数组。


const mySubjectId = ['154', '773', '245'];


function countSubjects(subjects) {

 let cnt = 0;

 

 subjects.forEach(subject => {

   if (mySubjectId.includes(subject.id)) {

     cnt += 1;

   }

 });

 

 return cnt;

}


countSubjects([

 { id: '223', teacher: 'Mark' },

 { id: '154', teacher: 'Linda' }

]);

// 1

另一方面,当你想对数据进行某些操作而不创建新数组时,forEach 很有用。顺便说一句,可以使用 filter 重构示例。


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

综上所述,我建议你在创建一个新的数组时使用map,当你不需要制作一个新的数组,而是要对数据做一些事情时,就使用forEach。


速度比较

有些帖子提到 map 比 forEach 快。所以,我很好奇这是不是真的。我找到了这个对比结果。






该代码看起来非常相似,但结果却相反。有些测试说 forEach 更快,有些说 map 更快。也许你在告诉自己 map/forEach 比其他的快,你可能是对的。老实说,我不确定。我认为在现代Web开发中,可读性比 map 和 forEach 之间的速度重要得多。


但可以肯定的是——两者都比JavaScript内置的 for 循环慢。

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




细聊Concent & Recoil , 探索react数据流的新开发模式

seo达人

序言

之前发表了一篇文章 redux、mobx、concent特性大比拼, 看后生如何对局前辈,吸引了不少感兴趣的小伙伴入群开始了解和使用 concent,并获得了很多正向的反馈,实实在在的帮助他们提高了开发体验,群里人数虽然还很少,但大家热情高涨,技术讨论氛围浓厚,对很多新鲜技术都有保持一定的敏感度,如上个月开始逐渐被提及得越来越多的出自facebook的状态管理方案 recoil,虽然还处于实验状态,但是相必大家已经私底下开始欲欲跃试了,毕竟出生名门,有fb背书,一定会大放异彩。


不过当我体验完recoil后,我对其中标榜的更新保持了怀疑态度,有一些误导的嫌疑,这一点下文会单独分析,是否属于误导读者在读完本文后自然可以得出结论,总之本文主要是分析Concent与Recoil的代码风格差异性,并探讨它们对我们将来的开发模式有何新的影响,以及思维上需要做什么样的转变。


数据流方案之3大流派

目前主流的数据流方案按形态都可以划分以下这三类


redux流派

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


mobx流派

借助definePerperty和Proxy完成数据劫持,从而达到响应式编程目的的代表,类mobx的作品也有不少,如dob等。


Context流派

这里的Context指的是react自带的Context api,基于Context api打造的数据流方案通常主打轻量、易用、概览少,代表作品有unstated、constate等,大多数作品的核心代码可能不超过500行。


到此我们看看Recoil应该属于哪一类?很显然按其特征属于Context流派,那么我们上面说的主打轻量对

Recoil并不适用了,打开其源码库发现代码并不是几百行完事的,所以基于Context api做得好用且强大就未必轻量,由此看出facebook对Recoil是有野心并给予厚望的。


我们同时也看看Concent属于哪一类呢?Concent在v2版本之后,重构数据追踪机制,启用了defineProperty和Proxy特性,得以让react应用既保留了不可变的追求,又享受到了运行时依赖收集和ui更新的性能提升福利,既然启用了defineProperty和Proxy,那么看起来Concent应该属于mobx流派?


事实上Concent属于一种全新的流派,不依赖react的Context api,不破坏react组件本身的形态,保持追求不可变的哲学,仅在react自身的渲染调度机制之上建立一层逻辑层状态分发调度机制,defineProperty和Proxy只是用于辅助收集实例和衍生数据对模块数据的依赖,而修改数据入口还是setState(或基于setState封装的dispatch, invoke, sync),让Concent可以0入侵的接入react应用,真正的即插即用和无感知接入。


即插即用的核心原理是,Concent自建了一个平行于react运行时的全局上下文,精心维护这模块与实例之间的归属关系,同时接管了组件实例的更新入口setState,保留原始的setState为reactSetState,所有当用户调用setState时,concent除了调用reactSetState更新当前实例ui,同时智能判断提交的状态是否也还有别的实例关心其变化,然后一并拿出来依次执行这些实例的reactSetState,进而达到了状态全部同步的目的。




Recoil初体验

我们以常用的counter来举例,熟悉一下Recoil暴露的四个高频使用的api


atom,定义状态

selector, 定义派生数据

useRecoilState,消费状态

useRecoilValue,消费派生数据

定义状态

外部使用atom接口,定义一个key为num,初始值为0的状态


const numState = atom({

 key: "num",

 default: 0

});

定义派生数据

外部使用selector接口,定义一个key为numx10,初始值是依赖numState再次计算而得到


const numx10Val = selector({

 key: "numx10",

 get: ({ get }) => {

   const num = get(numState);

   return num * 10;

 }

});

定义异步的派生数据

selector的get支持定义异步函数


需要注意的点是,如果有依赖,必需先书写好依赖在开始执行异步逻辑

const delay = () => new Promise(r => setTimeout(r, 1000));


const asyncNumx10Val = selector({

 key: "asyncNumx10",

 get: async ({ get }) => {

   // !!!这句话不能放在delay之下, selector需要同步的确定依赖

   const num = get(numState);

   await delay();

   return num * 10;

 }

});

消费状态

组件里使用useRecoilState接口,传入想要获去的状态(由atom创建而得)


const NumView = () => {

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


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


 return (

   <div>

     {num}<br/>

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

   </div>

 );

}

消费派生数据

组件里使用useRecoilValue接口,传入想要获去的派生数据(由selector创建而得),同步派生数据和异步派生数据,皆可通过此接口获得


const NumValView = () => {

 const numx10 = useRecoilValue(numx10Val);

 const asyncNumx10 = useRecoilValue(asyncNumx10Val);


 return (

   <div>

     numx10 :{numx10}<br/>

   </div>

 );

};

渲染它们查看结果

暴露定义好的这两个组件, 查看在线示例


export default ()=>{

 return (

   <>

     <NumView />

     <NumValView />

   </>

 );

};

顶层节点包裹React.Suspense和RecoilRoot,前者用于配合异步计算函数需要,后者用于注入Recoil上下文


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

ReactDOM.render(

 <React.StrictMode>

   <React.Suspense fallback={<div>Loading...</div>}>

     <RecoilRoot>

       <Demo />

     </RecoilRoot>

   </React.Suspense>

 </React.StrictMode>,

 rootElement

);



Concent初体验

如果读过concent文档(还在持续建设中...),可能部分人会认为api太多,难于记住,其实大部分都是可选的语法糖,我们以counter为例,只需要使用到以下两个api即可


run,定义模块状态(必需)、模块计算(可选)、模块观察(可选)

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

setState,修改状态

定义状态&修改状态

以下示例我们先脱离ui,直接完成定义状态&修改状态的目的


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


run({

 counter: {// 声明一个counter模块

   state: { num: 1 }, // 定义状态

 }

});


console.log(getState('counter').num);// log: 1

setState('counter', {num:10});// 修改counter模块的num值为10

console.log(getState('counter').num);// log: 10

我们可以看到,此处和redux很类似,需要定义一个单一的状态树,同时第一层key就引导用户将数据模块化管理起来.


引入reducer

上述示例中我们直接掉一个呢setState修改数据,但是真实的情况是数据落地前有很多同步的或者异步的业务逻辑操作,所以我们对模块填在reducer定义,用来声明修改数据的方法集合。


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


const delay = () => new Promise(r => setTimeout(r, 1000));


const state = () => ({ num: 1 });// 状态声明

const reducer = {// reducer声明

 inc(payload, moduleState) {

   return { num: moduleState.num + 1 };

 },

 async asyncInc(payload, moduleState) {

   await delay();

   return { num: moduleState.num + 1 };

 }

};


run({

 counter: { state, reducer }

});

然后我们用dispatch来触发修改状态的方法


因dispatch会返回一个Promise,所以我们需要用一个async 包裹起来执行代码

import { dispatch } from "concent";


(async ()=>{

 console.log(getState("counter").num);// log 1

 await dispatch("counter/inc");// 同步修改

 console.log(getState("counter").num);// log 2

 await dispatch("counter/asyncInc");// 异步修改

 console.log(getState("counter").num);// log 3

})()

注意dispatch调用时基于字符串匹配方式,之所以保留这样的调用方式是为了照顾需要动态调用的场景,其实更推荐的写法是


import { dispatch } from "concent";


(async ()=>{

 console.log(getState("counter").num);// log 1

 await dispatch(reducer.inc);// 同步修改

 console.log(getState("counter").num);// log 2

 await dispatch(reducer.asyncInc);// 异步修改

 console.log(getState("counter").num);// log 3

})()

接入react

上述示例主要演示了如何定义状态和修改状态,那么接下来我们需要用到以下两个api来帮助react组件生成实例上下文(等同于与vue 3 setup里提到的渲染上下文),以及获得消费concent模块数据的能力


register, 注册类组件为concent组件

useConcent, 注册函数组件为concent组件

import { register, useConcent } from "concent";


@register("counter")

class ClsComp extends React.Component {

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

 render() {

   return (

     <div>

       <h1>class comp: {this.state.num}</h1>

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

     </div>

   );

 }

}


function FnComp() {

 const { state, setState } = useConcent("counter");

 const changeNum = () => setState({ num: 20 });

 

 return (

   <div>

     <h1>fn comp: {state.num}</h1>

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

   </div>

 );

}

注意到两种写法区别很小,除了组件的定义方式不一样,其实渲染逻辑和数据来源都一模一样。


渲染它们查看结果

在线示例


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

ReactDOM.render(

 <React.StrictMode>

   <div>

     <ClsComp />

     <FnComp />

   </div>

 </React.StrictMode>,

 rootElement

);



对比Recoil,我们发现没有顶层并没有Provider或者Root类似的组件包裹,react组件就已接入concent,做到真正的即插即用和无感知接入,同时api保留为与react一致的写法。


组件调用reducer

concent为每一个组件实例都生成了实例上下文,方便用户直接通过ctx.mr调用reducer方法


mr 为 moduleReducer的简写,直接书写为ctx.moduleReducer也是合法的

//  --------- 对于类组件 -----------

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

// ===> 修改为

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


//  --------- 对于函数组件 -----------

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

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

异步计算函数

run接口里支持扩展computed属性,即让用户定义一堆衍生数据的计算函数集合,它们可以是同步的也可以是异步的,同时支持一个函数用另一个函数的输出作为输入来做二次计算,计算的输入依赖是自动收集到的。


const computed = {// 定义计算函数集合

 numx10({ num }) {

   return num * 10;

 },

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

 // 结构出num,表示当前计算依赖是num,仅当num发生变化时触发此函数重计算

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

   // 必需调用setInitialVal给numx10_2一个初始值,

   // 该函数仅在初次computed触发时执行一次

   f.setInitialVal(num * 55);

   await delay();

   return num * 100;

 },

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

   f.setInitialVal(num * 1);

   await delay();

   // 使用numx10_2再次计算

   const ret = num * f.cuVal.numx10_2;

   if (ret % 40000 === 0) throw new Error("-->mock error");

   return ret;

 }

}


// 配置到counter模块

run({

 counter: { state, reducer, computed }

});

上述计算函数里,我们刻意让numx10_3在某个时候报错,对于此错误,我们可以在run接口的第二位options配置里定义errorHandler来捕捉。


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

   errorHandler: (err)=>{

       alert(err.message);

   }

})

当然更好的做法,利用concent-plugin-async-computed-status插件来完成对所有模块计算函数执行状态的统一管理。


import cuStatusPlugin from "concent-plugin-async-computed-status";


run(

 {/**storeConfig*/},

 {

   errorHandler: err => {

     console.error('errorHandler ', err);

     // alert(err.message);

   },

   plugins: [cuStatusPlugin], // 配置异步计算函数执行状态管理插件

 }

);

该插件会自动向concent配置一个cuStatus模块,方便组件连接到它,消费相关计算函数的执行状态数据


function Test() {

 const { moduleComputed, connectedState, setState, state, ccUniqueKey } = useConcent({

   module: "counter",// 属于counter模块,状态直接从state获得

   connect: ["cuStatus"],// 连接到cuStatus模块,状态从connectedState.{$moduleName}获得

 });

 const changeNum = () => setState({ num: state.num + 1 });

 

 // 获得counter模块的计算函数执行状态

 const counterCuStatus = connectedState.cuStatus.counter;

 // 当然,可以更细粒度的获得指定结算函数的执行状态

 // const {['counter/numx10_2']:num1Status, ['counter/numx10_3']: num2Status} = connectedState.cuStatus;


 return (

   <div>

     {state.num}

     <br />

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

     {/** 此处拿到错误可以用于渲染,当然也抛出去 */}

     {/** 让ErrorBoundary之类的组件捕捉并渲染降级页面 */}

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

     <br />

     {moduleComputed.numx10_2}

     <br />

     {moduleComputed.numx10_3}

     <br />

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

   </div>

 );

}

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


查看在线示例


更新

开篇我说对Recoli提到的更新保持了怀疑态度,有一些误导的嫌疑,此处我们将揭开疑团


大家知道hook使用规则是不能写在条件控制语句里的,这意味着下面语句是不允许的


const NumView = () => {

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

 if(show){// error

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

 }

}

所以用户如果ui渲染里如果某个状态用不到此数据时,某处改变了num值依然会触发NumView重渲染,但是concent的实例上下文里取出来的state和moduleComputed是一个Proxy对象,是在实时的收集每一轮渲染所需要的依赖,这才是真正意义上的按需渲染和更新。


const NumView = () => {

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

 const {state} = useConcent('counter');

 // show为true时,当前实例的渲染对state.num的渲染有依赖

 return {show ? <h1>{state.num}</h1> : 'nothing'}

}



点我查看代码示例


当然如果用户对num值有ui渲染完毕后,有发生改变时需要做其他事的需求,类似useEffect的效果,concent也支持用户将其抽到setup里,定义effect来完成此场景诉求,相比useEffect,setup里的ctx.effect只需定义一次,同时只需传递key名称,concent会自动对比前一刻和当前刻的值来决定是否要触发副作用函数。


conset setup = (ctx)=>{

 ctx.effect(()=>{

   console.log('do something when num changed');

   return ()=>console.log('clear up');

 }, ['num'])

}


function Test1(){

 useConcent({module:'cunter', setup});

 return <h1>for setup<h1/>

}

更多关于effect与useEffect请查看此文


current mode

关于concent是否支持current mode这个疑问呢,这里先说答案,concent是100%完全支持的,或者进一步说,所有状态管理工具,最终触发的都是setState或forceUpdate,我们只要在渲染过程中不要写具有任何副作用的代码,让相同的状态输入得到的渲染结果幂,即是在current mode下运行安全的代码。


current mode只是对我们的代码提出了更苛刻的要求。


// bad

function Test(){

  track.upload('renderTrigger');// 上报渲染触发事件

  return <h1>bad case</h1>

}


// good

function Test(){

  useEffect(()=>{

     // 就算仅执行了一次setState, current mode下该组件可能会重复渲染,

     // 但react内部会保证该副作用只触发一次

     track.upload('renderTrigger');

  })

  return <h1>bad case</h1>

}

我们首先要理解current mode原理是因为fiber架构模拟出了和整个渲染堆栈(即fiber node上存储的信息),得以有机会让react自己以组件为单位调度组件的渲染过程,可以悬停并再次进入渲染,安排优先级高的先渲染,重度渲染的组件会切片为多个时间段反复渲染,而concent的上下文本身是独立于react存在的(接入concent不需要再顶层包裹任何Provider), 只负责处理业务生成新的数据,然后按需派发给对应的实例(实例的状态本身是一个个孤岛,concent只负责同步建立起了依赖的store的数据),之后就是react自己的调度流程,修改状态的函数并不会因为组件反复重入而多次执行(这点需要我们遵循不该在渲染过程中书写包含有副作用的代码原则),react仅仅是调度组件的渲染时机,而组件的中断和重入针对也是这个渲染过程。


所以同样的,对于concent


const setup = (ctx)=>{

 ctx.effect(()=>{

    // effect是对useEffect的封装,

    // 同样在current mode下该副作用也只触发一次(由react保证)

     track.upload('renderTrigger');

 });

}


// good

function Test2(){

  useConcent({setup})

  return <h1>good case</h1>

}

同样的,依赖收集在current mode模式下,重复渲染仅仅是导致触发了多次收集,只要状态输入一样,渲染结果幂等,收集到的依赖结果也是幂等的。


// 假设这是一个渲染很耗时的组件,在current mode模式下可能会被中断渲染

function HeavyComp(){

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


// 这里读取了num 和 numBig两个值,收集到了依赖

// 即当仅当counter模块的num、numBig的发生变化时,才触发其重渲染(最终还是调用setState)

// 而counter模块的其他值发生变化时,不会触发该实例的setState

 return (

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

 );

}

最后我们可以梳理一下,hook本身是支持把逻辑剥离到用的自定义hook(无ui返回的函数),而其他状态管理也只是多做了一层工作,引导用户把逻辑剥离到它们的规则之下,最终还是把业务处理数据交回给react组件调用其setState或forceUpdate触发重渲染,current mode的引入并不会对现有的状态管理或者新生的状态管理方案有任何影响,仅仅是对用户的ui代码提出了更高的要求,以免因为current mode引发难以排除的bug


为此react还特别提供了React.Strict组件来故意触发双调用机制, https://reactjs.org/docs/stri... 以引导用户书写更符合规范的react代码,以便适配将来提供的current mode。

react所有新特性其实都是被fiber激活了,有了fiber架构,衍生出了hook、time slicing、suspense以及将来的Concurrent Mode,class组件和function组件都可以在Concurrent Mode下安全工作,只要遵循规范即可。


摘取自: https://reactjs.org/docs/stri...


Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:


Class component constructor, render, and shouldComponentUpdate methods

Class component static getDerivedStateFromProps method

Function component bodies

State updater functions (the first argument to setState)

Functions passed to useState, useMemo, or useReducer

所以呢,React.Strict其实为了引导用户写能够在Concurrent Mode里运行的代码而提供的辅助api,先让用户慢慢习惯这些限制,循序渐进一步一步来,最后再推出Concurrent Mode。


结语

Recoil推崇状态和派生数据更细粒度控制,写法上demo看起来简单,实际上代码规模大之后依然很繁琐。


// 定义状态

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

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

// 定义衍生数据

const numx2Val = selector({

 key: "numx2",

 get: ({ get }) => get(numState) * 2,

});

const numBigx2Val = selector({

 key: "numBigx2",

 get: ({ get }) => get(numBigState) * 2,

});

const numSumBigVal = selector({

 key: "numSumBig",

 get: ({ get }) => get(numState) + get(numBigState),

});


// ---> ui处消费状态或衍生数据

const [num] = useRecoilState(numState);

const [numBig] = useRecoilState(numBigState);

const numx2 = useRecoilValue(numx2Val);

const numBigx2 = useRecoilValue(numBigx2Val);

const numSumBig = useRecoilValue(numSumBigVal);

Concent遵循redux单一状态树的本质,推崇模块化管理数据以及派生数据,同时依靠Proxy能力完成了运行时依赖收集和追求不可变的完美整合。


run({

 counter: {// 声明一个counter模块

   state: { num: 1, numBig: 100 }, // 定义状态

   computed:{// 定义计算,参数列表里解构具体的状态时确定了依赖

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

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

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

    }

 },

});


// ---> ui处消费状态或衍生数据,在ui处结构了才产生依赖

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

const { numx2, numBigx2, numSumBig} = moduleComputed;

const { num, numBig } = state;

所以你将获得:


运行时的依赖收集 ,同时也遵循react不可变的原则

一切皆函数(state, reducer, computed, watch, event...),能获得更友好的ts支持

支持中间件和插件机制,很容易兼容redux生态

同时支持集中与分形模块配置,同步与异步模块加载,对大型工程的弹性重构过程更加友好


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



如何使JavaScript休眠或等待

seo达人

JavaScript不具有 sleep() 函数,该函数会导致代码在恢复执行之前等待指定的时间段。如果需要JavaScript等待,该怎么做呢?


假设您想将三则消息记录到Javascript控制台,每条消息之间要延迟一秒钟。JavaScript中没有 sleep() 方法,所以你可以尝试使用下一个最好的方法 setTimeout()。


不幸的是,setTimeout() 不能像你期望的那样正常工作,这取决于你如何使用它。你可能已经在JavaScript循环中的某个点上试过了,看到 setTimeout() 似乎根本不起作用。


问题的产生是由于将 setTimeout() 误解为 sleep() 函数,而实际上它是按照自己的一套规则工作的。


在本文中,我将解释如何使用 setTimeout(),包括如何使用它来制作一个睡眠函数,使JavaScript暂停执行并在连续的代码行之间等待。


浏览一下 setTimeout() 的文档,它似乎需要一个 "延迟 "参数,以毫秒为单位。


回到原始问题,您尝试调用 setTimeout(1000) 在两次调用 console.log() 函数之间等待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}`)

}

这段代码的结果完全没有延迟,就像 setTimeout() 不存在一样。


回顾文档,你会发现问题在于实际上第一个参数应该是函数调用,而不是延迟。毕竟,setTimeout() 实际上不是 sleep() 方法。


你重写代码以将回调函数作为第一个参数并将必需的延迟作为第二个参数:


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的日志信息在经过1000ms(1秒)的单次延时后,会一起显示,而不是每次重复调用之间延时1秒的理想效果。


在讨论如何解决此问题之前,让我们更详细地研究一下 setTimeout() 函数。


检查setTimeout ()

你可能已经注意到上面第二个代码片段中使用了箭头函数。这些是必需的,因为你需要将匿名回调函数传递给 setTimeout(),该函数将在超时后运行要执行的代码。


在匿名函数中,你可以指定在超时时间后执行的任意代码:


// 使用箭头语法的匿名回调函数。

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

// 这等同于使用function关键字

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

理论上,你可以只传递函数作为第一个参数,回调函数的参数作为剩余的参数,但对我来说,这似乎从来没有正确的工作:


// 应该能用,但不能用

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

人们使用字符串解决此问题,但是不建议这样做。从字符串执行JavaScript具有安全隐患,因为任何不当行为者都可以运行作为字符串注入的任意代码。


// 应该没用,但确实有用

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

那么,为什么在我们的第一组代码示例中 setTimeout() 失败?好像我们在正确使用它,每次都重复了1000ms的延迟。


原因是 setTimeout() 作为同步代码执行,并且对 setTimeout() 的多次调用均同时运行。每次调用 setTimeout() 都会创建异步代码,该代码将在给定延迟后稍后执行。由于代码段中的每个延迟都是相同的(1000毫秒),因此所有排队的代码将在1秒钟的单个延迟后同时运行。


如前所述,setTimeout() 实际上不是 sleep() 函数,取而代之的是,它只是将异步代码排入队列以供以后执行。幸运的是,可以使用 setTimeout() 在JavaScript中创建自己的 sleep() 函数。


如何编写sleep函数

通过Promises,async 和 await 的功能,您可以编写一个 sleep() 函数,该函数将按预期运行。


但是,你只能从 async 函数中调用此自定义 sleep() 函数,并且需要将其与 await 关键字一起使用。


这段代码演示了如何编写一个 sleep() 函数:


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() 函数的功能与您预期的完全一样,因为 await 导致代码的同步执行暂停,直到Promise被解决为止。


一个简单的选择

另外,你可以在第一次调用 setTimeout() 时指定增加的超时时间。


以下代码等效于上一个示例:


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

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

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

使用增加超时是可行的,因为代码是同时执行的,所以指定的回调函数将在同步代码执行的1、2和3秒后执行。


它会循环运行吗?

如你所料,以上两种暂停JavaScript执行的选项都可以在循环中正常工作。让我们看两个简单的例子。


这是使用自定义 sleep() 函数的代码段:


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)

}

我更喜欢后一种语法,特别是在循环中使用。


总结

JavaScript可能没有 sleep() 或 wait() 函数,但是使用内置的 setTimeout() 函数很容易创建一个JavaScript,只要你谨慎使用它即可。


就其本身而言,setTimeout() 不能用作 sleep() 函数,但是你可以使用 async 和 await 创建自定义JavaScript sleep() 函数。


采用不同的方法,可以将交错的(增加的)超时传递给 setTimeout() 来模拟 sleep() 函数。之所以可行,是因为所有对setTimeout() 的调用都是同步执行的,就像JavaScript通常一样。


希望这可以帮助你在代码中引入一些延迟——仅使用原始JavaScript,而无需外部库或框架。


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

5 个 JS 数组技巧可提高你的开发技能

seo达人

1. 随机排列

在开发者,有时候我们需要对数组的顺序进行重新的洗牌。 在 JS 中并没有提供数组随机排序的方法,这里提供一个随机排序的方法:


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

在开发者,我们经常需要过滤重复的值,这里提供几种方式来过滤数组的重复值。


使用 Set 对象

使用 Set() 函数,此函数可与单个值数组一起使用。对于数组中嵌套的对象值而言,不是一个好的选择。


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. 按属性对 对象数组 进行排序

我们知道 JS 数组中的 sort 方法是按字典顺序进行排序的,所以对于字符串类, 该方法是可以很好的正常工作,但对于数据元素是对象类型,就不太好使了,这里我们需要自定义一个排序方法。


在比较函数中,我们将根据以下条件返回值:


小于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. 把数组转成以指定符号分隔的字符串

JS 中有个方法可以做到这一点,就是使用数组中的 .join() 方法,我们可以传入指定的符号来做数组进行分隔。


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


data.join(',');

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

5. 从数组中选择一个元素

对于此任务,我们有多种方式,一种是使用 forEach 组合 if-else 的方式 ,另一种可以使用filter 方法,但是使用forEach 和filter的缺点是:


在forEach中,我们要额外的遍历其它不需要元素,并且还要使用 if 语句来提取所需的值。

在filter 方法中,我们有一个简单的比较操作,但是它将返回的是一个数组,而是我们想要是根据给定条件从数组中获得单个对象。

为了解决这个问题,我们可以使用 find函数从数组中找到确切的元素并返回该对象,这里我们不需要使用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.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


Vue 数据更新了但页面没有更新的 7 种情况汇总及延伸总结

seo达人

1. Vue 无法检测实例被创建时不存在于 data 中的 property

原因:由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

场景:


var vm = new Vue({

 data:{},

 // 页面不会变化

 template: '<div>{{message}}</div>'

})

vm.message = 'Hello!' // `vm.message` 不是响应式的

解决办法:


var vm = new Vue({

 data: {

   // 声明 a、b 为一个空值字符串

   message: '',

 },

 template: '<div>{{ message }}</div>'

})

vm.message = 'Hello!'

2. Vue 无法检测对象 property 的添加或移除

原因:官方 - 由于 JavaScript(ES5) 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。

场景:


var vm = new Vue({

 data:{

   obj: {

     id: 001

   }

 },

 // 页面不会变化

 template: '<div>{{ obj.message }}</div>'

})


vm.obj.message = 'hello' // 不是响应式的

delete vm.obj.id       // 不是响应式的

解决办法:


// 动态添加 - Vue.set

Vue.set(vm.obj, propertyName, newValue)


// 动态添加 - vm.$set

vm.$set(vm.obj, propertyName, newValue)


// 动态添加多个

// 代替 Object.assign(this.obj, { a: 1, b: 2 })

this.obj = Object.assign({}, this.obj, { a: 1, b: 2 })


// 动态移除 - Vue.delete

Vue.delete(vm.obj, propertyName)


// 动态移除 - vm.$delete

vm.$delete(vm.obj, propertyName)

3. Vue 不能检测通过数组索引直接修改一个数组项

原因:官方 - 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化;尤雨溪 - 性能代价和获得用户体验不成正比。

场景:


var vm = new Vue({

 data: {

   items: ['a', 'b', 'c']

 }

})

vm.items[1] = 'x' // 不是响应性的

解决办法:


// Vue.set

Vue.set(vm.items, indexOfItem, newValue)


// vm.$set

vm.$set(vm.items, indexOfItem, newValue)


// Array.prototype.splice

vm.items.splice(indexOfItem, 1, newValue)

拓展:Object.defineProperty() 可以监测数组的变化

Object.defineProperty() 可以监测数组的变化。但对数组新增一个属性(index)不会监测到数据变化,因为无法监测到新增数组的下标(index),删除一个属性(index)也是。

场景:


var arr = [1, 2, 3, 4]

arr.forEach(function(item, index) {

   Object.defineProperty(arr, index, {

   set: function(value) {

     console.log('触发 setter')

     item = value

   },

   get: function() {

     console.log('触发 getter')

     return item

   }

 })

})

arr[1] = '123'  // 触发 setter

arr[1]          // 触发 getter 返回值为 "123"

arr[5] = 5      // 不会触发 setter 和 getter

4. Vue 不能监测直接修改数组长度的变化

原因:官方 - 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化;尤雨溪 - 性能代价和获得用户体验不成正比。

场景:


var vm = new Vue({

 data: {

   items: ['a', 'b', 'c']

 }

})

vm.items.length = 2 // 不是响应性的

解决办法:


vm.items.splice(newLength)

5. 在异步更新执行之前操作 DOM 数据不会变化

原因:Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

场景:


<div id="example">{{message}}</div>

var vm = new Vue({

 el: '#example',

 data: {

   message: '123'

 }

})

vm.message = 'new message' // 更改数据

vm.$el.textContent === 'new message' // false

vm.$el.style.color = 'red' // 页面没有变化

解决办法:


var vm = new Vue({

 el: '#example',

 data: {

   message: '123'

 }

})

vm.message = 'new message' // 更改数据

//使用 Vue.nextTick(callback) callback 将在 DOM 更新完成后被调用

Vue.nextTick(function () {

 vm.$el.textContent === 'new message' // true

 vm.$el.style.color = 'red' // 文字颜色变成红色

})

拓展:异步更新带来的数据响应的误解

<!-- 页面显示:我更新啦! -->

<div id="example">{{message.text}}</div>

var vm = new Vue({

 el: '#example',

 data: {

   message: {},

 }

})

vm.$nextTick(function () {

 this.message = {}

 this.message.text = '我更新啦!'

})

上段代码中,我们在 data 对象中声明了一个 message 空对象,然后在下次 DOM 更新循环结束之后触发的异步回调中,执行了如下两段代码:


this.message = {};

this.message.text = '我更新啦!'

到这里,模版更新了,页面最后会显示 我更新啦!。


模板更新了,应该具有响应式特性,如果这么想那么你就已经走入了误区。


一开始我们在 data 对象中只是声明了一个 message 空对象,并不具有 text 属性,所以该 text 属性是不具有响应式特性的。


但模板切切实实已经更新了,这又是怎么回事呢?


那是因为 Vue.js 的 DOM 更新是异步的,即当 setter 操作发生后,指令并不会立马更新,指令的更新操作会有一个延迟,当指令更新真正执行的时候,此时 text 属性已经赋值,所以指令更新模板时得到的是新值。


模板中每个指令/数据绑定都有一个对应的 watcher 对象,在计算过程中它把属性记录为依赖。之后当依赖的 setter 被调用时,会触发 watcher 重新计算 ,也就会导致它的关联指令更新 DOM。



具体流程如下所示:


执行 this.message = {}; 时, setter 被调用。

Vue.js 追踪到 message 依赖的 setter 被调用后,会触发 watcher 重新计算。

this.message.text = '我更新啦!'; 对 text 属性进行赋值。

异步回调逻辑执行结束之后,就会导致它的关联指令更新 DOM,指令更新开始执行。

所以真正的触发模版更新的操作是 this.message = {};这一句引起的,因为触发了 setter,所以单看上述例子,具有响应式特性的数据只有 message 这一层,它的动态添加的属性是不具备的。


对应上述第二点 - Vue 无法检测对象 property 的添加或移除

6. 循环嵌套层级太深,视图不更新?

看到网上有些人说数据更新的层级太深,导致数据不更新或者更新缓慢从而导致试图不更新?


由于我没有遇到过这种情况,在我试图重现这种场景的情况下,发现并没有上述情况的发生,所以对于这一点不进行过多描述(如果有人在真实场景下遇到这种情况留个言吧)。


针对上述情况有人给出的解决方案是使用强制更新:


如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事。

vm.$forceUpdate()

7. 拓展:路由参数变化时,页面不更新(数据不更新)

拓展一个因为路由参数变化,而导致页面不更新的问题,页面不更新本质上就是数据没有更新。


原因:路由视图组件引用了相同组件时,当路由参会变化时,会导致该组件无法更新,也就是我们常说中的页面无法更新的问题。

场景:


<div id="app">

 <ul>

   <li><router-link to="/home/foo">To Foo</router-link></li>    

   <li><router-link to="/home/baz">To Baz</router-link></li>    

   <li><router-link to="/home/bar">To Bar</router-link></li>    

 </ul>    

 <router-view></router-view>

</div>

const Home = {

 template: `<div>{{message}}</div>`,

 data() {

   return {

     message: this.$route.params.name

   }

 }

}


const router = new VueRouter({

 mode:'history',

   routes: [

   {path: '/home', component: Home },

   {path: '/home/:name', component: Home }

 ]

})


new Vue({

 el: '#app',

 router

})

上段代码中,我们在路由构建选项 routes 中配置了一个动态路由 '/home/:name',它们共用一个路由组件 Home,这代表他们复用 RouterView 。


当进行路由切换时,页面只会渲染第一次路由匹配到的参数,之后再进行路由切换时,message 是没有变化的。


解决办法:


解决的办法有很多种,这里只列举我常用到几种方法。

通过 watch 监听 $route 的变化。


const Home = {

 template: `<div>{{message}}</div>`,

 data() {

   return {

     message: this.$route.params.name

   }

 },

 watch: {

      '$route': function() {

      this.message = this.$route.params.name

   }

   }

}

...

new Vue({

 el: '#app',

 router

})

给 <router-view> 绑定 key 属性,这样 Vue 就会认为这是不同的 <router-view>。


弊端:如果从 /home 跳转到 /user 等其他路由下,我们是不用担心组件更新问题的,所以这个时候 key 属性是多余的。

<div id="app">

 ...

 <router-view :key="key"></router-view>

</div>

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档