脫離文檔流的自定義彈層

背景

web端日常開發經常會遇到各種彈層需求,彈層內容千奇百怪。根據我們日常的組件化開發經驗,我們會把彈層分爲2個組件

  • 彈層組件:包含蒙版, 展示隱藏邏輯
  • 內容組件:本次需求實際組件

clipboard.png

使用

import CitySelect from './components/CitySelect';
import modal from '@/components/modal';

...
openCitySelect() {
    modal.popup({
        content: CitySelect,
        props: {
            data,
            onSave: () => {...}
        }
    });
}
...

說明

這種用法的好處是顯而易見的

  1. 調用方CitySelect都不需要維護一個visible變量
  2. 調用方CitySelect模塊分離更加清晰,調用方只需要觸發一次openCitySelect,之後就和彈層再無關係,CitySelect只關心自身邏輯不需要關心彈層的狀態。

下面來看此api在vuereact上面的實現

Vue實現

vue下面實現此api的方法較多,可以使用dom操作, 動態組件, render函數來實現。我們以動態組件爲例:

<template>
  <div class="container" v-show="visible">
    <div class="mask"></div>
    <component :is="currentView" v-if="currentView" ref="comp" :v-bind="propsData"></component>
  </div>
</template>

<script>
const Popup = {
  props: {
    title: { type: String, default: '提示' },
    propsData: { type: Object },
    currentView: { type: Object },
  },
  data () {
    return {
      visible: false
    }
  },
  mounted () {
    // 創建後直接打開
    this.$nextTick(this.open)
  },
  methods: {
    open () {
      this.visible = true;
    },
    close () {
      this.visible = false;
      // 移除元素
      if (this.$el.parentNode) {
        this.$el.parentNode.removeChild(this.$el)
      }
    }
  }
}

export default {
  popup({ content, title, props }) {
    let Comp = Vue.extend(Popup);
    let instance = new Comp({
      propsData: {
        propsData: props,
        currentView: content,
        title
      }
    }).$mount();
    document.appendChild(instance.$el);
  } 
};
</script>

React實現

React實現需要配合react-dom,rn的話可以在app入口加一個常駐彈窗容器來實現,原理大致相同。
這裏還是以web端爲例,代碼直接從ant抄過來的。

// 彈窗組件
class Popup extends Component {

  componentDidMount() {

  }

  render() {
    const {
      close,
      props = {},
      visible = false,
    } = this.props;

    if (!visible) {
      return null;
    }

    const DlgContent = content;

    return (
      <Router>
        <Provider {...store}>
          <div className={styles.container}>
            <div className="mask"></div>
            <DlgContent close={close} {...props} />
          </div>
        </Provider>
      </Router>
    )
  }
}

// 方法
const popup = function(config) {

  const div = document.createElement('div');
  document.body.appendChild(div);
  let currentConfig = { ...config, visible: true, close };

  function close(...args) {
    currentConfig = {
      ...currentConfig,
      visible: false
    };

    render(currentConfig);
    setTimeout(destroy, 100);
  }


  function destroy() {
    const unmountResult = ReactDOM.unmountComponentAtNode(div);
    if (unmountResult && div.parentNode) {
      div.parentNode.removeChild(div);
    }
  }

  function render(config) {
    ReactDOM.render(<Popup {...config} />, div);
  }

  render(currentConfig);

  return {
    destroy: close
  };
};

const modal = { popup } ;
export default modal;

注意

由於彈窗是我們手動new出來的,並沒有包含在入口的jsx引用中,所以類似routerstorelocale這些Provider需要手動重新包裝下,這樣才能像頁面一樣使用相關功能

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章