vue组件化

vue组件开发

组件的本质:产生虚拟DOM

配置VueComponent所需的属性;
实例化VueComponent;
render();
virtual DOM;
DOM;
定义:

组件是可复用的vue实例,准确的说是VueComponent的实例,继承自Vue。
我们编写的组件代码本质为组件配置,框架后续会生成其构造函数,基于VueComponent。

优点:

增加代码的复用性、可维护性、可测试性。

使用场景:

通用组件:如按钮组件、输入框组件
业务组件:如登录组件、轮播组件
页面组件:如列表页、详情页面

特点

高内聚,低耦合

组件间通信方式
  • props
  • $emit / $on
  • vuex
  • $parent / $children
  • $attrs / $listeners
  • provide / inject
1、组件注册

1.1全局注册

import DtContainer from './components/DtContainer'

Vue.component('dt-container', DtContainer)

使用

<template>
    <dt-container></dt-container>
</template>

一次注册,多次使用,但不容易追溯

1.2 局部(推荐,依赖可追溯)

import DtContainer from './components/DtContainer'

new Vue({
    el: '#app',
    component: {
        'dt-container': DtContainer
    }
})
2、传值 Props
export default {
    name: 'DtContainer',
    props: ['title']
}
// 若希望传的值是必填的字符串
props: {
	title: {
		type: 'String',
	    required: true,
	    defaultValue: '' //默认值
	}
}
3、组件内部通知父元素变化
// DtButton.js
<template>
    <button @click="handleClick">发生变化</button>
</template>
<script>
    exports default {
    methods: {
        handleClick() {
            this.$emit('clickme', {msg: hello})
        }
    }
}
</script>

父页面

<template>
    <div id="app">
        <dt-button @clickme="handleClick">点击</dt-button>
    </div>
</template>
<script>
    exports default {
    components: {
    	DtButton
    }
    methods: {
        handleClick(obj) {
            console.log(obj.msg) // hello
        }
    }
}
</script>
4、插槽

组件

<win>
	<template slot="head">	// 对应具名插槽
		<h3>window</h3>
	</template>
	content...	// 对应匿名插槽
	<template v-slot:foot>	// 对应具名插槽,v-slot为vue 2.6后的写法
		<button>click</button>
	</template>
	// 获取prop
	<template v-slot:todo="slotProps">
		<h1>{{slotProps.first + slotProps.second}}</h1>
	</template>
	/** 第二种写法,解构插槽Prop
	<template v-slot:todo="{first, second}">
		<h1>{{first + second}}</h1>
	</template>
	*/
</win>

使用组件

//win.vue
<tempalte>
	<div>
		<div>
			<slot name="head"></slot>	// 具名插槽
		</div>
		<slot></slot>	// 匿名插槽
		<div>
			<slot name="foot"></slot>	// 具名插槽
		</div>
		<div>
			<slot name="todo" :first="one" :second="two"></slot>
		</div>
	</div>
</tempalte>
5、provide 和 inject

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。且只能够由父组件向子组件提供数据。

// 父组件中
<script>
export default {
	name: 'parent',
	provide(){
	    return {
	      someValue: '由父组件提供'
	    }
  	},
  	data() {
  	}
}
</script>

// 子组件中
<script>
export default {
	name: 'child',
	inject: ['someValue'],
  	data() {
  	},
  	created() {
  		console.log(this.someValue) // 由父组件提供
  	}
}
</script>

简单实现form表单

1、Input

v-model是一个特殊的语法糖,相当于绑定了 :value 和 @input 两个,下面两个写法是一样的

<dt-input v-model='content'></dt-input>
<dt-input :value='content' @input='content= $event'></dt-input>
// Input.vue
<template>
	<div>
	    <input :type="type" :value="inputValue" @input="inputHandler">
	</div>
</template>

<script>
    export default {
        props: {
            value:{
                type:String,
                default:''
            },
            type:{	// 	input 类型传递
                type:String,
                default:'text'
            }
        },
        data() {
            return {// 单向数据流原则:组件内不能修改props属性
                inputValue: this.value
            }
        },
        methods: {
            inputHandler(e) {
                this.inputValue = e.target.value;
                // 通知父组件值更新
                this.$emit('input', this.inputValue)

                // 通知FormItem做校验,$parent 指的是FormItem
                this.$parent.$emit('validate', this.inputValue)
            }
        },
    }
