Vue项目剖析

博客地址

zengzhengrong

前言

在github搜索了一些关于vue的前端源码,感觉这个demo组件少,内建自带api填充数据,以及在结构目录和设计模式可以用来参考,便打算clone下来探究探究
项目来源:github
要求:
需要一定的Vue基础

结构目录

image

components目录

有5个组件:

  1. Blog为components目录的根组件,均为其余4个组件的父组件
  2. BlogFeed是用来显示blog列表的组件,包括用来执行过滤显示,对于本项目来说,他的过滤显示有三种情况:
    • 主界面(显示全部的blog封面列表)
    • 只显示同类或者同个作者的blog封面列表
    • blog详情(此时只显示该篇blog封面)
  3. BlogFooter显示页脚的组件
  4. BlogNav显示导航栏的组件
  5. BlogPost显示blog详情的组件

接下来从路由开始讲起

路由配置

路由的配置文件都放在router/index.js文件里

  • router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Blog from '../components'

Vue.use(Router)

export default new Router({
  mode: 'history',
  linkActiveClass: 'active',
  routes: [{
    path: '/',
    name: 'feed',
    component: Blog
  }, {
    path: '/by/:author',
    name: 'author',
    props: true,
    component: Blog
  }, {
    path: '/read/:post',
    name: 'post',
    props: true,
    component: Blog
  }]
})

三个路由均使用了相同的组件Blog,Blog组件可以调度其子组件的显示
1.主页面路由path: '/'命名为feed,显示站点根目录,没有过滤任何blog
2.第二个path: '/by/:author'是根据作者过滤blog列表并根据author变量作为props传入Blog组件中实现过滤
3.第三个path: '/read/:post'跟上面一样,BlogFeed组件的blog列表只有一个blog,然后显示单个blog的内容详情的BlogPost组件

挂载组件

在src/main.js

import Vue from 'vue'
import App from './App'
import router from './router'
import * as resources from './resources'
import resource from './plugins/resource'
import deviceQueries from './plugins/device-queries'
import Pace from 'pace-progress'

Vue.config.productionTip = false

Vue.use(resource, {
  resources,
  endpoint: '/static/api'
})

Vue.use(deviceQueries, {
  phone: 'max-width: 567px',
  tablet: 'min-width: 568px',
  mobile: 'max-width: 1024px',
  laptop: 'min-width: 1025px',
  desktop: 'min-width: 1280px',
  monitor: 'min-width: 1448px'
})

new Vue({
  router,
  render: h => h(App),
  mounted() {
    Pace.start()
    Pace.on('hide', () => {
      document.dispatchEvent(new Event('app.rendered'));
    })
  }
}).$mount('#app')

在这里我们的Vue实例,为整个项目的根实例,components目录的Blog组件也是它的子组件(实例)
1.首先我们先引入了几个插件:router、resource插件以及api接口资源resources、device-queries(设备自适应)、pace-progress(进度条加载)
2.配置resource插件

Vue.use(resource, {
  resources,
  endpoint: '/static/api'
})

resource插件在/src/plugins目录里,这里得一个参数是设置本地api路径,以便resources目录文件接口调用本地资源,endpoint是将Blog*.js文件里path的路径结合来获取static里的数据
3.同样配置device-queries插件

Vue.use(deviceQueries, {
  phone: 'max-width: 567px',
  tablet: 'min-width: 568px',
  mobile: 'max-width: 1024px',
  laptop: 'min-width: 1025px',
  desktop: 'min-width: 1280px',
  monitor: 'min-width: 1448px'
})

设置参数就是每个设备的最大宽度

4.配置路由被写成单独的js文件了,直接引入router文件即可
5.创建Vue实例,并挂载router路由和pace-progress插件,最后将实例化后的VUe实例挂载到index.html

main.js的Vue实例将会挂载到
根目录下的index.html

<body>
<div id="app"></div>
</body>

