背景
最近组内有一个新项目,需要用的Toast这样一个组件。心里想,这样的组件,还不是分分钟就搞定呀。然后一头砸进去了开始写。
如何做
toast 的展示与否,跟展示什么我都交给父组件去做,Toast本身只管展示就可以了,内部需要任何别的逻辑 第一版代码如下
import React from 'react';
import styles from './style.module.css';
const Toast = ({ content, showToast }) => {
if (!showToast) {
return null;
}
return <div className={styles.container}>{content}</div>;
};
export default Toast;
然后再调用的地方,发现如果我需要使用Toast,那么我使用的地方都得引入一下,这显然很不友好。
然后我就将控制Toast的展示与否交由最外层的App组件去做, 如果那里需要使用Toast,那么就将handleShowToast 传递下去就好了
代码如下
import React, { useState } from 'react';
import Toast from '@components/Toast';
const App = (props) => {
const [showToast, setToastVisible] = useState(false);
const [content, setToastContent] = useState('');
const handleShowToast = (toastContent, delay) => {
setToastVisible(true);
setToastContent(toastContent);
const timer = setTimeOut(() => {
setToastVisible(false);
setToastContent('');
}, delay)
}
return (
<div>
<Toast showToast={showToast} content={content}/>
<div>
...
</div>
</div>
);
};
export default App;
但是这样用起来还是不舒服,如果页面多了,那岂不是每一处页面都需要这么处理,如果层级比较深,那岂不是要一层一层的传递下去?
综上所碰到的问题,我思考了一下我想要的Toast组件的样子
- 只需页面引入一次即可
- Toast 本身的逻辑自己处理,不需要调用方去管
- Toast 需要暴露一些 API,让业务方去调用
整理完上面的问题,想起了观察者模式的应用,我是不是可以,在 Toast 本身去订阅几个事件,然后当我想要处理跟Toast 相关逻辑的时候我再 emit 相关事件,事情是不是就变的简单了呢?
首先我需要一个事件系统,这个事件系统需要满足以下功能
- on 方法去订阅事件
- off 去解除订阅
- emit 方法去触发事件
- 有一个 list 去存储所有相关事件。 最终实现的 event 如下
interface EventD {
list: any;
}
export default class Event implements EventD {
list = new Map();
on(event: string, callback: any) {
if (!this.list.has(event)) {
this.list.set(event, []);
}
this.list.get(event).push(callback);
return this;
}
off(event: string) {
this.list.delete(event);
return this;
}
emit(event: string, ...args: any) {
if (this.list.has(event)) {
this.list.get(event).forEach((callback: any) => setTimeout(() => callback(...args), 0));
}
}
}
有了事件系统,我们再去改造以下我们的Toast
import React, { useEffect, useState } from 'react';
import Event from '@lib/event';
import styles from './style.module.css';
const event = new Event();
const Toast = () => {
const [showToast, setToastVisible] = useState(false);
const [content, setToastContent] = useState('');
useEffect(() => {
event.on('showToast', (toastContent: string, delay: number = 2000) => {
setToastContent(toastContent);
setToastVisible(true);
const timer = setTimeout(() => {
clearTimeout(timer);
setToastVisible(false);
}, delay);
});
return () => {
event.off('showToast');
};
});
if (!showToast) {
return null;
}
return <div className={styles.container}>{content}</div>;
};
export default Toast;
export const showToast = (content: string, delay?: number) => event.emit('showToast', content, delay);
这样我们就可以在APP组件内引用一次即可,如果需要展示 toast 的时候,只需要调用一下 Toast 组件暴露的 showToast 方法即可。
总结
- 类似 Toast 这样需要事件系统的组件还有很多,我们都可以往这样的思维方式上面去改造
- 在写 React 的时候不要总局限于,父子间的一层一层的传递