从零开始解说vue中动态组件的创建和使用

3,295 阅读3分钟

功能介绍

  1. 逻辑: 用户手动选择加入那些组件
  2. 方式:通过element ui tree组件复选框来选择,加载哪个组件

1.在html页面构建

加入keep-alive是保持这些组件的状态,以避免反复重渲染导致的性能问题

el-tree(:data="treeData"
        ref="tree"
        class="filter-tree"
        show-checkbox
        node-key="menuId"
        @check="handleCheck"
        :props="defaultProps")
keep-alive(v-for="(item, index) in compArr" :key="index")
  component(:is="addComponents(item)"
            @value-begin="getBegin"
            @value-updated="getUpdate")

2. 导入组件

// 导入组件
import overView from './report_overview/index'
import transparent from './report_transparent/index'
import reliable from './report_reliable/index'

3. 定义组件

// 定义组件
const components = {
  overView,
  transparent,
  reliable,
}

4. 关联组件到固定id

唯一的id,可以用来加载id对应的组件

// 关联组件,这里是把treeData里面定义的menuId和组件相关联
const componentsView = {
  '1': 'overView',
  '2': 'reliable',
  '3': 'transparent',
}

5. 创建数据源,选择节点,加载节点对应组件

  • 逻辑:
  • 第一步,点击tree组件,可以知道选择的目录
  • 第二步,把目录和组件相关联
  • 第三步,根据这个目录,去寻找对应的组件来渲染
/**
 * 选择节点,加载对应组件
 * @param data
 */
handleCheck(e, a) {
  console.log('一个', e, a);
  // 选择节点
  // 1. 添加或者删除节点对应的组件
  // 2. a.checkedKeys是一个数组,对应当前选择的节点,所以点击的是时候根据a.checkedKeys,来添加对应的组件
 
  this.compArr = []
  // 处理只有id,但没有关联组件的节点
  a.checkedKeys.map((item, index) => {
    if (!componentsView[item]) {
      this.$message({
        message: '当前页面未添加对应组件,请联系管理员',
        type: 'warning'
      });
      // 从数组取消当前复选框对应的id
      a.checkedKeys.splice(index, 1)
    }
  })
  // 按tree组件的选择增加对应的组件名称到数组
  a.checkedKeys.map((item) => {
    this.compArr.push({menuId: item, componentName: componentsView[item]})
  })
  
  // 按当前路径赋值到tree组件,当删除某一个复选框后,需要手动对当前tree节点赋值,不然不刷新
  this.$refs.tree.setCheckedKeys(a.checkedKeys)
},

动态组件component需要用的组件数据

 /**
  * 返回需要渲染的组件
  * @param item
  * @returns {*}
  */
addComponents(item){
  return components[item.componentName]
},

6. 根据组件内接口返回,来确认当前组件是否已经完成加载

在子组件内调用接口,这里的menuId是手动写死的,增加一个页面id就自增1,这个值完全可以由父组件传入

async getData() {
  let api = '/'
  let requestData = {}
  // 进入接口初始化为未加载状态,把组件状态广播至父组件
  this.loading = true
  this.$emit('value-begin', {menuId: "00", flag: false})
  
  let res = await this.$Http.axiosPost(api, requestData)
  if (res.code === 200) {
    this.loading = false
    // 获取返回数据后,把加载成功的状态,广播至父组件
    this.$emit('value-updated', {menuId: "00", flag: true})
  }
},

在父组件的methods中加入以下方法

/**
 * 初始化所有的组件
 */
getUpdate(e) {
  console.log('开始', e)
  this.currentStatusArr.map((item, index) => { // 如果数组已经有当前组件id,则删除掉当前组件信息,重新初始化
    if (item.menuId === e.menuId) {
      this.currentStatusArr.splice(index, 1)
    }
  })
  this.currentStatusArr.push(e)
},

/**
 * 确认所有组件接口已经返回
 */
getBegin(e) {
  console.log('结束')
  this.currentStatusArr.map((item, index) => { // 如果数组已经有当前组件id,则删除掉当前组件信息,重新赋值
    if (item.menuId === e.menuId) {
      this.currentStatusArr.splice(index, 1)
    }
  })
  this.currentStatusArr.push(e)
},