而路由所挂载的组件都在这<router-view id="app"/>个标签中显示,在这个项目里,各个路由都挂载的组件都是Blog组件,所以这个标签将显示Blog组件
src/App.vue:

<template>
  <router-view id="app"/>
</template>

<script>
export default { name: 'app' }
</script>

<style lang="scss">
@import './sass/app';
</style>

这里的id="app"将会在组件中最上层的标签添加id=app这个属性。
接下来开始了解项目组件

Blog组件

Blog组件是其他4个组件的父组件,也是本项目全部路由所挂载的组件,根据不同的url动态调度Blog组件以至调度各个子组件
源码:

<template>
  <main class="blog" :class="{ 'blog--reading': this.post }">
    <blog-nav :content="content" :filters="filters" :navs="navs"/>
    <blog-feed :filters="filters"/>
    <blog-post :post="post"/>
    <blog-footer/>
  </main>
</template>

<script>
import BlogNav from './BlogNav'
import BlogFeed from './BlogFeed'
import BlogPost from './BlogPost'
import BlogFooter from './BlogFooter'

export default {
  name: 'blog',
  components: { BlogNav, BlogFeed, BlogPost, BlogFooter },
  resource: 'Blog',
  props: {
    post: String,
    author: String
  },

  data() {
    return {
      navs: 0,
      title: '',
      labels: {
        post: '',
        author: ''
      }
    }
  },

  computed: {
    content() {
      return { title: this.title, labels: this.labels }
    },
    filters() {
      let filters = {}
      console.log(this.post)
      console.log(this.author)
      if (this.post) filters.post = this.post
      if (this.author) filters.author = this.author

      return filters
    }
  },

  watch: {
    '$route.name' (to, from) {
      if (to !== from) this.navs++
    }
  },

  mounted() {
    this.$getResource('blog')
      .then(x => {
        // use pace hook to know when rendering is ready
      })
  }
}
</script>

组件模版:

<template>
  <main class="blog" :class="{ 'blog--reading': this.post }">
    <blog-nav :content="content" :filters="filters" :navs="navs"/>
    <blog-nav :filters="filters"/>
    <blog-post :post="post"/>
    <blog-footer/>
  </main>
</template>

用一个main标签包含了其他四个组件,并设置了一个动态class'blog--reading': this.post如果是有post即是从路由传来的blog标题名则添加'blog--reading'的样式
blog-nav、blog-nav、blog-post三个组件都设置了父组件属性绑定,用于将数据传递给子组件
逻辑部分:

<script>
import BlogNav from './BlogNav'
import BlogFeed from './BlogFeed'
import BlogPost from './BlogPost'
import BlogFooter from './BlogFooter'

export default {
  name: 'blog',
  components: { BlogNav, BlogFeed, BlogPost, BlogFooter },
  resource: 'Blog',
  props: {
    post: String,
    author: String
  },

  data() {
    return {
      navs: 0,
      title: '',
      labels: {
        post: '',
        author: ''
      }
    }
  },

  computed: {
    content() {
      console.log(this.labels)
      return { title: this.title, labels: this.labels }
    },
    filters() {
      let filters = {}
      console.log(this.post)
      console.log(this.author)
      if (this.post) filters.post = this.post
      if (this.author) filters.author = this.author
      console.log(filters)
      return filters
    }
  },

  watch: {
    '$route.name' (to, from) {
      if (to !== from) this.navs++
      console.log(this.navs)
    }
  },

  mounted() {
    
    console.log(this.$route.name)
    console.log(this.filters)
    this.$getResource('blog')
      .then(x => {
        // use pace hook to know when rendering is ready
      })
  }
}
</script>

1.首先引入了四个组件
2.在实例部分我们设定了Blog组件的name属性这个属性用于使用模版标签。
并在components属性中配置四个子组件,以便在父组件模版中使用。
resource属性是用于获取本地api接口文件resources中index所输出的对应接口,这里对应的resources/Blog.js。如果不填写这个属性,则无法在生命周期函数中获取api中的数据。
props属性接受路由传进来的参数,并验证他的数据类型是否是string
3.在data()方法中,我们定义navs、title、labels对象,navs用于计数,传递到nav组件会用到,title和labels都是从Blog.js获取数据的属性,只是在重新在组件中定义,以用this获取数据。
resources/Blog.js:

