如何通过JavaScript实现组件化和模块化
组件化和模块化
为什么会有组件化和模块化?当我们的项目复杂度不断上升,项目维护成本逐渐提高之后,对于旧项目的维护就变得格外困难。前端如果按照以前的编程方式,将许多html和js代码都封装在同一个页面,甚至于我们大多数时候在编写html页面的时候会将同一段代码复制黏贴到不同的页面上,那么无疑对于维护者来说是一场巨大的灾难,所以组件化和模块化的出现便成为了一种必然。
组件化和模块化其实思想内核时一致的,甚至于我认为组件化是脱胎于模块化。它们二者的目的都是为了减少我们维护项目的成本,减少代码的冗余,提高代码的阅读性。
这两者让我们可以将频繁使用到的代码,复杂的页面,划分成一个个独立而又互相联系的小方块,就像积木一样,最终可以拼凑出一座更好的代码大楼。
模块化
提起前端的模块化,就离不开ES6。随着ES6的出现,更多的指令,命令和功能也随之出现,我们可以编程出更友好的代码。
es6中出现了module的说法,即模块。通过将我们的js代码封装成一个个module,然后使用export和import命令,我们就可以更好地去管理我们的功能代码,将通用的代码统一封装成一个个小模块,将大型的功能代码划分成简单易懂的函数方法。
'use strict'//导出变量export const a = '100'; //导出方法export const dogSay = function(){ console.log('wang wang');}//导出函数export function catSay(){ console.log('miao miao'); }// export catSay 这样也行// 使用的地方导入// export导出的是多个,需要解构取值import {a,dogSay,catSay} from 'url'
正是因为ES6这一更新,便有了我们将模块一个个划分,也有了我们更好地去使用插件,封装插件的途径,当我们正确使用模块化,就可以减少代码的冗余,减少页面结构的臃肿,也更好地可以去将各种功能插件封装到项目中。
组件化
组件化的中心思想其实跟模块化是大同小异的,可以简单地认为模块化是对于我们的JS功能代码的一个说法,而组件化是对于我们html,css代码的一个说法,当我们的JS功能代码可以被封装成一个个小的模块后,经过不断地思考演化,就引出了组件化这一说法。
组件化对应前端三剑客中的html和css,作为一个前端开发者,如果没有组件化之前,经常能碰到的一个场景就是复制同样的html代码和js代码到不同页面上,去做这种重复而无聊的工作。
或者其实我们可以用后端提供的组件化,比如php,jsp中,它们可以提供一个组件化的解决方案,就是通过封装同一文件,然后php,jsp去识别读取我们页面上对应的标签。
但是这样的方案在前端后端分离开发中显得是相对麻烦而且依赖性太强了,并不能算是前端真正的组件化解决方案。
所以前端的各种框架横空出世,当然它们也是通过node等类型的一个服务端语言来实现我们的前端组件化,但是相对于php和jsp,它们显然更适合我们的前后端分离开发。
这里以Vue封装组件作为例子,通过一个简单的封装组件,我们可以清晰地看到组件是如何被封装,以及如何应用。
Vue中的组件化
Vue中我们使用emit和props完成组件的通讯。通讯也就是说我们可以传递一些参数给这个组件母版,获得一个按照我们需要生成的组件。类似于我们定义一个类,可以传递一些参数,来实例化一个独特的对象。
props
props就是当我们使用组件时,就要传递一些参数给到我们的母版,这样才能得到我们想要的组件的样式或者对应的一些功能。比如一个input,如果我们希望它是不可被输入的,那我们就可通过传递一个参数,值为false,然后props中的参数会接收到对应的值,再将它传给我们的组件中的html标签input。
emit
为什么会有emit?因为我们封装一个输入框母版之后,当我们在某一个页面使用这个母版生成的子组件并且想要添加一个@input的方法,如果直接@input=“function”,会发现是没效果的。
我们需要的应该是它的母版,在母版上的input事件绑定一个function,然后当输入触发了这个function后通过emit(声明一个函数如inputFun)发送出去,然后我们在子组件上可以接收到这个信号@inputFun=“getFun”,只要我们监听了inputFun,它就会将有输入这个信号传给我们在子组件定义的getFun。
<template> <div class="cat-input" :class="{ 'cat-input--suffix': showSuffix }"> <!-- type:先判断是否有传入显示密码,控制输入框类型是文本/密码,然后是type传入的值 --> <input :type="showPassword ? (passwordVisiable ? 'text' : 'password') : type" class="cat-input__inner" :placeholder="placeholder" :name="name" :disabled="disabled" :class="{ 'is-disabled': disabled }" :value="value" @input="handleinput" /> <span class="cat-input__suffix" v-if="showSuffix"> <i class="cat-input__icon cat-icon-circle-close" v-if="clearable && value" @click="clear" ></i> <i class="cat-input__icon cat-icon-view" :class="{ 'cat-icon-view-active': passwordVisiable }" v-if="showPassword" @click="handlepwd" ></i> </span> </div></template><script>export default { name: "CatInput", props: { placeholder: { type: String, default: "", }, type: { type: String, default: "text", }, name: { type: String, default: "", }, disabled: { type: Boolean, default: false, }, value: { type: String, default: "", }, clearable: { type: Boolean, default: false, }, showPassword: { type: Boolean, default: false, }, }, data() { return { passwordVisiable: false, //控制是否显示密码 }; }, methods: { handleinput(event) { //父组件在绑定v-model时,其实就绑定的input事件,因此父组件不需要再声明事件了 this.$emit("input", event.target.value); }, clear() { this.$emit("input", ""); }, handlepwd() { this.passwordVisiable = !this.passwordVisiable; }, }, computed: { //有清空/显示密码,添加类名、显示span showSuffix() { return this.clearable || this.showPassword; }, },};</script>
// 组件需要注册才能使用,要么全局,要么在页面上局部注册。import CatInput from "./components/input.vue";Vue.component(CatInput.name, CatInput);
<template> <!-- 输入框需要绑定v-model,实际上是一个语法糖,等价于: :value="uname" @input="uname=$event.target.value" --> <div> <Cat-input placeholder="请输入用户名" type="text" v-model="uname" clearable ></Cat-input> <br /> <Cat-input placeholder="禁用的输入框" disabled></Cat-input> <br /> <Cat-input placeholder="请输入密码" v-model="upwd" show-password ></Cat-input> </div></template><script>export default { data() { return { uname: "", upwd: "", }; },};</script><style lang="scss" scoped>.cat-input { margin-bottom: 10px;}</style>