和尤雨溪一塊兒進階vue(二)

這個系列文章將從下面幾個方面來介紹vuejavascript

  1. reactivity(響應式)
  2. plugin(插件)
  3. render(渲染函數)
  4. routing(路由)
  5. state-management(狀態管理)
  6. international(多語言支持) 上一篇文章已經介紹了第一部分響應式reactivity, 連接地址 這篇準備介紹第插件,渲染函數以及路由, 這一部分代碼不少,看不懂本身多動手敲敲

plugin

開發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>
複製代碼

render

咱們來看看vue的渲染函數renderjava

平時開發寫vue文件都是用模板template的方法寫html,模板會被編譯成render函數,流程以下:react

初始化的時候git

  • 模板會被編譯成render函數
  • render函數返回虛擬DOM(virtual Dom)
  • 生成真正的DOM

數據更新的時候github

  • render函數返回新的virtual Dom
  • 新的virtual Dom和舊的virtual Dom作diff
  • 將差別運用到真實DOM

若是咱們直接用render函數的方式而不是template,就能夠免去模板編譯成render函數的步驟,性能會更高正則表達式

virtaul DOM

真正的DOMvue-router

  • 建立方式: document.createElement('div')
  • 內容: [document Element]
  • 真實的DOM是瀏覽器引擎用C++實現的,咱們沒法操控,只是暴露出上面的JS接口供咱們調用,操做很昂貴

virtual DOMexpress

  • 建立方式:vm.$createElement('div')
  • 內容: {tag: 'div', data: {attr: {}}, children: []}
  • 虛擬DOM就是js對象
  • 操做cheap

render API

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事件的監控, 咱們來看看不借助這個插件如何實現路由的功能

1 初級版

藉助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>
複製代碼

2 改進版

解耦微改進一下,將路由提取到一個路由表中,

<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>
複製代碼

3 最終版

上面都是處理簡單的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>
複製代碼

第二篇完結撒花!!!!, 第三篇月底不知道有沒有時間更,沒時間就清明節更了。

相關文章
相關標籤/搜索