一次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 的时候不要总局限于,父子间的一层一层的传递
相关文章
相关标签/搜索