> For the complete documentation index, see [llms.txt](https://gb.akanote.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gb.akanote.cn/vue/day03.md).

# Part3-指令与传值

## 一、Vue指令（了解）

#### 1、深入响应式原理

> Vue 最独特的特性之一，是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时，视图会进行更新。Vue里面是怎么做到的的呢？其实就是使用了`Object.defineProperty` 把Vue内的属性全部转成 `getter/setter`。`Object.defineProperty` 是 ES5 中一个无法 shim 的特性，这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

`Object.defineProperty` 实现了**对象劫持**这个功能

> <https://github.com/vuejs/vue/blob/1.0/src/observer/index.js>
>
> <https://github.com/vuejs/vue/blob/2.6/src/core/observer/index.js>

**语法：**

> Object.defineProperty(obj, prop, desc)
>
> 1. `obj` 需要定义属性的当前对象
> 2. `prop` 当前需要定义的属性名
> 3. `desc` 属性描述符

**数据属性：**

> 通过Object.defineProperty()为对象定义属性，有两种形式，分别为数据描述符，存取描述符，下面分别描述两者的区别：
>
> 1. `value` 表示它的默认值
> 2. `writable` 如果为true标识可以被修改，如果为false标识不能被修改（默认值为false）
> 3. `configurable` 描述属性是否配置，以及可否删除，可以认为是总开关 默认值 false（不可删除）
> 4. `enumerable` 描述属性是否出现在for in 或者 Object.keys()的遍历中 默认值false(不能遍历)

```js
let obj = {};
Object.defineProperty(obj, 'name', {
    value: '张三'
})
obj.name = '李四'
console.log(obj.name) // 张三
```

```js
let obj = {};
Object.defineProperty(obj, 'name', {
    value: '张三',
    writable: true
})
obj.name = '李四'
console.log(obj.name)
```

```js
let obj = {};
  Object.defineProperty(obj, 'name', {
    value: '张三',
    writable: true,
    configurable: true,
    enumerable: true
  })
  obj.name = '李四'
  // delete obj.name
  console.log(obj.name) // 李四
  console.log(Object.keys(obj)) // ['name']
```

**存取属性：**

```js
let obj = {};
let temp = null;
Object.defineProperty(obj, 'name', {
    get() {
        return temp
    },
    set(val) {
        temp = val
    }
})
obj.name = '李四'
console.log(obj.name)
```

面试题回答：

> vue的双向数据绑定原理是什么？
>
> vue数据双向绑定是通过数据劫持结合“发布者-订阅者模式”的方式来实现的。 vue是通过Object.defineProperty()来实现数据劫持，其中会有getter()和setter方法；当读取属性值时，就会触发getter()方法，在view中如果数据发生了变化，就会通过Object.defineProperty()对属性设置一个setter函数，当数据改变了就会来触发这个函数；
>
> 参考：<https://segmentfault.com/a/1190000014274840>
>
> 参考：<https://zhuanlan.zhihu.com/p/51357583>

#### 2、自定义指令

除了一些内置的制定（v-model和v-show\...）,Vue也允许注册自定义指令。

```js
// 注册一个全局自定义指令 v-focus
Vue.directive('demo', {
	inserted: function (el, binding, vnode) {
		// 
		console.log(el, binding, vnode);
	}
})
```

```
// 组件中注册局部指令
new Vue({
	el: '#app',
	data: {},
	directives: {
		demo: {
			inserted: function (el, binding, vnode) {
				cosnole.log(el, binding, vnode);
			}
		}
	}
})
```

```html
// 在模板中使用自定义指令
<div v-demo>
    
</div>
```

**钩子函数：**

* `bind` : 只调用一次，指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
* `inserted` ：被绑定元素插入父节点时调用 (仅保证父节点存在，但不一定已被插入文档中)。
* `update`：所在组件的 VNode 更新时调用，**但是可能发生在其子 VNode 更新之前**。指令的值可能发生了改变，也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
* `componentUpdated`：指令所在组件的 VNode **及其子 VNode** 全部更新后调用。
* `unbind`：只调用一次，指令与元素解绑时调用。

接下来我们来看一下钩子函数的参数 (即 `el`、`binding`、`vnode` 和 `oldVnode`)。

**钩子函数的参数：**

* `el`：指令所绑定的元素，可以用来直接操作 DOM 。
* `binding`：一个对象，包含以下属性：
  * `name`：指令名，不包括 `v-` 前缀。
  * `value`：指令的绑定值，例如：`v-my-directive="1 + 1"` 中，绑定值为 `2`。
  * `oldValue`：指令绑定的前一个值，仅在 `update` 和 `componentUpdated` 钩子中可用。无论值是否改变都可用。
  * `expression`：字符串形式的指令表达式。例如 `v-my-directive="1 + 1"` 中，表达式为 `"1 + 1"`。
  * `modifiers`：一个包含修饰符的对象。例如：`v-my-directive.foo.bar` 中，修饰符对象为 `{ foo: true, bar: true }`。
* `vnode`：Vue 编译生成的虚拟节点。
* `oldVnode`：上一个虚拟节点，仅在 `update` 和 `componentUpdated` 钩子中可用。

> 实现类似v-show的自定义指令

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <p v-demo="status">12</p>
    <button @click="status = !status">取反</button>
  </div>
</body>
<script>
const vm = new Vue({
  el: '#app',
  data: {
    status: true
  },
  directives: {
    demo: {
      inserted(el, binding, vnode) {
        console.log(el, binding)
        if (binding.value) {
          el.style.display = 'block'
        } else {
          el.style.display = 'none'
        }
      },
      update(el, binding, vnode, oldVnode){
        console.log(el, binding)
        if (binding.value) {
          el.style.display = 'block'
        } else {
          el.style.display = 'none'
        }
      }
    }
  }
})
</script>
</html>
```

## 二、Vue组件化开发（掌握）

### 1、什么是组件化？

> 面对复杂问题的处理方式，把问题拆解成很多个能处理的小问题，再将其放在整体中，会发现大的问题也会迎刃而解。
>
> 而组件化的思想也类似：
>
> 1. 如果我们实现一个页面结构和逻辑非常复杂的页面时，如果全部一起实现会变得非常复杂，而且也不利于后续的维护和迭代功能。
> 2. 但如果我们这时候把页面分成一个个小的功能块，每个功能块能完成属于自己这部分独立的功能，那么整个页面之后的维护和迭代也会变得非常容易。

### 2、Vue组件化思想

> 组件化是Vue重要的思想
>
> 1. 它提供了一种抽象，让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
> 2. 任何的应用都会被抽象成一颗组件树。

[![1568877913978](https://i.loli.net/2021/03/19/9GAhKC6iymduEcz.png)](https://i.loli.net/2021/03/19/9GAhKC6iymduEcz.png)

> 组件化思想的应用开发：
>
> 1. 有了组件化的思想，我们在之后的开发中就要充分的利用它。
> 2. 尽可能的将页面拆分成一个个小的、可复用的组件。
> 3. 这样让我们的代码更加方便组织和管理，并且扩展性也更强。

### 3、全局组件

> 通过`Vue.component('组件名称', {})`，通过这个方法注册的都是全局组件，也就是他们再注册之后可以用在任何新创建的`Vue` 实例挂载的区域内。

```html
<body>
  <div id="app">
    <my-con></my-con>
    <div>
      <my-con></my-con>
    </div>
  </div>
  <my-con></my-con>
</body>
<script>
  Vue.component('my-con', {
    template: '<section><h3>组件标题</h3><p>组件内容</p></section>'
  })
  const vm = new Vue({
    el: '#app'
  })
</script>
```

> 上面案例中，在`<div id="app">...</div>` 外的组件 `my-con` 没有替换成组件真正的页面结构，是因为 `new Vue()` 挂载在 `id=app` 的节点上，不在这个节点上标签，不会受到Vue的影响。

### 4、局部组件

> 通过 `Vue.component` 方式注册的组件，称之为全局组件。任何地方都可以使用。全局注册往往是不够理想的。比如，如果你使用一个像 webpack 这样的构建系统，全局注册所有的组件意味着即便你已经不再使用一个组件了，它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

**注册局部组件**

```html
<body>
  <div id="app">
    <my-con></my-con>
    <div>
      <my-con></my-con>
    </div>
  </div>
  <div id="app1">
    <my-con1></my-con1>
  </div>
</body>
<template id="template1">
  <section>
    <h3>组件标题</h3>
    <p>组件内容</p>
  </section>
</template>
<template id="template2">
  <section>
    <h3>组件标题B</h3>
    <p>组件内容B</p>
  </section>
</template>
<script>
  var componentA = {
    template: '#template1'
  }

  var componentB = {
    template: '#template2'
  }
  const vm = new Vue({
    el: '#app',
    components: {
      'my-con': componentA
    }
  })
  const vm1 = new Vue({
    el: '#app1',
    components: {
      'my-con1': componentB
    }
  })
</script>
```

**父组件和子组件**

> 组件和组件之间存在层级关系，而其中一种最重要的关系就是父子组件关系。

### 5、组件可以访问Vue实例数据吗？

[![1568884635108](https://i.loli.net/2021/03/19/JWLlbAzYNgSR9XV.png)](https://i.loli.net/2021/03/19/JWLlbAzYNgSR9XV.png)

[![1568884656744](https://i.loli.net/2021/03/19/hVfH1rd7tDnYcjA.png)](https://i.loli.net/2021/03/19/hVfH1rd7tDnYcjA.png)

> 那组件如果要使用data定义自己属性保存数据要怎么做呢？
>
> 1. 组件对象也有一个data的属性（也有methods等属性，下面我们有用到）
> 2. 只是这个data属性必须是一个函数，而且函数返回一个对象 ，对象保存着数据

```html
<body>
  <div id="app">
    <my-con></my-con>
    <div>
      <my-con></my-con>
    </div>
  </div>
  <div id="app1">
    <my-con1></my-con1>
  </div>
</body>
<template id="template1">
  <section>
    <h3>{{title}}</h3>
    <p>组件内容</p>
  </section>
</template>
<template id="template2">
  <section>
    <h3>{{title}}B</h3>
    <p>组件内容B</p>
    <aa></aa>
  </section>
</template>
<script>
  var componentA = {
    template: '#template1',
    data() {
      return {
        title: 'zujianbiaoti'
      }
    }
  }

  var componentB = {
    template: '#template2',
    data() {
      return {
        title: 'zj'
      }
    },
    components: {
      'aa': {
        template: '<div>aa</div>'
      }
    }
  }
  const vm = new Vue({
    el: '#app',
    data: {title: '组件标题'},
    components: {
      'my-con': componentA
    }
  })
  const vm1 = new Vue({
    el: '#app1',
    components: {
      'my-con1': componentB
    }
  })
</script>
```

> 为什么data在组件中必须是一个函数呢？
>
> 原因是在于Vue让每个组件对象都返回一个新的对象，因为如果是同一个对象的，组件在多次使用后会相互影响。

### 6、父子组件间的通讯

#### 父级向子级传递

> 在组件中，使用选项props来声明需要从父级接收到的数据。
>
> props的值有两种方式：
>
> 1. 字符串数组，数组中的字符串就是传递时的名称。
> 2. 对象，对象可以设置传递时的类型（String，Number，Boolean，Array， Object，Date，Function，Symbol），也可以设置默认值等。

```html
<body>
  <div id="app1">
    <my-con1></my-con1>
  </div>
</body>

<template id="template2">
  <section>
    <h3>{{title}}B</h3>
    <p>组件内容B</p>
    <!-- my-con1组件内的aa组件 --> 
    <aa v-bind:parent-txt="childtxt"></aa>
  </section>
</template>
<script>
  var componentB = {
    template: '#template2',
    data() {
      return {
        title: 'zj',
        childtxt: 'child text'
      }
    },
    components: {
      'aa': {
        template: '<div>{{parentTxt}}</div>',
        props: ['parentTxt']
      }
    }
  }
  
  const vm1 = new Vue({
    el: '#app1',
    components: {
      'my-con1': componentB
    }
  })
</script>
```

#### 子级向父级传递

> 父组件向子组件传递数据，通过自定义事件

```html
<body>
  <div id="app1">
    <my-con1></my-con1>
  </div>
</body>

<template id="template2">
  <section>
    <h3>{{title}}B</h3>
    <p>组件内容B</p>
    <aa v-bind:parent-txt="childtxt" v-on:changetitle="changeTitle"></aa>
  </section>
</template>
<script>
  var componentB = {
    template: '#template2',
    data() {
      return {
        title: 'zj',
        childtxt: 'child text'
      }
    },
    components: {
      'aa': {
        template: '<div v-on:click="change">{{parentTxt}}</div>',
        props: ['parentTxt'],
        methods: {
          change() {
            this.$emit('changetitle', {
              a: 1
            })
          }
        }
      }
    },
    methods: {
      changeTitle(obj) {
        console.log(obj)
        this.title = obj.a
      }
    }
  }

  const vm1 = new Vue({
    el: '#app1',
    components: {
      'my-con1': componentB
    }
  })
</script>
```

> 案例分析：
>
> 1. 现在的需求是点击子组件`aa` 然后把父组件`my-con1`上的标题给改变；
> 2. 首先，在父组件的具体页面结构找到子组件`aa` ，在子组件`aa` 上使用`v-on:changetitle="changeTitle"` , `changetitle`是子组件的自定义事件名称，`changeTitle`是父组件`my-con1`里的`methods`属性定义的方法；
> 3. 其次，在子组件`aa`里为div绑定点击事件 `v-on:click="change"`, 在子组件`aa`里的methods定义change方法，change方法里使用`this.$emit('changetitle')`，使用$emit方法来触发绑定在子组件上的自定义事件，$emit第一个参数就是上一步定义的自定义事件`changetitle`,第二个参数就是传递到父组件的参数，可以不传。

**弹窗例子:**

[![1568945029817](https://i.loli.net/2021/03/19/vTjgxkrQiphL4EF.png)](https://i.loli.net/2021/03/19/vTjgxkrQiphL4EF.png)

```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    html,body, #app1{
      width: 100%;
      height: 100%;
    }
    
    .wrapper {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 100%;
      background-color: rgba(0,0,0, 0.3);
    }
    .content {
      width: 220px;
      height: 160px;
      background-color: #fff;
    }
    .title {
      height: 40px;
      line-height: 40px;
      text-align: center;
    }
    .msg {
      display: flex;
      align-items: center;
      justify-content: center;
      box-sizing: border-box;
      padding: 5px 10px;
      border-top: 1px solid #eee;
      border-bottom: 1px solid #eee;
      height: 80px;
      text-align: center;
      color: gray;
      font-size: 14px;
    }
    .bottom {
      display: flex;
      height: 40px;
      line-height: 40px;
      text-align: center;
    }
    .bottom div {
      flex: 1;
      color: green;
    }
    .bottom div:nth-of-type(1) {
      border-right: 1px solid #eee;
      color: red;
    }
  </style>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app1">
    <my-con v-show="showCon" :txt-obj="textObj" @btn-handle="handleBtnClick"></my-con>
  </div>
