9月vue学习笔记

243 阅读13分钟

以下笔记基本跟随B站黑马程序员的结构学习

第一天 前端工程化与webpack

课程目标

image.png

前端工程化

  • 前端开发并不是简单的使用现成的组件库拖拽式的东平西凑
  • 实际开发中是模块化(js/css的模块化)、组件化(复用现有的UI结构、样式、行为)、规范化(使用git等工具,目录结构合理有序)、自动化(自动构建、部署、测试)
  • 组件化可以使用Layui和element+等组件库
  • 目录结构规范化可以使用脚手架的框架
  • 模块化是功能的聚类,组件化是样式的聚类
  • 目前主流的前端工程化解决方案是webpack(项目)和parcel(第三方库)

webpack

主要功能:①模块化开发②处理代码压缩(为了提高速度)混淆③处理浏览器js兼容性④性能优化

在vue、react中实际上都是基于webpack进行开发的

创建列表隔行变色项目

npm install jquery -S

-S(--save)命令明确告知npm记录依赖在package.json中,虽然默认会记录

此时如果使用

import $ from 'jquery'

这个es6的语法,将其作为js解释的浏览器会出现以下报错:

image.png

此时应该使用webpack

安装配置webpack

安装

使用npm安装webpack和webpack-cli

-D(--save-dev)作用同-S都是进行记录,不过是记录在package.json中的devDependencies

dependencies记录的是开发中需要使用,部署时也要使用的

devDependencies时只在开发中使用的,部署上线并不需要用到

(可以在npmjs.com中查看具体的第三方库应该如何使用)

配置

image.png

使用

运行项目后多出了dist目录,其中有main.js,是根据使用的第三方库(如jQuery)和自己编写的js代码自动生成的具有兼容的js

直接在需要处导入main.js即可使用es6命令

不同mode的应用场景

开发模式下并不会压缩代码,所以生成的文件较大

发布模式(production)下会对代码进行压缩,但是run dev所需的事件会稍微长一点(压缩过程)

entry和output

运行npm run dev时,实际上是运行webpack,找到webpack-config.js中的导出项,于上文是mode

在webpack4.x及以上版本中,默认约定如下:

①entry: 默认的打包文件入口为src -> index.js

②output: 默认的输出文件入口为dist -> main.js

具体的修改配置方法:

image.png

__dirname代表当前文件的目录,再此就是项目的根目录

插件

修改源代码并不能直接重新渲染网页

在当前项目中,修改源代码并不能直接重新渲染网页,需要重新启动项目才能改变main.js,所以才能改变网页

可以使用webpack插件webpack-dev-server监听源代码然后修改

安装插件同样使用npm安装,然后在package.json中修改dev命令

之前直接查看文件使用的是ftp协议,而现在会打开一个端口(8080)然后在其中查看,这时使用的是http协议

如果这时在8080端口中进入src,会直接展示页面,这时由于浏览器的性质:进入文件夹中自动展示index.html页面

需要特备注意的是,server会自动生成一个在内存中的output文件,虽然dist中有但并不是真正会被监听的,所有的修改是及时保存在内存中的文件中的,而dist中的静态的文件只有重新run之后才会改变

也就是说想要实现直接修改网页的效果,我们需要引用的是内存中的js文件,即/main.js(虽然看不见)

html-webpack-plugin

该插件可以帮助打开8080端口时直接进入指定html页面,配置方式如下:

image.png

这个页面时复制在内存中的index.html,同时会自动引入main.js,对src中的html修改不会影响

配置devServer

open属性可以修改是否直接打开页面

host属性可以修改主机地址

port属性可以修改端口号

loader

webpack中默认只能处理js文件,其他格式的模块(比如css)需要用特定的loader才能处理

同时如果js文件中包含了高级js语法,如果配备了babel文件,也会调用loader进行处理

同样的使用npm安装loader(比如css-loader)然后在webpack-config.js中配置特定后缀使用特定loader

image.png

css文件的调用

为了更加模块化开发,不应在html中引入css,而是在js中用import引入css文件

如上图,调用css-loader和style-loader对其进行处理,注意rules从后往前进行处理,先通过css,处理的结果再交给style,最后的结果会合并到main.js中

less文件

less.-loader -> css-loader ->style-loader

注意要下载less.loader和less,因为前者依赖于后者

base64

在普通的引用图片渲染时,要等到所有标签先渲染完了,浏览器才会请求图片,但是使用base64格式就可以让图片直接被渲染出来,在页面很多小图标时可以避免很多不必要的网络请求

