05月26, 2021

Vue实战(十一)-- Watch 与 命令式导航

十一、Watch 与 命令式导航

为了讲解这部分的内容,首先加入新的分页组件

封装分页组件

具体分页API,参考ElementUI Pagination

创建Pager.vue组件

<template>
  <div>
    <el-pagination     
        layout="prev, pager, next" 
        :page-size="limit"
        :current-page="page"
        :pager-count="pagerCount"
        :total="total"
        @current-change="handleCurrentChange">
    </el-pagination>
  </div>
</template>

<script>
export default {
    props:["limit","page","pagerCount","total"],
    methods:{
        handleCurrentChange(page){
            console.log(page);
            this.$emit("pageChange",page);
        }
    }
};
</script>
<style></style>

在ChannelNews.vue中使用

没有数据的时候,先模拟数据测试一下

<template>
  <div>
    新闻页面,频道id是===>{{$route.params.id}}
    <Pager 
      :total="total" 
      :page="page" 
      :limit="limit" 
      :pagerCount="pagerCount"
      @pageChange="handlePageChange"></Pager>
  </div>
</template>
<script>
import Center from '@/components/Center'
import Pager from '@/components/Pager'
export default {
  components:{
    Center,Pager
  },
  data(){
    return {
      total:1000,
      pagerCount:11,
      limit:10,
      page:1
    }
  },
  methods:{
    handlePageChange(page){
      console.log("page=====" + page);
    }
  }
}
</script>
<style>
</style>

注意:ElementUI的分页组件有个小坑,页码pager-count属性的数量只能是奇数

然后,加入新闻数据:

<template>
  <div>
    新闻页面,频道id是===>{{$route.params.id}}
    <NewList :news="news"></NewList>
    <Pager 
      :total="total" 
      :page="page" 
      :limit="limit" 
      :pagerCount="pagerCount"
      @pageChange="handlePageChange"></Pager>
  </div>
</template>
<script>
import Center from '@/components/Center'
import Pager from '@/components/Pager'
import NewList from '@/components/NewList'
import {getNewsList} from '@/services/NewsService'
export default {
  components:{
    Center,Pager,NewList
  },
  data(){
    return {
      total:1000,
      pagerCount:11,
      limit:10,
      page:1,
      news:[]
    }
  },
  methods:{
    handlePageChange(page){
      console.log("page=====" + page);
    },
    async getData(){
      let resp = await getNewsList(
        this.$route.params.id,
        this.page,
        this.limit
      );
      this.news = resp.contentlist;
      this.total = resp.allNum;
    }
  },
  created() {
    this.getData();
  },
}
</script>
<style>
</style>

新闻列表数据是显示出来了,但是现在点击分页并不能改变新闻列表的值。

关键点是,现在点击分页页码,不可能和在created中获取的数据产生任何关联,因此代码要做出修改,应该是点击分页页面的时候去获取远程数据,并且在每次改变的时候要改变数据。

试着把获取数据的内容放到触发分页的方法中:

...前后代码省略
methods:{
    handlePageChange(page){
      console.log("page=====" + page);
      this.getData();
    },
    ......
}

但是现在其实还是有一个小bug,如果在url地址上输入分页信息,并不是实现分页的效果 比如输入:127.0.0.1:8080/频道id?page=3 这种形式,我们并不能捕获?page=3后面的查询参数,要实现这个效果,我们可以使用命令式导航

命令式导航

之前我们都是通过<router-link>标签来实现路由导航,也可以通过编码的方式实现导航

this.$router.push();

我们可以直接使用下面的代码

...其他代码省略
computed:{
    page(){
      return +this.$route.query.page || 1;
    }
},
methods:{
    handlePageChange(page){
      console.log("page=====" + page);
      // console.log("this.$route.query.page1=====" + this.$route.query.page);
      this.$router.push("?page="+page);
      // console.log("this.$route.query.page2=====" + this.$route.query.page);
      this.getData();
    },
    ...其他代码省略
}

this.$router.push("?page="+page);这句代码的意义是:如果当前url地址不变化,就将当前url地址后面加上page变量,比如之前是 http://localhost:8080/channels/5572a108b3cdc86cf39001cd,加上这句代码之后就会变成 http://localhost:8080/channels/5572a108b3cdc86cf39001cd?page=3,相当于把当前页面参数传递了过去

而之前放在data()中的变量,也直接换成了计算属性,

this.$route.query.page 这句代码的意义是,从当前路由中取出page属性的值,注意值是一个字符串,所以return +this.$route.query.page || 1;这句代码的意义是返回当前的页码,并且转换为数字类型,如果没有,就返回1

所以这一段代码加入之后,分页已经可以正常使用了。

注意:

this.$router.push("?page="+page);

只是一种简写,如果前面的参数可能发生变化,比如每次切换频道,频道id都会发生变化,那就需要使用下面的写法

