Skip to content
久久日记本
曾经年少爱追梦,一心只想往前飞
  • 首页
  • 博客
    • 博客历史
    • 主题
    • 个人文集
  • 关于
    • 正在读的书
    • 作品归档
    • 2018作品归档
    • 联系我
  • 友情链接
  • 留言板
❄
❅
❆
❄
❅
❆
❄
❅
❆
❄
Front-End

深入理解VueJS组件间数据传递

Posted on 2019年1月18日 by 九九 / 1212 Views

深入理解VueJS组件间数据传递

1.引言

最近在用nodejs+mysql+vuejs做一个网站,期间因为需要再次看了不少关于vuejs的文档,所以这是一篇关于vuejs的文章。

首先来看这样一个界面:

screenshot 201901181035 2.png

这是 google 搜索结果页面,这个页面展示了一个简单的列表过滤效果,那么这篇文章的例子将围绕这幅图。

我们将实现如下功能:

1.搜索关键字输入框;
2.搜索结果列表;
3.搜索结果跟随"搜索关键字"高亮关键字

2.父子布局与数据绑定


(searchbox.vue) |--keyword 父组件--| |--子组件--|->article list (article.vue)

设计出以上父子组件嵌套的效果:

父组件包含一个搜索框和一个子组件,该子组件是v-for的渲染列表。

父组件

<template>
  <div id="searchbox">
    <div>
      <el-input></el-input>
    </div>
    <ul>
      <ui-article v-for="(article,index) in list" :key="index"></ui-article>
    </ul>
  </div>
</template>

<script>
import Article from './Article.vue';

export default {
  name: 'SearchBox',
  components: {
    'ui-article': Article,
  },
  data() {
    return {
      list:[
          {
            title: 'The Allegations Against R. Kelly: An Abridged History',
            date: '2019-01-17 12:07'
          },
          {
            title: 'Jawbox Announces Reunion Tour After A Decade Away',
            date: '2019-01-17 16:34'
          },
          {
            title: 'A Song Called \'Quiet\' Struck A Chord With Women. Two Years Later, It is Still Ringing',
            date: '2019-01-16 01:21'
          }
      ]
    };
  }
};
</script>

<style lang="scss" scoped>
.searchbox {
}
</style>

子组件

<template>
  <li class="article">
    <span class="title">title</span>
    <span class="user">author</span>
    <span class="el-tag el-tag--info">date</span>
  </li>
</template>

<script>
export default {
  name: 'Article',
  components: {
  },
  props: [],
  data () {
    return {
    };
  },
  mounted: function () {
  },
  computed: {
  },
  filters: {
  },
  methods: {
  }
};
</script>

<style lang="scss" scoped>
.article {
}
</style>

一个简单的父子嵌套模式就完成了,我们填充上需要传递的数据:

父组件

<el-input v-model="keyword" placeholder="keyword"></el-input>

<ui-article v-for="(article,index) in list" :key="index" :initarticle="article" :initkeyword="keyword" ></ui-article>

子组件

<template>
  <li class="article">
    <span class="title">{{artilce.title}}</span>
    <span class="user">author</span>
    <span class="el-tag el-tag--info">{{article.date}}</span>
  </li>
</template>

props: [
  'initarticle',
  'initkeyword'
],
data () {
  return {
    article: this.initarticle,
    keyword: this.initkeyword
  };
}

3.过滤效果

根据关键字过滤列表:

<ui-intro v-for="(article,index) in list" :key="index" :initarticle="article" :initkeyword="keyword" v-show="keyword.length==0 || (keyword.length>0 && article.title.indexOf(keyword.trim())>=0)"></ui-intro>

很简单,类似AngularJS,我们只需要使用v-if或者v-show来达到前端过滤的效果。

4.关键字高亮:父组件传值给子组件

达到了搜索效果,那么还有最后一步,如果对搜索的关键字在搜索结果中高亮显示呢。

需要给每个文章标题中的关键字加上HTML样式就可以解决问题了。

在父组件中将输入框绑定键盘事件来触发高亮事件:

<el-input v-model="keyword" placeholder="keyword" @keyup.native="filterArticle()"></el-input>