具体的业务场景比如:使用import导入图片,然后动态的给img src赋值

这时候可以这样

import logo from './images/logo.jpg'
$('.box').attr('src',logo)

对jpg进行处理需要使用url-loader和file-loader,设置limit让只有小于limit的图片转为base64格式

在配置url-loader时用&区分多个参数

module: {
	rules: [
		{test: /\.jpg|png|gif$/, use: 'url-loader?limit=22229&outputPath=images'}
	]
}

处理样式的过程

在import文件时返回的是undefined,但是最后生成的main.js(或者bundle.js)会包含引入的文件

如果查看最后的main.js会发现是MODULE CSS_LOADER_EXPORT .push(样式内容)

所以在很多情况下,import接受到的内容是undefined,比如css。这时其实没有必要命名,直接使用即可

import './css/index.css'

js高级语法(babel)

某些webpack无法处理的高级js语法(比如@装饰器),可以使用babel-loader进行处理转换为低级的js代码

安装babel-loader,babel/core,babel/@plugin-proposal-decorators三个loader

用排除项排除第三方包的转换来节约转换速度,且第三方包也不需要考虑兼容性问题,只需要转换自己写的js

image.png

注意,使用babel-loader时还需要创建babel-config.js,进行如下配置声明babel可用的插件babel/plugin-proposal-decorators

module.exports = {
    plugins: [[]'@babel/plugin-proposal-decorators', {legacy: true}]
}
装饰器简单介绍
function info(target) {
    target.info = 'person info'
}

@info
class Person {}

console.log(Person.info)  //person info

info装饰器为person类赋予了一个info属性

打包发布

发布项目就是前端把项目生成一份最终的结果(包含所有资源),发送给后端,后端再进行上线

现在主要需要解决的是:bundle.js等现在存在于内存中,需要生成实际在物理磁盘中的文件

image.png

可以使用clean-webpack-plugin自动的删除dist目录,然后修改之前的代码生成js目录js->dist这样不至于有多个文件且结构清晰

关于插件的用法可以在npm官网查询

sourceMap

sourceMap是一个信息文件,里面存放着位置信息,也就是储存着webpack压缩混淆后的代码转换前的位置

可以让报错工具直接显示原始代码而不是bundle,js中的代码

开发环境下默认的sourcemap记录的是生成后的代码位置,如果要让其变为源代码的行数,配置

module.export = {
    mode:'development'
    devtool: 'eval-source-map'
    // 其他配置项
}

在发布时出于安全性考虑建议关闭sourcemap,防止源代码泄露

也可以使用nosources-source-map只定位行号不给出源码

CLI

在实际开发中并不需要自己配置webpack,可以使用脚手架cli一键生成带有webpack的项目

第二天 Vue基础入门

课程目标

image.png

Vue简介

  • 是一个框架
    • 框架是一套解决方案,根据特定的结构处理问题
  • 数据驱动视图
    • vue监听数据变化,自动重新渲染页面结构,不需要手动操作DOM元素
    • 数据驱动视图是单向的数据绑定
  • 双向数据绑定(v-model)
    • 在表单等时,用户填写内容同步到后台

MVVM

Vue实现上述特性的原理时MVVM,即Model,View,ViewModel

Model表示当前页面渲染时所依赖的数据源

View表示当前页面的DOM结构

ViewModel表示Vue的实例,时MVVM的核心

  • DOM Listeners监听DOM结构(view -> model)

  • Data Bindings绑定数据到页面(model -> view)

基本使用

  1. 导入vue.js的脚本文件

    • 导入了vue.js的文件之后,在window下生成vue实例的构造函数
  2. 在页面中声明一个被vue控制的DOM区域

    • <div id='app'>{{msg}}</div>
      
  3. 创建vm实例对象

    • //vue 2.x
      const vm = new Vue({
          el: '#app'
          msg: 'hello vue'
      })
      //vue 3.x
      const Counter = {
        data() {
          return {
           msg:'hello vue'
          }
        }
      }
      Vue.createApp(Counter).mount('#app')
      

image.png

指令

指令是模板语法,用于辅助开发者渲染页面结构

可以分为6类:

内容渲染指令

渲染DOM元素中的文本内容

v-text

会覆盖元素内部原有的文本内容

<p v-text="username">被覆盖</p>

{{}} 插值表达式(Mustache)

<p>{{username}}不被覆盖</p>

v-html