/**
* 判断页面加载
*/
judgePage() {
  let flag = true
  // 如果当前页面没有加载完,则返回false
  this.currentStatusArr.map((item) => {
    if (!item.flag) {
      flag = false
    }
  })
  // 返回false,说明某个页面接口数据没有加载完,提示用户等待
  if (!flag) {
    this.$message({
      type:'warning',
      message:'页面加载未完成,请稍后尝试'
    })
    return
  }
}

7.完整的js页面

7-1. 父组件页面js

// 导入组件
import overView from './report_overview/index'
import transparent from './report_transparent/index'
import reliable from './report_reliable/index'

// 定义组件
const components = {
  overView,
  transparent,
  reliable,
}

// 关联组件,这里是把treeData里面定义的menuId和组件相关联
const componentsView = {
  '1': 'overView',
  '2': 'reliable',
  '3': 'transparent',
}

export default {
  data () {
    return {
        startTime: '',
        treeData:   [
            {
                menuId: 1,
                label: '一级 overView',
                children: [],
            },
            {
                menuId: 2,
                label: '一级 reliable',
                children: [],
            },
            {
                menuId: 3,
                label: '一级 transparent',
                children: [],
            },
        ]
        defaultProps: {
            children: 'children',
            label: 'label'
        },
        compArr: [], // 渲染数组
        currentStatusArr: [], // 确认数组内的组件是否渲染都完成
    };
  },
  methods: {
    /**
     * 第一步,点击tree组件,可以知道选择的目录
     * 第二步,把目录和组件相关联
     * 第三步,根据这个目录,去寻找对应的组件渲染
     */
  
    /**
     * 选择节点,加载对应组件
     * @param data
     */
    handleCheck(e, a) {
      console.log('一个', e, a);
      // 选择节点
      // 1. 添加或者删除节点对应的组件
      // 2. a.checkedKeys是一个数组,对应当前选择的节点,所以点击的是时候根据a.checkedKeys,来添加对应的组件
     
      this.compArr = []
      // 处理未上线的路径
      a.checkedKeys.map((item, index) => {
        if (!componentsView[item]) {
          this.$message({
            message: '当前页面未添加报告,请联系管理员',
            type: 'warning'
          });
          // 从数组取消当前复选框对应的id
          a.checkedKeys.splice(index, 1)
        }
      })
      // 按tress组件的选择增加对应的组件数据到数组
      a.checkedKeys.map((item) => {
        this.compArr.push({menuId: item, componentName: componentsView[item]})
      })
      
      // 按当前路径赋值到tree组件,当删除某一个复选框后,需要手动对当前tree节点赋值,不然不刷新
      this.$refs.tree.setCheckedKeys(a.checkedKeys)
    },
   
    /**
     * 返回需要渲染的组件
     * @param item
     * @returns {*}
     */
    addComponents(item){
      return components[item.componentName]
    },
    
  },
  components: {}, // 没有用到普通组件的需要用的components
  mounted () {
    /*
    * 实例挂载之后调用,但是并不是所有子组件也都一起挂载完成
    */
    console.log ('5. mounted', this.$options.name);
    // 添加默认组件
    this.compArr = [{menuId: 1, componentName: 'overView'}]
  }
}

7-1. 子组件页面js

export default {
  data () {
    return {
      loading: false
    };
  },
  props:[],
  watch: {},
  computed: {},
  methods: {
  
    async getData() {
      let api = '/'
      let requestData = {}
  
      this.loading = true
      this.$emit('value-begin', {menuId: "00", flag: false})
      
      let res = await this.$Http.axiosPost(api, requestData)
      if (res.code === 200) {
        this.loading = false
        this.$emit('value-updated', {menuId: "00", flag: true})
      }
    },
  },
 mounted () {
    /*
    * 实例挂载之后调用,但是并不是所有子组件也都一起挂载完成
    */
    console.log ('5. mounted');
    this.toBase64()
  },
};

8. 待优化的点

组件的关联应该是动态的,后台返回关联数据,前端根据关联数据来渲染对应的数据存储到数组内,而不是前端根据id来手动关联。

也就是2,3,4这几个步骤的数据应该是后台给,不是前端写死的,前端获取数据之后进行处理就行了。

(ps:这应该是一个解耦的过程,算不算java里面的控制反转?)