filterArticle: function () {
  let keyword = this.keyword;
  let temp = this.list; // Object.assign({}, this.post.list);
  temp.map(item => {
    if (item.title.indexOf(keyword) >= 0) {
      item.selected = true;
      item.titlehtml = item.title.replace(keyword, '<span class="colored">' + keyword + '</span>');
    } else {
      item.selected = false;
      item.titlehtml = item.title;
    }
  });
  this.post.list = temp;
}

在子组件中修改绑定为:

<span class="title">{{artilce.titlehtml}}</span>

更新完数据后,子组件的dom并未重新渲染。

用log标记,发现数据确实已经更新,但是dom尚未重新得到渲染。

我们可以在这篇文章中找到原因:Vue 父组件ajax异步更新数据,子组件获取不到

子组件钩子mounted应该在刚开始页面渲染时候执行一次,执行的时候如果你的父组件异步请求还未执行回调,子组件mounted获取不到值的,正确的做法是在子组件里面使用watch监听lineone,如果lineone改变后即可更新renderChart

看起来加一个watch可以解决问题,但是一切看起来复杂起来。

(注:未尝试)

插一个题外的知识:@keyup.native

由于element-ui 的input会生成这样的dom

<div class="el-input">
  <input type="text" autocomplete="off" placeholder="keyword" class="el-input__inner">
</div>

所以单纯使用:keyup并不能达到监听事件的目的。

监听原生事件,我们可以使用

v-on:keyup.native
@keyup.native

关于这个,可以参考这两篇文章:

el-input如何响应v-on:keyup.enter

用 v-on 监听原生事件

5.关键字高亮:使用filter过滤器

我们考虑在子组件中监视keyword的变化,来达到过滤当前一条数据的目的。

首先, 父组件keyword输入框不需要绑定任何事件,直接需要传递 article和keyword给子组件,在子组件中实现监视。

在子组件中添加一个watch来监视数据的变化。

  watch: {
    initarticle () {
      this.article = this.initarticle;
    },
    initkeyword () {
      this.keyword = this.initkeyword;
    }
  }

实际上这里主要是监视keyword的变化,更新dom

使用filter过滤器,这是在写AngularJS种常用的一种方式,查了查,VueJS有同样的方式。

先注释上一步[4]的filterArticle解决方法。

根据文档,带参数的filter用法如下:

{{param1 | function(param2)}}

function(param1,param2){}

在子组件中添加filter:

{{article.title | colorTitle(keyword)}}

filters: {
  colorTitle: function (title, keyword) {
    return keyword.length === 0 ? title : title.replace(keyword, '<span class="colored">' + keyword + '</span>');
  }
}

(注:如果不使用参数,直接使用this.keyword和this.title是不是也可以获取到这两个值呢?经过实验,发现filter方法中无法拿到props的值)

通过官方文档计算属性和侦听器,我们可以使用computed来实现模板内的表达式,这里不再探讨。

刷新页面,发现随着keyword的变化,title中的关键字成功被加入了<span class="colored"></span>

但是有个问题,如何让HTML的title友好的渲染呢。

6.使用 v-html渲染

使用v-html可以渲染带标签的字符串,

<span class="title" v-html="article.title"></span>

但是有个问题,v-html中无法使用filter,通过这个issue: v-html does not work with filters

我们可以直接在v-html中调用方法来解决问题。

<span class="titles" v-html="colorTitle(article.title,keyword)"></span>


methods: {
  colorTitle: function (title, keyword) {
    return keyword.length === 0 ? title : title.replace(keyword, '<span class="colored">' + keyword + '</span>');
  }
}

刷新页面,title输出的HTML被正确的展示了出来。

7.高亮colored样式

为colored样式添加高亮效果

  .title {
    .colored {
      color: black;
      background-color: #FFFF00;
    }
  }

然而样式并未正确展示在页面上。

参考文档 Deep Selectors: 深度作用选择器 ,知道有个叫做深度作用选择器的东西,感觉雷同上面的监听原生事件。

文档这样描述 如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符: (然而经过实验>>>操作符号并不能解决,这里不知为何?)

在.colored前标记/deep/:

  .title {
    /deep/ .colored {
      color: black;
      background-color: #FFFF00;
    }
  }

刷新页面,完美解决。

screenshot 201901181035 1.png

8.展开文章详情:子组件触发父组件

上面列表实现,如何实现展开详情呢。

有这样一个思路:点击子组件 文章列表某一条,触发点击事件,该事件触发父组件获取该文章详情的请求更新数据。

