背景
最近組內有一個新專案,需要用的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 的時候不要總侷限於,父子間的一層一層的傳遞