Skip to content

1.非顶层多个请求使用Promise.all实现所有异步请求结束之后再向下执行(if语句下的选择性Promise,如何保证执行完毕再向下执行)

forEach 循环会遍历 this.searchForm 对象的属性,并根据属性的不同执行不同的异步操作。每次遇到异步操作时,它会发起请求并继续执行下一次循环或后续代码,而不会等待异步操作的结果返回。

js
      Object.keys(this.searchForm).forEach(key => {
        if (key == "accountType") {
          getDictionaryList({ typeId: 6070 }).then(ret => {
            if (ret.code === 0) {
              let accountTypelist = ret.data;
              this.searchForm[key].options = accountTypelist.map(e => ({
                key: e.id,
                value: e.name
              }));
              console.log(accountTypelist);
            }
          });
          this.$set(this.formModel, key, "现金账户"); //默认选中,提交时处理为 id
        }
        if (key == "payMethod") {
          queryPayDetailList({ accountType: 6070001 }).then(ret => {
            if (ret.code === 0) {
              let payMethodList = ret.data;
              this.searchForm[key].options = payMethodList.map(e => ({
                key: e.payDetail,
                value: e.payDetailName
              }));
              // console.log(payMethodLst);
            }
          });
        }
        if (key == "createBy") {
          getSubUsers({}).then(ret => {
            if (ret.code === 0) {
              let createrList = ret.data;
              this.searchForm[key].options = createrList.map(e => ({
                key: e.userId,
                value: e.userName
              }));
              // console.log(createrList);
            }
          });
        }
      });

注意:这里是非顶层的多个异步请求,我们无法使用async+await的方法实现异步转为同步,因为 await 只能在顶层使用,无法在 if 语句等内部进行使用!

在这种情况下,如果你想要在所有异步操作执行完成后再继续执行后续代码,可以使用 Promise.all 方法来等待所有的异步操作完成。Promise.all 接受一个包含多个 Promise 对象的数组,并返回一个新的 Promise 对象。这个新的 Promise 对象在所有的 Promise 对象都成功完成时被解决,或者任意一个 Promise 对象失败时被拒绝。

js
const promises = Object.keys(this.searchForm).map(key => { //使用 map 方法,有返回值
  if (key === "accountType") {
    return getDictionaryList({ typeId: 6070 }).then(ret => { //返回 promise 对象
      if (ret.code === 0) {
        const accountTypelist = ret.data;
        this.searchForm[key].options = accountTypelist.map(e => ({
          key: e.id,
          value: e.name
        }));
        console.log(accountTypelist);
      }
    });
  }
  if (key === "payMethod") {
    return queryPayDetailList({ accountType: 6070001 }).then(ret => {
      if (ret.code === 0) {
        const payMethodList = ret.data;
        this.searchForm[key].options = payMethodList.map(e => ({
          key: e.payDetail,
          value: e.payDetailName
        }));
        // console.log(payMethodLst);
      }
    });
  }
  if (key === "createBy") {
    return getSubUsers({}).then(ret => {
      if (ret.code === 0) {
        const createrList = ret.data;
        this.searchForm[key].options = createrList.map(e => ({
          key: e.userId,
          value: e.userName
        }));
        // console.log(createrList);
      }
    });
  }
});

Promise.all(promises).then(() => { //执行所有 promise 对象
  // 在所有异步操作完成后执行后续代码
  // ...
});

2.pubsub 订阅发布的坑

首先我们要明确,订阅发布是不区分实例的,也就是说一个实例发布消息之后,其他的所有的已经订阅的,没有被销毁的(处于挂载状态的)实例都能接收到消息。不像两个组件之间传值,只有两者会互相收到。

这可能造成问题:有的实例我们并不想让其接收到相关消息,避免额外的损耗和动作的产生。

这个时候我们有几种解决方案:

1.在发布者那里做限制,只有在满足一定的条件下才进行发布

2.在接收者那里做限制,只有满足一定条件才触发动作

3.让组件即时被卸载(卸载之前写上取消订阅的方法),不要用 keep-alive(但是如果是一个大页面里面包裹很多小页面,这些小页面一定是同时挂载中的,切换小页面也不会卸载,所以这个时候不用 keep-alive也不好使!)

实际场景:大页面包着 3 个小页面,在切换tab的时候我们希望小页面的分页组件的 size 和 index 可以进行重置!

实现思路:

监听被激活的页面——>发布消息给全局分页组件(table 里面)——>重置 size 和 index——>反向发布消息给子组件(需要判断是否发送,只有符合条件才发送,否则每个实例都会发送消息,会造成很多没必要的响应)——>子组件接收新的 size和 index,如果当前是被激活的,那么就重新请求数据!

1、首先在大页面(父页面)中定义点击 tabs 的切换函数:设置标志变量的改变,让子页面可以感知到当前被激活的页面是谁

