vue组件和插槽

01-v-model语法糖

官网文档:组件使用v-model

  • v-model本质上是 value属性和input事件的一层包装

  • v-model的作用:提供数据的双向绑定

    • 数据发生了改变,页面会自动变 v-bind:value

    • 页面输入改变 , 数据会自动变化 v-on:input

  • v-model是语法糖, v-model等价于 给一个input框提供了 :value属性以及 @input事件

很显然如果每次使用input框,都需要提供value和input事件,比较麻烦,所以使用v-model

  • App.vue

<template>
  <div>
    <h1>根组件App.vue</h1>
    <!-- 
      1.v-model = "msg"
        (1)data中的数据变化,表单的值也会变化     :value="msg"
        (2)表单的值发生变化,data中的数据也会变化  @input="msg=$event.target.value"
    -->
    <input type="text" v-model="msg" />
    <hr />
    <!-- 这种写法与上面写法功能一致 -->
    <input type="text" :value="msg" @input="msg = $event.target.value" />
    <hr />
    <!-- 这种写法也与上面写法一致 -->
    <input type="text" :value="msg" @input="doInput" />
    <hr />
  </div>
</template>
​
<script>
export default {
  data() {
    return {
      msg: ""
    };
  },
  methods: {
    doInput(e) {
      this.msg = e.target.value;
    }
  }
};
</script>
​
<style>
</style>

02-组件使用v-model

  • 我们经常遇到一种场景:

    1. 父组件提供一个数据给子组件使用(父传子)

    2. 子组件又需要修改父组件传过来的这个数据,所以需要子传父把值传给父组件。

  • 这种场景可以使用v-model进行简写。

    • 定义组件的时候,注意接收的值叫value, 子传父触发的事件叫 input

如果父传子的props值叫 value, 且 子传父触发的事件叫 input 。 那么这两个功能就可以使用v-model来简写

03-ref和$refs(vue操作dom)

  • 官方文档:API — Vue.js

  • ref作用:在vue中操作dom元素或组件vm实例

    • vue不推荐我们直接操作dom。如果真的要在vue中操作dom,可以使用ref语法

      • 说人话 : vue不能直接操作dom,真的要操作也要按vue规定的语法来。(ref语法)

  • 每个 vue 的组件实例上,都包含一个$refs 对象,里面存储着对应的DOM 元素或组件的引用。

ref语法使用流程语法

(1)给标签添加自定义属性red :<button ref="属性名"></button>

  • vue会自动把页面所有的ref属性,挂载到vue实例的$ref对象中

(2)通过 vm.$refs.属性名 获取该标签

  • 一定要注意 : vue在mounted勾子中完成页面真实DOM渲染,所以最早能获取dom的就是mounted钩子

ref易错点

1.添加的的时候是: ref

2.获取的时候是: $refs

1 给需要获取的 dom 元素或者组件, 添加 ref 属性

<template>
  <div>
    <h1>根组件App.vue</h1>
    <div ref="box">我是div盒子</div>
    <Goods ref="goods"></Goods>
    <button @click="fn">点我获取ref</button>
  </div>
</template>

2 通过 this.$refs.xxx 获取, 拿到组件可以调用组件的方法

<script>
//导入子组件
import Goods from "./components/Goods.vue";
export default {
  //注册组件
  components: { Goods },
  //方法
  methods: {
    fn() {
      // 如果ref给dom元素添加,获取的就是dom对象
      console.log(this.$refs.box);//DOM对象
      // 如果ref给组件添加,获取的就是组件vm实例
      console.log(this.$refs.goods);//组件vue实例
      this.$refs.goods.sayHi();
    }
  }
};
</script>

  • 子组件代码Goods.vue

<template>
  <div>
      <h2>我是子组件</h2>
  </div>
</template>
​
<script>
export default {
    methods: {
        sayHi(){
            console.log('你好我是子组件')
        }
    },
}
</script>
​
<style>
​
</style>

04-$nextTick使用

需求1: 点击按钮, 切换显示输入框

<template>
  <div>
    <!-- 需求1: 点击按钮, 切换显示输入框-->
    <input type="text" v-if="showInput">
    <button @click="fn" v-else>点此搜索</button>
  </div>
</template>
​
<script>
export default {
  data () {
    return {
      showInput: false
    }
  },
  methods: {
    fn () {
      this.showInput = true
    }
  }
}
</script>
​
<style>
​
</style>

需求2: 显示输入框的同时, 要获取焦点

