你想学 Vue?那还不来看看什么是 Vue 组件化开发

   日期:2020-07-17     浏览:64    评论:0    
核心提示:为什么需要组件化开发,许多答案都是复用,而我却认为分而治之或许是更好的答案






1.1 组件化简介

1.1.1 概述

  将实现页面某一部分功能的结构、样式和逻辑封装成为一个整体,使其高内聚,低耦合,达到分治与复用的目的。在前端范畴,我们可以用下面的这张图来简单地理解组件化:

  这样看起来,组件化前端开发就像造一辆车,我们将轮子、发动机、悬挂、车身车门等等各部分组装成一辆车,轮子、发动机就是组件,车就是最终产品。我们将页头、侧边栏、页脚、内容区等等组件拼装起来组成了我们的页面。

1.1.2 意义

  在谈到组件化的意义时,很多人的看法都是组件化的目的是复用。在某一篇博客看到另一种想法,分而治之,我比较这一种想法,良好地组件化以后的组件,会表现出高内聚低耦合的特征,这会给我们带来好处;组件之间不会相互影响,能有效减少出现问题时定位和解决问题的时间;组件化程度高的页面,具有清晰的页面组织和高可读性的 HTML 结构代码,组件之间的关系一目了然;组件化会强迫开发人员划清各个组件的功能边界,使得开发出的功能更加健壮;所以分而治之才是组件化的意义所在,复用只是它的副作用。同时我们还有很多其他方式都可以做到复用,这并不是组件化的专利。


1.1.3 组件化与模块化

  模块化是一种处理复杂系统分解成为更好的可管理模块的方式。它可以通过在不同组件设定不同的功能,把一个问题分解成多个小的独立、互相作用的组件,来处理复杂、大型的软件。似乎在后端领域,组件化和模块化说的是同一件事。但在我的理解中,前端领域的组件化和模块化是两个概念。先说结论:组件化是从产品功能角度进行分割,模块化是从代码实现角度进行分割,模块化是组件化的前提和基础。
  当我们将一段代码写成一个模块的时候,它有可能是一个函数、一个对象或者其他什么做了一件单一事情的东西,我们将它做成模块是因为它完成了一个单一的功能,并且这个功能很多地方都可能用得到。而当一个组件被从产品中抽象出来,它有时候就只是一个模块,但有时候却有相对复杂的实现,它就可能会有多个模块。我们说一个日期选择器是一个组件,但实现它的时候,我们分成了计算模块、渲染模块、用户输入响应模块等等模块来实现。一个单一产品功能的实现,可能是由多个模块来实现的。这样理解起来,其实可以说组件化是更粗粒度的模块化,它是在产品功能上的模块化。






1.2 组件注册

1.2.1 全局组件

  使用 Vue.component("组件名称", { }) 进行组件注册,全局组件注册后,任何 Vue 实例都可以使用;组件其实也是一个 Vue 实例,因此它在定义时也有:data、methods、生命周期函数等。不同的是组件不会与页面的元素绑定,否则就无法复用了,因此没有 el 属性。但是组件渲染需要 html 模板,所以增加了 template 属性,值就是 HTML 模板,模板中只能有一个根标签。data 必须是一个函数,不再是一个对象。因此组件复用时各个组件之间互补影响。

<div id="app">
    <first></first>
</div>
    
