表单组件
简单地自己造一下轮子,实现一下vue的自定组件,这里主要先从form组件开始实践。
form组件需求分析:1、指定数据、校验规则;高内聚、低耦合;
2、这里为了实现form组件的输入功能,我们自定义一个input组件来实现;为保证低耦合性,仅使用并实现双向绑定基本功能。
3、校验的实现跟input分开,再自定义一个KFormItem组件来实现校验的功能。执行校验的组件为KFormItem组件,但通知父组件执行校验的是其子组件KINput,由于事件是谁派发谁监听,不能用this.$emit,需要用this.parent.$emit。之后,在KFormItrm组件上监听校验事件,执行具体校验。
实现功能:维护数据;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div>
<input :type="type" :value="value" @input="onInput" v-bind="$attrs"> </div> </template>
<script> export default { inheritAttrs: false, props: { value: { type: String, default: '' }, type: { type: String, default: 'text' } }, methods: { onInput(e) { this.$emit('input', e.target.value)
this.$parent.$emit('validate') } }, } </script>
<style scoped>
</style>
|
KformItem组件包裹Input组件,同时在前面添加label标签;
实现功能:1、执行校验2、显示错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| <template> <div> <label v-if="label">{{label}}</label> <slot></slot>
<p v-if="error">{{error}}</p> </div> </template>
<script>
import Schema from "async-validator";
export default { inject: ["form"], data() { return { error: "" }; }, props: { label: { type: String, default: "" }, prop: { type: String } }, mounted() { this.$on("validate", () => { this.validate(); }); }, methods: { validate() { const rules = this.form.rules[this.prop]; const value = this.form.model[this.prop];
const desc = { [this.prop]: rules }; const schema = new Schema(desc); return schema.validate({ [this.prop]: value }, errors => { if (errors) { this.error = errors[0].message; } else { this.error = ""; } }); } } }; </script>
<style scoped> </style>
|
一层层地往上面的层级进行靠近,在KForm的层级,除了实现slot将下面的层级包裹,同样也要接受数据并处理。
最终目的是在KForm上除了接受数据模型model以外,还要声明校验规则rules,因此声明数据model、rules。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <template> <div> <slot></slot> </div> </template>
<script> export default { provide() { return { form: this }; }, props: { model: { type: Object, required: true }, rules: { type: Object } }, methods: { validate(cb) { const tasks = this.$children .filter(item => item.prop) .map(item => item.validate());
Promise.all(tasks) .then(() => cb(true)) .catch(() => cb(false)); } } }; </script>
<style scoped> </style>
|
Index
这样,在index层级里面,我们将所有的自定义组件封装起来,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| <template> <div>
<KForm :model="userInfo" :rules="rules" ref="loginForm"> <KFormItem label="用户名" prop="username"> <KInput v-model="userInfo.username" placeholder="请输入用户名"></KInput> </KFormItem> <KFormItem label="密码" prop="password"> <KInput type="password" v-model="userInfo.password" placeholder="请输入密码"></KInput> </KFormItem> <KFormItem> <button @click="login">登录</button> </KFormItem> </KForm> </div> </template>
<script> import ElementTest from "@/components/form/ElementTest.vue"; import KInput from "@/components/form/KInput.vue"; import KFormItem from "@/components/form/KFormItem.vue"; import KForm from "@/components/form/KForm.vue"; import Notice from "@/components/Notice.vue";
export default { data() { return { userInfo: { username: "tom", password: "" }, rules: { username: [{ required: true, message: "请输入用户名称" }], password: [{ required: true, message: "请输入密码" }] } }; }, components: { ElementTest, KInput, KFormItem, KForm }, methods: { login() { this.$refs["loginForm"].validate(valid => { const notice = this.$create(Notice, { title: "", message: valid ? "请求登录!" : "校验失败!", duration: 2000 }); notice.show(); }); } } }; </script>
<style scoped> </style>
|
VueRouter
需求分析
1、作为一个插件存在:实现VueRouter类和install方法
2、实现两个全局组件:router-view用于显示匹配组件内容,router-link用于跳转
3、监控url变化:监听hashchange或popstate事件
4、响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并显示
实现VueRouter插件
在toyRouter.js里面,我们需要实现一个插件,先创建VueRouter类,并且实现其install方法,该方法会在当前的Vue原型链上挂载$router。使用vue.mixin来混入生命周期中,保证每个vue均能实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class KVueRouter { constructor(options) { this.$options = options console.log(this.$options);
KVueRouter.install = function (_Vue) { Vue = _Vue;
Vue.mixin({ beforeCreate() { if (this.$options.router) { Vue.prototype.$router = this.$options.router } } }) }
export default KVueRouter
|
router-link
为实现功能,将其写成独立的组件,并在vueRouter的install方法中再将其引入。
router-link实现的功能是:解析用户输入的路由值,并重新渲染一个输入路由值对应的a标签。且在纯运行时的环境,不能使用template而需要用render函数来渲染页面,这里我们需要渲染一个a标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| export default { props: { to: { type: String, required: true }, }, render(h) { console.log(this.$slots); return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default) } }
|
router-view
router-view实现的功能基本上是:根据当前的路由值,获取对应的component并渲染在当前位置。
1、需要监控路由的变化,且为了实现路由改变时,便实时刷新页面,因此需要设置当前路由值为响应式数据。
2、为了确保查找的快速,在新建VueRouter时就应该以用户输入路由配置,初始化一个hash表,这样每次获取当前路由值的component时,只用O(1)时间即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class KVueRouter { constructor(options) { this.$options = options console.log(this.$options); Vue.util.defineReactive(this, 'current', '/')
window.addEventListener('hashchange', this.onHashChange.bind(this)) window.addEventListener('load', this.onHashChange.bind(this))
this.routeMap = {} options.routes.forEach(route => { this.routeMap[route.path] = route }) }
onHashChange() { console.log(window.location.hash); this.current = window.location.hash.slice(1); } }
|
而在router-view页面要实现的功能就要简单些了,主要是从路由映射hsahMap中获取对应组件,这里同样要用render函数来渲染。
1 2 3 4 5 6 7 8 9 10
| export default { render(h) { const {routeMap, current} = this.$router; console.log(routeMap,current); const component = routeMap[current].component || null; return h(component) } }
|
Vuex
目的:集中管理数据、可预测的改变数据;类似于整个项目数据的大管家,确保整个程序的状态、数据能够保持同步的状态,维持稳定。
需求分析
1、实现一个插件:声明Store类,挂载$store
2、:创建响应式的state,保存mutations、actions和getters
3、实现commit根据用户传入type执行对应mutation
4、实现dispatch根据用户传入type执行对应action,同时传递上下文
5、实现getters,按照getters定义对state做派生
实现store插件
声明Store、install方法,并创建一个响应式的state。同时利用存取器,避免用户直接去取state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| let Vue;
class Store { constructor(options) { this._mutations = options.mutations; this._actions = options.actions;
this._vm = new Vue({ data: { $$state: options.state } })
this.commit = this.commit.bind(this) this.dispatch = this.dispatch.bind(this) }
get state() { console.log(this._vm); return this._vm._data.$$state }
set state(v) { console.error('无法修改!'); }
function install(_Vue) { Vue = _Vue;
Vue.mixin({ beforeCreate() { if (this.$options.store) { Vue.prototype.$store = this.$options.store } } }) }
export default { Store, install }
|
实现commit、dispatch方法
在写方法之前需要将this.commit、this.dispatch绑定为该store实例,在构造函数上加上:this.commit = this.commit.bind(this);this.dispatch = this.dispatch.bind(this)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Store {
commit(type, payload) { const entry = this._mutations[type] if (entry) { entry(this.state, payload) } }
dispatch(type, payload) { const entry = this._actions[type] if (entry) { entry(this, payload) } } }
|
实现getters