一:vue.js简介
vue是一个响应式的前段视图层框架。
响应式:我们在编写模版代码时,仅需关注数据变化即可,数据变化,ui即会变化
视图层:类似我们接触的其他前端模版,仅仅是ui层面的内容
框架:库提供由其父代码调用的功能,而框架定义整个应用程序的设计。开发人员不调用框架,而是调用库,相反,框架以某种特定方式调用和使用代码。
1、vue.js的简单demo
例子1:
<div id='app'>
<p>Message is: {{ message }}</p>
<input v-on:input="onInput" v-bind:value="message"></input>
</div>
<script>
var app = new Vue({
el:'#idd',
data:{message},
methods:{
onInput(e) {
this.message = e.target.value;
}
}
})
</script>
例子2:
<div id='app'></div>
<script>
new Vue({
el: '#idd',
template: `<h1>hello world!</h1>`,
componts:{}
//或者引入组件,在template里使用
created() {
this.$log();
}
})
</script>
2、vue.js引入
vuejs有多种引入方式。
1)、直接引用vue.js,适合小型项目获部分使用vue
引入全部vuejs,运行时编译及渲染;引入部分vuejs,仅引入渲染部分
2)使用vue-cli工程化启动整个vue项目
二、vuejs细节
{{}}是模版中用于动态显示数据的,它接受一个表达式,这里注意一下表达式和语句的区别。ifelse或者一行表达式后面跟着分号就是一个语句,一个方法,一个变量就是表达式。
1、标签中的属性
v-bind:我们能将data中的值绑定到当前属性中,可简写为:
v-on:能够绑定实例中配置的事件,可简写为@
v-for:列表级渲染,迭代渲染所有子元素
v-if/v-else/v-show:控制子元素视图显隐
v-model:应用于表单,创建与元素的双向绑定,v-model内部绑定了input方法
v-html:将最终的值结果渲染为html
v-text:等同于直接在文本处使用{{}}
2、 a t t r s , attrs, attrs,listeners,inheritAttrs, s l o t s , slots, slots,props
a t t r s : 包含了父作用域中不被认为 ( 且不预期为 ) p r o p s 的特性绑定 ( c l a s s 和 s t y l e 除外 ) 。当一个组件没有声明任何 p r o p s 时,这里会包含所有父作用域的绑定 ( c l a s s 和 s t y l e 除外 ) ,并且可以通过 v − b i n d = ” attrs: 包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 props 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=” attrs:包含了父作用域中不被认为(且不预期为)props的特性绑定(class和style除外)。当一个组件没有声明任何props时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v−bind=”attrs” 传入内部组件——即它适合在中间层组件,将接收的父组件绑定的所有除了自己的props声明的属性之外的属性,然后传递给子组件。
l i s t e n e r s : 包含了父作用域中的 ( 不含 . n a t i v e 修饰器的 ) v − o n 事件监听器。它可以通过 v − o n = ” listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=” listeners:包含了父作用域中的(不含.native修饰器的)v−on事件监听器。它可以通过v−on=”listeners” 传入内部组件——和attrs类似。
inheritAttrs:
默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例属性 $attrs 可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。
inheritAttrs:默认值 true,继承所有的父组件属性(除 props 的特定绑定)作为普通的HTML特性应用在子组件的根元素上,如果你不希望组件的根元素继承特性设置 inheritAttrs: false ,但是 class 属性会继承。(简单的说,inheritAttrs:true 继承除 props 之外的所有属性;inheritAttrs:false 只继承 class style 属性)
上述特性的使用完全可以降低在不使用Vuex以及事件总线的情况下,组件跨级props以及事件传递的复杂度。
inheritAttrs例子:
<script>
<template>
<div class="parent">
<child-component aaa="1111"></child-component>
</div>
</template>
<script>
import ChildComponent from './child'
export default {
components: {
ChildComponent
}
}
</script>
<script>
<template>
<div class="child">子组件</div>
</template>
<script>
export default {
inheritAttrs: true,
mounted() {
console.log('this.$attrs', this.$attrs)
// 当inheritAttrs为true时:输出this.$attrs, {a: "1111"}
// 当inheritAttrs为false时:输输出this.$attrs, {a: "1111"}
// 所以可以看出来$attrs值与inheritAttrs无关
}
}
</script>
元素结果:如果自组件props接受了aaa则不会有对应这句话:默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。如果你不希望组件的根元素继承特性设置 inheritAttrs: false ,但是 class 属性会继承。
例子
a
t
t
r
s
和
attrs和
attrs和listeners:
a组件
<template>
<div id="app">
<!-- 此处监听了两个事件,可以在B组件或者C组件中直接触发 -->
<child1 :pchild1="child1" :pchild2="child2" :pchild3="child3" @method1="onMethod1" @method2="onMethod2"></child1>
</div>
</template>
<script>
import Child1 from "./Child1.vue";
export default {
data() {
return {
child1:'1',
child2: 2,
child3:{
name:'child3'
}
};
},
components: { Child1 },
methods: {
onMethod1(msg1) {
console.log(`${msg1} running`);
},
onMethod2(msg2) {
console.log(`${msg2} running`);
},
},
};
</script>
child1:
<template>
<div class="child-1">
<h2>in child1</h2>
<p>props: {{ pchild1 }}</p>
<p>$attrs: {{ $attrs }}</p>
<hr/>
<!-- 通过 v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) -->
<!-- C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 -->
<child2 v-bind="$attrs" v-on="$listeners"></child2>
</div>
</template>
<script>
import Child2 from "./Child2.vue";
export default {
data() {
return {
child1:'child1'
};
},
components: { Child2 },
props: {
pchild1:{
type:String
}
},
inheritAttrs: false,
mounted() {
this.$emit("method1",this.child1);
},
};
</script>
child2:
<template>
<div class="child-2">
<h2>in child2:</h2>
<p>props: {{ pChild2 }}</p>
<p>$attrs: {{ $attrs }}</p>
<p>pchild3Name: {{ $attrs.pchild3.name }}</p>
<hr/>
</div>
</template>
<script>
export default {
data() {
return {
child2:'child2'
};
},
props: {
pChild2:{//注意这是大写,父组件传递过来的是小写,所以这里它没有值,且attr里有pchild2属性
type:String,
}
},
inheritAttrs: false,
mounted() {
this.$emit("method2",this.child2);
},
};
</script>
结果就是子组件里的
a
t
t
r
s
会获取父组件绑定的属性,子组件设置了
p
r
o
p
s
就会剔除这个
p
r
o
p
s
的属性,
attrs会获取父组件绑定的属性,子组件设置了props就会剔除这个props的属性,
attrs会获取父组件绑定的属性,子组件设置了props就会剔除这个props的属性,attrs里不会包含这个属性。
l
i
s
t
e
n
e
r
s
是获取父组件绑定的所有监听方法。比如
c
h
i
l
d
1
自己加属性进行绑定和加监听事件,
c
h
i
l
d
2
的
listeners是获取父组件绑定的所有监听方法。比如child1自己加属性进行绑定和加监听事件,child2的
listeners是获取父组件绑定的所有监听方法。比如child1自己加属性进行绑定和加监听事件,child2的attr和
l
i
s
t
e
n
e
r
是包含祖父组件和
c
h
i
l
d
1
组件的属性和监听方法,因为
c
h
i
l
d
1
进行
b
i
n
d
和
v
−
o
n
了。所以中间组件要记得
v
−
b
i
n
d
=
"
listener是包含祖父组件和child1组件的属性和监听方法,因为child1进行bind和v-on了。所以中间组件要记得v-bind="
listener是包含祖父组件和child1组件的属性和监听方法,因为child1进行bind和v−on了。所以中间组件要记得v−bind="attrs" v-on="$listeners"才能传递父组件的属性和方法给孙子组件。
s l o t s : t h i s . slots:this. slots:this.slot.default是子组件获取默认插槽的节点内容,如果是具名插槽,则是this.$slot.xxx来获取,这个在render函数里获取比较方便
$props:子组件内部接受的所有在父组件绑定的属性除了class,style除外。
3、compute属性
⼤部分时候,我们在模版也就是 html 中写表达式会让模版变得复杂,所以我们可以通过计算属性来简化我们的模版。
但⼤多数时候,我们也可以通过定义⽅法的形式来直接在表达式内调⽤函数。不过计算属性也可以模拟出使⽤参数的形式,计算属性和方法相比:
1)computed属性有惰性依赖,如果它内部依赖的值没有变化,它就不会进行计算,而方法只要ui进行渲染就会重新触发计算,即便变化的不是自己的值
2)computed不可以传值进去,function可以
4、组件
区分有状态组件与⽆状态组件
⼤部分时候,我们需要区分⼀些具有副作⽤的组件,例如某些组件我们需要发送 ajax 请求之后渲染⼀些数据,这时候我们就需要将这部分数据内容进⾏⼀个区分,推荐做法将 UI 部分渲染于⼦组件中,做⼀个只通过传⼊数据渲染的⽆状态组件,⽽在有副作⽤组件中维护所有的数据。
三:vue生命周期
创建:beforeCreate->create->beforeMount->mounted->beforeDestory->destoryed
data数据改变时:beforeUpdate->updated
activated:
当页面重新显示的时候,执行。搭配 keep-alive、localStrage 和临时变量做页面性能优化。
deactivated:
当页面即将被隐藏或替换成其他页面时,执行。可以用来解绑在 activated 上绑定的全局事件。
注意:只有当组件在 <keep-alive>
内被切换,才会有activated 和deactivated 这两个钩子函数。keep-alive是用来缓存组件的,即组件在第一次进入进行创建后再次切换路由进出不会进行创建销毁,只会触发activated和deactivated两个钩子函数,它还提供include和exclude属性配置对应的组件名称(可以是export下的配置的name属性)即可只保留该组件进行缓存和对该组件不进行缓存的操作。
父组件和子组件的创建:
父beforeCreate->父create->父beforeMount->子beforeCreate->子create->子beforeMount->子mounted->父mounted;
销毁:父beforeDestory->子beforeDestory->子destoryed->父destoryed
update:父beforeUpdate->子beforeUpdate->子updated->父updated
vue中的图片动态引入
1、 使用网络上的图片资源
data() {
return {
imgSrc: 'http://easy-stage.longhu.net/files/images/7f458e55f6954078aa8e8efb2c45cc40.jpg'
}
}
2、使用import导入本地资源
import imgSrc from '../../images/web_bg.png'
export default {
data() {
return {
imgSrc: imgSrc
}
}
}
3、使用 require 导入
data() {
return {
imgSrc: require('../../images/web_bg.png')
}
}
require是运行时加载模块,但import命令会被javascript引擎静态分析,先于模块内其他模块执行,做不到运行时加载,因此为了实现类似于require的动态加载,就提出了实现一个import()函数方法
// example
let imgUrl = '';
// 与require参数类似,不能通过纯参数的方式引入模块。正确的引入方式可查看以上require的引入方式
import('../assets/tree/tree.png').then(res => {
imgUrl = res;
});
require不能引入变量名,比如以下使用出错
let imgUrlStr = '../images/a.png';
let imgUrl = require(imgUrlStr);
因为动态添加src被当做静态资源处理了,而被编译过后的静态路径无法正确的引入资源,所以要加上require。
我们发现,使用静态的地址去引入一张图片,图片的路径和图片的名称已经发生了改变,并且编译后过后的静态地址是可以成功的引入资源的。这是因为,在默认情况下,src目录下面的所有文件都会被打包,src下面的图片也会被打包在新的文件夹下并生成新的文件名。编译过后的静态地址引入的是打包过后的图片地址,从而可以正确的引用资源。
动态添加的src,被编译过后的静态路径为什么无法正确的引入资源?
因为动态的添加的src编译过后的地址,与图片资源编译过后的资源地址不一致, 导致无法正确的引入资源
vue项目最终会被打包成一个dist目录,那么是什么帮我们完成这个打包的呢,没错,就是webpack。在vue项目中的引入一张图片的时候,细心的同学会发现,有的时候,浏览器上显示图片地址是一个base64,有的时候,是一个被编译过后的文件地址。
module.exports = {
// 使用configureWebpack对象,下面可以直接按照webpack中的写法进行编写
// 编写的内容,最终会被webpack-merge插件合并到webpack.config.js主配置文件中
configureWebpack: {
module: {
rules: [
{
test: /.(png|jpe?g|gif|webp|avif)(?.*)?$/,
type: ‘asset’,
parser: {
dataUrlCondition: {
// 这里我将默认的大小限制改成6k。
// 当图片小于6k时候,使用base64引入图片;大于6k时,打包到dist目录下再进行引入
maxSize: 1024 * 6
}
}
}
]
}
}
}
上图就是vue中webpack默认的图片打包规则。设置 type: ‘asset’,默认的,对于小于8k的图片,会将图片转成base64 直接插入图片,不会再在dist目录生成新图片。对于大于8k的图片,会打包进dist目录,之后将新图片地址返回给src。
我们现在知道vue最终是通过webpack打包,并且会在webpack配置文件中编写一系列打包规则。而webpack中的打包规则,针对的其实是一个一个模块,换而言之webpack只会对模块进行打包。那webpack怎么将图片当成一个模块呢,这就要用到我们的正主require。
当我们使用require方法引入一张图片的时候,webpack会将这张图片当成一个模块,并根据配置文件中的规则进行打包。我们可以将require当成一个桥梁,使用了require方法引入的资源,该资源就会当成模块并根据配置文件进行打包,并返回最终的打包结果。
调用require方法引入一张图片之后发生了什么
1.如果这张图片小于项目中设置的资源限制大小,则会返回图片的base64插入到require方法的调用处
2.如果这张图片大于项目中设置的资源限制大小,则会将这个图片编译成一个新的图片资源。require方法返回新的图片资源路径及文件名。
为什么加上require能正确的引入资源
因为通过require方法拿到的文件地址,是资源文件编译过后的文件地址(dist下生成的文件或base64文件),因此可以找对应的文件,从而成功引入资源。
因为动态添加的src,编译过后的文件地址和被编译过后的资源文件地址不一致,从而无法正确引入资源。而使用require,返回的就是资源文件被编译后的文件地址,从而可以正确的引入资源。
5. 问题3中,静态的引入一张图片,没有使用require,为什么返回的依然是编译过后的文件地址?
答:在webpack编译的vue文件的时候,遇见src等属性会默认的使用require引入资源路径。引用vue-cli官方的一段原话
*当你在 JavaScript、CSS 或 .vue 文件中使用相对路径 (必须以 . 开头) 引用一个静态资源时,该资源将会被包含进入 webpack 的依赖图中。在其编译过程中,所有诸如 、background: url(…) 和 CSS @import 的资源 URL 都会被解析为一个模块依赖。
例如,url(./image.png) 会被翻译为 require(‘./image.png’)。
按照问题5中所说,那么动态添加src的时候也会使用require引入,为什么src编译过后的地址,与图片资源编译过后的资源地址不一致
答:因为动态引入一张图片的时候,src后面的属性值,实际上是一个变量。webpack会根据v-bind指令去解析src后面的属性值。并不会通过reuqire引入资源路径。这也是为什么需要手动的添加require。
7.据说public下面的文件不会被编译,那我们使用静态路径去引入资源的时候,也会默认的使用require引入吗?
官方的原文是这样子的:
任何放置在 public 文件夹的静态资源都会被简单的复制,而不经过 webpack。你需要通过绝对路径来引用它们。
答:不会,使用require引入资源的前提的该资源是webpack解析的模块,而public下的文件压根就不会走编译,也就不会使用到require。
8.为什么使用public下的资源一定要绝对路径
答:因为虽然public文件不会被编译,但是src下的文件都会被编译。由于引入的是public下的资源,不会走require,会直接返回代码中的定义的文件地址,该地址无法在编译后的文件目录(dist目录)下找到对应的文件,会导致引入资源失败。
9.上文件中提到的webpack,为什么引入资源的时候要有base64和打包到dist目录下两种的方式,全部打包到的dist目录下,他不香吗?
答:为了减少http请求。页面中通过路径引入的图片,实际上都会向服务器发送一个请求拿到这张图片。对于资源较小的文件,设置成base64,既可以减少请求,也不会影响到页面的加载性能。