<script> Vue.component("first",{ // 使用 ` 将 HTML 代码包裹起来,可以不用加换行符,使用 " 需要加换行符 template: ` <div> <h1>China---{{year}}---{{msg}}</h1> </div> `, data() { return { msg: "大好河山", year: 2019 } } }); var app = new Vue({ el:"#app" }); </script>

1.2.3 局部组件

局部组件只有在当前注册它的 Vue 实例中使用

语法

// 注册组件
var component_name = { 
		template:'',
        data(){
            return {}
        }
};

// 引用组件
var app = new Vue({
    el:"#app",
    components:{
        component_name: first // 将定义的对象注册为组件
        component_name 		// 组件名与对象名一致时可以简写
    }
})  

示例

<div id="app">
    <!-- 组件复用 -->
    <first></first>
    <first></first>
    <first></first>
</div>
    
<script> var first = { template:'<button v-on:click="count++">{{ count }} 次</button>', data(){ return { count:0 } } } var app = new Vue({ el:"#app", components: { first } }); </script>






1.3 单文件组件

1.3.1 概述

  显然,组件注册的两种写法都是在同一个页面完成的,而且没有 CSS 样式等,这样的组件是没有意义的,因此我们需要将“组件”抽离开来。Vue 单文件组件每个单文件组件的后缀名都是 .vue 优化了传统全局组件的一些弊端(模板缺乏高亮、没有 css 等) 每一个 Vue 单文件组件都由 template、script、style 三部分组成。

<template>
   <!-- 组件代码区域 -->
</template>

<style scoped>  </style>

<script> // js 代码区域 </script>

1.3.2 编写一个组件

  由于每个组件都是独立的,要想被引用,需要将其暴露出来,使用 export default { } 导出默认成员,使用 export A 可以按需导出。引用组件的那一方需要使用 import 接收名称 from "模块路径" 将其到导入本页面。使用 import { A, B } from "模块路径" 可以按需导入。

<template>
    <div>
        <span class="font">{{msg}}</span>
        <button @click="fun">组件中的按钮</button>
    </div>
</template>

<style scoped> .font { font-size: 24px; color: #42b983; } </style>

<script> // 只能存在一个默认暴露,内容与 Vue 示例类似 export default { // 组件的名称 name: 'myComponent', // 数据,与 Vue 实例类似 data() { return { msg: "Hello World", } }, // 方法,与 Vue 实例类似 methods: { fun() { alert("我是组件的方法") } } } // 单独暴露方法 export function funC() { alert("这是组件中 funC() 方法") } </script>

  如上所示,一个简单的组件就写好了,export default {} 中可以有 Vue 实例中除 el 属性的所有类容。如果我们只想要抽取方法,不想要页面,此时我们虽然可以写在 Vue 单文件组件中,但是非常不方便,我们可以将其写到 JS 文件中。

export function funA() {
    alert("这是 js 中 funA() 方法")
}

export function funB() {
    alert("这是 js 中 funB() 方法")
}

  一般我们使用 Vue 进行开发需要抽取的东西就只有组件和方法了,下面我们就可以将 MyComponent.vue 组件和 Fun.js 引入到页面中。页面也不再是熟知的 HTML,而是之前用到的 Vue 单文件组件。因此我们称引用组件的那一方为父组件,被引用的那一方为子组件。下面我们在 views 新建 vue 单文件组件编写我们的代码。Vue 中路径有个新的写法 @/ 表示 src 目录下。

<template>
    <div>
        <my-component></my-component>
        <button @click="onA">按钮A</button>
        <button @click="onB">按钮B</button>
        <button @click="onC">按钮C</button>
    </div>
</template>

<style scoped> </style>

<script> // 引入文件 import MyComponent from "@/components/MyComponent"; import {funC} from "../components/MyComponent"; import {funA, funB} from "@/components/MyFun"; export default { // 局部注册组件 components: { myComponent: MyComponent, }, data() { return {} }, methods: { onA() { funA(); }, onB() { funB(); }, onC() { funC(); } } } </script>

  启动项目,打开 http://localhost:8080/#/test,哎?怎么啥也没有,难道是哪里写错了。发现在 views 文件夹中还有两个文件,我们来访问试试,发现http://localhost:8080/#/home 也没有东西而 http://localhost:8080/#/about 有东西。




  产生上述问题的原因是 Vue 通过路由来找页面,而我们没有配置路由,所以找不到页面,那为什么自带的 Home.vue 也找不到呢?这是因为该页面配置的路径并不是 /home,查看路由可以发现 Home.vue 配置的路径是 /,所以直接使用 http://localhost:8080/#/ 就可以找到了。路由这里后面后详细说,这里只需要在 /route/index.js 中配好就可以了。参考 About、Home 的配置,我们配置好 Test 的路由,就可以访问到页面了。

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

  const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import( '../views/About.vue')
  },
  {
    path: '/test',
    name: 'Test',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import( '@/views/Test')
  }
]

const router = new VueRouter({
  routes
})

export default router





1.4 组件之间的通信

1.4.1 父向子通信

  父组件发送信息的形式是以属性的形式绑定值到子组件身上。 然后子组件用属性 props 接收,使用 props 向子组件传递数据,首先要在子组件中定义子组件能接受的 props,然后在父组件中子组件的自定义元素上将数据传递给它。

子组件(MyComponent.vue)

<template>
    <div>
        <span class="font">{{msg}}</span>
        <h1 class="font">{{title}}</h1>
        <span class="font">{{text}}</span>
    </div>
</template>

<style scoped> .font { font-size: 24px; color: #42b983; } </style>

<script> export default { // 一个 vue 中只能存在一个 name: 'myComponent', props: ['title', 'text'], data() { return { msg: "Hello World", } }, methods: { } } </script>

父组件(Test.vue)

<template>
    <div>
        <my-component :title="title" :text="text"></my-component>
    </div>
</template>

<style scoped> </style>

<script> import MyComponent from "@/components/MyComponent"; export default { components: { myComponent: MyComponent, }, data() { return { title: "中国", text: "Java 开发者", } }, methods: { } } </script>


1.4.2 子向父通信

  但是如果子组件需要向父组件传递数据,则不能通过 props,Vue 2 中强调“单项数据流”,所谓“单向数据流”,即是数据的变动只能由外向内传递,而不能由内向外传递。组件只能将从 props 传递进来的数据进行使用,不能对其进行修改。我们唯一能做的,就是在子组件想要修改数据时,发送事件通知父组件修改。父组件通过监听子组件发送的这个事件,来决定需要做什么。 即:子组件不能直接向父组件传值,我们可以通过操作父组件的方法来实现通信。子组件用 $emit() 触发事件,父组件使用 v-on 来监听事件。

子组件(MyComponent.vue)

<template>
    <div>
        子组件的数值:<span class="font">{{num}}</span>
        <br/>
        <button @click="fun"></button>
    </div>
</template>

<style scoped>
    .font {
        font-size: 24px;
        color: #42b983;
    }
</style>

<script>
    export default {
        name: 'myComponent',
        props: ['num'],
        data() {
            return {
            }
        },
        methods: {
            fun() {
                this.$emit("add-num");
            }
        }
    }
</script>

父组件(Test.vue)

<template>
    <div>
        父组件的数值:{{num}}
        <br/>
        <my-component :num="num" @add-num="add"></my-component>
    </div>
</template>

<style scoped>
</style>

<script>
    import MyComponent from "@/components/MyComponent";

    export default {
        components: {
            myComponent: MyComponent,
        },
        data() {
            return {
                num: 10,
            }
        },
        methods: {
            add() {
                this.num++;
            }
        }
    }
</script>


1.4.3 兄弟组件通信

  兄弟组件之间传递数据需要借助于中间桥梁,通过中间桥梁传递数据,中间桥梁一般是空的 Vue 实例。 传递数据方,通过 hub.$emit(方法名,参数) 触发事件。接收数据方,通过在 mounted 钩子中用 hub.$on(方法名,(参数) => {}) 监听事件。销毁事件通过 hub.$off(方法名) 进行销毁,销毁之后无法进行传递数据。

中间桥梁(hub.js)

import Vue from "vue";

export default new Vue();

组件1(MyComponent1.vue)

<template>
    <div>
        子组件1的数值:<span class="font">{{numa}}</span>
        &emsp;&emsp;
        <button @click="fun">2 号组件 + 1</button>
    </div>
</template>

<style scoped>
    .font {
        font-size: 24px;
        color: #42b983;
    }
</style>

<script>
    import hub from "@/components/hub";
    export default {
        name: 'myComponent1',
        data() {
            return {
                numa: 0,
            }
        },
        mounted () {
            // 此处需要使用箭头函数,否侧 this 不代表组件
            hub.$on("myComponent1",(value) => {
                this.numa += value;
            })
        },
        methods: {
            fun() {
                hub.$emit("myComponent2",1);
            }
        },
    }
</script>

组件2(MyComponent2.vue)

<template>
    <div>
        子组件2的数值:<span class="font">{{numb}}</span>
        &emsp;&emsp;
        <button @click="fun">1 号组件 + 2</button>
    </div>
</template>

<style scoped>
    .font {
        font-size: 24px;
        color: #42b983;
    }
</style>

<script>
    import hub from "@/components/hub";

    export default {
        name: 'myComponent2',
        data() {
            return {
                numb: 0,
            }
        },
        mounted() {
            hub.$on("myComponent2",(value) => {
                this.numb += value;
            })
        },
        methods: {
            fun() {
                debugger
                hub.$emit("myComponent1",2);
            }
        },
    }
</script>

父组件(Test.vue)

<template>
    <div>
        <my-component1 ></my-component1>
        <my-component2 ></my-component2>
    </div>
</template>

<style scoped>
</style>

<script>
    import MyComponent1 from "@/components/MyComponent1";
    import MyComponent2 from "@/components/MyComponent2";

    export default {
        components: {
            myComponent1: MyComponent1,
            myComponent2: MyComponent2,
        },
        data() {
            return {
            }
        },
        methods: {
        }
    }
</script>

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服