美文网首页
Vue 创建自己的组件(一)

Vue 创建自己的组件(一)

作者: 你在补考名单上 | 来源:发表于2019-04-02 21:53 被阅读0次

    转载请注明出处和原作者:你在补考名单上

    经过一顿复制粘贴之后,UI 框架提供的控件已经不能满足你了。这时你可能会尝试着自己封装一下需要的控件,但如果在入门的时候没有认真地学习每个知识点(就像我一样),运行起来就发现和预期的效果不一样。下面带着你一起来封装一个控件,顺带回顾一下知识点。

    适合人群

    1. 刚学完 Vue,想实践一下。
    2. 学了一段时间,但是自己封装的时候不熟悉。
    3. 想快速学习 Vue。

    本文主要内容

    • v-bind
    • props

    文章底部有完整代码


    如果你更喜欢官方文档,可以看这里:组件注册

    组件的作用就是将一段多次重复用到代码提取出来,减轻后期维护的工作量。考虑到这里,那么首先就得明确有那些变量是可以提取出来的。

    以封装一个简单的列表控件为例子,一步步讲解如何创建自己的组件。这里我用的是 Android RecyclerView 使用的思路,先创建一个 Item View,然后放到一个 List 中。

    先看看最终效果图:


    preview.png

    let’s do it.

    Item View

    首先要做的就是创建一个 Item View,它就是列表中的每一项。这个 Item 并不复杂,只有四个元素:图片,标题,内容,标签。

    确定了设计后,提取公共的部分。把它们拿出来,放到 props 中。

    props: {
        img: String,
        title: String,
        desc: String,
        tag: String,
        activeTag: {
          type: Boolean,
          default: false,
        }
      },
    

    props 和 v-bind

    Vue.js: Prop

    props 可以理解为构造方法的参数,在实例化的时候传入。有两种写法:

    props: ['img', 'title', 'desc'],
    props: {
        img: String,
        title: String,
        desc: String,
      },
    

    但是第二种写法包含了类型检查,通常使用第二种。这里需要注意一下类型的首字母是大写,是 Javascript 的类型。

    定义好 props 后,就可以在模板里使用了。给出模版的代码:

    <template>
      <div>
        <div class="itemContainer">
          <img class="img" :src="img">
          <div class="content">
            <div class="contentPanel">
              <div class="title">{{title}}</div>
              <div class="desc">{{desc}}</div>
            </div>
            <div class="tagPanel">
              <span class="tag" :class="computedTag">{{tag}}</span>
            </div>
          </div>
        </div>
      </div>
    </template>
    

    可以看到,在模板中使用了 Mustache 语法来插值,这是最简单的文本赋值方式。

    关于 v-bind

    v-bind 是一个绑定 HTML 属性的指令,它的作用就是可以把动态地把一个属性插入到标签中。自定义的组件里声明的 props 就是这个属性,官方文档中提及了 props 的使用方式:如果要传入一个表达式,需要使用 v-bind 指令告诉 Vue 这是一个表达式而不是一个字符串

    大多情况下,<img/>src是动态传入的。所以要使用 v-bind 告诉 Vue 这里传入的是一个图片路径。

    所以,写成(:srcv-bind:src的简写):

    <img class="img" :src="img"/>
    

    这里要注意在传入图片路径时区分相对路径和绝对路径。如果是相对路径,要使用require(),否则会获取不到正确的图片路径。

    可变样式

    tag 被设计成两种背景颜色。为了实现这个需求,需要使用:class,这是一个常用的变换样式的写法。它表示动态地在现有的 class 中插入另一个 class。在这里传入了一个对象:computedTag。这个对象的定义:

    computed: {
        computedTag() {
          if (this.activeTag === true) {
            return "tagActive";
          }
          return "tagInactive";
        }
      },
    

    可以看到这是个计算属性,通过计算 props 中 activeTag 的值来返回一个 class。最终效果就是:把 tagActive 或 tagInactive 添加到 class 中。另外,这里注意到计算属性中可以使用 props 中的对象。换句话说,props 和 data 中的对象可以在计算属性和方法中使用,但是要注意不要修改 props。具体的原因在官网中:

    Vue.js: 单向数据流

    小结

    封装的核心就是 props 和 v-bind。可以联想到 Javascript 中把函数作为参数的传入方式,如果没有在函数名后加上(),则表示传入一个函数对象,如果加上了()则表示传入一个函数的返回结果。

    Item View 已经封装好了,下面把它们放到一个 List 中。

    List

    先给出 List 的部分代码:

    <template>
      <div>
        <div v-for="(item,index) in data" :key="item.id">
          <HelloItem
            :title="item.title"
            :desc="item.desc"
            :img="item.img"
            :activeTag="item.activeTag"
            :tag="item.tag"
          />
          <div class="divider" v-if="showDivider && index !== data.length-1"/>
        </div>
      </div>
    </template>
    

    先关注<HelloItem/>,这是上一步封装好的 Item View。如果把 v-bind 去掉,像这样:

     <HelloItem
            title="item.title"
            desc="item.desc"
            img="item.img"
            activeTag="item.activeTag"
            tag="item.tag"
          />
    

    运行一下,会看到页面变成了这样:


    去掉v-bind.png

    这也作证了 v-bind 的用途:如果你需要插入变量,v-bind 是必不可少的。

    渲染列表

    v-for 用于渲染一个列表。这里的问题不多,主要还是 :key的问题。如果少了 key,会导致一个页面不会刷新的问题。当然这里还是看实际情况,我在实践过程中就遇到过类似的问题。最后是通过后台取了 id 来触发 Vue 的刷新。如果你在实践中遇到了不会刷新的问题,检查接口返回的数据没有问题后,就要检查是否添加了 key

    渲染分割线

    最后是一个分割线的内容。这里用了 v-if 来判断是否为最后一个 Item View,如果是最后一个则不渲染分割线。官方教程中提到不要把 v-for 和 v-if 放在同一个标签中,注意一下就可以。

    总结

    封装一个控件需要注意的地方:

    1. props 设计合理
    2. 传值时注意是表达式还是字符串

    最后贴上所有代码:

    HelloItem.vue

    <template>
      <div>
        <div class="itemContainer">
          <img class="img" :src="img">
          <div class="content">
            <div class="contentPanel">
              <div class="title">{{title}}</div>
              <div class="desc">{{desc}}</div>
            </div>
            <div class="tagPanel">
              <span class="tag" :class="computedTag">{{tag}}</span>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        img: String,
        title: String,
        desc: String,
        tag: String,
        activeTag: {
          type: Boolean,
          default: false,
        }
      },
      data() {
        return {};
      },
      computed: {
        computedTag() {
          if (this.activeTag === true) {
            return "tagActive";
          }
          return "tagInactive";
        }
      },
      created() {},
      methods: {}
    };
    </script>
    
    <style scoped>
    .itemContainer {
      padding: 16px;
      display: flex;
    }
    .img {
      width: 48px;
      height: 48px;
    }
    .content {
      width: 100%;
      display: flex;
      flex-direction: row;
      justify-content: space-between;
    }
    .contentPanel {
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      margin-left: 16px;
    }
    .tagPanel {
      display: flex;
      flex-direction: column;
      justify-content: space-between;
    }
    .tag {
      padding: 1px 6px;
      border-radius: 4px;
      color: white;
      text-align: center;
      font-size: 11pt;
    }
    .tagActive {
      background-color: #df6b5c;
    }
    .tagInactive {
      background-color: #888888;
    }
    .title {
      font-size: 18px;
      font-weight: 900;
    }
    .desc {
      font-size: 14px;
      color: #888888;
    }
    </style>
    
    

    HelloList.vue

    <template>
      <div>
        <div v-for="(item,index) in data" :key="item.id">
          <HelloItem
            :title="item.title"
            :desc="item.desc"
            :img="item.img"
            :activeTag="item.activeTag"
            :tag="item.tag"
          />
          <div class="divider" v-if="showDivider && index !== data.length-1"/>
        </div>
      </div>
    </template>
    
    <script>
    import HelloItem from "@/components/HelloItem.vue";
    export default {
      props: {
        data: Array,
        showDivider: {
          type: Boolean,
          default: true
        }
      },
      components: {
        HelloItem
      },
      methods: {},
      computed: {},
      methods: {}
    };
    </script>
    
    <style>
    .divider {
      border: 0.5px solid #dfdfdf;
      margin-left: 74px;
      margin-right: 16px;
    }
    </style>
    
    

    Home.vue

    <template>
      <div class="home">
        <HelloList :data="mockDatas"/>
      </div>
    </template>
    
    <script>
    // @ is an alias to /src
    import HelloList from "@/components/HelloList.vue";
    
    export default {
      name: "home",
      components: {
        HelloList
      },
      mounted() {
        for (let i = 0; i < 5; i++) {
          let activeTag = false;
          if (i % 2 === 0) {
            activeTag = true;
          }
          this.mockDatas.push({
            title: `Title${i}`,
            desc: `desc${i}`,
            img: require("../assets/logo.png"),
            activeTag: activeTag,
            tag: "Tag"
          });
        }
      },
      data() {
        return {
          mockDatas: []
        };
      },
      methods: {}
    };
    </script>
    
    

    相关文章

      网友评论

          本文标题:Vue 创建自己的组件(一)

          本文链接:https://www.haomeiwen.com/subject/mqoubqtx.html