export default {
  blog() {
    return {
      path: '/blog.json',
      resolve: (response, mappers) => {
        let blog = response.results[0]
        return mappers.merge({
          title: blog.title,
          labels: {
            post: blog.post_label,
            author: blog.author_label
          }
        })
      }
    }
  }
}

title: blog.title获取网站的标题
labels: {...}这里有两个,如果是在blog详情页面则显示post的返回标签,如果是author则是显示分类时候的返回标签。
这里数据在
static/api/blog.json:

{
  "results": [{
    "title": "In Plain Vue",
    "post_label": "Exit reading mode",
    "author_label": "View all authors"
  }]
}

计算属性computed

content函数

    content() {
      // 因模版使用了计算属性方法,则优先渲染DOM,计算属性会优先处理,这个弹窗会出现两次
      // 第一次没有在mounted生命周期函数中获取数据,弹出一个空字符串(这里在data中定义)
      // 第二次在执行生命周期函数mounted时获取到数据,重新执行了一遍计算属性的方法,这次弹出就含有字符的字符串
      alert(this.labels.post)
      console.log(this.labels)
      return { title: this.title, labels: this.labels }
    }

这个计算属性绑定到blog-nav组件,用于该组件接受绑定数据,返回了网站的标题和显示的labels,如何选择labels的post还是author,会根据filters方法来判断。

filters方法:

    filters() {
      let filters = {}
      if (this.post) filters.post = this.post
      if (this.author) filters.author = this.author
      return filters
    }

1.声明了一个只在该函数为作用于的一个空对象filters
2.然后判断是否有路由传递post属性或者是
author属性,有的话将在filters将设置属性(只可能是含有一个属性),最后将返回filters

watch监听属性

用于监听参数变化后所执行的函数

  watch: {
    '$route.name' (to, from) {
      if (to !== from) this.navs++
      console.log(this.navs)
    }
  }

这里我们监听route.name,即路由命名空间,跳转不同的路由,不同的路由有不一样命名空间,参数是新的值和旧的值,如果新旧值不同则说明路由变化过,nav计数就加一,这个用于确定返回的上一次路由,后面在nav组件会提到。

mounted生命周期函数

  mounted() {
    this.$getResource('blog')
      .then(x => {
        // use pace hook to know when rendering is ready
      })
  }

1.这里会先到获取Blog.js输出函数blog(),该函数获取api数据,以至可以使用this.title和this.labels,这两个属性值发生改变此时会重新执行计算属性,然后在执行then后续的逻辑。

Blog讲解组件到此为止,该组件主要是计算属性和生命周期函数的配合获取数据,因优先处理DOM,计算属性会比mounted周期函数先执行,此时还是空对象,到后面执行了mounted函数获取到api数据,重新执行了计算属性,以调度了各个组件的执行。

BlogNav组件

模版

<template>
  <nav class="nav">
    <h1 class="nav__title">
      <router-link to="/">{{ content.title }}</router-link>
    </h1>

    <transition-group tag="menu" name="nav__item" class="nav__menu">
      <li v-for="label in labels" class="nav__item" :key="label" @click="navBack">
        <i class="nav__item--icon"></i>
        <span class="nav__item--label">{{ label }}</span>
      </li>
    </transition-group>
  </nav>
</template>