当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的.focus() 方法即可。

直接调用会报错, 因为 vue 是 异步dom更新的 (提升渲染效率), this.showInput = true 执行完时, 实际的 dom 还没渲染出来

<input ref="inp" type="text" v-if="showInput">
​
fn () {
  this.isShowInput = true
  this.$refs.inp.focus()
}

组件的 $nextTick(callback) 方法,会把 callback 回调推迟到下一个 DOM 更新周期之后执行。

通俗的理解是:等组件的DOM 刷新之后,再执行 callback 回调函数。从而能保证 callback 函数可以操作到最新的 DOM 元素。

<template>
  <div>
    <!-- 需求1: 点击按钮, 切换显示输入框
         需求2: 显示输入框的同时, 要获取焦点
     -->
    <input type="text" v-if="showInput" ref="inp">
    <button @click="fn" v-else>点此搜索</button>
  </div>
</template>
​
<script>
export default {
  data () {
    return {
      showInput: false
    }
  },
  methods: {
    fn () {
      // 显示输入框
      this.showInput = true
      // 输入框获取焦点
​
      /* 报错原因: 
        (1) vue更新DOM是一个异步的过程。(虽然设置了showInput为true,但是不会立即显示输入框)
        (2) 异步代码需要等当前队列同步代码全部执行完毕之后才会执行
        解决方案:
        this.$nextTick(callback) : 会等组件的DOM刷新之后再来执行callback回调函数
      */
​
      // this.$refs.inp.focus() // 报错
      this.$nextTick( ()=>{
        this.$refs.inp.focus()
      } )
    }
  }
}
</script>
​
<style>
​
</style>

05-dynamic动态组件

  • 什么是动态组件: 让多个组件使用同一个挂载点,并动态切换,这就是动态组件。

    • 混淆点解读:动态组件 看起来和v-if v-else功能有些类似,但其实两者是不同的。

      • v-if v-else : 只是根据条件来决定渲染哪一个盒子,不能像组件那样复用。

      • 动态组件:通过设置组件名,让一个挂载点可以切换不同的组件

  • App.vue

<template>
  <div>
    <h3>动态组件的演示</h3>
    <!-- 动态组件 => 多个组件使用同一个挂载点, 并可以动态的切换展示 -->
    <button @click="comName = 'UserAccount'">未登录</button>
    <button @click="comName = 'UserInfo'">已登录</button>
    
    <!-- 动态组件:让多个组件使用同一个挂载点,实现动态切换
        <component :is="组件名" ></component>
    -->
    <component :is="comName"></component>
  </div>
</template>
​
<script>
// 导入组件
import UserAccount from './components/UserAccount.vue'
import UserInfo from './components/UserInfo.vue'
​
export default {
  data () {
    return {
      comName: 'UserAccount'
    }
  },
  //注册组件
  components: {
    UserAccount,
    UserInfo
  }
}
</script>

  • UserAccount.vue

<template>
  <div class="user-account form-box">
    <div class="form-box-item">
      <label for="username">账 户: </label>
      <input id="username" type="text" placeholder="请输入用户名">
    </div>

    <div class="form-box-item">
      <label for="password">密 码: </label>
      <input id="password" type="text" placeholder="请输入密码">
    </div>
  </div>
</template>

<script>
export default {
  name: 'UserAccount'
}
</script>

<style lang="less">

</style>

  • UserInfo.vue

<template>
  <div class="user-info form-box">
    <div class="form-box-item">
      <label for="words">人生格言: </label>
      <input id="words" type="text" placeholder="请填写人生格言">
    </div>

    <div class="form-box-item">
      <label for="info">个人简介: </label>
      <input id="info" type="text" placeholder="请填写个人简介">
    </div>

    <div class="form-box-item">
      <label for="hobby">兴趣爱好: </label>
      <input id="hobby" type="text" placeholder="请填写兴趣爱好">
    </div>
  </div>
</template>

<script>
export default {
  name: 'UserInfo'
}
</script>

<style lang="less">

</style>

06-自定义指令

  • 官方文档:自定义指令 — Vue.js

  • 1.复习指令作用 : 给标签添加额外的功能

  • 2.复习指令本质 : 行内自定义属性

  • 3.自定义指令作用 : 给标签添加 vue没有的,额外的功能

1.1-自定义指令:局部注册

  • 局部注册:只能在当前组件使用

  • 需求:

    • 1.添加一个自定义指令 v-focus,作用是让input表单自动聚焦

    • 2.添加一个自定义指令v-color,作用是设置标签文本颜色