vue
<el-tabs
         v-model="activeName"
         @tab-click="handleClick"
         style="margin: 20px; width: '100%'"
         >
  <el-tab-pane label="消费记录" name="first"
               ><ExpenseCalendar :initPage="initPage1"></ExpenseCalendar
    ></el-tab-pane>
  <el-tab-pane label="充值记录" name="second"
               ><ReCharge :initPage="initPage2"></ReCharge
    ></el-tab-pane>
  <el-tab-pane label="提现记录" name="third"
               ><WithdrawalsRecord :initPage="initPage3"></WithdrawalsRecord
    ></el-tab-pane>
</el-tabs>
js
methods: {
    handleClick(tab, event) {
      console.log(tab, event);
      if (tab.index == "0") {
        this.initPage1 = true;
        this.initPage2 = false;
        this.initPage3 = false;
      } else if (tab.index == "1") {
        this.initPage1 = false;
        this.initPage2 = true;
        this.initPage3 = false;
      } else if (tab.index == "2") {
        this.initPage1 = false;
        this.initPage2 = false;
        this.initPage3 = true;
      }
    }
  },

2、子页面监听

js
    <MyTable
      :titleData="titleData"
      :tableData="tableData"
      :totalCount="totalCount"
      :fileName="'ExpenseCalendar'"
    >
        watch: {
    initPage(newVal, oldVal) {
      if (newVal === true) {
        // this.pageCurrent = 1;
        // this.pageSize = 10;
        console.log("pageCurrent", this.pageCurrent);
        console.log("pageSize", this.pageSize);
        pubsub.publish("initPage", "ExpenseCalendar"); //发布消息,重置分页信息
      }
    }
  },
      mounted() {
    // this.doSearch();
    this.pubId1 = pubsub.subscribe("changeSize", (_, data) => { //订阅消息,根据分页信息进行查询
      if (this.pageSize !== data && this.initPage === true) {
        this.pageSize = data;
        this.doSearch(); //只会改变pageSize参数,其他原有的参数不会变的?因为 serach 参数是Search 组件构建出来返回给我们这个组件的,我们只需要保存一下就可以了!——> 所以在这里调用 doSearch 没有问题,不需要去 Search 组件中触发事件!
      }
    });
    this.pubId2 = pubsub.subscribe("changeCurrent", (_, data) => {
      if (this.pageCurrent !== data && this.initPage === true) {
        this.pageCurrent = data;
        this.doSearch();
      }
    });
  },

3、分页组件 table

js
<el-pagination
  @size-change="handleSizeChange"
  @current-change="handleCurrentChange"
  :current-page.sync="currentPage"
  :page-sizes="[10, 20, 30, 40]"
  :page-size.sync="pageSize"
  layout="total, sizes, prev, pager, next, jumper"
  :total="total"
  style="margin-bottom:20px"
>
  </el-pagination>  

  methods: {
    handleSizeChange(val) {
      console.log(`每页 ${val} 条`);
      pubsub.publish("changeSize", val); //反向发布消息
    },
      handleCurrentChange(val) {
        console.log(`当前页: ${val}`);
        pubsub.publish("changeCurrent", val); //反向发布消息
      }
  }, 
  mounted() {
    // this.doSearch();
    this.pubId = pubsub.subscribe("initPage", (_, data) => {
      console.log(this.fileName, data);
      if (this.fileName) {
        if (this.fileName == data) {
          // alert(111);
          this.currentPage = 1;
          this.pageSize = 10;
          this.handleSizeChange(10);
          this.handleCurrentChange(1);
        }
      }
    });
  },

注意:当两个组件之间满足父子关系的时候,如果在满足需求的情况下,一定用 prop+自定义事件的方式进行通讯,非必要不要使用 pubsub 发布订阅全局通讯,会造成不必要的麻烦,增加很多的代码量(因为需要对个别的未卸载组件进行条件判断,防止进行不必要的操作),因为发布订阅会给所有订阅的未销毁的实例发送消息!

当我们确实需要将消息发送给所有订阅的组件时,才去考虑使用 pubsub!

非必要不要使用 pubsub 发布订阅全局通讯!(例如:页面组件 与 search组件 与 table 组件 之间完全使用自定义事件进行通讯就足够了!)

3.element给 current-page 属性添加 .sync 修饰符:解决修改current-page后,视图上还是没改变

vue
    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page.sync="currentPage"
      :page-sizes="[10, 20, 30, 40]"
      :page-size.sync="pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      style="margin-bottom:20px"
    >
    </el-pagination>

  this.currentPage = 1; //这里我们主动修改了属性值,如果不加.sync是不会在页面上响应出来的!
  this.pageSize = 10;

在上述示例中,currentPage 是父组件中的一个变量,它被传递给了分页组件,并通过 .sync 修饰符建立了双向绑定。当父组件中的 currentPage 发生变化时,分页组件会相应地更新显示当前页数。反之,当分页组件的当前页数发生变化时(用户在分页组件中选择了新的页数),父组件中的 currentPage 也会更新,从而保持了两者之间的同步。