<p v-html="vhtml">被覆盖</p>
<script>
const vm = new Vue({
            el: "#app",
            data: {
                //其他数据...
                vhtml: '<h1 style="color: red">html标签还能带有样式</h1>'
            }
})
</script>

会生成指定的html元素到渲染区域,同时覆盖掉原有内容

属性绑定指令

上述的渲染指令只能用在内容节点,不能在属性中使用,需要的话要使用v-bind

<input type="text" v-bind:placeholder="tips">

可以简写为冒号

<input type="text" :placeholder="tips">

实际上,在指令中都可以编写js代码

<p>{{tips}}的反转结果是{{tips.split('').reverse().join('')}}</p>
<div :title="'box'+index">这是一个div</div>

事件绑定指令

使用v-on(简写@)可以绑定一个处理函数到事件上

<button type="button" @click='doPlus(1)'>增加1</button>
<p>{{count}}</p>
<script>
const vm = new Vue({
            el: "#app",
            data: {
                count: 0,
            },
            methods: {
                doPlus(n) {
                        this.count += n
                }
            }
        })
</script>

在绑定事件的处理函数调用时可以使用()也可以不使用,在需要参数时必须要,无参数时不一定需要

事件名一般为原生DOM对象的原生事件名去掉on改为使用@。如onkeyup-> @keyup

同时可以传入事件作为参数,使用target找到事件触发的目标,对其进行操作

oddTurnBlue(e) {
    this.count += 1
    if(this.count % 2 != 0) {
    	e.target.style.backgroundColor = 'blue'
    }else {
    	e.target.style.backgroundColor = ''
    }
}

同时vue提供了一个内置变量$event,可以同时传入其他参数

<button type="button" @click='oddTurnBlue(10,$event)'>count奇数变蓝</button>
oddTurnBlue(n, e) {
    this.count += n
    if(this.count % 2 != 0) {
    	e.target.style.backgroundColor = 'blue'
    }else {
    	e.target.style.backgroundColor = ''
    }
}

事件修饰符

对事件可以使用各种修饰符,比如prevent阻止默认行为(比如跳转和提交)和stop阻止事件冒泡(冒泡:子DOM节点和父DOM节点都有某个事件,触发了子再触发父。阻止冒泡就是不触发父节点的事件)

<a href='www.baidu.com' @click.prevent='jump'>不进行跳转而进行jump</a>

监听键盘事件时,可以监听具体的键盘,比如@keyup.enter='enter'

双向绑定指令

v-model双向数据绑定指令可以在不操作DOM的前提下获取表单的数据,而v-bind中页面变化不会改变数据源

<p>用户的名字为: {{username}}</p>
<input type="text" v-model="username" placeholder="请输入用户名">

同时可以设置加载模式

  • .number 只得到数值
  • .trim 自动去除头尾空格
  • .lazy 懒加载,只有focus移走才会改变

条件渲染指令

条件渲染语句有v-if和v-show,辅助开发者控制dom的显示和隐藏

<p v-if='flag'>flag为true展示,false隐藏</p>	
<p v-show='flag'>flag为true则展示,false隐藏</p>	

区别在于v-if=false时直接创建或移除整个dom节点,在元素中无法查找到,而v-show=false则是设置为display:none

如果要频繁切换元素的显示状态,用v-show性能会更好

而如果默认情况时不需要展示该元素时,为了直接不创建,用v-if会性能更好

同时可以使用v-else,v-else-if充当if的else-if块,可以连续使用

列表渲染指令

v-for可以基于一个数组来渲染一个列表结构,使用item in items结构

<ul>
    <li v-for="item in items">姓名是: {{item.name}}</li>
</ul>
items: [
    {id: 1,name: 'vimerio'},
    {id: 2,name: 'chen'}
]

同时还可以指定数组中元素的索引

<tbody>
	<tr v-for="(item, index) in items">
        <td>{{item.name}}</td>
        <td>{{item.index}}</td>
    </tr>
</tbody>

官方推荐对v-for进行key属性的绑定,且key应该为id,key的值不可以重复

不应该使用index作为key的值,因为索引不具有唯一性(唯一性不只是不重复,而是指与实体具有强的绑定关系)unshift,push等都会改变数组

比如在一个列表中允许用户选中第二个item给与其selected=true的属性,若为索引则vue会绑定属性在index=1的item上,但是这时用户新增了一个item在最前方(lsit.unshift()),于是索引集体向后移动一位,用户选中的项目改变了,这就是列表状态的紊乱

使用key可以提升性能,防止列表状态紊乱

