vue官网称vue.js是渐进式的JavaScript框架,但什么是渐进式?什么是框架呢?
在项目的开发中,起初是一个简单的demo,使用vue的基础知识就足够了。
但随着项目的开发,页面会越来越多,这时就需要使用vue-router(路由)来管理页面,从而实现组件化开发。
后期数据会逐渐增多,这时就可以使用vuex(状态管理工具)来管理数据。
库(Lib):一系列函数的集合,需要实现某个功能,由开发人员去调取。类似于自己购买配件,需要自己去组装成一台台式电脑之后才能使用。
框架(Framework):一套完整的解决方案,框架中制定了一套规则,使用框架的时候,只需要按照规则,把代码放到合适的地方,然后框架会在合适的时机,主动调用开发人员的代码。类似于购买了笔记本电脑,只需要按照它给定的方式去使用。
二者核心的区别在于:控制反转。库的控制权在开发人员,但框架的控制权在于框架。
M:model(数据层)专门用于操作数据
V:view(视图层)相对于前端来说就是页面
C:controller(控制层)是数据层与视图层沟通的桥梁,用于处理逻辑业务
M:model(数据层)
V:view(视图层)
VM:view model(视图模型)
核心 : M <===> VM <===> V
vue的数据双向绑定(v-model) 是通过 数据劫持
Object.defineProperty() 来实现的,因此本质上vue是单向数据流
。
注意 :Object.defineProperty() 是 es5 提出来的一个 无法 shim(兼容) 的特性 , 无法兼容ie8以下。
<input type="text" id="txt" />
let obj = {}
let temp
var txt = document.querySelector("#txt")
//V→M
txt.oninput =>{
obj.name = this.value
}
// M→V
// 参数1 : 要给哪个对象设置属性
// 参数2 : 给对象设置什么属性
// 参数3 : 属性的修饰符
Object.defineProperty(obj,'name',{
set(newVal){
temp = newVal
txt.value = newVal
},
get(){
return temp
}
})
指令是HTML标签上特殊的属性,添加额外的功能。vue的指令统一是‘v-’开头
作用:实现数据的双向绑定。
使用场景:只能用于表单元素(文本框、单选框、多选框、文本域、下拉框等)
注意:使用在不同的表单元素,绑定的类型也不一样。例如:文本框是文本内容,单选框绑定的是状态(false/true)
作用:展示数据
用法类似于innerText与innerHtml,v-text不识别标签,v-html识别标签。与插值表达式实现的效果一样。
作用:动态显示一个属性值
写法:
<div v-bind:title="msg">嗯哼div>
<div :title="msg">嗯哼div>
运用
<style>
.blue{color:blue}
.fz{font-size:24px}
style>
<div id="app">
<div :class="{blue:isBlue,fz:isFz}">嗯哼div>
div>
<script>
const vm = new Vue({
el:"#app",
data:{
isBlue:true,
isFz:false
}
})
script>
v-model | v-bind | |
---|---|---|
写法 | |
|
使用场景 | 表单元素 | 任何元素的属性 |
方向 | 双向数据绑定 | 单向M→V |
作用:注册/绑定事件
写法:
<button v-on:click="fn">嗯哼button>
<button @click="fn">嗯哼button>
官方地址
格式:@事件.事件修饰符 = “事件函数”
范围:修饰任意事件
.prevent | .stop | .capture | .once | .self | .passive | |
---|---|---|---|---|---|---|
功能 | 阻止默认行为 | 阻止冒泡(添加在子元素) | 捕获(添加在父元素) | 只触发一次(添加在子元素) | 只有点击自己才触发(关闭冒泡和捕获) | 用于移动端提高性能,只需判断一次是捕获还是冒泡 |
官方地址
格式:@事件.按键修饰符 = “事件函数”
范围:修饰按键事件
.enter | .up | .down | .left | .right | .esc | delete | |
---|---|---|---|---|---|---|---|
功能 | 回车 | 上 | 下 | 左 | 右 | esc键 | 删除 |
作用:遍历数据,根据数据里元素的个数创建对应个数的标签
注意
:
<ul>
<li v-for="(item,index) in arr" :key="index">{{item}}li>
ul>
<ul>
<li v-for="(value,key,index) in obj" :key="index">{{key}}:{{value}}li>
ul>
<ul>
<li v-for="item in arrObj" :key="item.id">{{item.name}}li>
ul>
作用:控制元素的显示与隐藏
区别:控制方式不同
v-show | v-if | |
---|---|---|
显示 | display:block | 新建节点 |
隐藏 | display:none | 删除节点 |
因为v-if操作的是节点,因此比较消耗性能 ,操作频率高的情况下,不推荐使用v-if
包含v-if、v-else-if、v-else,用法与if(){}else if (){}else(){}类似
<div id="app">
<p v-if="age >= 18">成年人p>
<p v-else-if="age >= 12">青少年p>
<p v-else="age < 12">儿童p>
div>
v-pre:不解析,可直接显示 Mustache 标签即“{{ }}”符号
v-once :解析一次
v-cloak :遮盖,可以隐藏未编译的 Mustache 标签直到实例准备完毕。
<div v-cloak>{{ num}}div>
<style>
[v-cloak]{
display:none
}
style>
<div id="app">
num1 : <input type="text" v-model="num1" /> +
num2 :<input type="text" v-model="num2" /> =
<span>{{ sum }}span>
div>
<script>
const vm = new Vue({
el: '#app',
data: {
num1: '',
num2: ''
},
computed: {
sum() {
return +this.num1 + +this.num2
}
}
})
script>
DOM异步更新,若需获取DOM更新后的数据就需要用setTimeOut(延时器)或者 $nextTick 。但延时器的时间不好把控,因此需要使用 $nextTick。
this. $nextTick()在DOM异步更新结束之后,自动执行回调函数,可在回调函数中获取自己所需的数据。
<div id="app">
<p>{{ num }}p>
<button @click="fn">按钮button>
div>
<script>
const vm = new Vue({
el: '#app',
data: {
num: 100
},
methods: {
fn() {
console.log(document.querySelector('p').innerText)
this.num += 1
// 方式1 : 延时器
// setTimeout(() => {
// console.log(document.querySelector('h1').innerText)
// }, 1000)
// 方式2 : $nextTick,DOM更新完毕后,就可以获取DOM数据了
this.$nextTick(() => {
console.log(document.querySelector('p').innerText)
})
}
}
})
script>
data中存在的数据,vue可监测到,在进行修改时,数据能响应式改变;但data中不存在的数据,数据就不会响应式变化。如果想让data中不存在的数据进行响应式更新,就需要借助于 $set
格式:this. $set(对象,属性,初始值)
this.$set(this.obj,'name','stitch')
作用:监听数据的变化,可应用于本地持久化
使用:
//简单类型
watch:{
//参数1:新值
//参数2:旧值(可省略)
被监听数据(newVal,oldVal){
console.log(newVal)
}
}
//复杂类型(复杂类型监听到的是地址,地址一直未改变,因此监听数据的变化需要深度监听)
//方式1:深度监听
watch:{
obj:{
//深度监听
deep:true,
//立即监听(非必要)
immediate:true,
//处理函数
handler(newVal,oldVal){
console.log(newVal.name)
}
}
}
//方式2:直接监听需要的属性
watch:{
'obj.name'(newVal){
console.log(newVal)
}
}
computed是根据一个或多个值(data里的值),通过计算得到一个新值,当data的值发生变化得到的新值随着改变,是多对一或者一对一的关系。别人影响我。
watch监听的是data里的数据,当数据改变时,影响接下来的操作,是一对多的关系。自己改变影响别人。
作用:vue.js允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示。
步骤:
参数1:过滤器名称
参数2:回调函数(参数1:需格式化的数据,参数2(可省略):过滤器传过来的参数)
注意:回调函数一定要有返回值
Vue.filter('filterName', function (value) {
return value的处理
})
new Vue({
el:'#app',
data:{
date: new Date()
}
})
new Vue({
el:'#app',
data:{
date: new Date()
}
filters: {
filterName(value) {
return value的处理
}
}
})
<p> {{ date | filterName }} p>
<div v-bind:id="rawId | formatId">div>
概念: 生命周期函数又称为钩子函数
注意: 钩子函数到对应阶段时自动调用;命名是规定好的
钩子函数 | 阶段 | 使用场景 |
---|---|---|
beforeCreate | 挂载阶段 初始化内部使用的事件,开启生命周期 | 无法获取data中的数据;加loading事件 |
created★ | 挂载阶段 数据响应式之后 | 发送Ajax请求;操作data中的数据;获取本地存储数据 |
beforeMount | 挂载阶段 DOM渲染前,数据与模板初始化完成 | \ |
mounted ★ | 挂载阶段 DOM渲染完成后 | 操作DOM元素;发送Ajax请求 |
beforeUpdate | 更新阶段 更新前 | 获取更新前的数据 |
updated ☆ | 更新阶段 数据更新完成后 | 获取更新后的数据 |
beforeDestory | 卸载阶段 | \ |
destoryed | 卸载阶段 | 清理开启的定时器;移除手动创建的DOM对象等 |
概念:可复用的UI模块;本质上是一个Vue实例。
注册:
Vue.component('child',{
template:`<div> child div>`,
data(){
return{
}
}
})
const vm = new Vue({
components : {
child : {
template : `<div>childdiv>`
}
}
})
const child = {
template : `<div>div>`
}
const vm = new Vue({
components : child
})
注意:
1. 注册组件需要在vue实例前
2. template只有一个根组件
3. 配置对象和vue实例中大部分一样
4. 组件中的data是一个函数,函数里面返回一个对象是组件里的数据,因为我们希望组件被复用,但不希望组件中的数据被复用
组件化开发
概念:将一个页面抽象成一个个独立的组件。
优点:可复用性强
与模块化开发的区别:模块化开发重业务,重逻辑,重功能,而组件化开发重界面/UI。
原因:组件是一个封闭的个体,组件之间无法直接获取对方的数据,因此需要组件之间的通信机制来访问数据。
实现思路:子组件通过配置项props,自定义一个属性,父组件通过这个属性将数据传递给子组件。
<div id="app">
<child :msg="pmsg">child>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
Vue.component('child',{
template:`子组件:{{msg}}`,
// 2.子组件通过配置项props,指定需接收来的数据
props:{
msg : String
}
})
const vm = new Vue({
el:'#app',
data:{
pmsg:'父组件中的数据'
}
})
script>
实现思路:子组件通过触发自定义事件,利用函数传参的方式,将数据传递给父组件。
<div id="app">
<p>父组件:{{cmsg}}p>
<child @event="pfn">child>
div>
<script>
Vue.component('child',{
template:``,
data(){
return{
cmsg:'子组件中的数据'
}
},
created(){
// 3.触发自定义事件
this.$emit('event',this.cmsg)
}
})
const vm = new Vue({
el:'#app',
data:{
cmsg:''
},
methods:{
// 1.在父组件中准备好一个方法
pfn(cmsg) {
this.cmsg = cmsg
}
}
})
script>
vm.$emit( event, arg )
:触发当前实例上的事件,(参数1:事件名,参数2:传递的参数)
实现思路:通过事件总线(event bus)为媒介,发送数据的组件触发事件,接收数据的组件注册事件
<div id="app">
<child1>child1>
<child2>child2>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
// 1.创建事件总线
const bus = new Vue()
Vue.component('child1',{
template:`child1:`,
data(){
return{
msg1:'child1发送的数据'
}
},
methods:{
send(){
// 2.发送数据的组件,触发事件
bus.$emit('transferData',this.msg1)
}
}
})
Vue.component('child2',{
template:`child2:{{msg2}}`,
data(){
return{
msg2:''
}
},
created(){
// 3.接收数据的组件,注册事件
bus.$on('transferData',(res)=>{
this.msg2 = res
})
}
})
const vm = new Vue({
el:'#app'
})
script>
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
大小写问题
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
<blog-post post-title="hello!">blog-post>
<script>
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '{{ postTitle }}
'
})
script>
prop校验
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
作用:获取标签/组件
先注册ref,凡是注册过ref的标签/数组,都会被refs收集起来,refs是一个键值对(键:ref值; 值:注册refs的组件或元素)。
<div id="app">
<child ref='c'>child>
div>
<script>
Vue.component('child',{
template:``,
data(){
return{
cmsg:'子组件中的数据'
}
}
})
const vm = new Vue({
el:'#app',
mounted(){
//2.使用
//父组件可获取到子组件
console.log(this.$refs.c)
//父组件可获取到子组件中的信息
console.log(this.$refs.c.cmsg)
}
})
script>
注意: 若想一进来就要获取$refs的值,最早也要等DOM元素渲染之后,即mounted中。
学习目的:为了更好管理单页面应用程序(SPA)
路由是浏览器URL中的哈希值,与展示视图内容之间的对应规则。
vue中的路由是由hash和component的对应关系,一个哈希值对应一个组件。
准备工作 (3个)
安装 : npm i vue-router
引入 :
注意: 引入路由一定要在引入vue之后,因为vue-router是基于vue工作的
<script src="./vue.js">script>
<script src="./node_modules/vue-router/dist/vue-router.js">script>
实例路由对象 + 挂载到vue上
实例路由对象 : const router = new VueRouter()
挂载到vue上 : new Vue({ router,data,methods })
验证路由是否挂载成功, 就看打开页面,最后面有没有个 #/
具体步骤
1). 手动:在地址栏手动输入(测试用)
2). 声明式导航:
3). 编程式导航:
跳转:this.$router.push('/one') 有记录,可返回
返回:this.$router.back()
替换:this.$router.replace('/one') 没有记录,不可返回(返回到根目录,常用于支付)
// path : 路由路径
// component : 将来要展示的路由组件
routes: [
{ path: '/one', component: One },
{ path: '/two', component: Two }
]
<router-view />
一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息
一个哈希值路径 对应 一个路由对象
$route.path★
string
"/detail/4"
。# 后面?前面的内容
$route.params★
Object
$route.name★
string
$route.meta 元信息★
Object
$route.query
Object
/4?age=21
,则有 $route.query.age == 21
,如果没有查询参数,则是个空对象。$route.hash
类型: string
当前路由的 hash 值 (带 #
) ,如果没有 hash 值,则为空字符串。
$route.fullPath
string
# 演示 :
<router-link to="/detail/4?age=21#one">detail</router-link>
{ path: '/detail/:id?', component: detail ,name='detail',meta:{title:'stitch'}}
在组件内 created打印 this.$route
> fullPath: "/detail/4?id=001#one"
> hash : "#one"
> params : {id:'4'}
> query : {age : 21}
> path : '/detail/4'
> name:'detail'
> meta:{title:'stitch'}
作用:根据不同的条件动态的去配置路由规则。在vue-router的路由路径中,可以使用动态路径参数给路径动态的传值。
动态路由在来回切换时,由于它们都是指向同一组件,Vue不会销毁再重新创建这个组件,而是复用这个组件。
<div id="app">
<router-link to="/detail/1">手机1router-link>
<router-link to="/detail/2">手机2router-link>
<router-link to="/detail/3">手机3router-link>
<router-view >router-view>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script src="../node_modules/vue-router/dist/vue-router.js">script>
<script >
//3.组件
//获取参数的三种方式
const detail = {
// 方式1 : 组件中直接读取
template:`详情页信息:{{$route.params.id}}`,
// 方式2 : js直接读取
created(){
// 打印只会打印一次,因为组件是复用的,每次进来钩子函数只会执行一次
console.log(this.$route.params.id)
},
// 方式3 : 监听路由的参数。为什么不需要深度监听,因为一个路径变化,就会对应一个对新的路由对象(地址变)
watch:{
$route(to,from){
console.log(to.params.id)
}
}
}
const router = new VueRouter({
//2.匹配规则
routes : [
{path:'/detail/:id?',component:detail}
]
})
const vm = new Vue({
el:'#app',
router,
components :{detail}
})
script>
作用:嵌套路由就是在路由里面嵌套它的子路由,简而言之,就是让一个组件显示在另一个组件中。
const Parent = {
//1.在父组件中留一个出口
template:`Parent `
}
const Child = {
template:`Child`
}
const router = new VueRouter({
routes:[{
path:'/parent',
component:Parent,
//2.子组件的路由的路由规则需放到父组件的children中
/**
* path:'/child' =>入口:'/child'
* path:'child' =>入口:'/parent/child'
*/
// children:[{path:'/child',component:Child}]
children:[{path:'child',component:Child}]
}]
})
作用:在创建Router实例的时候,在 routes中给某个路由设置名称name值作为标识,然后就可以通过这个名称来代替path路径去指向某个路由组件
<router-link :to="{name:'one'}">Onerouter-link>
//命名name
routes:[
{path:'/one',name:'one',component:One}
]
作用:想同时或同级在一个页面中展示多个视图,而不是嵌套展示,则可以在页面中定义多个单独命名的视图
没添加name时,显示默认视图routes:[
{
//1.配置名字
path:'/',components:{
h:Header,
m:Main,
//指定默认显示视图
default:Footer
}
}
]
<router-view name='h'>router-view>
<router-view name='m'>router-view>
<router-view >router-view>
const router = new VueRouter({
routes: [
//方式1:路径
{ path: '/', redirect: '/one' },
//方式2:name
{ path: '/', redirect: { name: 'one' }},
//方式3:函数
//to:目标路由对象
{ path: '/', redirect: to => {
//return '/two'
//可根据路由中的条件,来决定重定向到哪
if(to.params.id ==1){
return {name:'one'}
}else{
return {name:'two'}
}
}}
]
})
const One = {
//使用
template:`组件One:{{id}}`,
//指定
props:['id','aa','bb']
}
const router = new VueRouter({
routes:[
// 方式1:原始
{path:'/one/:id?',component:One}
// 方式2:布尔模式 将路由参数id,作为组件one的属性存在
{path:'/one/:id?',component:One,props:true}
// 方式3:对象模式
{path:'/one',component:One,props:{aa:'aaa'}}
// 方式4:函数模式
{path:'/one',component:One,props:to=>{return {bb:'bbb'}}}
]
})
导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。
to: 即将要进入的目标 路由对象
from: 当前导航正要离开的路由对象
next:1).允许访问:next() 2).不允许访问:next(false) 3).跳转到其它界面访问:next(‘login’)
const isLogin = false
router.beforeEach((to,from,next)=>{
if(to.name == 'login'){
next()
}else{
// isLogin ? next():next(false)
isLogin ? next():next('/login')
}
})
全局前置路由守卫:beforeEach((to,from,next)=>{ })
全局后置路由守卫:afterEach((to,from)=>{ })
独享路由守卫:beforeEnter((to,from,next)=>{ })
组件内路由守卫:beforeRouteEnter((to,from,next)=>{ }) / beforeRouteLeave((to,from,next)=>{ }) 通过路由规则触发才有
activated路由组件被激活时触发。
deactivated路由组件失活时触发。
keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 transition 相似,keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。
项目名不能有汉字,不能取名叫 webpack
package.json
, 命令 : npm init -y
npm i -D webpack webpack-cli
webpack : webpack 工具的核心包
webpack-cli : 提供了一些在终端中使用的命令
-D(--save-dev) : 表示项目开发期间的依赖,也就是 : 线上代码中用不到这些包了
main.js
文件console.log('要被打包了');
package.json
的scripts
中,添加脚本// webpack 是webpack-cli 中提供的命令, 用来实现打包的
// ./main.js 入口文件,要打包哪个文件
"scripts": {
"build": "webpack main.js"
},
npm run build
mode
"build" : "webpack ./main.js --mode development"
如果没有设置 mode 配置项, webpack 会默认提供 生产环境(production);生产环境下, 打包生产的js文件都是压缩后的, 开发环境下代码一般是不压缩的
"build" : 入口 --output 出口
"build": "webpack ./src/js/main.js --output ./dist/bundle.js --mode development",
第一步 : 项目`根目录`下, 创建一个 `webpack.config.js`文件 (文件名固定死)
第二步 : 在 `webpack.config.js` 中,进行配置
* webpack 是基于 node的 , 所以配置文件符合 node 方式书写配置
* 注意 : 不要再这个文件中使用ES6的的模块化 import语法
* main.js里可以使用,是因为要通过webpack转化为es5的
* 而这个是webpack 的配置文件,它是要转化别人的,所以必须要通过
第三步 : 修改 `package.json` 中的 `scripts` , 脚本命令为: "build": "webpack"
第四步 : 执行命令 : `npm run build`
作用:
- 能够根据指定的模板文件 (index.html),自动生成一个新的 index.html,并且注入到dist文件夹下
- 能够自动引入js文件
安装: npm i html-webpack-plugin
配置 :
//第一步: 引入模块
const htmlWebpackPlugin = require('html-webpack-plugin')
//第二步: 配置
plugins: [
// 使用插件 指定模板
new htmlWebpackPlugin({
template: path.join(__dirname, './src/index.html')
})
]
作用 : 为使用 webpack 打包提供一个服务器环境
- 自动为我们的项目创建一个服务器
- 自动打开浏览器
- 自动监视文件变化,自动刷新浏览器…
安装: npm i -D webpack-dev-server
配置:
方式 1 : 命令行配置
脚本 : "dev" : "webpack-dev-server --open --port 3001 --hot"
运行: npm run dev
自动打开: 添加 --open
指定端口号 : 添加 --port 3001
热更新: --hot
( 局部更新 )
方式 2 : 配置文件配置
// hot 不要写在配置文件里面,,不然的话还要配其他插件麻烦
"dev" : "webpack-dev-server --hot",
devServer : {
open : true,
port : 3001
}
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 入口
entry: path.join(__dirname, './src/js/main.js'),
// 出口
output: {
// 出口目录
path: path.join(__dirname, './dist'),
filename: 'bundle.js'
},
// 开发模式
mode: 'development'
// 插件
plugins: [
// 使用htmlWebpackPlugin插件 指定模板
new htmlWebpackPlugin({
template: path.join(__dirname, './src/index.html')
})
]
// 使用webpack-dev-server插件
devServer : {
open : true,
port : 3001
}
}
npm run dev
==> 不会打包的 ,只会把项目放到服务器里npm run build
对项目进行打包,生成dist文件1 执行 : `npm run build`
2 模拟本地服务器 : 安装 : `npm i -g http-server`
3 把dist文件里的内容放到服务器里即可, 直接运行`http-server`
webpack 只能处理 js 文件,非 js(css.less.图片.字体等)处理处理不了, 借助 loader 加载器
npm i -D style-loader css-loader
webpack.config.js
中,添加个新的配置项 module
// loader
module: {
rules: [
//1.处理 css
// 注意点 use执行loader 顺序 从右往左
// css-loader : 读取css文件内容,将其转化为一个模块
// style-loader :拿到模块, 创建一个style标签,插入页面中
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
]
}
npm i -D less-loader less style-loader css-loader
module->rules
{ test :/\.less$/, use : ['style-loader','css-loader','less-loader'] },
npm i -D url-loader file-loader
module->rules
// 处理图片
{ test : /\.(jpg|png)$/, use : ['url-loader'] },
**注意:**url-loader (推荐) 和 file-loader 二选一即可
url-loader 默认会将图片转化为 base64 编码格式, 目的:提高性能,减少请求次数。
file-loader 在处理图片时, 会对文件进行重命名 :
原始: background-image: url(…/images/1.jpg);
处理后: background-image: url(9c9690b56876ea9b4d092c0baef31bb3.jpg);
base64 编码格式的图片说明 :
设置配置:
//方式1 :
{ test : /\.(jpg|png)$/, use : ['url-loader?limit=57417'] },
//方式2 :
{
test: /\.(jpg|png)$/, use: [
{
loader: 'url-loader',
options: {
// 比57417这个小 => 转化为base64
// 大于等于这个57417值 => 不会转base64 内部调用 file-loader 加载图片
limit: 57417
}
}
]
}
npm i -D url-loader
// 4. 处理字体图标
{ test:/\.(svg|woff|woff2|ttf|eot)$/,use:'url-loader'}
var o = { ...obj }
在谷歌上可以,edge 就不可以npm i -D babel-core babel-loader@7
npm i -D babel-preset-env babel-preset-stage-2
babel-polyfill与babel-plugin-transform-runtime
也是做兼容处理的,以前都是用这个,兼容更早的 { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
.babelrc
{
"presets": [
"env",
"stage-2"
],
-----------
// 暂时不用
// 如果未来某一天真的用到了polify
"plugins": [
["transform-runtime", {
"helpers": false,
"polyfill": true,
"regenerator": true,
"moduleName": "babel-runtime"
}]
后缀为 .vue 的文件
注意 : 单文件组件,无法直接在浏览器中使用,必须经过 webpack 这种打包工具,处理后,才能在浏览器中使用
<template>
template>
<style scoped>
/*scoped:加上之后当前组件内的样式只在当前组件生效*/
/* css代码 */
style>
<script>
export default {
// js代码
}
script>
因为 webpack 配置繁琐, 阻止一批想用 vue 但是不会 webpack 的开发人员,所以作者直接将所有 vue 项目中用到的配置全部帮你写好了,这样,就不需要开发人员再去配置基础 webpack 配置项了
vue版本 | 安装命令 | 初始化项目命令 |
---|---|---|
2.0 | npm i vue-cli -g |
vue init webpack 项目名 |
3.0及以上 | npm i @vue/cli -g |
vue create 项目名 /vue ui |
查看版本:vue -V
安装过程:
? Project name # 回车 (项目名称)
? Project description # 回车 (项目描述)
? Author # 回车 (作者)
? Vue build standalone # => 运行时+编译 => 详见下面的问题1
? Install vue-router? # Yes (是否添加路由)
? Use ESLint to lint your code? # Yes (是否使用Eslint)
? Pick an ESLint preset Standard # standard (使用eslint的什么标准)
? Set up unit tests # No
? Setup e2e tests with Nightwatch? # No
? Should we run `npm install` for you after the project has been created? # (recommended) npm
问题1 : 两种编译模式 和 @
参考 : vue.js => 安装 => 对不同构建本版本的解释
// 需要编译器
new Vue({
template: '{{ hi }}'
})
// 不需要编译器
new Vue({
render (h) {
return h('div', this.hi)
}
})
router/index.js =>
import HelloWorld from '@/components/HelloWorld'
import HelloWorld from 'C:/users/.../src/components/HelloWorld'
问题2 : ESLint
概念 : ESLint 是在 JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。
在 vscode等编辑工具 中, 可以提示语法错误
在许多方面,它和 JSLint、JSHint 相似,除了少数的例外:
如何使用 eslint ?
"vetur.format.defaultFormatterOptions": {
"prettier": {
"semi": false, //不需要分号
"singleQuote": true, //使用单引号
"wrap_attributes": "force-aligned"
}
},
// 从这里往下大家都给加上
"editor.formatOnSave": true, //#每次保存的时候自动格式化
"eslint.autoFixOnSave": true, // #每次保存的时候将代码按eslint格式进行修复
"eslint.validate": [
{ "language": "html", "autoFix": true },
{ "language": "vue", "autoFix": true },
{ "language": "javascript", "autoFix": true }
],
"prettier.eslintIntegration": true, // #让prettier使用eslint的代码格式进行校验
"prettier.semi": false, //#去掉代码结尾的分号
"prettier.singleQuote": true, //#使用带引号替代双引号
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
"editor.formatOnType": true, //#让函数(名)和后面的括号之间加个空格
"vetur.format.defaultFormatter.html": "js-beautify-html",
"vetur.format.defaultFormatter.js": "vscode-typescript"
关闭 Eslint :
参考 : config/index.js ==> 26行 : dev.useEslint
设置为false
重启项目: npm run dev
问题3 : vscode安装 格式化插件 Prettier
安装 vscode 插件 Prettier - Code formatter
功能1 : shift + alt + F => 格式化代码
功能2 : 配合 eslint : 见上一个问题的配置
问题4 : eslint 检测警告
`//eslint-disable-next-line` # 忽略下一行的警告 可以使用单行注释/多行注释,其他都是多行注释
`/*eslint-disable*/` # 忽略当前整个文件的警告 需放在文件最前面
`/* eslint-disable no-new */` # 忽略前面是new开头的警告
npm install -g cnpm --registry=https://registry.npm.taobao.org
npm i yarn -g
yarn add vue
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
// 假设操作成功 resolve => then
// resolve('成功')
// 假设操作失败 reject => catch
reject('失败')
},0)
})
p.then(res=>{
console.log('then:',res)
}).catch(err=>{
console.log('catch:',err)
})
promise封装 异步读取多个文件
const fs = require('fs')
function readFiles (filePath) {
const p = new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf-8', (err, data) => {
if (err) {
reject('读取文件失败')
}
resolve(data)
})
})
return p
}
readFiles('./a.txt')
.then(res1 => {
console.log(res1)
return readFiles('./b.txt')
})
.then(res2 => {
console.log(res2)
return readFiles('./c.txt')
})
.then(res3 => {
console.log(res3)
})
功能 | 格式 | |
---|---|---|
async | 修饰一个内部有异步操作的函数 | async + 异步函数 |
await | 等待一个异步处理的结果值 | await+ 异步操作(promise类型) |
const fs = require('fs')
function readFiles (filePath) {
const p = new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf-8', (err, data) => {
if (err) {
reject("读取文件失败")
}
resolve(data)
})
})
return p
}
async function fn () {
try {
let res1 = await readFiles('./a1.txt')
console.log(res1)
} catch (err) {
console.log('读取失败了')
}
let res2 = await readFiles('./b.txt')
console.log(res2)
}
fn()
Promise.resolve(value=>{}) :快速返回成功的数据
Promise.regect(reason=>{}):快速返回失败的数据
Promise.all(promises=>{ }) :返回一个新的promise,只有所有的promise都成功才成功,有一个失败的promise就失败
Promise.race(promises=>{ }) :第一个完成的promise的结果状态就是最终结果状态
pending: 未完成
resolved: 异步完成成功的结果
rejected: 异步完成失败的结果
promise是微任务,执行代码是先执行宏任务再执行微任务。script与timeout是宏任务,但timeout是下一次的宏任务。
状态管理工具
状态即数据, 状态管理就是管理组件中的data数据
Vuex 中的状态管理工具,采用了 集中式 方式统一管理项目中组件之间需要通讯的数据
npm i vuex
vuex
之前一定要先引入 vue
const store = new Vuex.Store()
store.state.num
store.state.num = 300
store.state.count = 300
可以修改值 , 但是vuex 也有严格模式,strict : true,
mutations : {}
increament(state) { state.count = 20; }
store.commit('increament')
// 传参最好传一个对象,多个值查看方便
store.commit('increament', {
num: 400
})
// payload 载荷
increament(state, payload) {
state.count = payload.num
}
需求 : 有个h1显示数字的标题, 点击按钮累加数字
{{ $store.state.num }}
this.$store.commit('addNum')
addNum(state) { state.num++ }
this.$store.commit('方法名');
this.$store.dispatch('方法名');
import { mapGetters } from "vuex";
computed:{
...mapGetters(['方法名'])
}
methods:{
...mapMutations(['方法名'])
}
methods:{
...mapActions(['方法名'])
}
代码 :
// 演示跨域问题
/* eslint-disable */
import axios from 'axios';
axios.get('https://api.douban.com/v2/movie/in_theaters').then(res => {
console.log(res)
})
报错 :
Access to XMLHttpRequest at 'https://api.douban.com/v2/movie/in_theaters' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
报错原因
项目运行在 http://localhost:8080
// I Your application is running here: http://localhost:8080
发送ajax请求 : //域名是 https://api.douban.com/v2/movie/in_theaters
因此出现跨域问题
修改 config/index.js
配置文件
proxyTable: {
'/myapi': {
// 代理的目标服务器地址
// https://api.douban.com/v2/movie/in_theaters
// /myapi/movie/in_theaters
target: 'https://douban.uieee.com/v2/',
pathRewrite: { '^/myapi': '' }, // 替换的请求地址
// 设置https
secure: false,
changeOrigin: true // 允许跨域 (必须设置该项)
}
},
最终代码
// axios.get('https://api.douban.com/v2/movie/in_theaters').then(res => {
axios.get("http://localhost:8080/api/movie/in_theaters").then(res => {
console.log(res);
});
最终配置 cli2.x :
proxyTable: {
'/myapi': {
// 代理的目标服务器地址
// https://api.douban.com/v2/movie/in_theaters
// /myapi/movie/in_theaters
target: 'https://douban.uieee.com/v2/',
pathRewrite: { '^/myapi': '' },
// 设置https
secure: false,
// 必须设置该项
changeOrigin: true
}
},
最终配置 3.X
vue.config.js
module.exports = {
devServer: {
proxy: {
'/myapi': {
// 代理的目标服务器地址
// https://api.douban.com/v2/movie/in_theaters
// /myapi/movie/in_theaters
target: 'https://douban.uieee.com/v2/',
pathRewrite: { '^/myapi': '' },
// 设置https
secure: false,
// 必须设置该项
changeOrigin: true
}
}
}
}
// 使用
axios.get('http://localhost:8080/myapi/movie/in_theaters').then(res => {
console.log(res)
})
axios.get('/myapi/movie/in_theaters').then(res => {
console.log(res)
})
重新启动 : npm run dev
(配置vue.config.js 都需要重启项目)
keep-alive是vue内置的一个组件,而这个组件的作用就是能够缓存不活动的组件,我们能够知道,一般情况下,组件进行切换的时候,默认会进行销毁,如果有需求,某个组件切换后不进行销毁,而是保存之前的状态,那么就可以利用keep-alive来实现。
被缓存的路由,第一次进入页面的时候会调用 created() 方法,第二次进入页面时就不会再调用 created() 方法了,因为页面被缓存起来了。那么我们如果页面改动了,怎么刷新页面呢?虽然 created() 方法不会被调用,但是activated、deactivated这两个生命周期钩子函数会被执行。也就是我们的接口可以写在 activated() 方法中。
// 将缓存 name 为 index 或者 home 的组件,结合动态组件使用
<keep-alive include="index,home">
<router-view :key="key" />
</keep-alive>
被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated 与 deactivated
1. activated
在 keep-alive 组件激活时调用
该钩子函数在服务器端渲染期间不被调用
2. deactivated
在 keep-alive 组件停用时调用
该钩子函数在服务器端渲染期间不被调用