1.网站标题设定了路由连接,指定了首页路由
2.```<transition-group></transition-group>``这个是Vue的内置组件,用于其内容变化列表过度,具体可以查看Vue官方介绍
3.该列表用v-for循环了li标签,同时v-on绑定绑定了方法navBack,后面会提到这个两个

逻辑部分:

<script>
export default {
  name: 'blog-nav',
  props: {
    navs: Number,
    content: Object,
    filters: {
      type: Object,
      default: () => {}
    }
  },

  computed: {
    labels() {
      console.log(Object.keys(this.filters)
        .map(filter => this.content.labels[filter]))
      return Object.keys(this.filters)
        .map(filter => this.content.labels[filter])
    }
  },

  methods: {
    navBack() {
      if (this.navs && !this.filters.author) this.$router.go(-1)
      else this.$router.push('/')
    }
  }
}
</script>

计算属性computed

  computed: {
    labels() {
      return Object.keys(this.filters)
        .map(filter => this.content.labels[filter])
    }
  },

1.计算属性的labels方法用于模版循环,根据父组件传过来的filters来判断返回显示哪个字符串内容
2.在父组件获取的filters中获取键名,执行map方法,以键名为参数执行函数获取在content中labels的值,返回一个数组

methods属性

navBack方法

  methods: {
    navBack() {
      if (this.navs && !this.filters.author) this.$router.go(-1)
      else this.$router.push('/')
    }

1.该方法绑定了在显示labels循环,用于判断返回的路由
2.navs不为0同时不在author路由时,执行返回到上一个路由
3.在这项目中只有一种情况,即从author路由进入post路由时候,返回时执行到上个路由(author路由)

BlogNav组件很简单,设计了api接口,并将接口数据稍加处理便用于模版中,逻辑很简单,接下来是比较复杂的BlogFeed组件

BlogFeed组件

用于显示blog列表,无论在哪个路由,blog列表都会有(blog个数根据是否过滤),只是在不同路由在显示数量有所不同。

组件模版:

<template>
  <transition-group tag="ul" :name="transition"  class="blog__feed">
    <li v-for="post in feed" class="preview" :key="post.id">
      <figure class="preview__figure" :class="figureClass" :style="getBgImg(post.image)">
        <transition name="v--fade">
          <figcaption v-if="!reading || $device.phone" class="preview__details">
            <router-link class="preview__title"
              :to="`/read/${post.id}`"
              @click.native="scrollTo(0, 220, scrollDelay)">
              {{ post.title }}
            </router-link>

            <div class="preview__meta">
              <time class="preview__published">
                {{ prettyDate(post.published) }}
              </time>

              <router-link class="preview__author"
                :to="`/by/${kebabify(post.author)}`"
                @click.native="scrollTo(0, 220, scrollDelay)">
                {{ post.author }}
              </router-link>
            </div>
          </figcaption>
        </transition>
      </figure>
    </li>
  </transition-group>
</template>

1.这里同样也使用了transition-group组件,并使用tag属性真实渲染为ul,过度效果前缀名name属性绑定了一个方法,因为需要动态改变它的过度效果,根据组件状态不同,过度也不同。
2.这里li循环了feed方法,feed方法在计算属性中,后面会讲到
3.用了figure和figcaption标签,作为封面和标题,figcaption用一个过度组件transition包含住
4.v-if="!reading || $device.phone"在figcaption添加了v-if,当阅读详情时(路由有post)不显示标题,但在手机模式下显示标题。
5.标题figcaption含有两个路由,分别到post和author

逻辑部分:

<script>
import { scrollTo, kebabify, prettyDate } from '../helpers'

export default {
  name: 'blog-feed',
  resource: 'BlogFeed',
  /*用于获取本地数据,resource插件所需要的属性,
  该值会执行到resources文件夹的js脚本,
  而js脚本会定向到api的数据,不添加发生错误mounted的getResource函数*/

  props: {
    filters: {
      type: Object,
      default: () => {}
    }
  },

  data() {
    return {
      posts: [],
      transition: 'preview-appear'
    }
  },

  computed: {
    reading() { return this.filters.post },
    scrollDelay() { return (this.$device.phone) ? 0 : 560 },
    figureClass() {
      return { 'preview__figure--mobile': this.$device.phone && this.reading }
    },
    feed() {
      const filterBy = {
        post: (filter, { id }) => filter === id,
        author: (filter, { author }) => filter === this.kebabify(author)
      }
      /*过滤列表为0时即为显示文章列表*/
      if (!Object.keys(this.filters).length) return this.posts
      /*post为数组的元素,是回调函数的参数,会遍历每个元素检验其回调函数逻辑正确
      回调函数会返回处理参数的过后的逻辑正确性(即通过回调函数)
      返回true表示保留该元素,false则不保留*/
      return this.posts.filter(post => {
        /* Object.keys(object) 获取对象的所有keys*/
        /* every() 方法测试数组的所有元素是否都通过了指定函数的测试*/
        return Object.keys(this.filters).every(filter => {
          return filterBy[filter](this.filters[filter], post)
        })
      })
    }
  },

  methods: {
    scrollTo,
    kebabify,
    prettyDate,
    getBgImg(src) {
      return { backgroundImage: `url(${src})` }/*在字符串引入变量*/
    },
    stackPosts(posts) {
      let interval
      const stack = () => {/* 声明一个局部变量,变量是个箭头函数*/
        this.posts.push(posts.shift())/*arry.shift()删除数组第一个元素并返回该元素*/

        if (!posts.length) {
          this.transition = 'preview'
          clearInterval(interval)
        }
      }

      interval = setInterval(stack, 125)
    }
  },

  mounted() {
    this.$getResource('feed')
      .then(posts => {
        // posts为static/api下的feed.json文件
        /* filters对象没有属性时执行stackPosts,即为显示文章列表*/
        if (!Object.keys(this.filters).length) {
          this.stackPosts(posts)
        }else {
          this.posts = posts
          this.transition = 'preview'
        }
      })
  }
}
</script>

1.引入了三个辅助函数,scrollTo, kebabify, prettyDate分别用于 滚动定位,格式化post.author名以跟author路由参数一样和日期格式化函数

三个文件都在src/helpers.js

import animateScroll from 'scrollto-with-animation'

export const scrollTo = (pos, duration = 600, delay = 0) => new Promise(resolve => {
  setTimeout(() => {
    animateScroll(document.documentElement, 'scrollTop', pos, duration, 'easeInOutSine', resolve)
  }, delay)
})

export const kebabify = (words) =>
  words
    .toLowerCase()
    .replace(' ', '-')

export const prettyDate = (date) =>
  new Date(date)
    .toString()
    .split(' ')
    .slice(0, 4)
    .join(' ')
    .replace(/( \d+)$/, ',$1')

2.设定组件模版名和确定resource接口BlogFeed
3.设定api接口filters
4.定义一个posts数组用来接受api的数据
5.定义一个动态过渡效果transition

计算属性computed

这里只讲一个比较重要方法
feed:

    feed() {
      const filterBy = {
        post: (filter, { id }) => filter === id,
        author: (filter, { author }) => filter === this.kebabify(author)
      }
      /*过滤列表为0时即为显示文章列表*/
      if (!Object.keys(this.filters).length) return this.posts
      /*post为数组的元素,是回调函数的参数,会遍历每个元素检验其回调函数逻辑正确
      回调函数会返回处理参数的过后的逻辑正确性(即通过回调函数)
      返回true表示保留该元素,false则不保留*/
      return this.posts.filter(post => {
        /* Object.keys(object) 获取对象的所有keys*/
        /* every() 方法测试数组的所有元素是否都通过了指定函数的测试*/
        return Object.keys(this.filters).every(filter => {
          return filterBy[filter](this.filters[filter], post)
        })
      })
    }

1.定义一个对象filterBy,用于通过此对象中的函数来返回正确需要显示的post
2.若没有传入路由参数,则返回全部的posts
3.返回一个经过过滤的posts数组,posts的每个元素只有通过测试的回调函数返回true,才能正确的被显示,返回false的会被过滤掉。这个feed方法经过多次回调最后只是过滤掉任何ID不匹配的帖子。这是一个非常好的实现方式,之后我们要添加过滤项(例如category),我们只需要在Blog组件的filters方法接收相应的路由参数和filterBy对象中添加属性,然后添加判断接受的路由参数和post中的数据是否吻合,即可实现过滤,非常轻松快捷。

methods属性

methods: {
    scrollTo,
    kebabify,
    prettyDate,
    getBgImg(src) {
      return { backgroundImage: `url(${src})` }/*在字符串引入变量*/
    },
    stackPosts(posts) {
      let interval
      const stack = () => {/* 声明一个局部变量,变量是个箭头函数*/
        this.posts.push(posts.shift())/*arry.shift()删除数组第一个元素并返回该元素*/

        if (!posts.length) {
          this.transition = 'preview'
          clearInterval(interval)
        }
      }

1.getBgImg绑定了style属性, 设定css样式背景图片
2.stackPosts,后面生命周期函数会调用他,此方法用来以对接收来的posts中的每个元素逐个显示,用到setInterval方法反复调用了stack,shift方法逐个从posts中传入到组件中的posts数组,一旦组件中的posts数组发生变化计算属性就立刻执行,我们可以在feed方法在

if (!Object.keys(this.filters).length) {console.log(this.posts.length); return this.posts}

中添加来测试,console的数字是逐个增加直到api的posts为0停止并最后用clearInterval清除反复调用任务

生命周期函数mounted

  mounted() {
    this.$getResource('feed')
      .then(posts => {
        // posts为static/api下的feed.json文件
        /* filters对象没有属性时执行stackPosts,即为显示文章列表*/
        if (!Object.keys(this.filters).length) {
          console.log(Object.keys(this.filters).length)
          console.log(this.posts)
          this.stackPosts(posts)
        } else {
          this.posts = posts
          this.transition = 'preview'
        }
      })
  }

同样是在resources/BlogFeed.js中出书的feed方法中获取数据,then的参数是获取到的数据,并用一个箭头函数执行获取数据后的操作,这里没有获取到路由参数的情况下将调用stackPosts,否则直接将posts赋给组件中的posts。

接下来就是最后一次组件-BlogPost,BlogFooter没有什么代码,这个省略过去

BlogPost组件

模版:

<template>
  <transition name="post">
    <article v-if="allReady" class="post">
      <header class="post__header">
        <h2 class="post__title">{{ title }}</h2>

        <h3 class="post__meta">by <router-link class="post__author"
          :to="`/by/${kebabify(author)}`">{{ author }}</router-link>
          <span class="post__sep"></span>
          <time>{{ prettyDate(published) }}</time>
        </h3>

        <blockquote class="post__subtitle">{{ description }}</blockquote>
      </header>

      <section class="post__body rte" v-html="content"></section>

      <footer class="post__footer">
        <vue-disqus v-if="commentsReady" shortname="vue-blog-demo"
          :key="post" :identifier="post" :url="`https://vue-blog-demo.netlify.com/read/${post}`"/>
      </footer>
    </article>
  </transition>