<template>
  <div>
    <h1>根组件</h1>
    <!-- 使用指令 v-指令名 -->
    <input type="text" v-focus/>
    <br />
    <p v-color=" bgc ">我是p标签,我使用了自定义指令v-red</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      bgc:'green'
    }
  },
  //自定义指令都写在这个对象里面
  directives: {
    //1.指令名:  focus
    focus: {
      // inserted(el) :  当指令被使用的时候会执行一次
      inserted(el) {
        //el : 你的指令写在哪一个标签上,这个el就是标签dom对象
        el.focus()
      },
      
    },
    //2.指令名: color
    color: {
      inserted(el,binding) {
        console.log(el)//指令所绑定的元素
        console.log(binding)//一个对象,包含指令名、指令值等数据
        console.log(binding.value)//指令值
        el.style.color = binding.value
      },
      // update(el,binding) : 当指令的值发生改变时触发
      update(el,binding) {
        console.log(el,binding)
        el.style.color = binding.value
      },
    }
  }
}
</script>

<style></style>

1.2-自定义指令:全局注册

  • 全局注册: 在main.js中注册,任何地方可用

// 全局指令 - 任何组件内"直接"使用
Vue.directive("focus", {
  inserted(el) {
    el.focus() // 触发标签的事件方法
  }
})

07-slot匿名插槽

课前准备

匿名(默认)插槽使用

插槽作用: 组件 传递 结构组件

官网文档:

通过插槽分发内容

插槽后备内容

插槽使用2个步骤

第一步:在组件中定义一个插槽 <slot>默认值:如果父组件没有传递则默认显示</slot>

第二步:在组件中传递结构: <子组件>父组件需要传递的结构</子组件>

  • 子组件goods.vue

<template>
  <div class="son">
    <h3>我是子组件</h3>
    <h4>商品名称</h4>
    <!-- 插槽:可以让父组件决定这里放什么。 也可以设置默认值 -->
    <slot>我是默认值</slot>
  </div>
</template>

<script>
export default {
  name: "goods",
  data() {
    return {}
  }
}
</script>

<style scoped>
.son {
  border: 1px solid red;
}
</style>

  • 父组件App.vue

<template>
  <div id="app">
    <h1>我是父组件</h1>
    <!-- 子组件1:插入购买链接 -->
    <goods>
      <button>
        <a href="http://www.jd.com">点击购买</a>
      </button>
    </goods>
    <!-- 子组件2:插入禁用点击的按钮 -->
    <goods>
      <button disabled>已卖完</button>
    </goods>
    <!-- 子组件3:没有插入内容,则显示默认插槽 -->
    <goods></goods>
  </div>
</template>

<script>
//导入局部组件
import goods from "./components/goods.vue"
export default {
  data() {
    return {}
  },
  components: {
    goods
  }
}
</script>

<style>
#app {
  border: 1px solid #000;
}
</style>

知识点验收

注意

  1. 插槽的作用是什么让父组件传递什么到子组件中?

    html结构

  2. 插槽的默认值写哪里?

    <solt>默认值</slot>

08-slot具名插槽

具名插槽语法如下

1.给子组件的<slot>添加name属性 : name="插槽名"

2.父组件使用v-slot:插槽名 : 给指定的插槽传递结构

  • 注意:这个v-slot指令必须要写在<template>标签中,否则会报错

  • <template>是HTML5新增的一个语义化标签,模板的意思。 这个标签本身不会被渲染,因此最终在页面是看不见的。 这个标签类似于div,就是一个空盒子容器。 与div唯一的区别就是它不会渲染。

vue中并不是所有的指令都能简写,有简写符号的指令主要有三个

v-on 指令 可以简写成 @

v-bind指令 可以简写成 :

