这个系列文章将从下面几个方面来介绍vuejavascript
开发vue的插件很简单,官网上有很是详细的说明,对Vue插件不了解的人建议移步Vue官网教程插件html
练个手吧, 写个简单的表单验证插件vue
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<div id="app">
<form @submit="validate">
<input v-model="text">
<br>
<input v-model="email">
<ul v-if="!$v.valid" style="color:red">
<li v-for="error in $v.errors">
{{ error }}
</li>
</ul>
<input type="submit" :disabled="!$v.valid">
</form>
</div>
<script> const emailRE = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ const validationPlugin = { install(Vue) { // 全局注入的方法 Vue.mixin({ computed: { $v() { const rules = this.$options.validations let valid = true let errors = [] Object.keys(rules || {}).forEach(key => { const rule = rules[key] const value = this[key] const result = rule.validate(value) if(!result) { valid = false errors.push( rule.message(key, value) ) } }) return { valid, errors } } } }) } } Vue.use(validationPlugin) new Vue({ el: '#app', data: { text: 'foo', email: '' }, validations: { text: { validate: value => value.length >= 5, message: (key, value) => `${key} should have a min length of 5, but got ${value.length}` }, email: { validate: value => emailRE.test(value), message: key => `${key} must be a valid email` } }, methods: { validate (e) { if (!this.$v.valid) { e.preventDefault() alert('not valid!') } } } }) </script>
复制代码
咱们来看看vue的渲染函数renderjava
平时开发写vue文件都是用模板template
的方法写html,模板会被编译成render函数,流程以下:react
初始化的时候git
数据更新的时候github
若是咱们直接用render函数的方式而不是template,就能够免去模板编译成render函数的步骤,性能会更高正则表达式
真正的DOMvue-router
document.createElement('div')
virtual DOMexpress
vm.$createElement('div')
{tag: 'div', data: {attr: {}}, children: []}
render(h) {
h(tag, data, children)
}
复制代码
template, jsx, render本质都是同样的, 都是一种dom和数据状态之间关系的表示
用法:
// tag能够是原生的html标签
render(h) {
return h('div', { attrs: {}}, [])
}
// 也能够是一个vue component
import vueComponent from '...'
render(h) {
h(vueComponent, {
props: {}
})
}
复制代码
练手时间
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<div id="app">
<example :tags="['h1','h2', 'h3']"></example>
</div>
<script> Vue.component('example', { props: ['tags'], render(h) { return h('div', {attrs: { class: 'hello' }}, this.tags.map((tag, i) => h(tag, i)) ) } }) new Vue({el: '#app'}) </script>
复制代码
函数组件,实际上是一个接收参数的函数,没有本身的状态,没有生命周期,不建立组件实例,也不能够在render里面调用this 练手时间
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<div id="app">
<function-component :tags="['h1','h2','h3']"></function-component>
</div>
<script> // 函数组件的渲染函数还会接收一个额外的 context 参数,为没有实例的函数组件提供上下文信息。 // 这里咱们用对象解构context const FunctionComponent = { functional: true, // 标记组件为 functional render (h, {props:{tags}}){ return h('div', {attrs: { class:'function' }}, tags.map((tag, i) => h(tag, i)) ) } } Vue.component('function-component', FunctionComponent) new Vue({el: '#app'}) </script>
复制代码
A higher-order component is a function that takes a component and returns a new component.
React的高阶组件很火,其实Vue也能够写高阶组件,咱们能够写一个来看看
如今有一个组件Avatar
const Avatar = {
props: ['src'],
template: `<img :src="src" />`
}
复制代码
Avatar这个组件的功能就是接受src显示图片,它是一个很是简单的组件,没有其余多余的功能,不少地方都会使用这个组件,可是获取src的逻辑可能不同,在父组件里面使用这个组件的时候,若是在父组件里面调取接口获取图片地址传递给它,那么就会污染了父组件,由于这不是父组件的逻辑
这个时候咱们就能够建立一个高阶组件,根据不一样逻辑进行封装,假设如今是根据username获取图片地址,咱们来封装一个smart-avatar,咱们在使用的这个组件的时候不须要传入完整图片路径,只须要传入username就能够。
一句话,高阶组件其实就是对简单的组件的和封装, 从而能够和父组件解耦
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<div id="app">
<smart-avatar username="vuejs" id="hello">
<div slot="foo">
这是一个具名插槽
</div>
</smart-avatar>
</div>
<script> // mock API function fetchURL(username, cb) { setTimeout(() => { cb('https://avatars3.githubusercontent.com/u/6128107?v=4&s=200') }, 500) } const Avatar = { props: ['src'], template: `<img :src="src" />` } // 高阶组件withAvatarUrl function withAvatarUrl(innerComponent, fetchURL) { return { props: ['username'], data() { return { url: `http://via.placeholder.com/200*200` } }, created() { fetchURL(this.username, url => { this.url = url; }) }, render(h) { // console.log(this.$slots.default); // console.log(this.$slots.foo); return h(innerComponent, { props: { src: this.url, attrs: this.$attrs } }, this.$slots.default) } } } const SmartAvatar = withAvatarUrl(Avatar, fetchURL); new Vue({ el: '#app', components: {SmartAvatar} }) </script>
复制代码
用过vue开发项目的应该都知道vue-router, url变化的时候会匹配对应的组件,其实原理就是hash事件的监控, 咱们来看看不借助这个插件如何实现路由的功能
借助vue的动态组件,能够实现一个简单的路由功能,以下
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<div id="app">
<a href="#foo">foo</a>
<a href="#bar">bar</a>
<component :is="url"></component>
</div>
<script> // 这是一个比较简单的解决方案,可是有一个问题,初始化的时候没法匹配 window.addEventListener('hashchange', () => { app.url = window.location.hash.slice(1) }); let app = new Vue({ el: '#app', data() { return { url: null } }, components: { foo: {template: `<div>foo-component</div>`}, bar: {template: `<div>bar-component</div>`} } }) </script>
复制代码
解耦微改进一下,将路由提取到一个路由表中,
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<div id="app">
</div>
<script> const Foo = {template: '<div>foo</div>'} const Bar = {template: '<div>bar</div>'} const NotFound = {template: '<div>not found</div>'} // 在对象里面统一配置路由 const routeTable = { 'foo': Foo, 'bar': Bar } window.addEventListener('hashchange', () => { app.url = window.location.hash.slice(1) }) let app = new Vue({ el:'#app', data() { return { url: window.location.hash.slice(1) } }, render(h) { return h('div', [ h('a', {attrs: {href: '#foo'}}, 'foo'), '|', h('a', {attrs: {href: '#bar'}}, 'bar'), h(routeTable[this.url] || NotFound), ]) } }) </script>
复制代码
上面都是处理简单的url, 实际开发的时候,配置的路由都是多页面,多组件,路由的path也会比较长,如/a/b, /a/b/c, 还有动态路由,好比/a/:id,这个时候上面的方法就不能准确匹配了, 若是你是正则达人,你能够本身试试解析这些复杂的path,不是的话就使用别人封装好的第三方库吧,这里推荐path-to-regexp, 这个库的做用做者一句话就概述完了:
Turn a path string such as
/user/:nameinto a regular expression
你们能够点进去连接了解一下用法,这里咱们介绍接下来要用的部分
const keys = []
const regexp = pathToRegexp('/foo/:id', keys)
// regexp = /^\/foo\/((?:[^\/]+?))(?:\/(?=$))?$/i
// keys = [{ delimiter: "/", name: "id", optional: false, partial: false, pattern: "[^\/]+?", prefix: "/", repeat: false}]
// 获得了正则表达式regexp, 传入实际的url执行正则的exec方法
// 不匹配
const match1 = regexp.exec('/test/route'); // null
const match3 = regexp.exec('/foo'); // null
// 匹配
const match2 = regexp.exec('/foo/fooId'); // ["/foo/fooId", "fooId", index: 0, input: "/foo/fooId", groups: undefined]
复制代码
ok, 咱们能够解析path了,来看看接下来如何实现
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
// 这里是下载到本地同目录引入的
<script src='./path-to-regexp.js'></script>
<div id="app"></div>
<script> const Foo = { props: ['id'], template: `<div>foo with id: {{id}} </div>` } const Bar = { template: `<div>bar</div>` } const NotFound = { template: `<div>not found</div>` } const routeTable = { '/foo/:id': Foo, '/bar': Bar, } // 处理路由 const compiledRoutes = []; Object.keys(routeTable).forEach(path => { const dynamicSegments = [] const regex = pathToRegexp(path, dynamicSegments) const component = routeTable[path] compiledRoutes.push({ component, regex, dynamicSegments }) }) window.addEventListener('hashchange', () => { app.url = window.location.hash.slice(1); }) const app = new Vue({ el: '#app', data() { return { url: window.location.hash.slice(1) } }, // 渲染那个路由,路由属性 render(h) { const url = '/' + this.url let componentToRender let props = {} compiledRoutes.some(route => { const match = route.regex.exec(url) if (match) { componentToRender = route.component // 上一步已经能够匹配到url对应的组件了 // 这里多作一步,获取动态id做为props的属性传入组件 route.dynamicSegments.forEach((segment,index) => { props[segment.name] = match[index+1] }) } }) return h('div', [ h('a', { attrs: { href: '#foo/123' } }, 'foo123'), '|', h('a', { attrs: { href: '#foo/234' } }, 'foo234'), '|', h('a', { attrs: { href: '#bar' } }, 'bar'), h(componentToRender || NotFound, { props }) ]) } }) </script>
复制代码
第二篇完结撒花!!!!, 第三篇月底不知道有没有时间更,没时间就清明节更了。