<tbody>
	<tr v-for="(item, index) in items" :key='item.id'>
        <td>{{item.name}}</td>
        <td>{{item.index}}</td>
    </tr>
</tbody>

案例——品牌列表

image.png

html&css

整体界面使用bootstrap制作,panel中使用panel-heading制作标题(‘添加品牌’),panel-body中使用input-group链接品牌名称span和input,同时设置panel-body为form-inline让button与其在同一行,button设置为primary蓝色。

表格用table,具体样式没啥好说的。

js

整体功能的实现也比较简单,这里说两个坑

label需要动态指定

image.png

需要动态指定("'cb'+item.id")input的id和label的for,不然就会导致label点击所产生的的更改都在第一个标签上出现。

删除功能

image.png

这里应该使用filter,返回包含所有满足条件的元素的新数组。在此处条件为item.id !== id,也就是剔除掉了item.id == id 的元素,进而实现删除。

第三天 Vue基础入门

学习目标

image.png

过滤器

vue3已经弃用,不需要掌握

简单的来说就是{{data | filter}},在插值表达式中定义一个filter函数来处理data,

可以分为私有过滤器和用vue实例化的全局过滤器

侦听器

watch侦听器可以让开发者监听数据的变化,只要监听值变化就会触发watch中的函数,函数一定要与数据名同名

const cm = new Vue({
	el: "#app",
	data: {username: ''},
    watch: {
        //监听username的变化
        //newVal是变化后的新值,oldVal是变化前的旧值
        username(newVal, oldVal) {
            console.log(newVal, oldval)
        }
    }
})

侦听器可以判断用户名是否被占用,调用jQuery中的Ajax发起请求,判断newVal是否被占用

$.get('https://www.xxx.cn/api/finduser/'+newVal)

如果希望页面渲染之后立马执行,需要把监听器设置为对象而不是函数,同时设置immediate: true

watch: {
    //定义对象格式侦听器
    username: {
        handle(newVal, oldVal) {
            console.log(newVal, oldval)
        },
        //进入页面立即触发(默认值为false)
        immediate: true
    }
}

如果侦听的是对象,对象中间属性的变化并不会被方法侦听器侦听到,同样需要对象侦听器进行深度监听,监听对象中每个属性的变化,不过也可以用方法侦听器来监听单个属性的变化

data: {
    info: {
        username: ''
    }
}
watch: {
    //定义对象格式侦听器
    username: {
        handle(newVal, oldVal) {
            console.log(newVal, oldval)
        },
        //开启深度监听,只要对象中任何一个属性发生变化了,都会触发侦听器handle
        deep: true
    },
    'info.username'(newVal, oldVal) {
            console.log(newVal, oldval)
        }
}

计算属性computed

计算属性是通过一系列运算之后最终得到的一个属性值,这个动态计算出来的值可以被模板结构或者methods方法使用,提高复用性

计算属性在定义时要定义为方法格式 name() {},但是使用时是直接当做属性使用

使用``模板字符串时,中间的属性要用${}框起来

data: {
    r:0, g: 0, b: 0
},
computed: {
	rgb() { return `rgb(${this.r}, ${this.g}, ${this.b})`}
},
methods: {
    show() {console.log(this.rgb)}
}

axios

axios是一个专注于网络请求的库,基本用法如下:

axios({
    method: '请求的类型',
    url: "请求的url地址",
}).then(result) => {
    //.then用来指定请求成功后的回调函数
    //形参中的result时请求成功后的结果
}

调用axios得到的返回值时一个promise对象,可以使用.then来进行处理成功事件,.catch处理失败事件

axios拿到服务器数据之后会进行包装,包含很多项,其中data时服务器真实的数据

axios传参

axios({
    method: '请求的类型',
    url: "请求的url地址",
    //url中的查询参数(get参数)
    params: {},
    //请求体参数(post参数)
    data: {}
}).then(result) => {
    //.then用来指定请求成功后的回调函数
    //形参中的result时请求成功后的结果
}

使用详情参见:axios详解

vue-cli

vue-cli是基于webpack的脚手架工具,可以帮助开发者快速生成项目,不用配置webpack

进入命令行快速创建:vue create 项目名称

image.png

第三项选择手动安装功能

image.png

css预处理器可以处理less等,linter是约定代码风格的,不是团队使用可以不装

生成项目之后,目录结构如下:

image.png

其中main.js是项目的入口文件,APP.vue是项目的根文件,由于是单页面应用SPA,所以只有一个index.html

vue项目的运行流程:通过main.js把APP.vue渲染到index.html中

