Skip to content

父子组件

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

image.png

新建一个snippets file 配置

image.png

自定义一个名字, 输入配置

image.png

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

image.png

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