所以可以这样来:

子组件:

<template>
  <!-- $emit('one-article') 中的 click 事件 触发父组件文章详情事件 -->
  <li class="article" v-on:click="$emit('one-article')">
    <!-- todo html -->
  </li>
</template>

父组件:

<ul>
  <ui-intro v-for="(article,index) in list" :key="index" :initarticle="article" :initkeyword="keyword" v-show="keyword.length==0 || (keyword.length>0 && article.title.indexOf(keyword.trim())>=0)"  v-on:one-article="oneArticle(article)"></ui-intro>
  <!-- oneArticle 为后面点击列表查看详情使用  -->
</ul>


<script>
export default {
  name: 'SearchBox',
  components: {
  },
  data () {
    return {
    };
  },
  mounted: function () {
  },
  methods: {
    oneArticle: function (article) {
      // todo 获取文章详情
    }
  }
};
</script>

接下来,我们可以继续做Detail组件来实现上面的文章内容的渲染。

通过这篇简单的文章,可以深入了解一下VueJS中父子组件传值可能的坑。

如果想完美避开各种传值的难点,在单页网页中,可以定义一个全局的PageModel来渲染页面并绑定渲染,事件触发只更新数据,并做到及时更新和清理数据,这样也不是不可以解决问题的。

screenshot 201901181035 3.png

9.完整Demo

懒得传git了,附上本次手写简单的demo,仅供参考:

父组件

<template>
  <div id="searchbox">
    <div>
      <el-input></el-input>
    </div>
    <ul>
      <ui-article v-for="(article,index) in list" :key="index"></ui-article>
    </ul>
  </div>
</template>
<template>
  <div id="searchbox">
    <div>
      <el-input v-model="keyword" placeholder="keyword" @keyup.native="filtertitle()"></el-input>
    </div>
    <ul>
      <ui-intro v-for="(article,index) in list" :key="index" :initarticle="article" :initkeyword="keyword" v-show="keyword.length==0 || (keyword.length>0 && article.title.indexOf(keyword.trim())>=0)"   v-on:one-article="oneArticle(article)"></ui-intro>
      <!-- oneArticle 为后面点击列表查看详情使用  -->
    </ul>
  </div>
</template>

<script>
import Article from './Article.vue';

export default {
  name: 'SearchBox',
  components: {
    'ui-article': Article,
  },
  data() {
    return {
      list:[
          {
            title: 'The Allegations Against R. Kelly: An Abridged History',
            date: '2019-01-17 12:07'
          },
          {
            title: 'Jawbox Announces Reunion Tour After A Decade Away',
            date: '2019-01-17 16:34'
          },
          {
            title: 'A Song Called \'Quiet\' Struck A Chord With Women. Two Years Later, It is Still Ringing',
            date: '2019-01-16 01:21'
          }
      ]
    };
  },
  mounted: function () {
  },
  methods: {
    oneArticle: function (article) {
      // todo 获取文章详情
    }
  }
};
</script>

<style lang="scss" scoped>
.searchbox {
}
</style>

子组件

<template>
  <!-- $emit('one-article') 触发父组件文章详情事件 -->
  <li class="article" v-on:click="$emit('one-article')">
    <!-- {{article.title}} -->
    <!-- {{article.title | colortitle(keyword)}} -->
    <span class="title" v-html="colortitle(article.title,keyword)"></span>
    <span class="user">author</span>
    <span class="el-tag el-tag--info">{{article.date}}</span>
  </li>
</template>

<script>
export default {
  name: 'Article',
  components: {
  },
  props: [
    'initarticle',
    'initkeyword'
  ],
  data () {
    return {
      article: this.initarticle,
      keyword: this.initkeyword
    };
  },
  watch: {
    initarticle () {
      this.article = this.initarticle;
    },
    initkeyword () {
      this.keyword = this.initkeyword;
    }
  },
  // mounted: function () {
  // },
  computed: {
    // colortitle: function () {
    //   return this.article.title.replace(this.keyword, '<span class="colored">' + this.keyword + '</span>');
    // }
  },
  filters: {
    // colortitle: function (title, keyword) {
    //   return keyword.length === 0 ? title : title.replace(keyword, '<span class="colored">' + keyword + '</span>');
    // }
  },
  methods: {
    colortitle: function (title, keyword) {
      return keyword.length === 0 ? title : title.replace(keyword, '<span class="colored">' + keyword + '</span>');
    }
  }
};
</script>