vue组件

以.vue结尾,每个组件可以分为三部分:template模板,script功能,style样式

在template中只能有一个根节点

其中script需要导出,组件中的data应该是一个函数,return出数据

如果要启用less语法,需要指定style的lang为less:<style lang="less"></style>

<script>
export default {
    data() {
        return {
            username: ''
        }
    }
}
</script>

js第四天 组件与生命周期

学习目标

image.png

组件之间的关系

组件在封装好之后,彼此之间互相独立,不存在关系

在使用组件时,根据彼此的嵌套关系,形成了父子关系、兄弟关系

使用组件的步骤

  1. 在根组件中用import语法导入需要的组件(vue自动配置@代表src目录)

    import Son from '@/components/Son.vue'
    
  2. 使用component节点注册组件

    export default {
      name: 'App',
      components: {
        Son
      }
    }
    
  3. 以标签形式直接使用

    <div id='#app'>
        <Son></Son>
    </div>
    

私有组件和全局组件

通过在一个组件的component节点下注册组件,子组件只能在父组件内部被使用,如果有组件使用频率高,需要在很多的组件下注册十分繁琐,所以可以注册一个全局组件,不用私有注册就可以全局使用

在main.js入口文件中:

import Count from '@/components/Count.vue'
//参数1是注册名,可以作为标签名,参数2是组件名
Vue.component('MyCount', Count)

组件的props属性

props是组件的自定义属性,在封装通用组件的时候,使用props可以让同一个组件满足不同的需求,提高复用性

export default {
  name: 'App',
  props: ['自定义属性A','自定义属性B','其他自定义属性...']
  data() {
  return {
  	}
  }
}

定义了自定义属性后,可以在组件同名标签中使用它,并且给它传递值,这个值可以被当做类似于data中的值被直接使用在template的插值表达式中,比如:

<template>
    <p>count 的值是 {{ 自定义属性A }}</p>
</template>

这时在父组件中使用时可以添加标签的属性传递值
<MyCount 自定义属性A ='9'></MyCount>

如果想要传递回一个number值,可以使用v-bind绑定属性,因为-bind中默认为js语句,而js语句中直接输入9,返回的是number9而不是字符串"9"

props一般是只读的,不要更改props的值,因为这个值是会被随意改变的

如果要使用props的值,应该将props的值转存到data中,比如:

export default {
  name: 'App',
  props: ['inita','initb']
  data() {
  return {
      count: this.inita,
      pace: this.initb
  	}
  }
}

如果组件的使用者并没有传入值,需要给出一个default值来防止出现undefined+1这种情况

同时还可以定义传入的值的类型type

还可以定义必传项required

需要定义这些属性时需要将props写成对象

props: {
	inita: {
        default: 0,
        type: Number,
        required: true
    },
    initb: {
        default: 1,
        type: Number
    }
}

样式冲突

默认情况下写在.vue中的style会全局生效,因为看似样式只写在了组件中,但是是在整个index.html中使用的,这就可能会造成多个组件中的样式冲突问题

如果要限制一个组件中的style只能被该组件使用,应该在style标签中添加scoped属性,scoped的原理就是给这个组件的所有元素生成一个独一无二的属性(data-v)然后通过属性选择器给它们添加样式

<style scoped="scoped"></style>

但是scoped是有缺陷的,如果想要在scoped的父组件中修改子组件的样式,将不会生效,这时需要使用deep

//子组件中包含h5
<style scoped="scoped">
    /deep/ h5 {
        color: pink
    }
</style>

vue组件的实例对象

浏览器是无法自动解析vue结尾的文件的,vue组件是通过package.json中的vue-template-compiler(vue模板编译器)解析为js文件给浏览器执行的

可以把.vue中的内容理解为一个构造函数,而组件的使用者用标签使用组件时生成了一个组件的实例对象

组件的生命周期

一个组件的生命周期包括个阶段,可以在生命周期的不同阶段进行各种操作

