首页
统计
赞助
留言
关于
更多
友链
壁纸
直播
Search
1
Joe — 一款个人类型Typecho主题
32,764 阅读
2
Joe开源目录程序系统
12,001 阅读
3
Typecho自定义后台编辑器功能
8,394 阅读
4
Joe主题实现自动更新
7,612 阅读
5
Joe主题自定义搭配色教程
4,511 阅读
WEB前端
CSS
React
Vue 2.0
Vue 3.0
JavaScript
TypeScript
Typecho
小程序
苹果CMS
其他
生活
登录
Search
https://78.al
累计撰写
76
篇文章
累计收到
3,760
条评论
首页
栏目
WEB前端
CSS
React
Vue 2.0
Vue 3.0
JavaScript
TypeScript
Typecho
小程序
苹果CMS
其他
生活
页面
统计
赞助
留言
关于
友链
壁纸
直播
搜索到
14
篇与
Vue 2.0
的结果
2021-08-04
Vue 2与Vue 3在自定义组件v-model上的区别
在vue开发中,通常会对一个自定义的组件进行封装,并实现v-model双向绑定功能在 Vue 2 中,通常这样实现父组件<template> <Child v-model="number"></Child> </template> <script> export default { data() { return { number: 0 } }, components: { Child: () => import("./Child.vue") } } </script>子组件<template> <button @click="handleClick">{{ value }}</button> </template> <script> export default { props: { value: Number }, methods: { handleClick() { // 通过emit一个input事件出去,实现 v-model this.$emit('input', this.value + 1) } } } </script>在 vue 3 中,通过这样实现父组件<template> <Child v-model="number"></Child> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; export default defineComponent({ setup() { const number = ref(0); return { number }; }, }); </script>子组件<template> <button @click="handleClick">{{ value }}</button> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ props: { // 更换成了 modelValue modelValue: Number }, setup(props, { emit }) { // 关闭弹出层 const handleClick = () => emit('update:modelValue', props.modelValue + 1); return { handleClick }; }, }); </script>
2021年08月04日
1,211 阅读
15 评论
20 点赞
2021-07-28
Vue.set与this.$set源码
Vue.set()和this.$set()应用的场景在 Vue 2.X 项目开发中,有时候需要对数组进行修改,或是对对象新增一个属性,但是发现页面并不会同步更新。例如:const vm = new Vue({ data: { arr: [1, 2], obj: { a: 3 } } }); vm.$data.arr[0] = 3; // 页面不会发生改变 vm.$data.obj.b = 3; // 页面不会发生改变此时就需要使用到 Vue.set() 或 this.$set()。这个2个的使用方法一样,不过一个是挂载在Vue身上,一个挂载在Vue.prototype上// Vue.set import { set } from '../observer/index' Vue.set = set // this.$set import { set } from '../observer/index' Vue.prototype.$set = se使用Vue.set或this.$set来实现页面不更新的问题:const vm = new Vue({ data: { arr: [1, 2], obj: { a: 3 } } }); // 修改数组 Vue.set(vm.$data.arr, 0, 3); vm.$set(vm.$data.arr, 0, 3); // 对象新增属性 Vue.set(vm.$data.obj, 'b', 3); vm.$set(vm.$data.obj, 'b', 3);set函数的源代码../observer/indexfunction set (target: Array<any> | Object, key: any, val: any): any { // isUndef(target):用于判断传入的target是否是undefined或null // isPrimitive(target):用于判断传入的target是否是原始数据类型 // 如果是 null 或者是 undefined 时,抛出异常 if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 如果传入了一个数组,并且传入的key是数组的有效值 if (Array.isArray(target) && isValidArrayIndex(key)) { // 用于设置数组的最大length // Math.max(target.length, key):取数组的length和key二者中的最大值 target.length = Math.max(target.length, key) // 直接调用数组身上的splice方法,因为Vue里变异了数组方法,调用会触发dep.notify() target.splice(key, 1, val) return val } // 接下来就是对象的判断了 // 如果传入的 key 在原对象中,说明已经是响应式了,直接修改即可 if (key in target && !(key in Object.prototype)) { target[key] = val return val } // 这里到最后的代码,都是对象不在原对象上,是新增的属性值 const ob = (target: any).__ob__ // 如果是Vue实例对象,或根数据对象,则抛出警告 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // 如果没有 __ob__ 表示当前 target 不是响应式的,那么直接赋值 if (!ob) { target[key] = val return val } // 剩下就是给响应式对象增加一个 key 新属性,并通知更新 defineReactive(ob.value, key, val) ob.dep.notify() return val }
2021年07月28日
643 阅读
2 评论
11 点赞
2021-07-06
Vue 二次封装组件的方法
介绍有的时候我们全局引用 UI 框架(类似于 ElementUI)的时候,有些简单的组件我们想进行二次封装,并确保能传递所需的 props 和事件,这时候就需要用到 vue 实例上的两个属性:$attrs 和$listeners。代码二次封装的组件<template> <!-- v-bind="$attrs":会将传递过来的属性属性值排放在这里,可以看成react里的{...props}展开运算符类似 v-on="$listeners":父组件绑定的@事件会传递到这里 --> <el-input v-bind="$attrs" v-on="$listeners"></el-input> </template>父组件使用这个组件<template> <HelloWorld placeholder="请输入内容111" @input="handleInput"/> </template> <script> export default { methods: { handleInput() { console.log(1); } }, components: { HelloWorld: () => import('@/components/HelloWorld.vue') } }; </script>
2021年07月06日
841 阅读
5 评论
8 点赞
2021-06-28
Vue底层判断标签的性能优化方法
在vue中,如果写div、span等正常的html标签,vue会解析成传统的html标签,但当写不是这些标签的时候,vue会认为他是一个组件,例如:<Custom></Custom>。是如何做到这种判断的呢,首先自己来实现一个这样的判断const tags = 'div,span,img,a'.split(",") function checkTag(tag) { return tags.some(item => item === tag) } console.log(checkTag('Custom')); // false console.log(checkTag('div')); // true console.log(checkTag('a')); // true这里的实现方案有很多,可以用for、some、forEach等,但是都是离不开循环,思考这样的一个问题,传入一个a,a在字符串最后一个位置,所以会循环4次来判断是否包含a,如果页面上的标签极多,甚至会有上万次的循环再来查看Vue实现这个的方式,大致源码如下const tags = 'div,span,img,a' function makeMap(str) { var map = Object.create(null); var list = str.split(','); for (var i = 0; i < list.length; i++) { map[list[i]] = true; } return function (val) { return map[val]; } } const isHtmlTag = makeMap(tags) isHtmlTag('div') isHtmlTag('Custom') isHtmlTag('a')这里可以看出,实现出一个柯里化函数,并且也是一个闭包状态。此时,在第一次调用时,会循环一次,后续不论在去判断什么标签,都不会再去循环,因为第一次的循环结果利用闭包已经存在了内存里,这就是闭包能带来的性能优化
2021年06月28日
638 阅读
4 评论
10 点赞
2021-06-28
Vue 2.X 拓展图片预览插件
Preview.jsimport ToastComponent from './Toast.vue' const Toast = {} Toast.install = Vue => { const ToastConstructor = Vue.extend(ToastComponent) const instance = new ToastConstructor() Vue.prototype.$Toast = function (options) { if (!options) { return console.error('Please Pass In The Data / 请传入内容') } if (options.constructor !== Object) { return console.error('Please Pass In The Object Format / 请传入对象格式') } options.msg = options.msg || '默认消息' options.duration = options.duration || 3000 if (document.getElementsByClassName('toast').length === 0) { instance.$mount(document.createElement('div')) document.body.appendChild(instance.$el) instance.msg = options.msg instance.visible = true setTimeout(() => { instance.visible = false document.body.removeChild(instance.$el) }, options.duration) } } } export default ToastPreview.vue<template> <transition name="bounce"> <div class="toast" v-show="visible"> <span>{{ msg }}</span> </div> </transition> </template> <script> export default { data() { return { msg: '', visible: false } } } </script> <style lang="css" scoped> .bounce-enter-active { animation: bounce-in 0.5s linear; } .bounce-leave-active { animation: bounce-in 0.5s linear reverse; } @keyframes bounce-in { 0% { transform: translate3d(-50%, 0, 0) scale(0); } 25% { transform: translate3d(-50%, 0, 0) scale(0.55); } 50% { transform: translate3d(-50%, 0, 0) scale(1); } 75% { transform: translate3d(-50%, 0, 0) scale(0.85); } 100% { transform: translate3d(-50%, 0, 0) scale(1); } } .toast { position: fixed; z-index: 5201314; top: 50%; left: 50%; transform: translate3d(-50%, 0, 0); background: rgba(0, 0, 0, 0.75); padding: 0 32px; height: 50px; line-height: 50px; color: #fff; user-select: none; border-radius: 4px; box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2); font-size: 14px; letter-spacing: 1px; } </style>
2021年06月28日
959 阅读
2 评论
16 点赞
2021-06-15
Vue面试题
MVC 和 MVVM 区别{callout color="#68bbdf"}MVC controller 控制层将数据层 model 层 的数据处理后显示在视图层 view层,同样视图层 view层 接收用户的指令也可以通过控制层 controller,作用到数据层 modelMVVM 隐藏了 controller 控制层,直接操控 View 视图层和 Model 数据层。MVC 的视图层和数据层交互需要通过控制层 controller 属于单向链接。MVVM 隐藏了控制层 controller,让视图层和数据层可以直接交互 属于双向连接{/callout}Vue的事件绑定原理{callout color="#68bbdf"}Vue中通过v-on或其语法糖@指令来给元素绑定事件并且提供了事件修饰符,基本流程是进行模板编译生成AST,生成render函数后并执行得到VNode,VNode生成真实DOM节点或者组件时候使用addEventListener方法进行事件绑定{/callout}为什么 data 是一个函数{callout color="#68bbdf"}组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据,防止变量污染{/callout}Vue 组件间通信{callout color="#68bbdf"}(1)props / $emit 适用 父子组件通信(2)$parent / $children 适用 父子组件通信(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信(4)$attrs/$listeners 适用于 隔代组件通信(5)provide / inject 适用于 隔代组件通信(6)Vuex 适用于 父子、隔代、兄弟组件通信(7)通过$refs获取组件实例{/callout}父组件如何监听到子组件的生命周期{callout color="#68bbdf"}(1)通过 $emit 触发父组件的事件// Parent.vue <Child @mounted="doSomething"/> <script> // Child.vue mounted() { this.$emit("mounted"); } </script>(2)通过hook来监听// Parent.vue <Child @hook:mounted="doSomething" ></Child>{/callout}vue-router 路由模式有几种{callout color="#68bbdf"}hash模式使用 URL hash 值来作路由。history依赖 HTML5 History APIabstract支持所有 JavaScript 运行环境,如 Node.js 服务器端如果发现没有浏览器的 API,路由会自动强制进入这个模式{/callout}Vue 数据双向绑定{callout color="#68bbdf"}实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性利用 Object.defineProperty() 对属性都加上 setter 和 getter这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者一旦数据有变动,收到通知,调用更新函数进行数据更新实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁主要的任务是订阅 Observer 中的属性值变化的消息当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher对监听器 Observer 和 订阅者 Watcher 进行统一管理。{/callout}v-if 与 v-for 为什么不建议一起使用{callout color="#68bbdf"}v-for 和 v-if 不要在同一个标签中使用,因为解析时先解析 v-for 再解析 v-if。如果遇到需要同时使用时可以考虑写成计算属性的方式{/callout}props自定义验证{callout color="#68bbdf"}props: { num: { default: 1, validator: function (value) { // 返回值为true则验证不通过,报错 return [1, 2, 3, 4, 5].indexOf(value) !== -1 } } }{/callout}watch的immediate属性{callout color="#68bbdf"}比如平时created时要请求一次数据,并且当搜索值改变,也要请求数据,我们会这么写created(){ this.getList() }, watch: { searchInputValue(){ this.getList() } }使用immediate完全可以这么写,当它为true时,会初始执行一次watch: { searchInputValue:{ handler: 'getList', immediate: true } }{/callout}computed实现传参{callout color="#68bbdf"}new Vue({ el: "#app", data() { return { params: { name: '杜恒', age: 18 } } }, computed: { total() { // 通过返回一个高阶函数 return (arg) => { return arg * 10 } } } }){/callout}vue的hook的使用{callout color="#68bbdf"}我们常用的使用定时器的方式export default{ data(){ timer:null }, mounted(){ this.timer = setInterval(()=>{ console.log('1'); },1000); } beforeDestory(){ clearInterval(this.timer); } }上面做法不好的地方在于:全局多定义一个timer变量,使用hook这么做:export default{ methods:{ fn(){ const timer = setInterval(()=>{ console.log('1'); },1000); this.$once('hook:beforeDestroy',()=>{ clearInterval(timer); }) } } }{/callout}Vue的el属性和$mount优先级{callout color="#68bbdf"}如以下的情况,el的优先级会大于$mountnew Vue({ router, store, el: '#app', render: h => h(App) }).$mount('#root'){/callout}Vue中的动态指令和参数{callout color="#68bbdf"}<template> <aButton @[someEvent]="handleSomeEvent()" :[someProps]="1000" />... </template> <script> ... data(){ return{ ... someEvent: someCondition ? "click" : "dbclick", someProps: someCondition ? "num" : "price" } }, methods: { handleSomeEvent(){ // handle some event } } </script>{/callout}相同的路由组件如何重新渲染{callout color="#68bbdf"}经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染,如果尝试在使用相同组件的路由之间进行切换,则不会发生任何变化const routes = [ { path: "/a", component: MyComponent }, { path: "/b", component: MyComponent }, ];如果想要重新渲染,就需要使用key<template> <router-view :key="$route.path"></router-view> </template>{/callout}
2021年06月15日
295 阅读
3 评论
1 点赞
2021-02-21
简单的模拟Vue替换插值表达式的功能
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"> {{ msg }}111{{ msg }} <div>{{msg}}</div> </div> <script> class Vue { constructor(options) { this.options = options /* 初始化编译dom */ this.compile() } compile() { let el = document.querySelector(this.options.el) if (!el) return console.warn('element not exist!') let childNodes = el.childNodes /* 递归编译节点 */ childNodes.length && this.compileNodes(childNodes) } compileNodes(childNodes) { /* 循环节点 */ childNodes.forEach(node => { /* 如果是文本节点,则进行替换插值表达式 */ if (node.nodeType === 3) { let reg = /\{\{\s*([^\{\{\}\}\s]+)\s*\}\}/g if (reg.test(node.textContent)) { /* $1就是插值表达式里面的属性值 */ let $1 = RegExp.$1 node.textContent = node.textContent.replace(reg, this.options.data[$1]) } } else { /* 如果不是文本节点,则进行递归,并且有子节点 */ node.childNodes.length && this.compileNodes(node.childNodes) } }) } } new Vue({ el: '#app', data: { msg: '测试数据' } }) </script> </body> </html>
2021年02月21日
260 阅读
0 评论
3 点赞
2021-02-20
Vue中解决对象内存地址一致的几种方案
在开发vue项目中,经常会遇到内存地址一样的问题,导致修改这里,另一处同时发生改变解决方案:1、利用JSON方法(极其不推荐,会丢失属性,例如函数、undefined)let obj = {} let newObj = JSON.parse(JSON.stringify(obj))2、深拷贝3、利用Object.assig方法export default { data() { return { test: {} } }, methods: { change(obj) { this.test = Object.assign({}, obj) } } }4、利用对象展开运算符let obj1 = { a: 1, b: 2 } let obj2 = {...obj1}
2021年02月20日
200 阅读
0 评论
2 点赞
2021-02-20
Vue中this.$options.data() 重置data里的数据
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="app"> <button @click="btnClick">click</button> <button @click="btnReset">reset</button> <p><input type="text" v-model="message"></p> <p>{{message}}</p> </div> <script src="./vue.js"></script> <script> let vm = new Vue({ el: "#app", data() { return { message: '' } }, methods: { btnClick() { this.message = 'bbbbbbbbbb' }, btnReset() { Object.assign(this.$data, this.$options.data.call(this)) } }, }) </script> </body> </html>
2021年02月20日
299 阅读
0 评论
3 点赞
2021-02-20
Vue解决路由重复点击报错的问题
router.js文件// 加上这个 const originalPush = Router.prototype.push; Router.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err) }
2021年02月20日
258 阅读
0 评论
2 点赞
2021-02-20
Vue限制输入框内容只能输入金额或数字
<template> <input @input="formatValue(value)" v-model="value"> </template> <script> export default = { data(){ return { value: "" } }, methods:{ // 只允许输入数字,其他一律不允许输入 formatValue(val){ this.value = this.value.replace(/[^\d]/g, "") }, // 只允许输入金额类型,最大两位小数(如:3.88) formatValue(val){ val = val.replace(/(^\s*)|(\s*$)/g, ""); if (!val) return this.value = ""; val = val.replace(/[^\d.]/g, ""); val = val.replace(/^\./g, ""); val = val .replace(".", "$#$") .replace(/\./g, "") .replace("$#$", "."); val = val.replace(/^(\-)*(\d+)\.(\d\d).*$/, "$1$2.$3"); this.value = val; }, } } </script>
2021年02月20日
684 阅读
5 评论
2 点赞
2021-02-08
Vue面包屑导航实现方案
例如想实现一个 首页 / 项目列表 / 我的项目 这样的面包屑1、首先在路由js文件里写上面包屑对应的名称router.jsexport default [ { path: "/", children: [ { path: "/todoList", component: TodoList, name: "todoList" meta: { breadName: "项目列表" } }, { path: "/todoList/mylist", component: MyList, name: "mylist" meta: { breadName: "我的项目" } }, ] } ]2、编写面包屑组件<template> <a-breadcrumb class="breadcrumb"> <a-breadcrumb-item v-for="(breadcrumb, index) in breadcrumbList" :key="index"> {{breadcrumb.meta.breadName}} </a-breadcrumb-item> </a-breadcrumb> </template> <script> export default { computed: { breadcrumbList () { // 先定义一个变量 用于存放计算出来的导航数组 let breadArr = [] // 获取当前页面的路由, 并将路由字符串截取第一个/, 然后组成数组 let path = this.$route.path.substr(1).split("/") // >>> ["todoList", mylist] // 获取所有页面的路由地址,通过this.$router.options拿到, 具体样子可以打印看看 const allRouter = this.$router.options.routes[0].children // 然后循环allRouter找到里面中符合的path的2个,将他们存起来 allRouter.forEach(_ => { // 判断path的每一项是否有allRouter里的某项 if (path.some(_item => _item === _.name)) { breadArr.push(_) } }) console.log(breadArr) // 打印下看看数据是否正确, 如果为两项, 并且每项里面都有meta内容, 则正确 // 因为这个数组里是没有 首页 这一项的, 我们自己手动添加一个就好, 此时用到unshift breadArr.unshift({ path: "/", meta: { breadName: "首页" } }) // 最后computed 需要return出去, return出去这个数组就好了 return breadArr } } } </script>
2021年02月08日
538 阅读
0 评论
7 点赞
1
2