</script>
2、FormItem
// FormItem.vue
<template>
  <div>
    <label v-if="label">{{label}}</label>
    <div>
      <slot></slot>
      <!-- 校验错误信息 -->
      <p v-if="validateStatus == 'error'" class="error">{{errorMessage}}</p>
    </div>
  </div>
</template>

<script>
import schema from "async-validator";

export default {
  inject: ["form"], // 注入form,获取model和rules
  props: ["label", "prop"],
  data() {
    return {
      validateStatus: "",
      errorMessage: ""
    };
  },
  created() {
    this.$on("validate", this.validate);
  },
  mounted() {
    // 挂载到form上时,派发一个添加事件
    if (this.prop) {
      this.$parent.$emit("formItemAdd", this);
    }
  },
  methods: {
    validate() {
      return new Promise(resolve => {
        // 校验当前项:依赖async-validate
        const descriptor = {
          //校验规则
          [this.prop]: this.form.rules[this.prop]
        };
        const validator = new schema(descriptor);
        // 使用es6计算属性动态设置key
        validator.validate({ [this.prop]: this.form.model[this.prop] }, errors => {
          if (errors) {
            // 校验失败
            this.validateStatus = "error";
            this.errorMessage = errors[0].message;

            resolve(false);//校验失败
          } else {
            this.validateStatus = "";
            this.errorMessage = "";

            resolve(true);
          }
        });
      });
    }
  }
};
</script>

<style scoped>
.error {
  color: #f00;
}
</style>
3、Form
// Form.vue
<template>
  <form>
    <slot></slot>
  </form>
</template>

<script>
export default {
  name: 'diy-form',
  provide() {
    return {
      // 将表单实例传递给后代
      form: this
    };
  },
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  },
  created() {
    // 缓存需要校验的表单项
    this.fields = [];
    this.$on("formItemAdd", item => this.fields.push(item));
  },
  methods: {
    async validate(callback) {
      // 将FormItem数组转换为validate()返回的promise数组
      const tasks = this.fields.map(item => item.validate());
      console.log(tasks);
      
      // 获取所有结果统一处理
      const results = await Promise.all(tasks);
      let ret = true;
      results.forEach(valid => {
        if (!valid) {
          ret = false; // 只要一个失败就失败
        }
      });
      callback(ret);
    }
  }
};
</script>

<style scoped>
</style>
组件使用
<template>
  <div>
    <!-- 自实现的表单 -->
    <k-form :model="ruleForm" :rules="rules" ref="loginForm2">
      <k-form-item label="用户名" prop="name">
        <k-input v-model="ruleForm.name"></k-input>
      </k-form-item>
      <k-form-item label="密码" prop="pwd">
        <k-input v-model="ruleForm.pwd" type="password"></k-input>
      </k-form-item>
      <k-form-item>
        <el-button type="primary" @click="submitForm2()">登录</el-button>
      </k-form-item>
    </k-form>
    {{ruleForm}}
  </div>
</template>

<script>
import KInput from "./Input.vue";
import KFormItem from "./FormItem.vue";
import KForm from "./Form.vue";

export default {
  components: { KInput, KFormItem, KForm },
  data() {
    return {
      someValue: "some value",
      ruleForm: {
        name: "",
        pwd: ""
      },
      rules: {
        name: [
          { required: true, message: "请输入名称" },
          { min: 6, max: 10, message: "请输入6~10位用户名" }
        ],
        pwd: [{ required: true, message: "请输入密码" }]
      }
    };
  },
  methods: {
    submitForm2() {
      this.$refs.loginForm2.validate(valid => {
        if (valid) {
          alert("提交登录!");
        } else {
          console.log("校验失败");
          return false;
        }
      });
    }
  }
};
</script>

<style scoped>
</style>

思考:如何跨层级的$emit派发事件

// 拓展一个公共方法(目标组件名,派发事件,参数)
$dispatch(componentName, eventName, params) {
	// 向父元素广播事件
	let parent = this.$parent || this.$root
	let name = parent.$options.name // 获取父元素的export default {name: "app",...}中的name值
	// 向上循环查找
	while (parent && (!name && name !== componentName)) {
		parent = parent.$parent
		if (parent) {
			name = parent.$options.name
		}
	}
	if (parent) {
		/** Function.apply(obj,args)方法能接收两个参数
		obj:这个对象将代替Function类里this对象
		args:这个是数组,它将作为参数传给Function **/
		parent.$emit.apply(parent, [eventName].concat(params))
	}
}
// formItem.vue 中的 this.$parent.$emit("formItemAdd", this) 可修改为
this.dispatch('diy-form', 'formItemAdd', this)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值