this.$router.push({
    name:"Channels",
    params:{
      id:this.$route.params.id
    },
    query:{
      page:page
    }
})

所以,最后我们的代码可以改成下面的样子:

......其他省略
computed:{
    page(){
      return +this.$route.query.page || 1;
    }
},
methods:{
    handlePageChange(page){
      this.$router.push({
          name:"Channels",
          params:{
            id:this.$route.params.id
          },
          query:{
            page:page
          }
      })
      this.getData();
    }
    ....
}

现在能点击不同的页码就行分页了,但是还有一个重要问题,初始没有,当然,同样可以在created()钩子函数中调用获取异步数据的方法,让数据在初始化的时候显示一下就可以了。

Watch 侦听属性

但是现在其实还有一个更重要的bug,我们切换不同的频道,发现频道id其实有变化,但是,新闻列表界面却没有任何变化。我们来回顾一下出现这个问题的过程

2021-05-19 21.18.39

页面渲染的过程

首页 -> 国内焦点

  1. 页面渲染的是Home.vue组件
  2. 点击了“国内焦点”
  3. 销毁“Home.vue”组件
  4. 创建和加载“ChannelNews.vue”组件

国内焦点 -> 国际焦点

  1. 页面渲染的是“ChannelNews.vue”组件
  2. 点击了国际焦点
  3. 由于同样都是“ChannelNews.vue”组件,所以组件不会销毁和重建,当然也就不会调用created()方法

watch的作用可以监控一个值的变换,并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。

简单来说,就是在某个值上,绑定一个事件监听,当这个值发生了变化,就会执行相关的方法

使用方式其实和计算属性,方法差不多

...
watch:{
    "监听的值":{
        //设置为true表示一开始就需要监听,不然第一次没有值的时候是不会监听的
        immediate:true,
        handler(){
            //需要执行的方法
        },
        //开启深度监听
        //deep:true
    }   
}
...

因此,在代码中我们可以加入watch侦听属性

...其他省略
// created() {
//   this.getData();
// },
watch: {
    "$route.params.id":{
      immediate:true,
      handler(){
        console.log(this.$route);
        this.getData();
      }
    }
},

最后,为了让界面更加的美观,可以加上加载的Loading组件以及在页面的最开始显示频道名称

ChannelNews.vue修改的代码

<template>
  <div>
    <div class="type-title">
      {{channelName}}
    </div>
    <Loading v-if="isLoading"></Loading>
    <NewList v-else :news="news"></NewList>
    <Pager 
      :total="total" 
      :page="page" 
      :limit="limit" 
      :pagerCount="pagerCount"
      @pageChange="handlePageChange"></Pager>
  </div>
</template>
<script>
import Center from '@/components/Center'
import Pager from '@/components/Pager'
import NewList from '@/components/NewList'
import Loading from '@/components/Loading'
import {getNewsList,getNewsChannels} from '@/services/NewsService'
export default {
  components:{
    Center,Pager,NewList,Loading
  },
  data(){
    return {
      total:0, //数量默认值
      pagerCount:11,//页码显示数量,必须是奇数
      limit:10, //每页数据显示数量
      news:[], //新闻数据
      isLoading:true, //是否加载Loading组件
      channelName:"",//频道名称
    }
  },
  computed:{
    page(){ //page计算属性,默认为1,其他时候从路由的url中取得page数据
      return +this.$route.query.page || 1;
    }
  },
  methods:{
    //切换页码的方法
    handlePageChange(page){
      //命令式路由
      this.$router.push({
          name:"Channels",
          params:{
            id:this.$route.params.id
          },
          query:{
            page:page
          }
      })
      this.getData();
    },
    //设置频道名称
    async setChannelName(){
      var resp = await getNewsChannels();
      // 由于只有阿里云只提供了获取全部频道的API
      // 因此只有通过循环找到channelId对应的频道
      var channel = resp.find((item)=>{
        return this.$route.params.id == item.channelId;
      });
      this.channelName = channel.name;
    },
    //获取远程数据
    async getData(){
      this.isLoading = true;
      let resp = await getNewsList(
        this.$route.params.id,
        this.page,
        this.limit
      );
      this.total = resp.allNum;
      this.news = resp.contentlist;
      this.isLoading = false;
    }
  },
  //watch侦听属性
  watch: {
    //侦听id值发生变化执行handler()函数
    "$route.params.id":{
      immediate:true,
      handler(){
        this.setChannelName();
        this.getData();
      }
    }
  },
}
</script>
<style scoped>
.type-title {
    font-size: 2em;
    color: #888;
    border-bottom: 1px solid #ccc;
    padding-bottom: 10px;
}
</style>

本文链接:http://www.yanhongzhi.com/post/VueInAction-11.html

-- EOF --

Comments