一, 前言
之前的博客聊过 vue2.0 和 react 的技术选型; 聊过 vue 的 axios 封装和 vuex 使用. 今天简单聊聊 vue 组件的封装.
vue 的 ui 框架现在是很多的, 但是鉴于移动设备的复杂性, 兼容性问题突出. 像 Mint-UI 等说实话很不错了, 但是坑也是不少, 而且很多功能也是仅凭这些实现不了, 这需要我们去封装自己的可复用组件
二, 封装组件的步骤
1. 建立组件的模板, 先把架子搭起来, 写写样式, 考虑你的组件的基本逻辑. os: 思考 1 小时, 码码 10 分钟, 程序猿的准则.
2. 准备组件的好数据输入. 即分析好逻辑, 定好 props 里面的数据, 类型.(后面详解)
3. 准备组件的好数据输出. 即根据组件逻辑, 做好要暴露出来的方法.(后面详解)
4. 封装完毕了, 直接调用即可.
os: 代码可以不看, 结论在文章最后
接下来以一个很简单的例子具体说明一下
现在先看一下 demo 的效果图
三, demo 代码
父组件:
- <template>
- <section class="f-mainPage">
- <!--selectFunc 选择完成的回调 searchList 下拉列表的数据 -->
- <search @selectFunc="selectFunc" :searchList="searchList" :selectValue="selectValue"></search>
- </section>
- </template>
- <script type="text/ecmascript-6">
- import Search from '../vuePlugin/search'
- export default {
- data() {
- return {
- searchList: ['草船借箭', '大富翁', '测试数据'],
- // 直接通过 props 传递对象 修改, 挺便捷的, 但是不规范
- selectValue: {
- data: '1'
- },
- // 通过 emit 修改, 规范写法
- selectValue2: ''
- }
- },
- mounted() {},
- methods: {
- pageGo(path) {
- this.$router.push('/' + path)
- },
- selectFunc(value) {
- this.selectValue2 = value
- console.log(this.selectValue)
- console.log(this.selectValue2)
- }
- },
- components: {
- Search
- }
- }
- </script>
- <style lang="sCSS" scoped>
- .f-mainPage{
- width: 100%;
- .g-banner{
- width: 100%;
- background-image: url(../../../static/main_bg.png);
- background-repeat: no-repeat;
- background-size: 100% 100%;
- position: relative;
- overflow: hidden;
- color: white;
- text-align: center;
- p:nth-child(1) {
- margin: 10px auto 0px auto;
- font-size: 1.3rem;
- }
- .f-banscri {
- margin: 15px auto 8px auto;
- font-size: 0.95rem;
- }
- .f-moneyMax{
- margin: 5px auto 0px auto;
- font-size: 2.4rem;
- }
- .f-returnCash{
- width: 120px;
- height: 35px;
- text-align: center;
- line-height: 35px;
- background-color: white;
- color: #169BD5;
- display: inline-block;
- border-radius: 5px;
- font-size: 1rem;
- margin-top: 35px;
- position: relative;
- .f-mmmbd{
- position: absolute;
- width: 100%;
- height: 100%;
- background-color: transparent;
- top: 0;
- left: 0;
- }
- }
- }
- .g-cashInfor{
- width: 100%;
- text-align: center;
- display: flex;
- justify-content: space-between;
- div{
- width: 50%;
- height: 60px;
- line-height: 60px;
- box-sizing: border-box;
- }
- div:nth-child(1){
- border-bottom: 1px solid #878787;
- border-right: 1px solid #878787;
- }
- div:nth-child(2){
- border-bottom: 1px solid #878787;
- }
- }
- .g-operate{
- width: 100%;
- height: auto;
- overflow: hidden;
- ul{
- list-style: none;
- padding: 0;
- margin: 0;
- font-size: 1.05rem;
- li{
- height: 60px;
- line-height: 60px;
- padding-left: 25px;
- position: relative;
- span{
- width: 20px;
- height: 20px;
- position: absolute;
- top: 20px;
- right: 20px;
- background-image: url(../../../static/go.png);
- background-repeat: no-repeat;
- background-size: 100% 100%;
- }
- }
- }
- .f-goodNews{
- width: 340px;
- height: 144.5px;
- margin: 20px auto 30px auto;
- text-align: center;
- background-image: url(../../../static/banner.png);
- background-repeat: no-repeat;
- background-size: 100% 100%;
- }
- }
- }
- </style>
子组件:
- <template>
- <div class="searchZJ">
- <div class="f-search">
- <div class="f-searchIn" v-bind:class="{searchInFous: this.fousFlag}">{{this.searchValue}}<span v-bind:class="{searchActive: this.searchFlag}" v-on:click="searchDown"></span></div>
- <div class="f-searchXl" v-if="this.dataHas" v-bind:style="{height:this.searchFous, border:this.searchBorder}">
- <div v-for="item in searchList" v-on:click="choseValue(item)">{{item}}</div>
- </div>
- <div class="f-searchXl" v-else>
- <div > 暂无数据 </div>
- </div>
- </div>
- </div>
- </template>
- <script type="text/ecmascript-6">
- export default {
- data() {
- return {
- data: [],
- dataHas: true,
- searchFlag: false,
- searchFous: '0',
- fousFlag: false,
- searchValue: '',
- searchBorder: 'none'
- }
- },
- props: {
- searchList: Array,
- selectValue: Object
- },
- mounted() {
- this.data = this.searchList
- },
- methods: {
- searchDown() {
- this.searchFlag === false ? this.searchFlag = true : this.searchFlag = false
- this.searchFous === '0' ? this.searchFous = 'auto' : this.searchFous = '0'
- this.searchBorder === 'none' ? this.searchBorder = '1px solid #D9D9D9' : this.searchBorder = 'none'
- this.fousFlag === false ? this.fousFlag = true : this.fousFlag = false
- },
- choseValue(value) {
- this.searchValue = value
- this.searchDown()
- this.selectValue.data = '我被修改了'
- this.$emit('selectFunc', value)
- }
- }
- }
- </script>
- <style scoped lang="stylus" rel="stylesheet/stylus">
- .f-search{
- width: 250px;
- height: auto;
- position: relative;
- margin-left: 20px;
- box-sizing: border-box;
- }
- .f-searchIn{
- width: 250px;
- height: 35px;
- line-height: 35px;
- font-size: 0.95rem;
- border-radius: 5px;
- overflow: hidden;
- position: relative;
- background-color: white;
- box-shadow: none;
- box-sizing: border-box;
- color: #000000;
- padding-left: 10px;
- border: 1px solid #A3A3A3;
- }
- .searchInFous{
- border: 1px solid #57C4F6;
- box-shadow: 0px 0px 5px #57C4F6;
- }
- .f-searchIn> span{
- display: block;
- width: 28px;
- height: 28px;
- background-image: url(../../../static/upDown.png);
- background-size: 100% 100%;
- background-repeat: no-repeat;
- background-position: 0px -13px;
- position: absolute;
- top: 10px;
- right: 5px;
- }
- .f-searchIn .searchActive{
- background-position: 0px 12px;
- top: -2px;
- }
- .f-search .f-searchXl{
- position: absolute;
- width: 100%;
- height: auto;
- max-height: 220px;
- top: 41px;
- left: -1px;
- border-radius: 5px;
- /*border: 1px solid #D9D9D9;*/
- background-color: white;
- overflow-x: hidden;
- overflow-y: scroll;
- }
- .f-search .f-searchXl> div{
- height: 35px;
- line-height: 38px;
- color: #000000;
- padding-left: 25px;
- font-size: 0.92rem;
- }
- .f-search .f-searchXl> div:hover{
- background-color: #D5F1FD;
- }
- </style>
四, 代码详解
1. 先说一下 props
我们在父组件中需要将子组件需要的数据导入, 用法如下:
<search @selectFunc="selectFunc" :searchList="searchList" :selectValue="selectValue"></search>
:searchList="searchList" 就是我们的数据, 这个可以写多个. 这里我传输了 2 个参数过去, 主要是做数据修改的说明. 大家可以先忽略.
在子组件中, 我们的接收和使用方法如下:
- props: {
- searchList: Array,
- selectValue: Object
- },
- mounted() {
- this.data = this.searchList
- },
我们在 props 中接收数据, 注意 props 对象里面 键值 是对改数据的 数据类型 的规定. 做了规范, 使用者就只能传输指定类型的数据, 否则报警告
二 props 对象中的数据, 我们可以直接在当前组件中使用 this.searchList, 可以直接使用. 至于原理嘛, 不懂的可以取脑补一下 js 的原型 . os: 这些基础, 在这就不做详述了
以上就是 props 传递过来的数据的使用了.
2. emit 的使用 (如何暴露组件方法)
我们已经会使用 父组件向子组件传数据了, 那如子组件如何来修改父组件的数据呢?
这里提供 2 种实现方法, 但是 第一种不推荐, 强烈不推荐
方式一:
- selectValue: {
- data: '1'
- },
- ...............
- this.selectValue.data = '我被修改了'
即, 父组件将 对象 数据传递给子组件, 子组件直接修改 props 过来的对象的值
可以实现, 感觉是一个比较快捷的方式. 但是不推荐, 这种方式写多了, 容易出错, 特别是多层组件嵌套的时候. 这种修改对代码的迭代和错误的捕捉都不友好, 所以建议大家别这样写.
他的实现原理简单提一下: 这个对象, 数组啦, 是引用数据类型, 说白了, 就是存储单元的信息是指针, 真正数据在别的地方, 通过指针查询的数据, 所以这样写, 对浏览器来说仅仅是传递了一个指针, 数据还是同一份数据. 所以你能修改.
方式二:
正儿八经的通过 $emit 方法去掉父组件的方法, 在父组件中修改 data 的数据.(根正苗红的方法, 规范写法)
- // 子组件
- this.$emit('selectFunc', value)
// 父组件
- <search @selectFunc="selectFunc" :searchList="searchList" :selectValue="selectValue"></search>
- selectFunc(value) {
- this.selectValue2 = value
- console.log(this.selectValue)
- console.log(this.selectValue2)
- }
将父组件的方法注入子组件 @selectFunc="selectFunc" , 然后在子组件中通过 $emit 调用他, 并传递参数. 达到修改的目的.
五, 总结
这里主要是总结一下 vue 组件封装的思路, 帮大家梳理一下. 很简单, 和 jQuery 插件, react 组件一样, 所有组件都是一个套路, 就是 函数思想.
组件就是做烤肠台机器, 我放进去猪肉, 再按一下各种开关, 然后你给我烤肠.
1. 定义好 你需要使用者传入的数据
2. 定义好 你提供给使用者的方法
3. 写好组件的内部逻辑
这就 OK 了, 一个完美的, 可复用的组件就完成了. os: 在此吐槽一下, 那些自认为是优秀的组件, 其实, 别人拿着没法用的代码. o(﹏)o
os: 愿大家工作过程中能规范编程习惯, 一起为前端代码大社区做贡献.
来源: https://www.cnblogs.com/pengfei-nie/p/9134367.html