一次Toast組件引起的思考

背景

最近組內有一個新項目,須要用的Toast這樣一個組件。內心想,這樣的組件,還不是分分鐘就搞定呀。而後一頭砸進去了開始寫。javascript

如何作

toast 的展現與否,跟展現什麼我都交給父組件去作,Toast自己只管展現就能夠了,內部須要任何別的邏輯 初版代碼以下css

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 傳遞下去就行了 代碼以下java

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;

複製代碼

可是這樣用起來仍是不舒服,若是頁面多了,那豈不是每一處頁面都須要這麼處理,若是層級比較深,那豈不是要一層一層的傳遞下去?react

綜上所碰到的問題,我思考了一下我想要的Toast組件的樣子bash

  1. 只需頁面引入一次便可
  2. Toast 自己的邏輯本身處理,不須要調用方去管
  3. Toast 須要暴露一些 API,讓業務方去調用

整理完上面的問題,想起了觀察者模式的應用,我是否是能夠,在 Toast 自己去訂閱幾個事件,而後當我想要處理跟Toast 相關邏輯的時候我再 emit 相關事件,事情是否是就變的簡單了呢?ui

首先我須要一個事件系統,這個事件系統須要知足如下功能this

  1. on 方法去訂閱事件
  2. off 去解除訂閱
  3. emit 方法去觸發事件
  4. 有一個 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));
    }
  }
}

複製代碼

有了事件系統,咱們再去改造如下咱們的Toastspa

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 方法便可。code

總結

  1. 相似 Toast 這樣須要事件系統的組件還有不少,咱們均可以往這樣的思惟方式上面去改造
  2. 在寫 React 的時候不要總侷限於,父子間的一層一層的傳遞
相關文章
相關標籤/搜索