v-slot指令 可以简写成 #

  • 课外思考? : 为什么具名插槽v-slot一定要写在<templaye>标签中呢

  • cell.vue

  • <template>
      <div class="cell">
          <div class="title" >
              <slot name="a">我是标题</slot>
          </div>
          <div class="content" >
              <slot name="b">我是内容</slot>
          </div>
          <div class="right" >
              <slot name="c">我是图标</slot>
          </div>
      </div>
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style>
        .cell{
            border: 1px solid #f00;
            height: 60px;
            padding: 10px;
            position: relative;
        }
    
        .title{
            float: left;
            
        }
    
        .content{
            position: absolute;
            bottom: 10px;
            left: 10px;
        }
    
        .right{
            float: right;
        }
    </style>
    
  • App.vue

  • <template>
      <div id="app">
        <h1>我是父组件</h1>
        <cell>
            <template v-slot:a>
                <strong>我是h3</strong>
                <span>标题内容</span>
            </template>
            <template v-slot:b>
                <a href="#">我是链接</a>
            </template>
            <template #c>
                <button>我是按钮</button>
            </template>
        </cell>
      </div>
    </template>
    
    <script>
    //导入局部组件
    import cell from "./components/cell.vue"
    export default {
      data() {
        return {}
      },
      components: {
        cell
      }
    }
    </script>
    
    <style>
    #app {
      border: 1px solid #000;
    }
    </style>
    
    

09-v-slot作用域插槽(难点)

1.插槽与props的异同点

相同点: 都是父传子

不同点:

props: 传递的是数据

插槽:传递的是html结构

2.作用域插槽和$emit异同点

相同点:都是子传父

不同点:

$emit : 子传父的数据通过事件来接收

作用域插槽:子传父的数据是通过插槽v-slot接收 (子传父的数据,只能给插槽用)

  • 1.插槽作用:父组件 传递 html结构 给 子组件

  • 2.具名插槽作用:父组件 传递 多个html结构 给 子组件

  • 3.作用域插槽作用:父组件 给 子组件 传递插槽 时,可以使用子组件内部的数据

作用域插槽语法如下

1.给子组件的<slot>添加一个自定义属性 : <slot :属性名="属性值" ></slot>

2.给父组件的<template>添加v-slot属性接收数据: <template v-slot="对象名"></template>

父组件使用子组件内部数据语法: 对象名.属性名

注意点 : 不要把 具名插槽语法作用域插槽 语法搞混淆

具名插槽: <template v-slot:name值></slot>

作用域插槽: <template v-slot="对象名"></slot>

  • App.vue

<template>
  <div>
      <!-- 
      1.匿名插槽 : 父组件传递 一个html结构 给子组件 
        (1)子组件:  <slot> 插槽默认内容 </slot>
        (2)父组件:  <子组件> html结构 </子组件>

      2.具名插槽 : 父组件传递 多个html结构 给子组件
        (1)子组件: 给插槽添加一个name(插槽名)
          <slot name="插槽名"> 插槽默认内容  </slot>
        (2)父组件: 使用 v-slot:插槽名 或  #插槽名
          <子组件> 
            <template v-slot:插槽名>
                html结构
            </template>
          </子组件>

      3.作用域插槽: 子组件传递 数据 给父组件插槽
        (1)子组件 : 给<slot>内置组件添加自定义属性
          <slot 属性名="属性值" > 插槽默认内容 </slot>
        (2)父组件 : 使用 v-slot="对象名"
     -->
     <scope>
       <template v-slot="obj">
         <p>{{ obj.a }}</p>
         <p>{{ obj.b }}</p>
         <p>{{ obj.c }}</p>
       </template>
     </scope>
  </div>
</template>

<script>
//导入局部组件
import scope from './components/scope.vue'
export default {
  //注册组件
  components: {
    scope
  },
  data() {
    return {

    }
  },
  
};
</script>

<style>
#app {
  border: 1px solid #000;
}
</style>

  • scope.vue

<template>
  <div class="box">
      <h2>学习作用域插槽</h2>
      <input v-model="msg" type="text" placeholder="输入搜索内容">
      <br>
      <!-- 定义插槽 -->
      <slot a="1" b="2" :c="msg"> 插槽默认内容 </slot>
  </div>
</template>

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

<style scoped>

.box{
    border: 1px solid #000;
}
</style>

