前端设计模式及应用 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第5天 与大家分享前端设计模式及应用,不足之处欢迎大家批评指正!
什么是设计模式?
定义:软件设计中常见问题的解决方案模型
- 历史经验的总结
- 与特定语言无关
发展背景
1.模式语言:城镇、建筑、建造(A Pattern Language:Towns,Buildings,Construction) 1977
2.设计模式:可复用面向对象软件的基础(Design Patterns:Elements of Reusable Object-Oriented Software) 1994
趋势
23种设计模式——分类
- 创建型
如何创建一个对象
- 结构型
如何灵活地将对象组装成较大的结构
- 行为型
负责对象间的高效通信和职责划分
浏览器中的设计模式
单例模式
- 定义
全局唯一访问对象
- 应用场景
缓存,全局状态管理等
1.定义
import {api} from "./utils";
const cache:Record<string, string> = {};
export const request async (url:String) => {
if(cache[url]){
return cache[url];
}
const response = await api(url);
cache[url] = response;
return response;
}
cache:全局唯一的变量,存储缓存
request:如果有缓存,返回,否则写入全局唯一的值里
2.使用
test("should response quickly second time", async() => {
await request("/user/1");
const startTime = Date.now();
const costTime = endTime - startTime;
expect(costTime).toBeLessThan(50);
});
在JavaScript中可以更灵活地实现单例模式
发布订阅模式
1.定义
一种订阅机制,可在订阅对象发生变化时通知订阅者
const button = document.getElementById("button");
const doSomthing1 = () => {
console.log("Send message to user");
};
const doSomthing2 = () => {
console.log("Log...");
}
button.addEventListener("click",doSomthing1);
button.addEventListener("click",doSomthing2);
// ...
2.应用场景
从系统架构之间的解耦,到业务中一些实现模式,像邮件订阅、上线订阅等等,应用广泛。
3.例子一:用发布订阅模式实现用户订阅上线
- 定义
type Notify = (User: user) => void;
export class User{
name:String;
status:"OffLine" | "online";
followers:{ user:User; notify: Notify }[];
constructor(name:String){
this.name = name;
this.status = "offLine";
this.followers = [];
}
subscribe(user:User, notify: Notify){
user.followers.push({user, notify});
}
online(){
this.status = "online";
this.followers.forEach(({notify}) => {
notify(this);
});
}
}
- 测试
test("should notify followers when user is online for multiple users", () => {
const user1 = new User("user1");
const user2 = new User("user2");
const user3 = new User("user3");
const mockNotifyUser1 = jest.fn();
const mockNotifyUser2 = jest.fn();
user1.subscribe(user3, mockNotifyUser1);
user2.subscribe(user3, mockNotifyUser2);
user3.online();
expect(mockNotifyUser1).toBeCalledWith(user3);
expect(mockNotifyUser2).toBeCalledWith(user3);
});
JavaScript中的设计模式
- 原型模式
- 代理模式
- 迭代器模式
原型模式
1.定义
复制已有对象来创建新的对象
2.应用场景
JS中对象创建的基本模式
3.例子:用原型模式创建上线订阅中的用户
- 定义
baseUser:定义自定义对象
createUser:创建对象,baseUser作为创建对象的原型
const baseUser: User = {
name: "";
status:"offLine",
followers:[],
subscribe(user, notify){
user.followers.push({user, notify});
},
online(){
this.status = "online";
this.followers.forEach({notify} => {
notify(this);
});
},
};
export const createUser = (name:String) => {
const user : User = Object.create(baseUser);
user.name = name;
user.followers = [];
return user;
}
- 使用
test("should notify followers when user is online for user prototypes", () => {
const user1 = new User("user1");
const user2 = new User("user2");
const user3 = new User("user3");
const mockNotifyUser1 = jest.fn();
const mockNotifyUser2 = jest.fn();
user1.subscribe(user3, mockNotifyUser1);
user2.subscribe(user3, mockNotifyUser2);
user3.online();
expect(mockNotifyUser1).toBeCalledWith(user3);
expect(mockNotifyUser2).toBeCalledWith(user3);
});
代理模式
1.定义
可自定义控制对原对象的访问模式,并且允许在更新前后做一些额外处理
2.应用场景
监控,代理工具,前端框架实现等等。 (改写原本范式)
3.例子:用代理模式实现用户状态订阅
- 最初用户状态订阅
最好一个方法做一件事情
online()方法:两件事情
(1)this.status = "online"用户状态上线
(2)notify通知
若添加方法,比较复杂,难以重用
type Notify = (User: user) => void;
export class User{
name:String;
status:"OffLine" | "online";
followers:{ user:User; notify: Notify }[];
constructor(name:String){
this.name = name;
this.status = "offLine";
this.followers = [];
}
subscribe(user:User, notify: Notify){
user.followers.push({user, notify});
}
online(){
this.status = "online";
this.followers.forEach(({notify}) => {
notify(this);
});
}
}
- 使用代理模式
export const createProxyUser = (name: String) => {
const User = new User(name);
const proxyUser = new Proxy(user, {
set:(target, prop: keyof User, value) => {
target[prop] = value;
if(prop === "status"){
notifyStatusHandlers(target, value);
}
return true;
}
});
const notifyStatusHandlers = (user: User, status:"online"| "offLine") => {
if(status === "online"){
user.followers.forEach(({notify}) => {
notify(user);
});
}
};
return proxyUser;
}
迭代器模式
1.定义
在不暴露数据类型的情况下访问集合中的数据
2.应用场景
数据结构中有多种数据类型,列表、树等,提供通用操作接口
3.例子:用for of 迭代所有组件
class MyDomElements {
tag:String;
children: MyDomElements[];
constructor(tag: string){
this.tag = tag;
this.children = [];
}
addChildren(component: MyDomElement){
this.Children.push(component);
}
[Symbol.iterator]() {
const List = [...this.children];
let node;
return{
next:() => {
while ((node = list.shift())) {
node.children.length > 0 && list.push(...node.children);
return {value: node, done: false};
}
return {value:null, done:true};
},
};
}
}
组合模式
1.定义
可多个对象组合使用,也可单个对象独立使用
2.应用场景
DOM,前端组件,文件目录,部门
3.例子:React的组件结构
Count代理操作
export const Count = () => {
const [count, setCount] = useState(0);
return (
<button onClick={()=> setCount((count) => count + 1)}>
count is: {count}
</button>
);
};
Count独立被渲染
function App() {
return (
<div className="App">
<Header />
<Count />
<Footer />
</div>
);
};
总结
思考
辩证看待传统设计模式
设计模式不是银弹(不能解决所有问题)
- 总结出抽象的模式相对简单,但想要抽象应用到场景中却非常困难
- 现代编程语言的多编程范式带来更多可能性
- 真正优秀的开源项目学习设计模式并不断实践