父子组件
app.vue
javascript
<template>
<div id="app">
<!--
父组件,
1. 想要往子组件传参,添加标签属性传值
2. 子组件props接收
-->
<h2>建国的黑店</h2>
<!-- <my-product title='无敌猪肘子' price="199" desc="好吃极了" />
<my-product title='爽口大西瓜' price="99" desc="忒甜了" />
<my-product title='红烧大排骨' price="299" desc="贵死了" /> -->
<!-- 加冒号和不加冒号的区别: 是否是动态绑定,动态绑定的意思就是使用的是data中的数据 -->
<my-product v-for="item in list" :key="item.id"
:title="item.title"
:price="item.price"
:desc="item.desc"
:id="item.id"
@cutPrice="handlePrice"
/>
</div>
</template>
<script>
// import 组件对象 from '路径'
// ==> 组件对象 不是一个组件名,但是我们一般在这里命名的时候就和组件名写成一样
import MyProduct from './components/05.MyProduct'
export default {
name: 'App',
data(){
return {
list: [
{ id: 1, title: '无敌猪肘子', price: 188 ,desc:'美味极了'},
{ id: 2, title: '爽口大西瓜', price: 360, desc:'爽歪歪'},
{ id: 3, title: '美味大闸蟹', price: 42, desc:'泰裤辣'}
]
}
},
methods:{
handlePrice(price, id){
console.log(`让id为${id}的商品价格砍${price}元素`)
const good = this.list.find(el => el.id === id) // 找到要修改的那个对象
// good.price -= price
if (good.price - price < 10){
return alert('不能再砍了,砍骨折了')
} else {
good.price -= price
}
}
},
// 组件导入
components:{
// "组件名":组件对象
// 组件名,=> 推荐大驼峰
MyProduct
}
}
</script>
子组件
javascript
<template>
<!-- 子组件 -->
<div class="goods">
<h3>{{title}}</h3>
<p>{{price}}</p>
<p>{{desc}}</p>
<button @click='cutPrice(id)'>砍一刀--{{id}}</button>
</div>
</template>
<script>
export default {
name:'MyProduct',
props:['title', 'price', 'desc','id'],
methods:{
cutPrice(id){
// 不能再子组件中去修改父组件穿过来的props值
// 数据流向 ==> 从父组件,到子组件
// this.price = this.price - 10
// 如果自组件想要修改父组件传的数据
// 怎么办呢,通知父组件,让父组件自己去修改这个数据
// 1. 第一个参数,子组件自定义的事件名,这个事件会在组件注册监听
// 2. 第二个参数,要传递给父组件的参数(信息)
// 如果要给对应的那个对象砍一刀,可以给一个id标识
this.$emit('cutPrice', 10, id)
// this.$emit('changePrice', 10)
}
}
}
</script>
<style scoped>
.goods{
border:1px solid skyblue;
width: 300px;
height: 200px;
}
</style>
代码块的配置
javascript
// 创建项目
// cmd 进入文件夹 vue create vue-demo
// cd vue-demo
// code . 以 vue-demo为项目目录 打开vscode
找到代码块配置
javascript
Ctrl + shift + P 输入 snippet 找到configure
新建一个snippets file 配置
自定义一个名字, 输入配置
javascript
{
// Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
"Print to console": {
"prefix": "vue",
"body": [
"<template>",
" <div>$0</div>",
"</template>",
"",
"<script>",
"export default {",
" components: {},",
" props: {},",
" data() {",
" return {",
" }",
" },",
" watch: {},",
" computed: {},",
" created() {},",
" mounted() {},",
" methods: {},",
"}",
"</script>",
"<style scoped>",
"</style>"
],
"description": "Log output to console"
}
}
单向数据流
app.vue父组件
javascript
<template>
<div id="app">
<my-demo :person="obj"></my-demo>
<my-test :person="obj"></my-test>
</div>
</template>
<script>
import MyDemo from './components/01-单向数据流.vue'
import MyTest from './components/01-MyTest.vue'
export default {
name: 'App',
components: {
MyDemo,
MyTest
},
data(){
return {
obj:{
name:'海龙',
age:6,
demo:{
price:10
}
}
}
},
}
</script>
子组件 01-单向数据流.vue
javascript
<template>
<div>
<h2>我是子组件</h2>
<p>{{p.demo.price}}</p>
<button @click="changeAge">点击改变年龄</button>
</div>
</template>
<script>
export default {
components: {},
props: {
person:Object
},
data() {
return {
// 3. 可以使用拷贝,切断数据之间的联系
// 浅拷贝 ---> 这个对象只有一层。。。
// p:{...this.person},
// 深拷贝
p:JSON.parse(JSON.stringify(this.person))
}
},
methods:{
changeAge(){
// 1. 修改props中的person 实际上改的还是父组件的person对象
// 单向数据流 ---> 不能直接修改props中的变量
this.person.age = 9
console.log(this.p)
// 2. 可以在data中声明一个变量接收props
// 但是父组件传递给子组件的是一个引用类型的数据.. 依然指向同一个地址
// this.p.age = 9
this.p.demo.price = 99
}
},
}
</script>
<style scoped>
</style>
01-MyTest.vue
javascript
<template>
<div>
<h2>我是子组件</h2>
<p>{{person}}</p>
<button @click="changeAge">点击改变年龄</button>
</div>
</template>
<script>
export default {
components: {},
props: {
person:Object
},
methods:{
},
data() {
return {
}
},
}
</script>
<style scoped>
</style>
为什么data是一个函数
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 每个组件实例 都是一个独立的对象,对象中有data数据,每个组件绑定的数据必须是不同的地址
// 每个组件应该有自己独立的数据,互不影响
// const obj = {}
// const obj1 = obj
// const obj2 = obj
// const obj3 = obj
// console.log(obj1 === obj2)
// 通过一个工厂函数(返回对象的函数)返回
function fn(){
// 每次调用函数的时候,都在内存中开辟了一个新的空间,创建了一个新的对象
const obj = {}
return obj
}
const obj1 = fn()
const obj2 = fn()
const obj3 = fn()
console.log(obj1 === obj2)
</script>
</body>
</html>
eslint校验
javascript
// 1. 通过编辑器设置:
在VSCode的设置中,可以添加以下设置来关闭ESLint校验:
"eslint.enable": false
// 2. 可以在项目根目录下创建一个.vscode目录,
// 这将仅禁用当前项目中的ESLint校验。
// 并在其中创建一个名为settings.json的文件。在settings.json中添加以下设置来禁用ESLint校验:
{
"eslint.enable": false
}
// 3. 禁用某个文件的eslint
/* eslint-disable */
// 4. 禁用指定规则
/* eslint-disable no-alert */
这个注释将禁用ESLint中的no-alert规则,允许您在代码中使用alert()方法而不会触发ESLint错误。
// 5. 禁用指定行
alert("Hello world!"); // eslint-disable-line
// 禁用下一行
// eslint-disable-next-line
// 6. 禁用指定段落
/* eslint-disable */
代码块
/* eslint-enable */
props校验
app.vue
javascript
<template>
<div id="app">
<!--
动态绑定 :money="1000" number
money="1000" string
动态绑定,如果加了: 外层的引号就不要看了
-->
<my-demo :money="1000" :flag="true" :panda="obj"></my-demo>
</div>
</template>
<script>
import MyDemo from './components/MyDemo.vue'
export default {
name: 'App',
components: {
MyDemo,
},
data(){
return {
money:1000,
age:99,
obj:{
name:'花花'
}
}
},
}
</script>
子组件
javascript
<template>
<div>
<h2>我是子组件</h2>
<p>{{ money }}</p>
<p>{{ flag }}</p>
<p>{{ age }}</p>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
components: {},
props: {
// 1. 单个的类型校验
money:Number,
flag:Boolean,
// 2. 某个值可以有多个可能性
params:[Number, String],
// 3. 必填项
age:{
type:Number,
required:true
},
// 4. 默认值
// 默认值和必填项只能有一个,互斥的
msg:{
type:String,
default:'hello world'
}
// 5. 如果要给复杂数据类型,添加默认值,必须用工厂函数返回一个对象
panda:{
type:Object,
// default(){
// return { name:'menglan'}
// },
// 默认给一个空对象
// default:() => ({})
},
arr:{
type:Array,
// 默认给一个空数组
// default:() => []
// 默认一个空数组
default: _ => [] // eslint-disable-line 可以取消校验
}
},
}
</script>
<style scoped>
</style>
组件通讯注意
javascript
// 父子组件传参
// 父组件到子组件:
// 1. 父组件通过 v-bind 添加标签属性,给子组件传值
// <Son money="100" :flag="true" :food="food"></Son>
// 2. 子组件中,通过props来接收
// props:['money', 'flag', 'food']
// 子组件到父组件
// 1. 在子组件中,通过 this.$emit('自定义事件名', 参数1,参数2)
// 2. 在父组件中
// <Son money="100" @自定义事件名="处理函数"></Son>
// methods:{
// 处理函数名(参数1, 参数2){
// // 逻辑
// }
// }
v-model原理
javascript
<template>
<div id="app">
<input type="text" v-model="msg1">
<hr>
<!-- 1. 单独写了:value 数据变化,视图更新 -->
<!-- 2. 视图变化-> 数据更新 @input事件监听页面的输入 -->
<input type="text" :value="msg2" @input="handleInput">
</div>
</template>
<script>
export default {
name: 'App',
data(){
return {
msg1:666,
msg2:777
}
},
methods:{
handleInput(e){
console.log(e.target.value) // string
this.msg2 = +e.target.value
}
}
}
</script>
v-model.lazy ==> :value + change事件
vue
<template>
<div id="app">
<input type="text" v-model="msg1">
<hr>
<h3>:value 和 @input 组合</h3>
<!-- 1. 单独写了:value 数据变化,视图更新 -->
<!-- 2. 视图变化-> 数据更新 @input事件监听页面的输入 -->
<input type="text" :value="msg2" @input="handleInput">
<hr>
<h3>:value 和 change事件的组合</h3>
<!-- .lazy 失去焦点或者回车的时候修改数据 -->
<input type="text" v-model.lazy="desc">
<!-- 1. :value 实现了数据变化 视图更新 -->
<!-- 2. @change 实现视图变化,数据更新 -->
<!-- <input type="text" :value="desc" @change="handleChange"> -->
<hr>
<h3>:checked 和 change事件</h3>
<!-- <input type="checkbox" v-model="flag"> -->
<!-- $event 在模板上,表示事件对象 -->
<input type="checkbox" :checked="flag" @change="flag = $event.target.checked">
<!-- 1. 默认情况 v-model :value + @input -->
<!-- 2. v-model.lazy input是change事件 :value + @change -->
<!-- 3. 如果input是checkbox :checked + @change -->
</div>
</template>
<script>
export default {
name: 'App',
data(){
return {
msg1:666,
msg2:777,
desc:'描述~~~',
flag:true
}
},
methods:{
handleInput(e){
// 默认参数就是事件对象
console.log(e.target.value) // string
this.msg2 = +e.target.value
},
handleChange(e){
console.log(e.target.value)
this.desc = e.target.value
}
}
}
</script>
准备结构
vue
<template>
<div>
<div class="box">
<h4>商品 - 大衣</h4>
<h5>价格 - 99 元</h5>
<!-- 需求:将下面的数字加减框,封装成一个通用的组件 -->
<div>
<button>-</button>
<span>1</span>
<button>+</button>
</div>
</div>
</div>
</template>
<style>
.box{
border:1px solid skyblue;
width:200px;
height:200px;
margin:10px;
padding:10px
}
</style>
app.vue
vue
<template>
<div>
<div class="box">
<h4>商品 - 大衣</h4>
<h5>价格 - 99 元</h5>
<!-- 需求:将下面的数字加减框,封装成一个通用的组件 -->
<number-box></number-box>
</div>
</div>
</template>
<script>
import NumberBox from './components/NumberBox.vue'
export default {
components: {
NumberBox
},
props: {},
}
</script>
<style>
.box{
border:1px solid skyblue;
width:200px;
height:200px;
margin:10px;
padding:10px
}
</style>
number-box.vue
vue
<template>
<div>
<button>-</button>
<span>1</span>
<button>+</button>
</div>
</template>
<script>
export default {
components: {},
props: {},
}
</script>
<style scoped>
</style>
v-model 作用于组件
子组件
vue
<template>
<div>
<button @click="sub">-</button>
<span>{{value}}</span>
<button @click="add">+</button>
</div>
</template>
<script>
export default {
data(){
return {
}
},
props: {
value:Number
},
methods:{
sub(){
// eslint-disable-next-line
// 让父元素去修改这个props传过来的value
// 10 - 1
this.$emit('input', this.value - 1)
// this.value--
},
add(){
this.$emit('input', this.value + 1)
}
},
components: {},
}
</script>
<style scoped>
span{
margin:10px;
}
</style>
父组件
vue
<template>
<div>
<div class="box">
<h4>商品 - 大衣</h4>
<h5>价格 - 99 元</h5>
<!-- 需求:将下面的数字加减框,封装成一个通用的组件 -->
<!-- $event -> 作为形参接收 自定义事件传过来的第一个参数 -->
<!-- <number-box :value="count" @input="count = $event"></number-box> -->
<number-box :value="count" @input="handleInput"></number-box>
<!-- v-model底层 ==> :value + @input -->
<number-box v-model="count"></number-box>
</div>
</div>
</template>
<script>
import NumberBox from './components/NumberBox.vue'
export default {
data(){
return {
count:10
}
},
methods:{
handleInput(val){
this.count = val
}
},
components: {
NumberBox
},
props: {},
}
</script>
<style>
.box{
border:1px solid skyblue;
width:200px;
height:200px;
margin:10px;
padding:10px
}
</style>
select框
子组件
vue
<template>
<!-- select框的value值就是选中哪个option的value值 -->
<select :value="value" @change="handleChange">
<option value="101">上海</option>
<option value="103">北京</option>
<option value="107">广州</option>
<option value="110">深圳</option>
</select>
</template>
<script>
export default {
props:{
value:String
},
methods:{
handleChange(e){
// console.log(e.target.value)
// v-model应用于组件时,这个自定义事件必须是input
this.$emit('input', e.target.value)
}
}
}
</script>
<style>
</style>
父组件
vue
<template>
<div>
<h2>选中了哪个城市 --- {{cityId}}</h2>
<!-- <base-select :value="cityId" @input="handleChange"></base-select> -->
<base-select v-model="cityId"></base-select>
<!-- :value + @input事件 -->
</div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data(){
return {
cityId:'103'
}
},
methods:{
handleChange(val){
console.log(val)
this.cityId = val
}
},
components: {
BaseSelect
},
props: {},
}
</script>
<style>
.box{
border:1px solid skyblue;
width:200px;
height:200px;
margin:10px;
padding:10px
}
</style>
ref和refs
获取组件中的dom元素
vue
<template>
<div>
<h3 ref="myH3">大标题</h3>
<p ref="p">这是一个段落</p>
<button @click="handleClick">获取dom-操作dom</button>
</div>
</template>
<script>
export default {
data(){
return {
}
},
methods:{
handleClick(){
// 1. document.querySelector 找到是整个文档中有没有这个dom元素
// const res = document.querySelector('h3')
// console.log(res)
// 2. ref和refs配合 --> 当前组件中的dom元素
// 1. 给目标元素加ref属性
// 2. 通过this.$refs.xxx 获取dom元素
console.log(this.$refs)
this.$refs.myH3.style.backgroundColor = 'pink'
this.$refs.p.style.color = 'orange'
console.log(this.$refs.myH3)
console.log(this.$refs.p)
}
},
}
</script>
<style>
.box{
border:1px solid skyblue;
width:200px;
height:200px;
margin:10px;
padding:10px
}
</style>
refs组件对象
vue
<template>
<div>
<h2>我是star组件</h2>
<div>{{age}}</div>
<button @click="sing">唱歌</button>
<button @click="dance">跳舞</button>
</div>
</template>
<script>
export default {
name:'MyStar',
data(){
return {
age:18
}
},
methods:{
sing(){
console.log('动词打次~')
},
dance(){
console.log('动词打次~嗨起来')
}
}
}
</script>
<style>
</style>
vue
<template>
<div>
<h3 ref="myH3">大标题</h3>
<p ref="p">这是一个段落</p>
<button @click="handleClick">获取dom-操作dom</button>
<hr>
<my-star ref="myStar"></my-star>
</div>
</template>
<script>
import MyStar from './components/MyStar.vue'
export default {
components:{
MyStar
},
data(){
return {
}
},
methods:{
handleClick(){
// 1. document.querySelector 找到是整个文档中有没有这个dom元素
// const res = document.querySelector('h3')
// console.log(res)
// 2. ref和refs配合 --> 当前组件中的dom元素
// console.log()
// this.$refs.myH3.style.backgroundColor = 'pink'
// this.$refs.p.style.color = 'orange'
// console.log(this.$refs.myH3)
// console.log(this.$refs.p)
// 1. 父组件可以通过$refs调用子组件中的方法
this.$refs.myStar.dance()
this.$refs.myStar.sing()
// 2. 虽然父组件可以访问子组件中的数据,可以操作,还是不太建议这么做
this.$refs.myStar.age++
}
},
}
</script>
<style>
.box{
border:1px solid skyblue;
width:200px;
height:200px;
margin:10px;
padding:10px
}
</style>
获取更新后的dom内容
javascript
<template>
<div>
<p ref="p">数字:{{count}}</p>
<button @click="handleClick">点击+1</button>
</div>
</template>
<script>
export default {
components:{
},
data(){
return {
count:10
}
},
methods:{
handleClick(){
// 执行这一步之后,data中的count已经变化了
this.count++
this.count++
this.count++
this.count++
this.count++
this.count++
// 获取dom元素的内容,dom元素的内容还是最早的样子
// 1. Vue是异步更新dom的, ==> Vue不会再数据修改之后,立即更新dom
console.log(this.$refs.p.innerHTML)
// 只有等dom内容更新之后,再获取
// setTimeout(() => {
// console.log(this.$refs.p.innerHTML)
// }, 0);
const p = new Promise((resolve)=>{
console.log('promise')
resolve()
})
p.then(() => {
console.log(this.$refs.p.innerHTML)
})
}
},
}
</script>
<style>
.box{
border:1px solid skyblue;
width:200px;
height:200px;
margin:10px;
padding:10px
}
</style>
$nextTick获取异步更新后的dom
vue
<template>
<div>
<p ref="p">数字:{{count}}</p>
<button @click="handleClick">点击+1</button>
</div>
</template>
<script>
export default {
components:{
},
data(){
return {
count:20
}
},
methods:{
handleClick(){
// 执行这一步之后,data中的count已经变化了
this.count++
// 假设每+1,就去更新dom
this.count++
// 又更新一次
this.count++
this.count++
this.count++
this.count++
// 获取dom元素的内容,dom元素的内容还是最早的样子
// 1. Vue是异步更新dom的, ==> Vue不会再数据修改之后,立即更新dom
console.log(this.$refs.p.innerHTML)
// 只有等dom内容更新之后,再获取
// setTimeout(() => {
// console.log(this.$refs.p.innerHTML)
// }, 0);
// const p = new Promise((resolve)=>{
// console.log('promise')
// resolve()
// })
// p.then(() => {
// console.log(this.$refs.p.innerHTML)
// })
this.$nextTick(() => {
// 等到dom更新之后,立马触发
console.log(this.$refs.p.innerHTML)
})
}
},
}
</script>
<style>
.box{
border:1px solid skyblue;
width:200px;
height:200px;
margin:10px;
padding:10px
}
</style>
虚拟DOM
https://juejin.cn/post/6994959998283907102 ==> 虚拟dom
javascript
template模板 ==> render()==> 虚拟dom ==> 真实dom
javascript
// this.$nextTick的实现原理 Vue是异步更新dom的(为了优化性能)
// nextTick本质上就是--> 宏微任务的一个降级处理
// promise.then ===> MutationObserver ==> setImmediate ==> setTimeout
// 宏任务
// 0. script
// 1. setTimeout / setInterval
// 2. setImmediate
// 微任务
// 1. promise.then() / catch() 回调函数
// 2. async await
// 3. MutationObserver() ==> 监听DOM的改变
// 4. process.nextTick