10-作用域插槽应用场景

  • 给大家捋一下这个按钮完整流程是咋样的。(因为作用域插槽涉及到两个知识点:组件传值 + 插槽)

    • 作用域插槽类似于 子传父,不同的是: 作用域插槽子组件传递数据,不是传给父组件的。而是传给父组件那个插槽的。(相当于只有插槽才能用子组件传递过来的数据)

    • (1) App.vue 有一个list数组

    • (2) 通过props父传子, 把App.vue的list传递给 student.vue的arr

    • (3) student.vue 渲染arr,把数组的数据渲染到页面表格中

    • (4)现在遇到问题了 : 有时候,student.vue组件希望表格的td能显示学生的头像。 有时候,student.vue组件希望表格的td能显示操作按钮 (同一个组件呈现不同的功能

    • (5)怎么解决这个问题呢? : 使用插槽来解决

      • 父组件App.vue 通过插槽把需要展示的结构 传递给 子组件student.vue

    • (6) 又遇到问题了。 图片的话,需要路径怎么办? 按钮的话,点击需要获取id怎么办?

      • 因为这个v-for指令是在子组件里面用到的, 这个obj也是子组件内部的数据。 父组件App.vue肯定是拿不到的。(父组件只是把数组子组件了,至于子组件内部怎么处理,App.vue是不知道的)

    • (7)解决方案: 子组件在渲染td的时候,通过作用域插槽,把当前渲染的obj传递给父组件。 这样父组件在传递html结构的时候,就可以把obj里面需要用到的数据也一并放在html结构中,传递给子组件了。

  • 作用域插槽本质: 子组件把自己当前数据 传递给 父组件插槽 。

    • 只能父组件插槽内部使用,而不是任何地方都能用。作用域一词因此而来

  • App.vue

  • <template>
    <!-- 
      1.父传子应用: 让组件在复用的时候, 加载不同的数据
      2.插槽应用: 让组件在复用的时候,加载不同的结构样式
     -->
      <div id="app">
        <h1>我是父组件</h1>
        <h2>显示头像</h2>
        <!-- 子组件 -->
        <student :list="list1">
            <template v-slot:head>
              <b style="color:red">头像</b>
            </template>
            <!-- 具名插槽 和 作用域插槽并存
            (1)具名插槽语法:  v-slot:插槽名
            (2)作用域插槽 : v-slot="对象名"
            问题: 两种语法都用到 v-slot,而一个标签只能写一个
            解决:
              v-slot:插槽名="对象名"
              #插槽名="对象名"
             -->
            <template v-slot:body="scope">
              <img :src="scope.row.headImgUrl" alt="">
            </template>
        </student>
        <h2>显示操作按钮</h2>
        <!-- 子组件 -->
        <student :list="list2">
          <template v-slot:head>
              <i  style="color:green">操作</i>
            </template>
    
            <template #body="scope">
              <button @click="list2.splice(scope.$index,1)">删除</button>
            </template>
        </student>
        
      </div>
    </template>
    
    <script>
    //导入局部组件
    import  student from './components/student.vue'
    export default {
      //注册组件
      components: {
        student
      },
      data() {
        return {
          list1: [
            {
              id: "13575",
              name: "小传同学",
              age: 18,
              headImgUrl:
                "http://yun.itheima.com/Upload/./Images/20210303/603f2d2153241.jpg"
            },
            {
              id: "62408",
              name: "小黑同学",
              age: 25,
              headImgUrl:
                "http://yun.itheima.com/Upload/./Images/20210304/6040b101a18ef.jpg"
            },
            {
              id: "73969",
            name: "智慧同学",
              age: 21,
              headImgUrl:
                "http://yun.itheima.com/Upload/./Images/20210302/603e0142e535f.jpg"
            }
          ],
          list2: [
            {
              id: "13575",
              name: "传同学",
              age: 8,
              headImgUrl:
                "http://yun.itheima.com/Upload/./Images/20210303/603f2d2153241.jpg"
            },
            {
              id: "62408",
              name: "黑同学",
              age: 5,
              headImgUrl:
                "http://yun.itheima.com/Upload/./Images/20210304/6040b101a18ef.jpg"
            },
            {
              id: "73969",
              name: "慧同学",
              age: 1,
              headImgUrl:
                "http://yun.itheima.com/Upload/./Images/20210302/603e0142e535f.jpg"
            }
          ]
        }
      },
    }
    </script>
    
    <style>
    #app {
      border: 1px solid #000;
    }
    </style>
    
    

  • student.vue

<template>
  <div>
    <table border="1">
      <thead>
        <tr>
          <th>序号</th>
          <th>姓名</th>
          <th>年龄</th>
          <th>
            <slot name="head">表头</slot>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(item,index) in list"
        :key="item.id"
        >
          <td>{{ index+1 }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.age }}</td>

          <td >
            <slot :row="item" :$index="index" name="body">内容</slot>
          </td>

        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  //父传子
  props:{
    list:Array
  },
  data() {
    return {}
  }
}
</script>

<style scoped>
table{
  margin-top: 20px;
}
td {
  height: 60px;
}

img{
  height: 90%;
}
</style>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值