</template>

1.也同样在article标签添加了过度效果
2.article标签显示条件绑定了allReady方法
3.header标签包含了title、author路由、published和description
4.用v-html属性,将blog的content以标准html的方式渲染
5.footer标签添加了disqus评论模块

逻辑部分:

<script>
import VueDisqus from 'vue-disqus/VueDisqus'
import { kebabify, prettyDate } from '../helpers'

export default {
  name: 'blog-post',
  resource: 'BlogPost',
  components: { VueDisqus },
  props: { post: String },

  data() {
    return {
      title: '',
      author: '',
      content: '',
      published: '',
      description: '',
      commentsReady: false,
      ready: false
    }
  },

  computed: {
    allReady() {
      return this.ready && this.post;
    }
  },

  watch: {
    post(to, from) {
    if (to === from || !this.post) return;
    //this.commentsReady = false
    this.$getResource('post', to)
    .then(this.showComments)
    .then(() => {
    this.ready = true;
    });
   }
  },

  methods: {
    kebabify,
    prettyDate,
    showComments() {
      // This is injected by prerender-spa-plugin on build time, we don't prerender disqus comments.
      // 运行build时,将注入预渲染插件,渲染的路由为各个post,可以在bulid配置文件中查看详细配置,不会预渲染disqus
      if (window.__PRERENDER_INJECTED &&
          window.__PRERENDER_INJECTED.prerendered) {
        return;
      }
      // 定时方法:一秒后运行this.commentsReady = true
      setTimeout(() => {
        this.commentsReady = true
      }, 1000)
    }
  },
  //该生命周期函数用于在路由post页面刷新时可调用api
  mounted() {
    
    if (!this.post) {
      this.ready = true;
      return;
    }

    this.$getResource('post', this.post)
      .then(this.showComments)
      .then(() => {
        this.ready = true;
      });
  }
}
</script>

