前言:vue2所用到的技术栈
1:vue/cli脚手架
2:vue2.0
a:创建vue项目(vue create 项目名称)
3:vant(移动端组件库)、elementul(pc端组件库)
4:vue-router--3(使用3的版本)
5:vuex--3(使用vuex3的版本)
一:vue基础
1:指令(常用)
a:v-model
b:v-text
c:v-html
d:v-bind / :
e:v-on / @
f:v-if
g:v-show
h:v-for
i:v-slot / #
j:{{}}
2:选项
a:data
b:computed
c:watch
d:methods
export default {
data(){
return {
}
},
computed:{
},
watch:{
},
methods:{
}
}
3:自定义组件(局部/全局)
- components(局部)component(全局)
- 例:
-
//所有组件导入必须注册 import vue from 'vue' import helloworld from './路径' //从别处导入自定义组件 //全局注册 vueponent('helloworld',helloworld) //局部注册 export default{ data(){ return { } }, components:{ helloworld //注册 } }
4:过度动画(还待研究)
5:混入
mixins({},{})(局部)、mixin({选项})(全局)
import vue from 'vue'
vue.mixin({
data(){
return {
}
},
compputed:{
}
})
6:过滤器
filters(局部)/ filter(全局)
Vue.filter('rmb', val=>{
// do something
return '¥'+Number(val).toFixed(2)
})
const app = new Vue({
el: '#app',
data: {
price: 99.9,
num: 202210923920
},
filters: {
'qf' (val) {
return 'QF:'+val
},
'sz' (val) {
return 'SZ:' + val
}
}
})
7:自定义指令
directives(局部) / directive(全局)
Vue.directive('form', {
bind (el, binding, vnode) {
// console.log('---el', el)
// console.log('---binding', binding)
// console.log('---vnode', vnode)
const { context } = vnode
const { expression, modifiers } = binding
const { lazy, trim, number, upper } = modifiers
// v-bind:value
el.value = context[expression]
// v-on:input
el.addEventListener(lazy?'blur':'input', function(ev){
// console.log('---事件', ev.target.value)
let val = ev.target.value
// if (trim) val = val.trim()
// if (number) val = Number(val)
// if (upper) val = val.toUpperCase()
context[expression] = val // 必须使用上下文来双向绑定
})
},
update (el, binding, vnode) {
el.value = vnode.context[binding.expression]
}
})
const app = new Vue({
el: '#app',
data: {
cc: 'green',
name: '张三'
},
directives: {
// fn写法,等价于 bind + update这两个钩子
'color': function (el, binding, vnode) {
// console.log('---el', el)
// console.log('---binding', binding)
// console.log('---vnode', vnode)
el.style.color = binding.value
},
// 'color': {
// bind (el, binding) {
// el.style.color = binding.value
// },
// update (el, binding) {
// el.style.color = binding.value
// },
// inserted () {
// console.log('---inserted')
// }
// }
}
})
8:插件
const QfPlugin = {
install (Vue) {
Vue.mixin({ data() { return { version: 'v2' }}})
Vueponent('qf-button', {
template: `
<div>我的按钮</div>
`
})
Vue.filter('rmb', val=>('¥'+val))
Vue.directive('color', function(el,binding){ el.style.color = binding.value})
Vue.prototype.$ajax= function(url,method,data) { console.log('---调接口') }
}
}
const SzPlugin = function (Vue) {
Vue.prototype.$msg = '来了就是深圳打工人'
}
Vue.use(QfPlugin) // 注册插件(调用插件上的install()方法,并传入Vue实参)
Vue.use(SzPlugin) // 如果插件的类型是Function,直接调用并传入Vue实参
const app = new Vue({
mounted () {
console.log('--version', this.version)
console.log('--$ajax', this.$ajax)
console.log('--$msg', this.$msg)
}
})
app.$mount('#app')
9:两个重要API
// 1、Vue.nextTick() / this.$nextTick()
// 事实:set操作,代码确实是同步的,但是set行为是异步的;set操00作修改声明变量,触发re-render生成新的虚拟DOM,进一步执行diff运算,找到脏节点集合,交给Vue背后的更新队列去执行循环更新。
// 什么是nextTick?在更新队列中每一个更新任务都是一个更新单元,nextTick表示下一个更新单元(更新周期)。
// 作用:这么描述一个场景,我们set操作data(更新DOM),你希望访问这个DOM的最新状态时,使用this.$nextTick(handler)。
// nextTick() 是某种程度(场景)下,可以使用 updated() 来替代;原则上建议使用 nextTick()。
// 2、this.$forceUpdate()
// 事实:Vue响应式是有缺陷的,什么缺陷?在复杂的Vue应用中,如果声明式变量是引用数据类型,当你set操作这些复杂的引用数据类型时,视图不更新。解决方案,set操作完成后,立即调用 this.$forceUpdate()强调更新(强制re-render)。
// 有时候,this.$forceUpdate()也无法解决上述问题,对set操作的变量进行一行深复制。
// 3、面试题
// 谈一谈你对 Vue.nextTick() 的理解?有什么用?(在nextTick访问最新的DOM)
// nextTick() 和 updated() 的区别 (前者只是表示一个更新单元已完成,后者是生命周期钩子表示整个页面更新完成)
// Vue响应式有没有缺陷呢?有什么缺陷?遇到这种问题你会怎么办?
// 什么是深拷贝?什么是浅拷贝?有哪些深拷贝的方法?让你手写一个深拷贝方法,你会怎么写?
const app = new Vue({
el: '#app',
data () {
return {
num: 1,
content: '',
info: {
user: {
children: [
{ name: '张三', age: 10 }
]
}
}
}
},
mounted () {
// 模拟调接口
setTimeout(()=>{
const res = '<div><a id="a" href="https://baidu">百度</a></div>'
this.content = res
// 上面这个set行为异步更新完成后,执行nextTick的回调
Vue.nextTick(()=>{
document.getElementById('a').style.color = 'black'
})
}, 200)
},
methods: {
add () {
this.num++
this.num++
this.$nextTick(()=>{
const tt = document.getElementById('h1').innerText
console.log('--tt', tt)
})
},
change () {
this.info.user.children[0].age++
// this.$forceUpdate()
// this.info = JSON.parse(JSON.stringify(this.info))
// this.info = _.cloneDeep(this.info)
}
}
})
10:组件通信
// 通信是组件或模块之间的数据交互。多重通信就形成了数据流,数据流管理的优劣决定了产品能否上线。数据流(通信)越混乱,代码越难维护。
6
// 2、Vue中有哪些常见的通信方案?
// (*1*)父子组件通信:父传子使用自定义属性(props),子传父使用自定义事件($emit())。
// (*2*)状态提升:当兄弟组件之间需要共享数据时,我们通常的做法是把这个数据定义它们的共同的父组件中,再通过自定义属性实现数据共享。
// (*3*)provide/inject:这是在组件树中,自上而下的一种数据通信方案,也就是说只能父级组件中向后代组件传递。需要注意的是,当provide提供动态数据(声明式变量)时,动态数据发生变化,后代组件们不会自动更新。这是为什么呢?你自己从生命周期流程的角度去思考。
// (4)ref通信:ref是Vue内置的一个属性,每一个HTML元素或组件都有这个属性;ref作用在HTML元素上得到DOM实例,ref作用在组件上得到组件实例。使用ref访问组件实例,进一步可以访问组件中的数据和方法。(说明:ref是一种快速的DOM的访问方式,当然ref也可作用在组件上得到组件实例。这些ref得到的DOM实例或组件实例,使用this.$refs来访问它们。ref尽量少用,除非某些难搞的需求。)
// (*5*)插槽通信:借助<slot>组件实现从子组件向父组件传递数据,借助this.$slots访问父组件中的插槽实例。(在自定义组件中使用this.$slots访问父组件给的插槽实例;在父组件插槽中使用#default='scoped'访问子组件<slot>回传的数据。这种通信在组件库中、工作中,非常常见!)
// (6)$parent/$children:借助$parent/$children可以实现,】件实例,可以做到在组件中随意穿梭。($parent表示的是当前组件的父组件实例,$children表示的是当前组件的子组件们。)
// (7)$attrs/$listeners:借助$attrs可访问父组件传递过来的自定义属性(除了class和style外),借助$listenrs可以访问父组件给的自定义事件。在某些场景下,$attrs/$listeners可以替代props/$emit()这种通用的通信方案。
// (*8*)事件总线:借助于Vue内置的事件系统($on/$emit/$off/$once)实现“订阅-发布”式的通信,这种通信方式是一种与组件层级无关的“一对多”的通信。(工作中很少用,一些特殊的Vue项目才用得到事件总线。)
// (9)Vuex通信:这是Vue架构中终极的通信方案,也是Vue架构中用的最多的一种通信方案。
Vueponent('qf-child-a', {
template: `
<div>
<div>大哥 {{num}}</div>
<button @click='$emit("add", 1)'>加1</button>
</div>
`,
props: {
num: { type: Number, default: 0 }
}
})
Vueponent('qf-child-b', {
template: `
<div>
<div>二哥 {{num}}</div>
<button @click='$emit("sub", 2)'>减2</button>
</div>
`,
props: {
num: { type: Number, default: 0 }
}
})
Vueponent('qf-child-c', {
template: `
<div>
<div>三哥 - {{msg}} - {{num}}</div>
<span v-for='i in list' v-text='i'></span>
</div>
`,
// 从组件树上下文中注入provide数据
inject: ['msg', 'list', 'num']
})
Vueponent('qf-child-d', {
template: `
<div>
<div>四哥:{{age}}</div>
</div>
`,
data () {
return {
age: 10
}
},
methods: {
changeAge (arg) {
this.age = arg || 10
}
}
})
Vueponent('qf-child-e', {
template:`
<div>
<div>五哥</div>
<slot name='default' foo='你好' bar='世界'>
<div>我的房子</div>
</slot>
<slot name='car' :car='car'>
<div>我的车位</div>
</slot>
</div>
`,
data () {
const cars = ['劳斯莱斯','奔驰','宝马']
const val = cars[Math.floor(Math.random()*3)]
return {
car: val
}
},
mounted () {
// 在子组件中,使用this.$slots来访问父组件给的插槽内容
console.log('--slots', this.$slots)
}
})
Vueponent('qf-child-f', {
template: `
<div>
<div>六哥 {{num}}</div>
</div>
`,
data () {
return {
num: 1
}
},
methods: {
add () {
this.num++
}
}
})
Vueponent('qf-child-g', {
template: `
<div class='page'>
<div>七哥</div>
<span v-for='i in pageArr' v-text='i' :class='{on:$attrs.page===i}' @click='$listeners.change(i)'></span>
</div>
`,
mounted () {
console.log('g---$attrs', this.$attrs)
console.log('g---$listeners', this.$listeners)
},
computed: {
pageArr () {
const p = this.$attrs.page
if (p<=3) return [1,2,3,4,5]
else return [p-2,p-1,p,p+1,p+2]
}
}
})
const app = new Vue({
el: '#app',
data () {
// do something
return {
num: 1,
page: 1
}
},
// 从当前组件节点开始,向后代组件们传递数据
// provide: {
// msg: '你好',
// list: [1,2,3,4]
// },
provide () {
// do something
return {
msg: '你好',
list: [1,2,3,4], // 静态数据
num: this.num // 动态数据
}
},
methods: {
handle () {
console.log('---refs', this.$refs)
this.$refs.box.style.color = 'red'
this.$refs.dd.changeAge(20)
},
changePage (arg) {
this.page = arg || 1
}
},
mounted () {
console.log('app---$parent', this.$parent)
console.log('app---$children[5]', this.$children[5])
}
})
// 1、事件总线 const bus = new Vue()
// bus.$on('频道', callback) 监听一个“频道”
// bus.$emit('频道', '消息') 向指定“频道”上发送消息
// bus.$off() 取消订阅某个“频道”
// bus.$once() 对某个“频道”只监听一次
// 2、什么是“订阅-发布”模式?
// 这里的事件总线就是一种“订阅-发布”模式;Vue源码中的Dep类也是一种“订阅-发布”模式。
// “订阅-发布”模式,也叫做“观察者模式”。
const bus = new Vue() // 事件总线(事件系统)
Vueponent('qf-teacher', {
template: `
<div>
<h2>老师在线</h2>
<input type="text" v-model='msg' @keyup.enter='send' />
<button @click='send'>发送</button>
<div v-html='content'></div>
</div>
`,
data () {
return {
msg: '',
content: ''
}
},
mounted () {
bus.$on('qfstu', msg => {
this.content += `<div>学生说:${msg}</div>`
})
},
methods: {
send () {
// 使用事件总线向 qf 频道上发送消息
bus.$emit('qf', this.msg)
this.msg = ''
}
}
})
Vueponent('qf-student', {
template: `
<div>
<h2>学生在线</h2>
<input type="text" v-model='msg' @keyup.enter='send' />
<button @click='send'>发送</button>
<div v-html='content'></div>
</div>
`,
data () {
return {
msg: '',
content: ''
}
},
mounted () {
// 使用事件总线,监听qf这个频道
bus.$on('qf', msg=>{
console.log('student--qf频道上有消息来了', msg)
this.content += `<div class='row'>老师说:${msg}</div>`
})
},
methods: {
send () {
bus.$emit('qfstu', this.msg)
this.msg = ''
}
}
})
const app = new Vue({
el: '#app'
})
11:生命周期
创建阶段:beforeCreate、created(**)
挂载阶段:beforeMount、mounted(**)
更新阶段:beforeUpdate、updated
销毁阶段:beforeDestroy(**)、destroyed
二:vue-router
-
- 如何在脚手架环境中集成Vue路由系统?(vue(2) + vue-router(3))
- 第一步:安装路由v3版本,注册路由
- cnpm i vue-router@3.5.4 -S // @用于指定版本、-S表示安装成功后把这个包记录在package.json的“dependencies”中。
- 新建src/router.js文件,注册路由Vue.use(VueRouter)
- 第二步:创建路由实例、定义路由规则,并在main.js挂载路由系统
- export default new VueRouter({mode, routes:[]})
- 在main.js挂载路由 new Vue({ router })
- 第三步:在合适的位置放置一个视图容器和菜单
- 在App.vue的视图中放置一个 <router-view>显示url匹配成功的组件。
- 在App.vue的视图中使用 <router-link>制作菜单,点击跳转url。
-
路由模式
-
hash路由:#,背后监听onhashchange事件实现,部署上线不会出现404
-
history路由:背后基于history api 实现,部署上线会出现404问题
-
-
全局组件
-
<router-link>
-
to属性跳转目标
-
tag属性用于最终渲染什么标签(默认时a标签)
-
-
<router-view>
-
name属性用于制定命令视图
-
-
-
两个内置API
-
$route:表示路由信息
-
this.$route.fullPath
-
this.$router.query
-
this.$route.params
-
this.$router.meta
-
-
$router:表示路由跳转
-
this.$router.push():路由跳转
-
this.$router.replace():向前跳转
-
this.$router.back():返回上一级
-
-
-
路由跳转
-
声明式跳转
-
<router-link>方式跳转
-
-
编程时跳转
-
$router方式跳转
-
-
-
路由传参
-
query传参:在跳转路由的url后面用?a=1&b=2&c=3这种方式传参,在另一个组件中使用this.$route.query接收。
-
动态路由传参:像这样 `{path: '/good/:id', component }`定义路由规则,在这条动态路由规则对应的组件中使用this.$route.params接收,或者开启props:true后使用 props选项来接收。
-
-
命名视图与命名路由
-
所谓的命名视图,意思是给<router-view>加一个name属性。
-
所谓的命名路由,意思是给{path,component}路由规则取一个名字。
-
-
两个优化:路由懒加载、重定向与别名
-
路由懒加载:当一个SPA应用程序中的页面足够多,我们需要根据路由系统进行按需加载组件(而不是一次性加载所有组件),该怎么实现呢?使用路由懒加载(背后原理是Webpack代码分割技术、Vue异步组件)。路由懒加载,是一种性能优化方案。
-
重定向与别名:当用户访问一个未定义的路由时,给一个重定向(跳转到另一个已定义的路由上),这是一种用户体验的优化。重定向规则,一般要放在路由规则的最后面。什么是别名?别名是path的简写,可以用于路由访问;什么时候需要用到别名?当path比较复杂时,需要给它设计一个别名。
-
-
两个难点:嵌套视图(嵌套路由)、导航守卫(路由元信息)
-
嵌套视图(嵌套路由):当我们设计类似知乎官网那样的一级菜单、二级菜单时,就要用到嵌套视图。所谓“嵌套视图”,从组件树的角度来讲,<router-view>所显示的组件的内部还有<router-view>;从路由规则的角度来讲,{path,component,children}带有children这个属性;从产品设计的角度来讲,一级菜单对应的页面中还有二级菜单。
-
导般守卫:在router实例对象上有三个重要的全局钩子(beforeEach、beforeResolve、afterEach),每次url发生变化时,都会触发这三个钩子按顺序执行。那么以后我可以在这些钩子编写验证逻辑,如果验证通过就放你过去,你就可以正常访问你想访问的页面;如果验证失败,就阻止你访问目标页面,这就实现“守卫”的效用了。在路由中,使用导航守卫和路由元信息,可以做鉴权、还可以做权限设计。
-
三:vuex
-
如何在脚手架环境中集成Vuex状态管理?
- 第一步:安装vuex指定版本,并注册Vue.use(Vuex) / cnpm i vuex@3.6.2 -S
- 第二步:创建store实例{五个概念}并抛出,在main.js挂载store
- 第三步:在组件中使用this.$store/四个map方法来使用store或走数据流程
-
如何安装devtools?devtools有什么用?(用于调试组件树、用于调试Vuex数据流)
-
关于axios使用
- 为了调接口,我们推荐使用axios这个HTTP工具(建议在npm上搜索axios,阅读一下它的文档)
- 为什么要使用axios?(axios是基于Promise的、在Web浏览器和Node应用中都可以使用)
- 如何使用axios?第一步:安装并封装(拦截器);第二步:使用封装过的axios实例进行调接口。
-
创建store时要用的五个概念(state/getters/mutations/actions/modules)
-
state: {} 用于定义可被组件共享数据,是具有响应式的;在组件中使用this.$store.state来访问它们。
-
getters: {fn} 用于计算state,相当于Vue的计算属性,当state发生变化时getters方法自动自动重新计算;在组件中使用this.$store.getters来访问它们。
-
mutations: {fn} 专门用于修改state的,所以mutations方法是这样fn(state,payload)定义的;mutations方法在actions中或组件中使用,使用$storemit('mutations方法',payload)来触发。
-
actions: {fn} 专门用于调接口的,所以actions方法是这样fn(store,payload)定义的;在组件中使用this.$store.dispatch('actions方法', payload)。
-
modules: {子store} 是一个Vuex架构层面的概念,用于拆分子store。大家在拆分子store务必在子store中使用namespaced:true开启命名空间。
-
-
在组件中推荐使用四个map*方法来访问store数据或操作store。
-
mapState/mapGetters,必须写在computed计算属性中,用于访问state/getters数据。映射进来后,就可以用this来访问这些数据了。
-
mapActions/mapMutations 必须写在methods选项中,用于访问mutations/actions方法。映射进来后,可以用this调用这些方法。
-
它们的语法是相同的:map*('命名空间', ['k1', 'k2'])
-
-
使用Vuex的几个原则(*)
-
原则1:只要使用Vuex一定要拆分store,拆分store后在根store上不要再使用state/mutations/actions
-
原则2:在子store务必开启命名空间namespaced:true。
-
原则3:在组件中尽可能不要使用$store,建议使用四个map*方法。
-
更多推荐
vue2项目详解技术点
发布评论