赞
踩
<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>
<script src="../node_modules/vue/dist/vue.js"></script> <div id="app"> <h1>{{ $t('welcome-message') }}</h1> <button @click="changeLang('en')">English</button> <button @click="changeLang('zh')">中文</button> <button @click="changeLang('nl')">Dutch</button></div> <script>const i18nPlugin = { install(Vue,locales){ Vue.prototype.$t=function(id){ return locales[this.$root.lang][id] } }} Vue.use(i18nPlugin, /* option */ { en: { 'welcome-message': 'hello' }, zh: { 'welcome-message': '你好' }, nl: { 'welcome-message': 'Hallo' }}) new Vue({ el: '#app', data: { lang: 'en' }, methods: { changeLang (lang) { this.lang = lang } }})</script>
Vue组件=Vue实例=new Vue(options)
相同名称的插槽替换
<template><div> <lazy-component /></div></template><script>const lazyComponent = () => import('Component.vue')export default {components: { lazyComponent }}</script>
基于路由拆分
const routes = [{ path: /foo', component: () => import('./RouteComponent.vue') }]
无状态 、实例、this上下文、生命周期
<TempVar :var1="`hello ${name}`" :var2="destroyClock ? 'hello vue' : 'hello world'"> <template v-slot="{ var1, var2 }"> {{ var1 }} {{ var2 }} </template></TempVar> <script>export default { functional:true, render:(h,ctx)=>{ return ctx.scopedSlots.default && ctx.scopedSlots.default(ctx.props||{}) }}</script>
<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>
function createApp ({ el, model, view, actions }) { const wrappedActions={} Object.keys(actions).forEach(key=>{ const originalAction=actions[key] wrappedActions[key]=()=>{ const nextModel=originalAction(model) vm.model=nextModel } }) const vm=new Vue({ el, data:{model}, render(h){ return view(h,this.model,wrappedActions) } })} createApp({ el: '#app', model: { count: 0 }, actions: { inc: ({ count }) => ({ count: count + 1 }), dec: ({ count }) => ({ count: count - 1 }) }, view: (h, model, actions) => h('div', { attrs: { id: 'app' }}, [ model.count, ' ', h('button', { on: { click: actions.inc }}, '+'), h('button', { on: { click: actions.dec }}, '-') ])})
<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>
const AsyncComp = () => ({ // 需要加载的组件。应当是一个 Promise component: import('./MyComp.vue'), // 加载中应当渲染的组件 loading: LoadingComp, // 出错时渲染的组件 error: ErrorComp, // 渲染加载中组件前的等待时间。默认:200ms。 delay: 200, // 最长等待时间。超出此时间则渲染错误组件。默认:Infinity timeout: 3000})Vue.component('async-example', AsyncComp)
<!-- 子组件 --><template> <ul> <li v-for="item in dataList">{{item}}</li> </ul> </template> <script> export default { props : { dataList : [] } }</script><!-- 父组件 --><template> <component-child v-bind:data-list="dataList"> </component-child> <input v-model="dataInput" v-on:keyup.13="addDataItem()" ></input></template> <script> import ComponentChild from './child.vue'export default { data () { return { dataInput: "", dataList : [ 'hello world!','welcome to use vue.js' ] } }, components : { ComponentChild }, methods : { addDataItem () { let self = this if( !(self.dataInput && self.dataInput.length > 0) ) { return } self.dataList.push( self.dataInput ) self.dataInput = "" } }}</script>
在组件中,可以使用 $emit, $on, $off 分别来分发、监听、取消监听事件
// NewTodoInput ---------------------// ...methods: { addTodo: function () { eventHub.$emit('add-todo', { text: this.newTodoText }) this.newTodoText = '' }}// DeleteTodoButton ---------------------// ...methods: { deleteTodo: function (id) { eventHub.$emit('delete-todo', id) }}// Todos ---------------------// ...created: function () { eventHub.$on('add-todo', this.addTodo) eventHub.$on('delete-todo', this.deleteTodo)},// 最好在组件销毁前// 清除事件监听beforeDestroy: function () { eventHub.$off('add-todo', this.addTodo) eventHub.$off('delete-todo', this.deleteTodo)},methods: { addTodo: function (newTodo) { this.todos.push(newTodo) }, deleteTodo: function (todoId) { this.todos = this.todos.filter(function (todo) { return todo.id !== todoId }) }}
$ref ref="xxx" $parent / $children:访问父 / 子实例
<input :value="value" v-bind="$attrs" v-on="listeners"> <script>computed: { listeners() { return { ...this.$listeners, input: event => this.$emit('input', event.target.value) } }}</script>
// 父级组件提供 'foo'var Provider = { provide: { foo: 'bar' }, // ...} // 子组件注入 'foo'var Child = { inject: ['foo'], created () { console.log(this.foo) // => "bar" } // ...}
// 父级组件提供 'state'var Provider = { provide: { state = Vue.observable({ count: 0 }) }, // ...} // 子组件注入 'foo'var Child = { inject: ['state'], created () { console.log(this.state) // => { count: 0 } } // ...}
const state={count:0} const Counter = { data(){return state}, render:h=>h('div',state.count)} new Vue({ el: '#app', components:{Counter}, methods:{ inc(){ state.count++ } }})
//中央事件总线var bus = new Vue();var app = new Vue({ el: "#app", template: ` <div> <brother1></brother1> <brother2></brother2> </div> `});// 在组件 brother1 的 methods 方法中触发事件bus.$emit("say-hello", "world");// 在组件 brother2 的 created 钩子函数中监听事件bus.$on("say-hello", function(arg) { console.log("hello " + arg); // hello world});
$dispatch 和 $broadcast 已经被弃用,使用Vuex代替
以下自实现参考 iview/emitter.js at 2.0 · iview/iview -github
function broadcast(componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { // todo 如果 params 是空数组,接收到的会是 undefined broadcast.apply(child, [componentName, eventName].concat([params])); } });}export default { methods: { dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } }};
单向数据流,双向绑定语法糖
<PersonalInfo v-model="phoneInfo" :zip-code.sync="zipCode"/><!-- 等同于 --><PersonalInfo :phone-info="phoneInfo" @change="val=>(phoneInfo=val)" :zip-code="zipCode" @update:zipCode="val=>(zipCode=val)"/>
数据劫持+观察订阅模式:
// 全局的依赖收集器Dep window.Dep = class Dep { constructor() {this.subscribers = new Set()} depend() {activeUpdate&&this.subscribers.add(activeUpdate)} notify() {this.subscribers.forEach(sub =>sub())} } let activeUpdate function autorun(update) { function wrapperUpdate() { activeUpdate = wrapperUpdate update() activeUpdate = null } wrapperUpdate() } function observer(obj) { Object.keys(obj).forEach(key => { var dep = new Dep() let internalValue = obj[key] Object.defineProperty(obj, key, { get() { // console.log(`getting key "${key}": ${internalValue}`) // 将当前正在运行的更新函数追加进订阅者列表 activeUpdate&&dep.depend() //收集依赖 return internalValue }, set(newVal) { //console.log(`setting key "${key}" to: ${internalValue}`) // 加个if判断,数据发生变化再触发更新 if(internalValue !== newVal) { internalValue = newVal dep.notify() // 触发依赖的更新 } } }) }) } let state = {count:0} observer(state); autorun(() => { console.log('state.count发生变化了', state.count) }) state.count = state.count + 5; // state.count发生变化了 0 // state.count发生变化了 5
实现mvvm-github
Model-View-ViewModel,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View
全局注册的选项,被引用到你的每个组件中 1、Vue.component 注册的 【全局组件】 2、Vue.filter 注册的 【全局过滤器】 3、Vue.directive 注册的 【全局指令】 4、Vue.mixin 注册的 【全局mixin】
合并权重 1、组件选项 2、组件 - mixin 3、组件 - mixin - mixin 4、..... x、全局 选项 函数合并叠加(data,provide) 数组叠加(created,watch) 原型叠加(components,filters,directives) 两个对象合并的时候,不会相互覆盖,而是 权重小的 被放到 权重大 的 的原型上 覆盖叠加(props,methods,computed,inject) 两个对象合并,如果有重复key,权重大的覆盖权重小的 直接替换(el,template,propData 等)
something | myFilter 被解析成_f('myFilter')( something )
something | myFilter
_f('myFilter')( something )
Vue.js 在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 run( Watcher 对象的一个方法,用来触发 patch 操作) 一遍。
真实DOM操作昂贵,虚拟DOM就是js对象,操作代价小
平时开发写vue文件都是用模板template的方法写html,模板会被编译成render函数,流程如下:
render API
//template, jsx, render本质都是一样的, 都是一种dom和数据状态之间关系的表示 render(h) { h(tag, data, children) } // tag可以是原生的html标签 render(h) { return h('div', { attrs: {}}, []) } // 也可以是一个vue component import vueComponent from '...' render(h) { h(vueComponent, { props: {} }) }
compile 编译可以分成 parse、 optimize 与 generate 三个阶段,最终需要得到 render function。
optimize(ast, options)
const code = generate(ast, options)
vue模板编译前后:
`<ul :class="bindCls" class="list" v-if="isShow"> <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li></ul>` with(this){ return (isShow) ? _c('ul', { staticClass: "list", class: bindCls }, _l((data), function(item, index) { return _c('li', { on: { "click": function($event) { clickItem(index) } } }, [_v(_s(item) + ":" + _s(index))]) }) ) : _e()}
对比react的jsx编译前后
`<div id="1" class="li-1"> Hello World <MyComp></MyComp></div>` h('div',{ id: '1', 'class': 'li-1' },'Hello World', h(MyComp, null))
vnode
{ el: div //对真实的节点的引用,本例中就是document.querySelector('#id.classA') tagName: 'DIV', //节点的标签 sel: 'div#v.classA' //节点的选择器 data: null, // 一个存储节点属性的对象,对应节点的el[prop]属性,例如onclick , style children: [], //存储子节点的数组,每个子节点也是vnode结构 text: null, //如果是文本节点,对应文本节点的textContent,否则为null}
if( oldCh && ch && oldCh !== ch ), 两个节点都有子节点,而且它们不一样,这样我们会调用updateChildren函数比较子节点
Vue 很“ 嚣张 ”,它宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,由于vue会跟踪每一个组件的依赖收集,通过setter / getter 以及一些函数的劫持,能够精确地知道变化,并在编译过程标记了static静态节点,在接下来新的Virtual DOM 并且和原来旧的 Virtual DOM进行比较时候,跳过static静态节点。所以不需要重新渲染整个组件树。 React默认是通过比较引用的方式进行,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。如果想避免不必要的子组件重新渲染,你需要在所有可能的地方使用PureComponent,或者手动实现shouldComponentUpdate方法。但是Vue中,你可以认定它是默认的优化。 摘自 https://juejin.im/post/5b617801518825615d2fc92c
Vue 很“ 嚣张 ”,它宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,由于vue会跟踪每一个组件的依赖收集,通过setter / getter 以及一些函数的劫持,能够精确地知道变化,并在编译过程标记了static静态节点,在接下来新的Virtual DOM 并且和原来旧的 Virtual DOM进行比较时候,跳过static静态节点。所以不需要重新渲染整个组件树。
React默认是通过比较引用的方式进行,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。如果想避免不必要的子组件重新渲染,你需要在所有可能的地方使用PureComponent,或者手动实现shouldComponentUpdate方法。但是Vue中,你可以认定它是默认的优化。
摘自 https://juejin.im/post/5b617801518825615d2fc92c
类vue vdom
snabbdom-github
snabbdom源码阅读分析
Vue 2.0 的 virtual-dom 实现简析
类react vdom
preact-github
preact工作原理
vue插件,通过hash /history 2中方式实现可配路由Hash
$router.push()
HashHistory.push()
History.transitionTo()
History.updateRoute()
app._route= route
vm.render()
History
实战
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 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 }) ]) }})
父组件怎么传值给子组件的 props
父组件的模板 会被解析成一个 模板渲染函数,执行时会绑定 父组件为作用域
(function() { with(this){ return _c('div',{staticClass:"a"},[ _c('testb',{attrs:{"child-name":parentName}}) ],1) }})
子组件拿到父组件赋值过后的 attr,筛选出 props,然后保存到实例的_props 中,并逐一复制到实例上,响应式的。
父组件数据变化,子组件props如何更新 父组件数据变化,触发set,从而通知依赖收集器的watcher重新渲染
vuex 仅仅是作为 vue 的一个插件而存在,不像 Redux,MobX 等库可以应用于所有框架,vuex 只能使用在 vue 上,很大的程度是因为其高度依赖于 vue 的 computed 依赖检测系统以及其插件系统,
vuex 整体思想诞生于 flux,可其的实现方式完完全全的使用了 vue 自身的响应式设计,依赖监听、依赖收集都属于 vue 对对象 Property set get 方法的代理劫持。vuex 中的 store 本质就是没有 template 的隐藏着的 vue 组件;
state
getter
mutaion
action
module
简单版Vuex实现
//min-vueximport Vue from 'vue'const Store = function Store (options = {}) { const {state = {}, mutations={}} = options this._vm = new Vue({ data: { $$state: state }, }) this._mutations = mutations}Store.prototype.commit = function(type, payload){ if(this._mutations[type]) { this._mutations[type](this.state, payload) }}Object.defineProperties(Store.prototype, { state: { get: function(){ return this._vm._data.$$state } }});export default {Store}
使用 Vuex 只需执行 Vue.use(Vuex),并在 Vue 的配置中传入一个 store 对象的示例,store 是如何实现注入的?
Vue.use(Vuex) 方法执行的是 install 方法,它实现了 Vue 实例对象的 init 方法封装和注入,使传入的 store 对象被设置到 Vue 上下文环境的store中。因此在VueComponent任意地方都能够通过this.store 访问到该 store。
state 内部支持模块配置和模块嵌套,如何实现的?
在 store 构造方法中有 makeLocalContext 方法,所有 module 都会有一个 local context,根据配置时的 path 进行匹配。所以执行如 dispatch('submitOrder', payload)这类 action 时,默认的拿到都是 module 的 local state,如果要访问最外层或者是其他 module 的 state,只能从 rootState 按照 path 路径逐步进行访问。
在执行 dispatch 触发 action(commit 同理)的时候,只需传入(type, payload),action 执行函数中第一个参数 store 从哪里获取的?
store 初始化时,所有配置的 action 和 mutation 以及 getters 均被封装过。在执行如 dispatch('submitOrder', payload)的时候,actions 中 type 为 submitOrder 的所有处理方法都是被封装后的,其第一个参数为当前的 store 对象,所以能够获取到 { dispatch, commit, state, rootState } 等数据。
Vuex 如何区分 state 是外部直接修改,还是通过 mutation 方法修改的?
Vuex 中修改 state 的唯一渠道就是执行 commit('xx', payload) 方法,其底层通过执行 this._withCommit(fn) 设置_committing 标志变量为 true,然后才能修改 state,修改完毕还需要还原_committing 变量。外部修改虽然能够直接修改 state,但是并没有修改_committing 标志位,所以只要 watch 一下 state,state change 时判断是否_committing 值为 true,即可判断修改的合法性。
调试时的"时空穿梭"功能是如何实现的?
devtoolPlugin 中提供了此功能。因为 dev 模式下所有的 state change 都会被记录下来,'时空穿梭' 功能其实就是将当前的 state 替换为记录中某个时刻的 state 状态,利用 store.replaceState(targetState) 方法将执行 this._vm.state = state 实现。
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。
然而,也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。
服务器渲染的 Vue.js 应用程序也可以被认为是"同构"或"通用",因为应用程序的大部分代码都可以在服务器和客户端上运行。
服务端渲染的核心就在于:通过vue-server-renderer插件的renderToString()方法,将Vue实例转换为字符串插入到html文件
vue 企业级应用模板-github
VueConf 2018 杭州(第二届Vue开发者大会)
Vue 3.0 进展 - 尤雨溪
深入响应式原理 —— Vue.js官网
Advanced Vue.js Features from the Ground Up - 尤雨溪
Vue 源码解析:深入响应式原理 - 黄轶
Vue 源码研究会 - 神仙朱
vue组件之间8种组件通信方式总结 - zhoulu_hp
Vue问得最多的面试题 - yangcheng
剖析 Vue.js 内部运行机制 - 染陌
转载于:https://www.cnblogs.com/seasonley/p/10915608.html