阅读 303
你不知道的Dialog使用技巧

你不知道的Dialog使用技巧

需求背景:

开发的某一天,产品经理找到我说需要在列表每个卡片上新增一个按钮,用户点击后能唤醒弹窗,然后用户可以在弹窗内输入并提交输入的内容,很容易吧!收到需求后,那我们就开始需求分析一下,而不是立马就去codeing(以下使用技术栈为Vue2.x,Element UI 2.15.6)

需求分析

方案1:

在每个卡片组件中都写一个按钮,然后再写一个弹窗组件,伪代码如下:

<template>
  <div class="post-card-component">
    ...省略部分代码
   <el-button type="text" @click="dialogVisible = true">点击打开 Dialog</el-button>

    <el-dialog
      title="提示"
      :visible.sync="dialogVisible"
      width="30%"
      :before-close="handleClose">
      <span>这是一段信息</span>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>

export default {
  	name: 'PostCard',
    data() {
      return {
        dialogVisible: false
      };
    },
    methods: {
      handleClose(done) {
        this.$confirm('确认关闭?')
          .then(_ => {
            done();
          })
          .catch(_ => {});
      }
    }
  };
</script>
复制代码

优点: 使用该卡片组件的人而言,不需要做任何变动,只需要照常引入即可

缺点: 很明显就是,如果一个列表中有多个卡片组件渲染,那必然也会渲染多个隐藏的dialog组件

方案2:

修改使用卡片组件的方式,传入唤醒dialog组件的方法,卡片组件内部触发,同时父组件只需要渲染一个dialog组件,以供所有的卡片组件使用,伪代码如下:

<template>
  <div class="post-list-component">
    <PostCard v-for="item in list" 
              :key="item.id" 
              :changeDialogVisible="changeDialogVisible"
     ></PostCard>
    <el-dialog
      title="提示"
      :visible.sync="dialogVisible"
      width="30%"
      :before-close="handleClose">
      <span>这是一段信息</span>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import PostCard from '@/components/PostCard'
export default {
  name: 'PostList',
  components: {
   PostCard
  },
  data() {
    return {
      list: [],
      dialogVisible: false
    };
  },
  methods: {
    changeDialogVisible() {
      this.dialogVisible = !this.dialogVisible
    },
    handleClose(done) {
      this.$confirm('确认关闭?')
        .then(_ => {
        done();
      })
        .catch(_ => {});
    }
  }
  };
</script>
复制代码

优点: 不用重复渲染多个dialog组件,性能提升了

缺点: 每个使用到卡片组件的地方都需要定义一个dialog组件,维护成本上去了

通过上面的分析,我们发现通用的上述2种方案并不能很好的满足我们的需求,那有没有更好的方式呢?

动态渲染组件

那是否可以将上面2种方案都合并一下,既可以不用修改原来引入组件的地方,又可以不用重复渲染?最后通过查阅资料发现 Vue.extend 就可以满足我们以上需求,不太了解该 Api的小伙伴可以点击链接复习一哦 ,先贴一下官方文档使用示例:

// 创建构造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
复制代码

嗯,看起来很容易~那我们就来试试看我们要如何修改,在卡片组件内先引入我们的弹窗组件,并不立即渲染,而是将渲染过程延后,当用户点击时才去动态渲染,同时将组件实例动态挂载在body上

<template>
  <div class="post-card-component">
    ...省略部分代码
   <el-button type="text" @click="dynamicCreatedialog">点击打开 Dialog</el-button>
  </div>
</template>

<script>
import Dialog from './components/Dialog.vue'
import Vue from 'vue'
export default {
  name: 'PostCard',
  components: {
   Dialog
  },
    data() {
      return {
        dialogVisible: false
      };
    },
    methods: {
      dynamicCreatedialog(done) {
       		const DialogComponentCtor = Vue.extend(Dialog)
          const dialogComponent = new DialogComponentCtor()
          document.body.appendChild(dialogComponent.$el)
      }
    }
  };
</script>
复制代码

然而你以为就这样结束了?在实际过程中,当我们每次点击都会创建 dialog 组件,然后body上就会追加有很多我们创建的Dom,那要如何做呢?在组件内,我们可以使用 v-if来控制组件是否渲染,当组件关闭后,将值改为 false 然后就可以销毁组件Dom

<template>
 <el-dialog
  v-if="live"
  title="提示"
  :visible.sync="dialogVisible"
  width="30%"
  :before-close="handleClose">
  <span>这是一段信息</span>
  <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
  </span>
</el-dialog>
</template>

<script>
export default {
    data() {
      return {
        live: true,
        dialogVisible: true
      };
    },
    methods: {
      handleClose(done) {
        this.$confirm('确认关闭?')
          .then(_ => {
          	this.live = false
            done();
          })
          .catch(_ => {});
      }
    }
  };
</script>
复制代码

至此就解决了之前2种方案的不足之处,既不用修改原来已引入该组件的地方,同时又没有照成不必要的渲染,那有同学就问了,这样渲染组件要如何传递 Props 呢? 答案也很简单,在 new Vue 过程中,会执行initStateinitState 又会执行 initProps 所以我们就看到了 props 是需要传入PropsData 字段的。

// src/core/instance/state.js

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}
复制代码

上面的示例我们就可以改成如下所示

<template>
  <div class="post-card-component">
    ...省略部分代码
   <el-button type="text" @click="dynamicCreatedialog">点击打开 Dialog</el-button>
  </div>
</template>

<script>
import Dialog from './components/Dialog.vue'
import Vue from 'vue'
export default {
  name: 'PostCard',
  components: {
   Dialog
  },
    data() {
      return {
        dialogVisible: false
      };
    },
    methods: {
      dynamicCreatedialog(done) {
       		const DialogComponentCtor = Vue.extend(Dialog)
          const dialogComponent = new DialogComponentCtor({
          	propsData: {
            	id: xxx,
              name: xxx
            }
          })
          document.body.appendChild(dialogComponent.$el)
      }
    }
  };
</script>
复制代码

总结:

当我们拿到需求后,并不要一上来就去写代码,而是要先分析梳理需求,然后去编码实现,看似都能完成需求(又不是不能用)那是否有还有更好的实现方式来实现,最后的方式可能比不是最优解,但目前来看也是不错的解决方案,最后如果你有更优解,可以评论回复一下哦~ 感激~

文章分类
前端
文章标签