设计模式是锤子,但是不能有了锤子,就看什么都是钉子。
现在对于编程设计模式的介绍,基本都基于面向对象开发方式。js虽然可以以面向对象的方式开发,但在结合react开发的过程中,现在以hooks模式的函数式编程更为主流,这使得设计模式在react中有时难以找到应用场景。找到设计模式的使用场景可能比学会设计模式的内容更有意义,不然学了也是无用武之地的屠龙术。
什么是单例模式?
单例单例,这个例指的是实例。实例哪里来,类的实例化中来。不过在js中如果不用面向对象的编程方式的话,这个单例就应该定义为函数的输出。毕竟实例对象也能通过函数得出,用工厂函数或者构造函数实例化都行。
所以单例模式可以狭义一点地定义为:某个函数执行一次或多次返回的结果都一样。这个一样是===恒等符的一样。一般而言返回是一个对象,这个对象之中都是第一次返回的那个。
单例模式可能是最简单的设计模式,基本实现流程如下图所示。
即函数如果执行过,就一直返回第一次执行的结果。
js单例模式通用使用方法
设计模式的关键在于功能与解耦兼得,所以我们需要一个通用的获取单例的方式,不用具体去实现某种单例。用高级函数可以很好地实现这一点:
const getSingle = (fn) => {
var result;
return (...arg) => {
return result ?? (result = fn(...arg));
};
};
实践一下:
const getCounter = () => {
return {
count: 0,
increment: () => {
this.count += 1
},
decrement: () => {
this.count -= 1
},
getCounter: () => {
return this.count;
}
}
}
const counter1 = getCounter();
const counter2 = getCounter();
console.log("counter1 === counter2: ", counter1 === counter2);
//output: counter1 === counter2: false
const singleGetCounter = getSingle(getCounter);
const counter3 = singleGetCounter();
const counter4 = singleGetCounter();
console.log("counter3 === counter4: ", counter3 === counter4);
// counter3 === counter4: true
react开发中,单例模式还存在吗?
上面所举的例子,是为了单例而单例,实际上我们也不会去这么用,并且在js编程中,好像我们确实没怎么用过单例模式。
单例模式的特点是产物全局唯一,其应用基本两类:
-
全局唯一的变量(公共状态)
-
需要惰性加载的全局唯一的页面元素 (登录弹窗)。
对于全局唯一变量:
但实际上我们在react或是js前端编程中好像不会特别注意什么是全局唯一的,因为全局唯一在js中是一件很容易做到的事。
const getCounter = () => {
return {
count: 0,
increment: () => {
this.count += 1
},
decrement: () => {
this.count -= 1
},
getCounter: () => {
return this.count;
}
}
}
const counter = getCount();
export { counter };
我们在其他地方引用这个类,都是全局唯一的。在面向对象的语言中不能直接创造对象,所以整出了个单例模式,但是在js中这个问题是不存在的。
此外,react中也有专门处理全局状态的redux,所以说为了全局唯一变量在js中使用单例模式的场景基本是不存在的。
惰性加载 + 全局唯一的页面元素:
就以登录弹窗为例吧:1. 弹窗不用的时候不加载,点了登录才出现,是为惰性加载。2. 这玩意全局就一个,这样的话写了东西也不会丢,用个display:none就把弹窗隐藏了。确实是个比较好的应用场景,在这两个场景下确实可以只能用单例来实现。
不过在用react开发的时候, 一般用Modal来实现登录弹窗,在semi组件库或antd中都有api可以控制弹窗是否关闭后销毁,这样就很容易做到全局唯一,我们只要在全局能拿到控制弹窗开关的变量即可。semi中还可以控制modal惰性加载,基本可以实现上面单例做到的所有事情,并且动画什么的也不用操心了。
结合Modal使用的话,惰性加载的全局唯一的弹窗就实现了,也就不用自己去操心单例模式了。
虽然日常开发不怎么会注意这些较为微小的性能点,以后顺手加上也是不错的。
还有一种应用场景,就是惰性计算。加入页面中有某个函数操作会触发一个十分耗时的计算,且这个计算是没有参数的,也就是当前页面上怎么算都是一个结果。这个可以用单例包一下这个函数,触发的时候再实行,用单例缓存这个结果。场景比较极端,其实很少用到。一般用个useMemo包一下,是一个较为常规的操作。
如果函数有参数的话,就考虑用个对象缓存一下结果。更谨慎的话,用缓存可以考虑用map实现先入先出缓存,防止内存泄露。
总结
-
单例模式很简单,但找到的react开发中的应用场景确不多。
-
全局变量用不上单例。
-
弹窗类直接用相应组件库属性更佳。
-
惰性计算与缓存可以用单例,不过场景比较狭窄。
-
一家之言,欢迎批评。