</body>

<template id="template1">
    <div class="wrapper">
        <div class="content">
          <p class="title">{{txtObj.title}}</p>
          <div class="msg">{{txtObj.msg}}</div>
          <div class="bottom">
            <div @click="cancel">{{txtObj.cancelTxt}}</div>
            <div @click="submit">{{txtObj.submitTxt}}</div>
          </div>
        </div>
      </div>
</template>
<script>
  var componentA = {
    template: '#template1',
    props: {
      txtObj: {
        type: Object,
        default: {}
      }
    },
    methods: {
      cancel() {
        // 自定义事件名称建议全小写
        this.$emit('btn-handle', 'cancel')
      },
      submit() {
        // 自定义事件名称建议全小写
        this.$emit('btn-handle', 'submit')
      },
    }
  }
  const vm1 = new Vue({
    el: '#app1',
    data: {
      textObj: {
        title: 'bug提示',
        msg: '亲，你还有53633个bug，是否要处理?',
        cancelTxt: '忽略，下班',
        submitTxt: '加班处理'
      },
      showCon: true
    },
    components: {
      myCon: componentA
    },
    methods: {
      handleBtnClick(type) {
        console.log(type)
        if (type === 'cancel') {
          this.showCon = false
        }
      }
    }
  })
</script>

</html>
```

#### 非父子组件通讯

> 实际工作中如果遇到跨组件或者非父组件间的传递数据，那该怎么办？第一个可以使用中央事件总线，也就是一个中介来完成。第二个就是使用 `Vuex` 提供的功能，这里先暂时不讨论这种方案，后续专门学习Vuex。

\*\*案例：\*\*点击按钮1，改变按钮2的背景颜色

[![1568942668557](https://i.loli.net/2021/03/19/4YWHOQLURSDpsCj.png)](https://i.loli.net/2021/03/19/4YWHOQLURSDpsCj.png)

```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
  <div id="app1">
    <my-con></my-con>
    <my-con1></my-con1>
  </div>