<style lang="scss" scoped>
.article {
  .title {
    /deep/ .colored {
      color: black;
      background-color: #FFFF00;
    }
  }
}
</style>

10.参考文档:

原生事件监听:

el-input如何响应v-on:keyup.enter

用 v-on 监听原生事件

组件刷新

Vue 父组件ajax异步更新数据,子组件获取不到

Object.assign() 浅拷贝与深拷贝

模板语法

计算属性和侦听器

filter绑定demo

Vue系列之computed使用详解(附demo,不定期更新)

Filter in PROPS in vue.js

v-html does not work with filters

模板样式

vue 中控制v-html 中的样式,但不影响全局的小技巧

vue中慎用style的scoped属性

Vue scoped CSS 与深度作用选择器 /deep/

Deep Selectors: 深度作用选择器

Vue
九九
过去的我们,现在的自己,往事,终会随风而逝。 View all posts by 九九 →

Post navigation

Older post
Java拾遗
Newer post
三年花生酱

标签云

2019ncov Android ASP.NET C# C/C++ CSS Div DX11 flask front-end GAE Git Java JJProject JS Life MSSQL MVC OpenSource Oracle Python React React-Native Software Tools Vue Webpack Website Window WP7 乱记 十年旧梦 天气 宝宝成长日记 小说 工作 情感 故障 散文 日记 游戏开发 网新实训笔记 花落梧桐 诗间集 转载

时光机

  • 2022年12月
  • 2022年4月
  • 2022年3月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年10月
  • 2021年9月
  • 2021年8月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年5月
  • 2019年12月
  • 2019年10月
  • 2019年9月
  • 2019年6月
  • 2019年5月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年9月
  • 2018年8月
  • 2018年7月
  • 2018年6月
  • 2018年3月
  • 2018年2月
  • 2018年1月
  • 2017年11月
  • 2017年10月
  • 2017年9月
  • 2017年7月
  • 2017年3月
  • 2017年1月
  • 2016年12月
  • 2016年11月
  • 2016年10月
  • 2016年7月
  • 2016年3月
  • 2016年2月
  • 2016年1月
  • 2015年12月
  • 2015年11月
  • 2015年10月
  • 2015年9月
  • 2015年8月
  • 2015年7月
  • 2015年4月
  • 2015年3月
  • 2015年2月
  • 2015年1月
  • 2014年12月
  • 2014年11月
  • 2014年10月
  • 2014年9月
  • 2014年8月
  • 2014年7月
  • 2014年6月
  • 2014年5月
  • 2014年4月
  • 2014年3月
  • 2014年2月
  • 2014年1月
  • 2013年12月
  • 2013年11月
  • 2013年10月
  • 2013年9月
  • 2013年8月
  • 2013年7月
  • 2013年6月
  • 2013年5月
  • 2013年4月
  • 2013年3月
  • 2013年1月
  • 2012年11月
  • 2012年10月
  • 2012年9月
  • 2012年8月
  • 2012年7月
  • 2012年6月
  • 2012年5月
  • 2012年4月
  • 2012年3月
  • 2012年2月
  • 2012年1月
  • 2011年12月
  • 2011年11月
  • 2011年10月
  • 2011年9月
  • 2011年8月
  • 2011年6月
  • 2011年5月
  • 2011年4月
  • 2011年3月
  • 2011年2月
  • 2010年12月
  • 2010年11月
  • 2010年10月
  • 2010年9月
  • 2010年8月
  • 2010年6月
  • 2010年5月
  • 2010年2月
  • 2010年1月
  • 2009年12月
  • 2009年11月
  • 2009年10月
  • 2009年9月
  • 2009年8月
  • 2009年7月
  • 2009年6月
  • 2009年5月
  • 2009年4月
  • 2009年3月
  • 2009年2月
  • 2009年1月
  • 2008年8月
  • 2008年6月
  • 2008年5月
  • 2008年4月
  • 2008年2月
  • 2007年11月
  • 2007年8月
  • 2007年6月
  • 2007年5月
  • 2007年4月
  • 2007年3月
  • 2007年2月
  • 2007年1月
  • 2006年10月
  • 2006年8月
© 2006 - 2023 久久日记本
Powered by WordPress | Theme: Graphy for 99diary