生命周期函数是vue的内置函数,会在特定时间点自动运行

  1. 组件创建阶段

    (Init Events&Lifecycle:这个阶段组建的props/data/methods尚未被创建,处于不可用状态)

    1. beforeCreate

    (Init injection&reactivity:这个阶段初始化props/data/methods)

    2.created:组件的prop/data/methods已经被创建好,但是模板结构尚未生成(尚未挂载)。

    ​ 这个阶段一般会发Ajax请求拿数据

    (Compile template into render function/Compile el's outerHTML as template)

    3.beforeMount:基于数据和模板,在内存中编译生成HTML结构,将要把内存中编译好的HTML结构渲染到 浏览器,此时还没有当前组件的dom结构

    (Create vm.$el and replace 'el' with it)

    4.mounted:浏览器包含当前组件的dom结构

  2. 组件运行阶段

    (when data changes)

    1. beforeUpdate:将要根据变化的数据重新渲染组件的模板(数据已经更新,DOM还是旧的

    (Virtual DOM re-render and patch)

    2.updated:想要获取更新后的dom节点,需要使用这个函数

  3. 组件销毁阶段(v-if=false是常见的组件销毁)

    1. beforeDestroy:

      (Teardown watchers, child components and event listeners)

      2.destroyed:

img

组件间的数据共享

父子组件数据共享

父向子——props

使用自定义属性props,父组件定义props的值就可以向子组件传值

子向父——自定义事件

在子组件中定义一个数据data,父组件也需要定义一个data中的数据来接受子组件的值,然后使用自定义事件this.$emit传值

在此,自定义了一个事件numchange,当事件numchange触发调用getNewCount

事件numchange是被this.$emit触发的,触发时传递了this.count,父组件在接受时用形参val捕获count并且转存到countFromSon中

//子组件
methods: {
    this.count += 1;
	this.$emit('numchange',this.count)
}

//父组件
<Son @numchange='getNewCount'></son>

...
methods: {
    getNewCount(val) {
        this.countFromSon = val
    }
}

兄弟组件数据共享——EventBus

生成一个新的vue实例作为bus,发送数据方调用bus.emit,并且传输指定数据;接收数据方调用bus.emit,并且传输指定数据;接收数据方调用bus.on,并且提供捕获数据的形参以及接收数据的data

image.png

第五天 ref & 购物车案例

学习目标

image.png

ref引用

因为在vue中使用MVVM,所以极少有操作dom的需求,并不需要安装jQuery

但是实在需要操作dom时,可以使用ref引用,在不调用dom的api的情况下获得dom的引用

在每一个vue组件实例中,都包含一个内置的$refs对象,里面存储着对应dom元素或组件的引用,默认情况指向一个空对象

当在组件模板中指定某个标签的ref属性值之后,refs中会包含这个ref以及标签refs中会包含这个ref以及标签 `refs: {myh1: h1}`

<template>
    <div>
        <h1 ref='myh1'></h1>
    </div>
</template>

这时候就可以通过:this.$refs.myh1来获取这个dom节点的引用,对其进行包括样式修改的一系列修改

引用组件实例也是一样的,在父组件模板中,子组件本来就是以一个自定义标签的形式出现的,这个自定义标签同样也可以有ref属性

其实这样实现父子组件的数据共享会更快一些

**注意:**当更新数据之后想要获取更新后的dom节点,需要调用this.$nextTick(callback函数)来执行,因为在组件的生命周期中,beforeupdate时dom还没有重新渲染(如果改变的是v-if的值,dom会直接undefined),需要等到dom重新渲染好之后再执行,同时这个函数中应该使用箭头函数来避免this的错误

购物车案例

感觉没有非常值得记录的知识点,主要就是熟悉一下之前的内容

基本用到的组件有四个:header、footer、goods、counter,数据是用axios拿的

header就一框,counter写了一万次了,真正要写的就是goods里面的列表渲染和footer里面的按钮事件

goods中通过id来记录state从而改变选中状态 this.$emit('stateChange',{id,value}),就像之前有一次讲过label的绑定问题(见第四天)。候选框本事有一个@change事件,给它一个处理函数就可以了

最后的总价用数组的filter和reduce去做会比较快,全选按钮用every做比较快

第六天 vue组件的高级用法

学习目标

image.png

动态组件

动态组件指的是动态切换组件的显示与隐藏

基本用法

使用vue内置的<component>组件作为占位符,用v-bind绑定一个data,在其中切换组件

<component :is='comName'>
    <button @click="comName = 'Left'"> 展示Left</button>
    <button @click="comName = 'Right'"> 展示Right</button>
</component>

keep-alive

在组件切换时,组件中的data不会记录,因为组件切换时,原有组件被销毁,即使之后切换回来一个同样的组件,也只是新的组件实例而不是原来的组件

<keep-alive>
    <component :is='comName'>
        <button @click="comName = 'Left'"> 展示Left</button>
        <button @click="comName = 'Right'"> 展示Right</button>
    </component>
</keep-alive>

keep-alive有对应的生命周期:缓存和激活

可以用deactivated和activated生命周期函数来监听缓存和激活事件

同时,可以使用keep-alive标签的include属性(默认全部缓存)来指定只有部分组件被缓存 <keep-alive include='Left,Right'>

当然,也可以使用exclude属性来排除不需要被缓存的组件,但是不能与include属性同时使用 <keep-alive exclude='Right'>

插槽slot

插槽是组件封装者在封装组件时,允许开发者把不确定的、希望用户指定的部分定义为插槽

可以在组件template中使用 <slot>标签进行占位,让用户自己定义想要使用的标签,同时直接写在slot标签中的内容会作为后备内容,如果用户没有在插槽中填充任何内容,就会启用后备内容

插槽可以避免组件的过度嵌套导致数据传输不方便,可以在子组件A中定义插槽,父组件B使用子组件A时在插槽处使用另一个组件C,避免把组件C作为B的子组件

具名插槽

插槽需要有一个name属性,默认为default,同时如果填充内容没有指定v-slot指令绑定name的话,默认填充到default插槽中,这也就是说可以通过name和v-slot的配合来使指定内容渲染到指定插槽

v-slot指令只能使用在template标签中,所以要用template标签包裹想要指定渲染的内容外

<template v-slot:default>
    <p>指定渲染到default插槽的内容</p>
</template>

v-slot可以简写为#

作用域(scope)插槽

在插槽中可以向用户传递一些信息,通过属性:值的方法写入slot标签传递,用户在使用时用对象接收信息

实际上就是一类子向父传值

<template #default='scope'>
    <p>指定渲染到default插槽的内容</p>
    <p>{{ scope }}</p>
</template>

自定义指令

开发者自己定义的指令,可以分为私有自定义指令(组件内的)和全局自定义指令

私有自定义指令

定义私有自定义指令v-color:

xxx.vue

directives: {
    color: {
        bind(el) {
            el.style.color= 'red'
        }
    }
}

当指令被绑定到元素身上时,就会触发bind函数,获取当前元素并存为el,然后就可以对el元素进行操作

如果需要指定值时,应该使用binding.value,这时就可以使用v-color=" 'red' "

//xxx.vue

directives: {
    color: {
        bind(el,binding) {
            el.style.color= binding.value
        }
    }
}

但是bind函数只会在第一次被绑定到元素上时被触发,如果想要动态的更改binding.value后改变指令效果需要使用update函数,写法同bind,写在bind函数下即可

如果bind和update中的逻辑完全相同,可以把对象格式的color改为函数格式的:

directives: {
    color(el,binding) {
            el.style.color= binding.value
    }		
}

全局自定义指令

//main.js

Vue.directive('color', function(el,binding) {
	el.style.color = binding.value
})

ESLint

代码约束工具,非团体可以不用

axios简便用法

如果多个组件都需要发起axios请求的话,可以在main.js中对vue的原型进行axios注册

Vue.prototype.$http = axios

然后就可以在各个组件中适用this.axios而不用反复import axios

同时可以在main.js中配置请求根路径

axios.default.baseURL = '请求根路径'

但是这样不利于api接口的复用

第七天 路由vue-router

学习目标

image.png

路由概念

路由就是hash地址(#锚链接)与组件的对应关系

锚链接会导致浏览历史的产生(可以前进后退)但是不会刷新页面

前端路由的工作方式:

  1. 用户点击路由链接,导致url地址栏中的hash地址改变(onhashchange事件)
  2. 前端路由监听到了hash地址的变化
  3. 前端路由把hash地址对应的组件渲染到浏览器中

路由基本用法

基本配置:

  1. 安装vue-router包 npm install vue-router -S

  2. 创建路由模块

    1. 创建 src/router/index.js

    2. image.png

    3. 导入并挂载路由模块 在main.js中render函数下声明router

    4. 声明路由链接和占位符

    5. 在app.vue中使用 <router-view>作为占位符

    6. 在router/index.js中配置链接

      在构造函数中生成名为routers的数组,放入地址与组件的对应关系(路由规则),要记住需要导入组件

      routers: [
      	{path: '/home', component: Home},
          {path: '/about', component: About},
      ]
      

在使用组件时,可以用a标签跳转到锚链接,但是最好使用router-link标签代替a标签

<router-link to='/home'>首页</router-link>

路由重定向

用户在访问地址A的时候,强制用户跳转到地址B,从而展示特定的组件页面

通过redirect实现

routers: [
	{path: '/', redirect: '/home'}
	{path: '/home', component: Home},
    {path: '/about', component: About},
]

嵌套路由

通过路由实现组件的嵌套展示,简单的说就是通过路由跳转多次,比如app->about->tab1

注意子级路由链接要带上父组件的hash地址,比如tab1的地址应写为:<router-link to='/about/tab1'>

声明子路由规则时需要使用children属性,子路由规则path可以不加斜线

routers: [
	{path: '/', redirect: '/home'}
	{path: '/home', component: Home},
    {path: '/about', component: About,redirect: '/about/tab1',children: [
        {path: 'tab1', component: Tab1},
        {path: 'tab2', component: Tab2}
    	]
    },
]

除了直接重定向,还可以使用默认子路由,如果children中某个规则的path值为空字符串,则其为默认子路由

routers: [
	{path: '/', redirect: '/home'}
	{path: '/home', component: Home},
    {path: '/about', component: About,children: [
        {path: '', component: Tab1},
        {path: 'tab2', component: Tab2}
    	]
    },
]

动态路由

如果很多类似的路由规则 /movie/1-100,如果重复定义规则则太过繁琐,可以把hash地址中可变的部分定义为参数项,从而提高复用性。

image.png

在router-link中无论是movie/1还是movie/2都会展示Movie组件,在Movie组件中可以根据id的值来展示对应电影的详情信息

**注意:**在hash地址中,/后面的参数项叫做 路径参数,而?后面的参数项叫做 查询参数

路由参数可以通过两种方式获取:

​ 1.通过Movie组件的this.$route.params.id来获取id值,从而进行id的传输进而展示特定的内容

​ 2.给当前路由规则开启props属性: props: ture,然后在组件的props中接收id

而查询参数则可以:

​ 1.通过Movie组件的this.$route.params.query来获取

导航

点击链接导致组件切换被称为 导航

导航可分为声明式导航和编程式导航

**声明式导航:**在浏览器中,点击链接实现导航(a标签或router-link标签)

**编程式导航:**在浏览器中,调用api实现导航(比如使用location.href)

编程式导航

this.$router.push('hash地址')

  • 跳转到指定hash地址,并且有浏览记录(可以前进后退)

this.$router.replace('hash地址')

  • 跳转到指定hash地址,并且替换掉当前的历史记录(无法后退回原来页面)

this.$router.go(数值n)

  • 在浏览历史中进行n步的前进(n为负数则为后退)
  • this.router.go(1)=this.router.go(1) = this.router.forward
  • this.router.go(1)=this.router.go(-1) = this.router.back

导航守卫

导航守卫可以控制路由的访问权限

全局前置守卫(全局生效,跳转前生效)

在index.js的router实例对象中调用beforeEach方法,每次发生导航跳转时都会调用回调函数

image.png

如果要让用户跳转到别的页面(不是to中的页面),可以用next('hash地址'),比如未登录的用户跳转到next('/login')

如果强制停留在当前页面,可以使用 next(false)

第八天 头条项目

学习目标

doc.toutiao.liulongbin.top/

1.项目结构

可以看到src中多了个views文件夹,其中也是组件

如果某个组件是通过路由来进行切换的,放在views

而可复用的组件,则放在components中

2.vant安装和配置

npm i vant -S进行安装

选择方式三安装,最快捷,虽然会很大,但是上线的时候可以采取措施让其优化

3.使用tabbar组件并开启路由模式

路由相关的组件页面放在views中,所以Home和User都放在views中

在app组件中生成一个tabbar并声明一个router-view标签来存放路由跳转的内容

开启tabbar的路由模式会自动切换高亮

image.png

image.png

4.制作home的header

使用navbar组件制作header

image.png

由于navbar和tabbar都是fixed,脱离标准文档流,所以应该设置一下上下边距以免内容无法显示

审查元素,找到控制颜色的元素,进行样式覆盖,注意十六进制颜色不要写成字符串,同时由于__title没有data-v,data-v实际上加给了hom-container,所以需要使用deep

image.png

如需要对一个组件所有实例都改变样式,需要定制主题

5.封装request模块

可以创建request模块,创建axios实例、配置baseURL并且输出,避免网址的重复输入

image.png

6.封装initArticleList方法

由于进入首页一开始就要拿到文章并且渲染,所以应该在created生命周期中写入initArticleList方法

image.png

async和await一起使用,将一个promise对象的值抽取出来,同时使用解构赋值{ data:res }只讲获得的数据的data分离保存,并且给与res的新名称,通过params参数指定查询参数