</body>

<template id="template1">
  <button @click="click1">按钮1</button>
</template>
<template id="template2">
  <button @click="click2" :style="{backgroundColor: fontColor}">按钮2</button>
</template>
<script>
  const bus = new Vue();
  const componentA = {
    template: '#template1',
    methods: {
      click1() {
        // 点击按钮1
        bus.$emit('xxx', this.getRandomColor());
      },
      getRandomColor() {
        return `rgb(${this.getRandomNum()},${this.getRandomNum()},${this.getRandomNum()})`
      },
      getRandomNum() {
        return Math.floor(Math.random() * 256)
      }
    }
  }
  const componentB = {
    template: '#template2',
    data() {
      return {
        fontColor: ''
      }
    },
    methods: {
      click2() {
        // 点击按钮2
      }
    },
    mounted() {
      bus.$on('xxx', (color) => {
        console.log(color)
        this.fontColor = color
      })
    }
  }
  const vm1 = new Vue({
    el: '#app1',
    components: {
      myCon: componentA,
      myCon1: componentB
    }
  })
</script>

</html>
```

### 7、编译作用域（掌握）

> 父组件模板的所有东西都会在父级作用域内编译；子组件模板的所有东西都会在子级作用域内编译。
>
> 也就是说父组件上的数据，只能在父组件上修改，不能传递到子组件里修改。

## 三、插槽（slot）

### 1. 为什么使用slot

> slot翻译为插槽，组件的插槽：
>
> 1. 组件的插槽也是为了让我们封装的组件更加具有扩展性。
> 2. 让使用者可以决定组件内容的一些内容到底展示什么。

**京东头部导航栏例子：**

[![1568945650561](https://tva1.sinaimg.cn/large/008i3skNgy1gvaenlp83yj60ah01aa9x02.jpg)](https://i.loli.net/2021/03/19/yhVL2zmFiOa3Yg5.png)

[![1568945654125](https://tva1.sinaimg.cn/large/008i3skNgy1gvaenp937oj60af01cjr802.jpg)](https://i.loli.net/2021/03/19/JGRyVguA3WQomFE.png)

[![1568945657300](https://tva1.sinaimg.cn/large/008i3skNgy1gvaenlz7zwj60ap01hq2q02.jpg)](https://i.loli.net/2021/03/19/HoDdBYxXWN7QMrV.png)

[![1568945660433](https://tva1.sinaimg.cn/large/008i3skNgy1gvaenmfe9sj60as01g3yc02.jpg)](https://i.loli.net/2021/03/19/WVxKeOv2kmsgRrj.png)

### 2. 如何在组件中使用slot呢？

> 如何去封装这类的组件呢？
>
> 1. 它们也很多区别，但是也有很多共性。
> 2. 如果，我们每一个单独去封装一个组件，显然不合适：比如每个页面都返回，这部分内容我们就要重复去封装。
> 3. 但是，如果我们封装成一个，好像也不合理：有些左侧是菜单，有些是返回，有些中间是搜索，有些是文字，等等
>
> 如何封装合适呢？**抽取共性，保留不同**
>
> 1. 最好的封装方式就是将共性抽取到组件中，将不同暴露为插槽。
> 2. 一旦我们预留了插槽，就可以让使用者根据自己的需求，决定插槽中插入什么内容。
> 3. 是搜索框，还是文字，还是菜单。由调用者自己来决定。

### 3. slot的基本使用（匿名插槽）

> 了解了为什么用slot，我们再来谈谈如何使用slot？
>
> 1. 在子组件中，使用特殊的元素`<slot>`就可以为子组件开启一个插槽。
> 2. 该插槽插入什么内容取决于父组件如何使用。

[![1568946418410](https://tva1.sinaimg.cn/large/008i3skNgy1gvaenn1lcej60hs08umyc02.jpg)](https://i.loli.net/2021/03/19/Imt3p5zTBowyFK2.png)

```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    
  </style>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app1">
    <my-con></my-con>
    <my-con>
      <h2>我是h2标签的内容</h2>
      <p>我是p标签的内容</p>
    </my-con>
  </div>