1.引入了vue的disqus组件和一些格式化方法
2.同样的设置组件标签名和resource的api接口,设计父组件传来的api,还有将组件传进该实例。
3.设定该组件的api获取的资源属性,到后面$getResource会将aip属性赋给组件的属性来完成替换,还有两个重要布尔属性,用来确定在满足何等条件时显示post和comments

计算属性computed

computed: {
    allReady,() {
      return this.ready && this.post;
    }
  },

allReady,绑定了article标签,返回true时才显示article的所有内容。
一旦ready或者post的值有改变,立刻刷新allReady方法

监听属性watch

 watch: {
    post(to, from) {
    if (to === from || !this.post) return;
    //this.commentsReady = false
    this.$getResource('post', to)
    .then(this.showComments)
    .then(() => {
    this.ready = true;
    });
   }
  }

监听post参数,如果新旧值一样或者没有post参数,则直接return。个人认为这里并不需要判断新旧值是否一样,直接判断是否处在post路由即可,处在post路由则执行api接口方法获取资源,获取成功后执行showComments方法,然后执行this.ready = true;
将article标签显示出来

methods属性

  methods: {
    kebabify,
    prettyDate,
    showComments() {
      // This is injected by prerender-spa-plugin on build time, we don't prerender disqus comments.
      // 运行build时,将注入预渲染插件,渲染的路由为各个post,可以在bulid配置文件中查看详细配置,不会预渲染disqus
      if (window.__PRERENDER_INJECTED &&
          window.__PRERENDER_INJECTED.prerendered) {
        return;
      }
      // 定时方法:一秒后运行this.commentsReady = true
      setTimeout(() => {
        this.commentsReady = true
      }, 1000)
    }
  },