总结:通过使用 .sync 修饰符,你可以确保在父组件和分页组件之间共享 currentPage 变量,一旦它的值发生变化,分页组件会重新渲染,显示新的当前页数。

另一种解释:el-pagination组件下的值是异步加载的,使得对于属性值的改变通常要在函数中去实现。 因此el-pagination组件对:current-page属性支持.sync修饰符,用于同步current-page的值

其他el-pagination的坑:

1.分页组件不显示

看看有没有设置:hide-on-single-page="true"?

如果设置了这个参数。那如果total只有一页时。分页组件就会不显示

注意:如果设置的size是每页10条。但是在开发测试过程中。如果数据太少的话。就不太好测试这个分页,这样的话。可以修改page-sizes里的参数,比如:[1,2,5,10, 20, 30, 40, 50, 100] ——> 小技巧

2.Element-ui分页组件的current-page属性只能绑定基础数类型的变量,不能够绑定以对象形式表示的变量(里面的属性也不行),因为这个属性没有办法监听到对象内部的具体某个属性的变化。

3.分页器失效

比如:页面初始化时,pagesize是10,但是page-sizes的数组中没有这个10,就会出现分页器失效的问题。

把我们需要的每页多少条参数写进sizes数组里面即可::page-sizes="[1,10,30,50]"

4.修改current-page后,视图上还是没改变

:current-page.sync="current"

增加一个.sync这样会生效

5.把分页组件单独写成一个子组件时。传值问题

current-page 是一个 v-model, 翻页的时候组件会直接修改current的值,而你的current是props,vue又不允许修改,所以就存在这个警告了。

你把current定义到当前组件即可, 如果父组件需要这个值,你可以通过emit传递过去

其他 element的坑:

1.Element-ui日期选择器组件的 valueFormat: "yyyy-MM-dd" ,只能这么写,否则显示的和选择的日期会不一致,大小写严格区分!

4.js判断一个变量为真还是假

在 JavaScript 中,有几种方法可以判断一个变量是真还是假。以下是常用的方法:

  1. 使用布尔值转换(if 语句判断):JavaScript 中有一个布尔值转换的规则,可以将任意数据类型转换为对应的布尔值。在布尔值转换中,以下情况被视为假(false):

    • false
    • 0
    • ""(空字符串)
    • null
    • undefined
    • NaN

    其他一切都被视为真(true)。

    示例:

    js
    let value = 0;
    
    if (value) {
      console.log("变量为真");
    } else {
      console.log("变量为假");
    }
  2. 使用逻辑运算符:可以使用逻辑运算符来判断变量的真假。常用的逻辑运算符有 &&(与)、||(或)和 !(非)。例如,使用 ! 运算符对一个变量进行取反,可以得到它的相反值。

    示例:

    js
    let value = 10;
    
    if (!value) {
      console.log("变量为假");
    } else {
      console.log("变量为真");
    }
  3. 使用类型比较:有时候需要严格判断一个变量的真假,而不进行隐式的类型转换。可以使用严格相等运算符 ===!== 来比较变量的值和类型。

    示例:

    js
    let value = "false";
    
    if (value === true) {
      console.log("变量为真");
    } else if (value === false) {
      console.log("变量为假");
    } else {
      console.log("变量不是布尔值");
    }

根据具体的需求,选择合适的方法来判断一个变量的真假。如果需要进行严格的类型判断,使用 ===!== 运算符,如果只需要知道一个值是否为真或假,可以使用布尔值转换或逻辑运算符。

5.js 中的 !! 运算符

在JavaScript中,双感叹号(!!)被用作将值强制转换为布尔值的技巧。具体而言,双感叹号用于将任何值转换为其对应的布尔值。

这种技巧的工作原理如下:

  1. 如果值为假值(例如:false、0、空字符串、null、undefined、NaN),那么使用双感叹号将其转换为布尔值后仍为false。
  2. 如果值为真值(例如:非零数字、非空字符串、对象、数组等),那么使用双感叹号将其转换为布尔值后仍为true。

示例:

js
let value1 = 0;
let value2 = "Hello";
let value3 = null;
let value4 = { name: "John" };

console.log(!!value1); // false,0为假值
console.log(!!value2); // true,非空字符串为真值
console.log(!!value3); // false,null为假值
console.log(!!value4); // true,对象为真值

这种技巧常用于确保一个变量的值为布尔类型,或者在进行条件判断时,将非布尔值转换为布尔值方便进行判断。

这种显式转换为布尔类型的做法有时可以用于确保某个变量的值为布尔类型,或者在一些特定场景下,需要将其他类型的值(如字符串、数字等)转换为布尔类型进行判断。在普通的if条件判断中,通常不需要进行显式转换,直接使用原始值即可得到正确的结果。