</body>

<template id="template1">
    <div>
      <slot>我是插槽中的默认内容！！</slot>
    </div>
</template>
<script>
  var componentA = {
    template: '#template1',
  }
  const vm1 = new Vue({
    el: '#app1',
    data: {
      
    },
    components: {
      myCon: componentA
    }
  })
</script>

</html>
```

### 4. 具名插槽

> 当子组件的功能复杂时，子组件的插槽可能并非是一个。
>
> 1. 比如我们封装一个导航栏的子组件，可能就需要三个插槽，分别代表左边、中间、右边。
> 2. 那么，外面在给插槽插入内容时，如何区分插入的是哪一个呢？
> 3. 这个时候，我们就需要给插槽起一个名字
>
> 如何使用具名插槽呢？
>
> 1. 非常简单，只要给slot元素一个name属性即可
> 2. `<slot name='myslot'></slot>`

```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    
  </style>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app1">
    <my-con>
      <div slot="left">左侧</div>
      <div slot="right">右侧</div>
      <div slot="center">中间</div>
    </my-con>
  </div>
</body>

<template id="template1">
    <div>
      <slot name="left">我是左侧插槽中的默认内容！！</slot>
      <slot name="center">我是中间侧插槽中的默认内容！！</slot>
      <slot name="right">我是右侧插槽中的默认内容！！</slot>
    </div>
