Vue从0基础到大神学习完整教程完整教程(附代码资料)主要内容讲述:vue基本概念,vue-cli的使用,vue的插值表达式,{{ gaga }},{{ if (obj.age > 18 ) { } }},vue指令,综合案例 - 文章标题编辑vue介绍,开发vue的方式,基本使用,如何覆盖webpack配置,目录分析与清理,vue单文件组件的说明,vue通过data提供数据,通过插值表达式显示数据,安装vue开发者工具,v-bind指令,v-on指令,v-if 和 v-show,v-model,v-text 和 v-html。day-08vuex介绍,语法,模块化,小结。面经PC端-element (上)初始化,request,router,login模块,layout模块,dashboard模块(了解)。面经PC端 - Element (下)Article / list 列表,Article / add 添加,Article / del 删除,Article / upd 修改,Article / preview 预览,yarn-补充。vue指令(下),成绩案例,计算属性,属性监听v-for,样式处理,基本结构与样式,基本渲染,删除,新增,处理日期格式,基本使用,计算属性的缓存的问题,成绩案例-计算属性处理总分 和 平均分,计算属性的完整写法,大小选,基本使用,复杂类型的监听-监听的完整写法,成绩案例-监听数据进行缓存,配置步骤 (两步),使用演示。vue指令(下),成绩案例,计算属性,属性监听v-for,样式处理,基本结构与样式,基本渲染,删除,新增,处理日期格式,基本使用,计算属性的缓存的问题,成绩案例-计算属性处理总分 和 平均分,计算属性的完整写法,大小选,基本使用,复杂类型的监听-监听的完整写法,成绩案例-监听数据进行缓存,配置步骤 (两步),使用演示。组件化开发,组件通信,todo案例,作业什么是组件化开发,组件的注册,全局注册组件,组件的样式冲突 ,组件通信 - 父传子 props 传值,v-for 遍历展示组件练习,单向数据流,组件通信 - 子传父,props 校验,布局,任务组件todo,列表,删除,修改:不做了!下面代码其实就是我想让大家练习,添加,剩余数量,清空已完成,小选与大选,筛选:作业,本地存储,附加练习_1.喜欢小狗狗吗,附加练习_2.点击文字变色,附加练习_3. 循环展示狗狗,附加练习_4.选择喜欢的狗。v-model,ref 和 nextTick,dynamic 动态组件,自定义指令,插槽,案例:商品列表v-model 语法糖,v-model给组件使用,动态组件的基本使用,自定义指令说明,自定义指令 - 局部注册,自定义指令 - 全局注册,自定义指令 - 指令的值,默认插槽 slot,后备内容 (默认值),具名插槽,作用域插槽,案例概览,静态结构,MyTag 组件,MyTable 组件。生命周期,单页应用程序与路由,vue-router研究生命周期的意义,生命周期函数(钩子函数),组件生命周期分类,SPA - 单页应用程序,路由介绍,vue-router介绍,vue-router使用,配置路由规则,路由的封装,vue路由 - 声明式(a标签转跳)导航,vue路由 - 重定向和模式,vue路由 - 编程式(JS代码进行转跳)导航,综合练习 - 面经基础版,组件缓存 keep-alive。面经 H5 端 - Vant(上)初始化,vant,axios封装,router,主题定制-了解,登录&注册。面经 H5 端 - Vant(下)列表,详情,收藏 与 喜欢,我的(个人中心)。Day01_vuex今日学习目标(边讲边练),1.vuex介绍,2.vuex学习内容,3.vuex例子准备,vuex-store准备,5.vuex-state数据源,【vuex-mutations定义-同步修改,【vuex-mutations使用,8.vuex-actions定义-异步修改,9.vuex-actions使用,10.vuex-重构购物车-准备Store,11.vuex-重构购物车-配置项(上午结束),vuex-getters定义-计算属性,13.vuex-getters使用,14.vuex-modules定义-分模块,15.分模块-影响state取值方式,16.分模块-命名空间,扩展: 使用Devtools调试vuex数据。
全套笔记资料代码移步: 前往gitee仓库查看
感兴趣的小伙伴可以自取哦,欢迎大家点赞转发~
组件化开发
什么是组件化开发
组件化开发 指的是:根据封装的思想,把页面上 可重用的部分
封装为 组件
,从而方便项目的 开发 和 维护。
一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为
例如: 所展示的效果,就契合了组件化开发的思想。
用户可以通过拖拽组件的方式,快速生成一个页面的布局结构。
前端组件化开发的好处主要体现在以下两方面:
-
提高了前端代码的复用性和灵活性
-
提升了开发效率和后期的可维护性
vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue
。
组件的注册
刚才我们创建使用的是 App.vue 根组件, 这个比较特殊, 是最大的一个根组件
而App.vue根组件内, 还可以写入一些小组件, 而这些组件, 要使用, 就需要先注册!
注册组件有两种注册方式: 分为“全局注册”和“局部注册”两种
- 被全局注册的组件,可以在任意的组件模板范围中使用 通过
Vue.component()
- 被局部注册的组件,只能在当前注册的组件模板范围内使用 通过
components
局部注册
- 把独立的组件封装一个
.vue文件中
,推荐放到components
文件夹
components
-- HmHeader.vue
-- HmContent.vue
-- HmFooter.vue
- 通过组件的
components
配置 局部注册组件
import HmHeader from './components/HmHeader'
import HmContent from './components/HmContent'
import HmFooter from './components/HmFooter'
export default {
// data methods filters computed watch
components: {
// 组件名: 组件
// 组件名:注意,不能和html内置的标签重名
// 使用的时候:直接通过组件名去使用
// HmHeader HmHeader hm-header
HmHeader,
HmContent,
HmFooter
}
}
==注意点:注册的组件的名字不能和HTML内置的标签重名==
- 可以在模板中使用组件,,,,使用组件和使用html的标签是一样的,,,可以多次使用
<template>
<div>
<!-- 组件注册好了,就跟使用html标签一样了 -->
<hm-header></hm-header>
<hm-content></hm-content>
<hm-footer></hm-footer>
</div>
</template>
==局部注册的组件只能在当前组件中使用==
全局注册组件
- 在
components
文件夹中创建一些新的组件
components
-- HmHeader.vue
-- HmContent.vue
-- HmFooter.vue
- 在
main.js
中通过Vue.component()
全局注册组件
import HmHeader from './components/HmHeader'
import HmContent from './components/HmContent'
import HmFooter from './components/HmFooter'
// 全局注册
// Vue.component(名字, 组件)
Vue.component('HmHeader', HmHeader)
Vue.component('HmContent', HmContent)
Vue.component('HmFooter', HmFooter)
- 使用
<template>
<div>
<!-- 组件注册好了,就跟使用html标签一样了 -->
<hm-header></hm-header>
<hm-content></hm-content>
<hm-footer></hm-footer>
</div>
</template>
==注意:全局注册的组件 可以在任意的组件中去使用==
组件名的大小写
在进行组件的注册时,定义组件名的方式有两种:
- 注册使用短横线命名法,例如 hm-header 和 hm-main
Vue.component('hm-button', HmButton)
使用时 <hm-button> </hm-button>
- 注册使用大驼峰命名法,例如 HmHeader 和 HmMain
Vue.component('HmButton', HmButton)
使用时 <HmButton> </HmButton>
和 <hm-button> </hm-button>
都可以
推荐定义组件名时, 用大驼峰命名法, 更加方便
全局注册
Vue.component('HmButton', HmButton)
局部注册:
components: {
HmHeader,
HmMain,
HmFooter
}
使用时, 推荐遵循html5规范, 小写横杠隔开
<hm-header></hm-header>
<hm-main></hm-main>
<hm-footer></hm-footer>
通过 name 注册组件 (了解)
组件在开发者工具中显示的名字可以通过name进行修改
在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称
组件内容:
<template>
<button>按钮组件</button>
</template>
<script>
export default {
name: 'HmButton'
}
</script>
<style lang="less">
button {
width: 80px;
height: 50px;
border-radius: 5px;
background-color: pink;
}
</style>
进行注册:
import HmButton from './components/hm-button.vue'
Vue.component(HmButton.name, HmButton) // 等价于 app.component('HmButton', HmButton)
组件的样式冲突 scoped
默认情况下,写在组件中的样式会全局生效
,因此很容易造成多个组件之间的样式冲突问题。
组件样式默认会作用到全局, 就会影响到整个 index.html 中的 dom 元素
全局样式
: 默认组件中的样式会作用到全局局部样式
: 可以给组件加上 scoped 属性, 可以让样式只作用于当前组件
<style lang="less" scoped>
div {
background-color: pink;
}
</style>
原理:
- 添加scoped后, 会给当前组件中所有元素, 添加上一个自定义属性
- 添加scoped后, 每个style样式, 也会加上对应的属性选择器
最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
组件通信
每个组件都有自己的数据, 提供在data中, 每个组件的数据是独立的, 组件数据无法互相直接访问 (合理的)
但是如果需要跨组件访问数据, 就需要用到组件通信
组件通信的方式有很多: 现在先关注两种, 父传子 子传父
组件通信 - 父传子 props 传值
语法:
- 父组件通过给子组件加属性传值
<Son price="100" title="不错" :info="msg"></Son>
- 子组件中, 通过props属性接收
props: ['price', 'title', 'info']
需求: 封装一个商品组件 my-product
my-product.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ info }}</p>
</div>
</template>
<script>
export default {
props: ['title', 'price', 'info']
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
v-for 遍历展示组件练习
需求: 遍历展示商品列表
假定, 发送请求回来的商品数据,
list: [
{ id: 1, proname: '超级好吃的棒棒糖', proprice: 18.8 },
{ id: 2, proname: '超级好吃的大鸡腿', proprice: 34.2 },
{ id: 3, proname: '超级无敌的冰激凌', proprice: 14.2 }
]
v-for 遍历展示
<template>
<div class="container">
<h3>我是app组件的内容</h3>
<my-product
v-for="item in list" :key="item.id"
:price="item.proprice"
:title="item.proname"
:info="msg">
</my-product>
</div>
</template>
单向数据流
/*
在vue中需要遵循单向数据流原则
1. 父组件的数据发生了改变,子组件会自动跟着变
2. 子组件不能直接修改父组件传递过来的props props是只读的
*/
==如果父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,,,,也应该避免==
组件通信 - 子传父
需求: 砍价
- 子组件可以通过
this.$emit('事件名', 参数1, 参数2, ...)
触发事件的同时传参的
this.$emit('sayPrice', 2)
- 父组件给子组件注册一个自定义事件
<my-product
...
@sayPrice="sayPrice">
</my-product>
父组件并提供对应的函数接收参数
methods: {
sayPrice (num) {
console.log(num)
}
},
props 校验
props 是父传子, 传递给子组件的数据, 为了提高 子组件被使用时 的稳定性, 可以进行props校验, 验证传递的数据是否符合要求
默认的数组形式, 不会进行校验, 如果希望校验, 需要提供对象形式的 props
风格指南:
props: {
...
}
props 提供了多种数据验证方案,例如:
- 基础的类型检查 Number
- 多个可能的类型 [String, Number]
- 必填项校验 required: true
- 默认值 default: 100
- 自定义验证函数
官网语法: [地址](
{
props: {
// 基础的类型检查
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// -------------------------------------------------------------------------
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
}
todo案例
完整效果演示
布局
- App.vue
<template>
<section class="todoapp">
<!-- 头部:输入框 -->
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="输入新计划" autofocus>
</header>
<!-- 列表: -->
<section class="main">
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li class="completed">
<div class="view">
<input class="toggle" type="checkbox" checked>
<label>吃饭</label>
<button class="destroy"></button>
</div>
</li>
</ul>
</section>
<!-- 底部:状态栏 -->
<footer class="footer">
<span class="todo-count">剩余<strong>0</strong>未完成 </span>
<ul class="filters">
<li>
<a class="selected" href="#/">全部</a>
</li>
<li>
<a href="#/active">未完成</a>
</li>
<li>
<a href="#/completed">已完成</a>
</li>
</ul>
<button class="clear-completed">清除已完成</button>
</footer>
</section>
</template>
- 在
main.js
中导入通用的样式 app.vue内部
import './styles/todos.css'
任务组件todo
- 把任务内容单独拿出来进行封装
todo.vue
<template>
<li class="completed">
<div class="view">
<input class="toggle" type="checkbox" checked>
<label>吃饭</label>
<button class="destroy"></button>
</div>
</li>
</template>
<script>
</script>
<style>
</style>
- 导入app.vue组件,进行注册使用
import todo from './components/todo.vue';
export default {
// 局部注册组件
components: {
todo
},
}
列表
- 在
App.vue
提供了任务列表数据
data () {
return {
list: [
{ id: 1, name: '吃饭', isDone: true },
{ id: 2, name: '睡觉', isDone: false },
{ id: 3, name: '打豆豆', isDone: true }
]
}
}
- App.vue通过父传子,把每一个循环的数据传递给子组件
<todo
v-for="item in list"
:key="item.id"
:item="item">
</todo>
todo.vue
接受数据,且渲染
export default {
props: ['item']
}
<li :class="{'completed': item.isDone}">
<div class="view">
<input class="toggle" type="checkbox" :checked="item.isDone">
<label>{{item.name}}</label>
<button class="destroy"></button>
</div>
</li>
删除
- 给删除按钮注册点击事件
<button class="destroy" @click="del(item.id)"></button>
- 通过
$emit
把值传给父组件
methods: {
del (id) {
this.$emit("onedel",id);
}
}
- 父组件中的子组件 注册事件
<todo
v-for="item in list"
:key="item.id"
:item="item"
@onedel="del">
</todo>
- 父组件通过回调函数接受参数
methods: {
del (id) {
this.list = this.list.filter(item => item.id !== id);
},
}
修改:不做了!下面代码其实就是我想让大家练习
- 给checkbox注册change事件
<input class="toggle" type="checkbox" :checked="item.isDone" @change="change(item.id)">
- 子传父,让父组件修改
change(id){
this.$emit("onechange",id);
},
- 父组件中的子组件注册事件
<todo
v-for="item in list"
:key="item.id"
:item="item"
@onechange="change"
@onedel="del">
</todo>
- 父组件修改状态
change (id) {
const result = this.list.find(item => item.id === id)
result.isDone = !result.isDone;
},
添加
- 父级组件中通过v-model获取到任务的名字
<input class="new-todo" placeholder="输入新计划" autofocus v-model="name" @keyup.enter="add">
data () {
return {
name: ''
}
},
- 回车的时候,获取数据,把数据添加到数组中,且清空数据;
methods: {
add () {
this.list.unshift({
id: Date.now(),
name:this.name,
isDone: false
});
this.name = "";
},
}
剩余数量
- 剩余数量:计算属性 统计没有完成的任务的数组
computed:{
// 没有完成的数组
left_list:function () {
return this.list.filter(item=>item.isDone==false).length;
},
},
- 视图中使用
<span class="todo-count">剩余<strong>{{left_list}}</strong>未完成 </span>
清空已完成
- 清空:筛选没有任务的数组,把this.list重新赋值
<button class="clear-completed" @click="clear">清除已完成</button>
clear(){
this.list = this.list.filter(item=>item.isDone==false);
}
小选与大选
- 小选:设置计算属性,统计是否所有任务全部完成
ck_all:(value){
return this.list.every(item=>item.isDone==true); find filter every sort reduce slice
}
- 设置给全选按钮:
<input id="toggle-all" class="toggle-all" type="checkbox" :checked="ck_all">
- 大选:
- 需要获取全选按钮的状态值,需要把
:checked="ck_all" 改为 v-model="ck_all"
- 计算属性如果被修改,需要用到完整写法
ck_all:{
get(){
return this.list.every(item=>item.isDone);
},
set(value){
this.list.forEach(item => item.isDone = value);
}
}
筛选:作业
- tab栏样式切换:注册点击事件,传入不同的状态;根据获取的不同状态进行类名选择
<li>
<a :class="{'selected':status=='all'}" href="#/" @click="select('all')">全部</a>
</li>
<li>
<a :class="{'selected':status=='none'}" href="#/active" @click="select('none')">未完成</a>
</li>
<li>
<a :class="{'selected':status=='done'}" href="#/completed" @click="select('done')">已完成</a>
</li>
- 执行函数:初始化赋值为
status:'all'
select(status){
this.status = status;
}
- 选择不同的状态,就展示不同的数据,那么数据是由状态决定的,计算属性
// 展示的列表
show:function () {
let arr;
switch (this.status) {
case "all":
arr = this.list;
break;
case "none":
arr = this.list.filter(item=>item.isDone==false);
break;
case "done":
arr = this.list.filter(item=>item.isDone==true);
break;
}
return arr;
}
- 循环遍历数据更换为:
v-for="item in show"
本地存储
- 把数据真实保留浏览器上,这样的话大家刷新完页面后,数据状态还是不变!
- 做本地存储:
- 侦听器:只需要写一个地方,专门用于侦听list;如果变,直接保存到本地;
- 数据发生改变的时候存:【老办法:考虑很多地方!本地存储遍地开花】
- 新增:做了
- 修改:没有具体函数,做本地存储,貌似不好做了!
- 删除:做了
- 清除已完成:做了!
- 监视数组的变化
watch: {
list: {
deep: true,
handler(newValue) {
localStorage.setItem('todoList', JSON.stringify(newValue))
}
}
}
- data中默认使用本地的数据
data(){
return {
list: JSON.parse(localStorage.getItem('todoList')) || [],
}
},
作业
附加练习_1.喜欢小狗狗吗
目标: 封装Dog组件, 用来复用显示图片和标题的
效果:
正确答案(==先不要看==)
components/practise/Dog1.vue
<template>
<div class="my_div">
<img
src="
alt=""
/>
<p>这是一个孤独可怜的狗</p>
</div>
</template>
<script>
export default {};
</script>
<style>
.my_div {
width: 200px;
border: 1px solid black;
text-align: center;
float: left;
}
.my_div img {
width: 100%;
height: 200px;
}
</style>
在App.vue中使用
<template>
<div>
<Dog></Dog>
<Dog/>
</div>
</template>
<script>
import Dog from '@/components/practise/Dog1'
export default {
components: {
Dog
}
}
</script>
<style>
</style>
总结: 重复部分封装成组件, 然后注册使用
附加练习_2.点击文字变色
目标: 修改Dog组件, 实现组件内点击变色
提示: 文字在组件内, 所以事件和方法都该在组件内-独立
图示:
正确代码(==先不要看==)
components/practise/Dog2.vue
<template>
<div class="my_div">
<img
src="
alt=""
/>
<p :style="{backgroundColor: colorStr}" @click="btn">这是一个孤独可怜的狗</p>
</div>
</template>
<script>
export default {
data(){
return {
colorStr: ""
}
},
methods: {
btn(){
this.colorStr = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`
}
}
};
</script>
<style>
.my_div {
width: 200px;
border: 1px solid black;
text-align: center;
float: left;
}
.my_div img {
width: 100%;
height: 200px;
}
</style>
附加练习_3. 循环展示狗狗
目标: 把数据循环用组件显示铺设
数据:
[
{
dogImgUrl:
"
dogName: "博美",
},
{
dogImgUrl:
"
dogName: "泰迪",
},
{
dogImgUrl:
"
dogName: "金毛",
},
{
dogImgUrl:
"
dogName: "哈士奇",
},
{
dogImgUrl:
"
dogName: "阿拉斯加",
},
{
dogImgUrl:
"
dogName: "萨摩耶",
},
]
图示:
正确代码(==不可复制==)
components/practise/Dog3.vue
<template>
<div class="my_div">
<img :src="imgurl" alt="" />
<p :style="{ backgroundColor: colorStr }" @click="btn">{{ dogname }}</p>
</div>
</template>
<script>
export default {
props: ["imgurl", "dogname"],
data() {
return {
colorStr: "",
};
},
methods: {
btn() {
this.colorStr = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
Math.random() * 256
)}, ${Math.floor(Math.random() * 256)})`;
},
},
};
</script>
<style scoped>
.my_div {
width: 200px;
border: 1px solid black;
text-align: center;
float: left;
}
.my_div img {
width: 100%;
height: 200px;
}
</style>
App.vue引入使用把数据循环传给组件显示
<template>
<div>
<Dog v-for="(obj, index) in arr"
:key="index"
:imgurl="obj.dogImgUrl"
:dogname="obj.dogName"
></Dog>
</div>
</template>
<script>
import Dog from '@/components/practise/Dog3'
export default {
data() {
return {
// 1. 准备数据
arr: [
{
dogImgUrl:
"
dogName: "博美",
},
{
dogImgUrl:
"
dogName: "泰迪",
},
{
dogImgUrl:
"
dogName: "金毛",
},
{
dogImgUrl:
"
dogName: "哈士奇",
},
{
dogImgUrl:
"
dogName: "阿拉斯加",
},
{
dogImgUrl:
"
dogName: "萨摩耶",
},
],
};
},
components: {
Dog
}
};
</script>
附加练习_4.选择喜欢的狗
目标: 用户每点击一次狗狗的名字, 就在右侧列表多显示一次名字
效果:
正确代码(==不可复制==)
components/practise/Dog4.vue
<template>
<div class="my_div">
<img :src="imgurl" alt="" />
<p :style="{ backgroundColor: colorStr }" @click="btn">{{ dogname }}</p>
</div>
</template>
<script>
export default {
props: ["imgurl", "dogname"],
data() {
return {
colorStr: "",
};
},
methods: {
btn() {
this.colorStr = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
Math.random() * 256
)}, ${Math.floor(Math.random() * 256)})`;
// 补充: 触发父级事件
this.$emit("love", this.dogname);
},
},
};
</script>
<style scoped>
.my_div {
width: 200px;
border: 1px solid black;
text-align: center;
float: left;
}
.my_div img {
width: 100%;
height: 200px;
}
</style>
App.vue
<template>
<div>
<Dog
v-for="(obj, index) in arr"
:key="index"
:imgurl="obj.dogImgUrl"
:dogname="obj.dogName"
@love="fn"
></Dog>
<hr />
<p>显示喜欢的狗:</p>
<ul>
<li v-for="(item, index) in loveArr" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
import Dog from "@/components/practise/Dog4";
export default {
data() {
return {
// 1. 准备数据
arr: [
{
dogImgUrl:
"
dogName: "博美",
},
{
dogImgUrl:
"
dogName: "泰迪",
},
{
dogImgUrl:
"
dogName: "金毛",
},
{
dogImgUrl:
"
dogName: "哈士奇",
},
{
dogImgUrl:
"
dogName: "阿拉斯加",
},
{
dogImgUrl:
"
dogName: "萨摩耶",
},
],
loveArr: []
};
},
components: {
Dog,
},
methods: {
fn(dogName) {
this.loveArr.push(dogName)
},
},
};
</script>
<style >
</style>