这里showComments方法会在api获取数据成功时执行,一秒后将会将commentsReady设定会true,至于预渲染插件则会在build时才启动。

生命周期函数mounted

mounted() {
    
    if (!this.post) {
      // this.ready = true;
      return;
    }

    this.$getResource('post', this.post)
      .then(this.showComments)
      .then(() => {
        this.ready = true;
      });
  }

该生命周期函数用于在路由post页面刷新时可调用api,不在post路由时直接return,个人认为将ready设置true也没有必要的,有人可能疑问问什么要获取两次api,你可以尝试一下将mounted函数注视掉,你会发现从feed路由进去post路由是完全没有问题的,但是你刷新一下就不会显示article的内容,原因很简单:重新刷新post路由页面,监听函数不会执行所以我们要在生命周期函数中获取api

至此可以说该项目的组件我给梳理了一遍

总结

项目难度不大,只有几个组件,但是里面设计模式真的是非常cool,尤其是过滤posts的BlogFeed组件,好的设计模式,在后续开发中可以更加快速的添加功能,而且也更加节省运算资源。在本地api设计中也有非常炫酷的操作,在api获取时使用了方法,该方法中处理了一些数据的显示(添加html标签)和API的数据结构定义(在组件业务中无需将api获取的数据属性赋值到组件属性)在组件使用数据时更加得心应手,与一般在vue组件内部调用api并做数据处理的方式不同,需要将数据处理转变为创建可替换的数据访问层

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355

推荐阅读更多精彩内容