</template>
<script>
  var componentA = {
    template: '#template1',
  }
  const vm1 = new Vue({
    el: '#app1',
    data: {
      
    },
    components: {
      myCon: componentA
    }
  })
</script>

</html>
```

### 5、课堂练习

京东导航栏

[![1568945654125](https://tva1.sinaimg.cn/large/008i3skNgy1gvaennc9dwj60af01cjr802.jpg)](https://i.loli.net/2021/03/19/vTI3b9jhEfyQ54S.png)

[![1568945657300](https://tva1.sinaimg.cn/large/008i3skNgy1gvaenlz7zwj60ap01hq2q02.jpg)](https://i.loli.net/2021/03/19/HoDdBYxXWN7QMrV.png)

[![1568945660433](https://tva1.sinaimg.cn/large/008i3skNgy1gvaenmfe9sj60as01g3yc02.jpg)](https://i.loli.net/2021/03/19/WVxKeOv2kmsgRrj.png)

### 6. 作用域插槽

> 默认情况下，父组件使用子组件，插槽数据默认是拿父组件的数据，而不是子组件拿数据。
>
> **作用域插槽**在父组件使用我们的子组件时， 插槽的数据从子组件中拿到数据，而不是从父组件拿到。

[![image-11](https://tva1.sinaimg.cn/large/008i3skNgy1gvaeno23krj60zk0lcafg02.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gvaeno23krj60zk0lcafg02.jpg)

### 7. 作用域插槽的多种写法

```jsx
// 1、基本写法
<one-comp>
  <button slot="btn" slot-scope="scope">按钮{{scope.msg}}</button>
</one-comp>

// 2、基本写法之模板写法
<one-comp>
  <template slot="btn" slot-scope="scope">
    <button>按钮{{scope.msg}}</button>
  </template>
</one-comp>

// 3、指令写法
<one-comp v-slot:btn="scope">
  <button>按钮{{scope.msg}}</button>
</one-comp>

// 4、指令写法之模板写法
<one-comp>
  <template v-slot:btn="scope">
    <button>按钮{{scope.msg}}</button>
  </template>
</one-comp>
```

## 四、作业

### 作业一：

完成百度首页（<https://baidu.com>）：

[![image-20211010183934277](https://tva1.sinaimg.cn/large/008i3skNgy1gvaehay8ctj61ja0u0tas02.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gvaehay8ctj61ja0u0tas02.jpg)

**作业要求：**&#x9875;面刷新输入框自动聚焦

### 作业二：

点击登录按钮，打开登录弹框：

[![image-20211010185235860](https://tva1.sinaimg.cn/large/008i3skNgy1gvaeuv64yaj61j90u041702.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gvaeuv64yaj61j90u041702.jpg)

**作业要求：**&#x767B;录弹框必须是组件话实现，而且点击x按钮，可以关闭登录弹框
