美文网首页Vue
Vue2自定义指令和插槽

Vue2自定义指令和插槽

作者: h2coder | 来源:发表于2023-08-11 18:25 被阅读0次

    自定义指令

    • 为什么要自定义指令?当我们需要操作DOM,但每次使用$nextTick太繁琐,我们希望可以优雅封装为一个指令,只要在DOM元素中,添加这个指令,例如 v-foucus ,输入框DOM元素能自动获取焦点

    全局注册指令

    定义指令(main.js)

    • 全局指令,所有组件都能使用
    import Vue from 'vue'
    import App from './App.vue'
    
    // 注意:不需要加v-,Vue会自动帮我们加上,使用的时候使用v-foucus
    // Vue.directive('v-foucs');// 错误写法
    
    // 全局注册,所有组件都可以使用,局部注册只能在注册的组件中使用
    Vue.directive('foucs', {
      // 当指令所在的标签,被渲染到页面的时候,回调
      inserted(element) {
        // element 就是 DOM 元素,谁用这个指令,那个DOM元素就是element
        element.focus();
      }
    });
    
    Vue.config.productionTip = false
    new Vue({
      render: h => h(App),
    }).$mount('#app')
    

    使用指令(App.vue)

    <template>
      <div>
        <h1>v-focus 自动获得焦点</h1>
        <!-- v-focus,自定义指令,能让 input 输入框渲染到页面时,自动获取焦点 -->
        <input type="text" v-foucs />
      </div>
    </template>
    
    <script>
    export default {
    };
    </script>
    
    <style>
    </style>
    

    局部注册指令

    • 局部指令,只有注册了指令的组件才能使用
    // App.vue
    <template>
      <div>
        <h1>v-focus 自动获得焦点</h1>
        <!-- v-focus,自定义指令,能让 input 输入框渲染到页面时,自动获取焦点 -->
        <input type="text" v-foucs />
      </div>
    </template>
    
    <script>
    export default {
      // 局部注册指令
      directives: {
        foucs: {
          // element是使用的该指令的DOM元素
          inserted(element) {
            // 操作DOM元素,获取焦点
            element.focus();
          },
        },
      },
    };
    </script>
    
    <style>
    </style>
    

    获取指令的值

    • 自定义指令和Vue提供的指令一样,自定义指令同样可以设置值和获取值

    • 下面就来封装一个和v-html一样功能的v-hml指令,功能是将指令的值设置给DOM元素的HTML,实现设置innerHTML的功能

    • inserted(el, binding),当绑定指令的DOM元素,渲染到页面时回调

    • update(el, binding),当指令绑定的值,发生改变时回调

    <template>
      <div>
        <!-- 点击按钮时,将下面div的内容,从h1改为button按钮 -->
        <button @click="change">修改</button>
        <div v-hml="msg"></div>
      </div>
    </template>
    
    <script>
    export default {
      // 局部注册指令
      directives: {
        // 指令名称,注意不能叫html,会和Vue定义好的发生冲突
        hml: {
          // 绑定指令的DOM元素,渲染到页面时回调
          inserted(el, binding) {
            // 更新DOM元素的innerHTML
            el.innerHTML = binding.value;
          },
          // 指令绑定的值,发生改变时回调
          update(el, binding) {
            el.innerHTML = binding.value;
          },
        },
      },
      data() {
        return {
          msg: "<h1>我是大标题</h1>",
        };
      },
      methods: {
        change() {
          // 将div的内容,从h1改为button按钮
          this.msg = "<button>我变成按钮啦</button>";
        },
      },
    };
    </script>
    
    <style>
    </style>
    

    插槽

    • 当一个组件的样式有很多种,但大体框架不变时,使用插槽,可以将会变的部分,让父组件提供,子组件只提供基础框架和样式,提高组件灵活性和可拓展性
    • 通过slot标签,也就是插槽标签,现在子组件中进行占位,父组件则提供不同的可变部分组件,Vue会将这部分组件填充到占位处

    默认插槽

    • 默认插槽,也就是没有名字插槽,也就是子组件只有一部分可提供给父组件设置
    • 其实默认插槽也是一种具名插槽,只是名字叫default
    • 下面,自定义一个弹窗组件,其中内容部分可变,可为文字、按钮、其他标签等

    弹窗子组件(MyDialog.vue)

    <template>
      <div class="dialog">
        <div class="dialog-header">
          <h3>友情提示</h3>
          <span class="close">✖️</span>
        </div>
    
        <div class="dialog-content">
          <!-- 内容,使用插槽,使用组件时,添加标签的内容,内容就会替换到插槽的位置 -->
          <slot></slot>
        </div>
        <div class="dialog-footer">
          <button>取消</button>
          <button>确认</button>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {};
      },
    };
    </script>
    
    <style scoped>
    * {
      margin: 0;
      padding: 0;
    }
    .dialog {
      width: 470px;
      height: 230px;
      padding: 0 25px;
      background-color: #ffffff;
      margin: 40px auto;
      border-radius: 5px;
    }
    .dialog-header {
      height: 70px;
      line-height: 70px;
      font-size: 20px;
      border-bottom: 1px solid #ccc;
      position: relative;
    }
    .dialog-header .close {
      position: absolute;
      right: 0px;
      top: 0px;
      cursor: pointer;
    }
    .dialog-content {
      height: 80px;
      font-size: 18px;
      padding: 15px 0;
    }
    .dialog-footer {
      display: flex;
      justify-content: flex-end;
    }
    .dialog-footer button {
      width: 65px;
      height: 35px;
      background-color: #ffffff;
      border: 1px solid #e1e3e9;
      cursor: pointer;
      outline: none;
      margin-left: 10px;
      border-radius: 3px;
    }
    .dialog-footer button:last-child {
      background-color: #007acc;
      color: #fff;
    }
    </style>
    

    父组件(App.vue)

    <template>
      <div>
        <!-- 默认插槽,只要给子组件的标签内容,添加内容,就会被替换到组件内部的 slot 标签中 -->
        <MyDialog>123</MyDialog>
    
        <!-- 填充按钮到插槽中 -->
        <MyDialog>
          <button>关闭</button>
        </MyDialog>
    
        <!-- 填充一个标题到插槽中 -->
        <MyDialog>
          <h1>大大的标题</h1>
        </MyDialog>
      </div>
    </template>
    
    <script>
    import MyDialog from "./components/MyDialog.vue";
    
    export default {
      data() {
        return {};
      },
      // 注册子组件
      components: {
        MyDialog,
      },
    };
    </script>
    
    <style>
    body {
      background-color: #b3b3b3;
    }
    </style>
    

    后备内容

    • 后备内容,也就是插槽的默认内容,当父组件没有填充组件或内容时,则使用子组件设置的默认内容

    子组件(MyDialog.vue)

    <template>
      <div class="dialog">
        <div class="dialog-header">
          <h3>友情提示</h3>
          <span class="close">✖️</span>
        </div>
    
        <div class="dialog-content">
          <!-- 往slot标签内部,编写内容,可以作为后备内容(默认值) -->
          <slot>
            <h5>我是默认内容</h5>
          </slot>
        </div>
        <div class="dialog-footer">
          <button>取消</button>
          <button>确认</button>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {};
      },
    };
    </script>
    
    <style scoped>
    // ...
    </style>
    

    父组件(App.vue)

    <template>
      <div>
        <!-- 传内容,则使用我们传的内容 -->
        <MyDialog>
          <button>点我</button>
        </MyDialog>
        <!-- 不传内容,会使用默认值 -->
        <MyDialog></MyDialog>
      </div>
    </template>
    
    <script>
    import MyDialog from "./components/MyDialog.vue";
    export default {
      data() {
        return {};
      },
      components: {
        MyDialog,
      },
    };
    </script>
    
    <style>
    body {
      background-color: #b3b3b3;
    }
    </style>
    

    具名插槽

    • 具名插槽,当子组件有好几个部分都需要暴露出来,给父组件设置时使用,其实就是插槽有名字,父组件填充内容时,必须指定填充的是哪个名字的插槽

    • 下面,弹窗子组件需要将头部、内容、尾部,暴露这3部分给父组件

    • 通过name属性,给插槽命名

    子组件(MyDialog.vue)

    <template>
      <div class="dialog">
        <div class="dialog-header">
          <!-- 头部 -->
          <slot name="head"></slot>
        </div>
        <div class="dialog-content">
          <!-- 内容 -->
          <slot name="content"></slot>
        </div>
        <div class="dialog-footer">
          <!-- 尾部 -->
          <slot name="footer"></slot>
        </div>
    
        <!-- 其实默认插槽,也是具名插槽,只是名称叫default -->
        <!-- <slot name="default"></slot> -->
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {};
      },
    };
    </script>
    
    <style scoped>
    // ...
    </style>
    

    父组件(App.vue)

    • 父组件,通过v-slot:插槽名称来指定插入的内容,对应子组件的哪个插槽
    • 号,是 v-slot: 的简写,开发中常用简写

    <template>
      <!-- 
        目前学到的指令简写:
        @ => v-on,事件监听
        # => v-slot:,插槽
        : => v-bind,动态绑定属性
       -->
      <div>
        <MyDialog>
          <!-- 通过template标签,将要添加插槽内容标签 -->
          <!-- 通过v-slot属性,指定插槽的名称 -->
    
          <!-- 头部 -->
          <template v-slot:head>
            <h1>大标题</h1>
          </template>
    
          <!-- 内容 -->
          <template v-slot:content>
            <h4>我是内容</h4>
          </template>
    
          <!-- 尾部 -->
          <!-- #号,是 v-slot: 的简写 -->
          <template #footer>
            <button>确定</button>
          </template>
    
          <!-- 默认插槽,其实也是具名插槽,只是名称叫default,所以也可以使用具名插槽的写法,插槽名字叫default -->
          <!-- <template v-slot:default>默认插槽的内容</template> -->
        </MyDialog>
      </div>
    </template>
    
    <script>
    import MyDialog from "./components/MyDialog.vue";
    
    export default {
      data() {
        return {};
      },
      components: {
        MyDialog,
      },
    };
    </script>
    
    <style>
    body {
      background-color: #b3b3b3;
    }
    </style>
    

    作用域插槽

    • 使用具名插槽,已经能实现多个部分暴露给父组件进行设置,但有一个问题还没有解决,就是子组件的数据,如何通过插槽抛出给父组件,解决方案就是作用域插槽。
    • 作用域插槽,是在具名插槽的基础上实现的,通过v-bind,将子组件的属性绑定给插槽,父组件在提供内容时,就能获取这些属性
    • 下面,让一个表格子组件,提供一个按钮位置的插槽,并将当前行的数据id回传给父组件,父组件就能使用这个id,实现查看按钮、删除按钮

    子组件(MyTable.vue)

    • 子组件,通过:id="item.id",来将这一行的数据id,提供给父组件使用
    <template>
      <table class="my-table">
        <thead>
          <tr>
            <th>序号</th>
            <th>姓名</th>
            <th>年纪</th>
            <th>操作</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>
              <!-- 作用域插槽,将当前遍历到的数据的id,传出去 -->
              <!-- Vue会将id,包装成一个对象,父组件要通过 template 标签,声明接收这个对象,然后通过 对象.属性名 来获取这个id值 -->
              <slot :id="item.id"></slot>
            </td>
          </tr>
        </tbody>
      </table>
    </template>
    
    <script>
    export default {
      props: {
        list: Array,
      },
    };
    </script>
    
    <style scoped>
    .my-table {
      width: 450px;
      text-align: center;
      border: 1px solid #ccc;
      font-size: 24px;
      margin: 30px auto;
    }
    .my-table thead {
      background-color: #1f74ff;
      color: #fff;
    }
    .my-table thead th {
      font-weight: normal;
    }
    .my-table thead tr {
      line-height: 40px;
    }
    .my-table th,
    .my-table td {
      border-bottom: 1px solid #ccc;
      border-right: 1px solid #ccc;
    }
    .my-table td:last-child {
      border-right: none;
    }
    .my-table tr:last-child td {
      border-bottom: none;
    }
    .my-table button {
      width: 65px;
      height: 35px;
      font-size: 18px;
      border: 1px solid #ccc;
      outline: none;
      border-radius: 3px;
      cursor: pointer;
      background-color: #ffffff;
      margin-left: 5px;
    }
    </style>
    

    父组件(App.vue)

    • 父组件,通过#插槽名="对象",来接受子组件提供的所有数据
    • 子组件slot插槽标签中,绑定的多个数据,都会给Vue包装为一对象,所以父组件使用id属性,需要通过对象.属性名来获取
    <template>
      <div>
        <MyTable :list="list">
          <!-- 3. 通过template #插槽名="变量名" 接收数据 -->
          <template #default="obj">
            <!-- 通过obj.id,来获取子组件传递过来的id数据 -->
            <button @click="del(obj.id)">删除</button>
          </template>
        </MyTable>
    
        <MyTable :list="list">
          <!-- 3. 通过template #插槽名="变量名" 接收数据 -->
          <template #default="obj">
            <button @click="showDetail(obj.id)">查看</button>
          </template>
        </MyTable>
      </div>
    </template>
    
    <script>
    import MyTable from "./components/MyTable.vue";
    export default {
      data() {
        return {
          list: [
            { id: 1, name: "张小花", age: 18 },
            { id: 2, name: "孙大明", age: 19 },
            { id: 3, name: "刘德忠", age: 17 },
          ],
        };
      },
      methods: {
        // 删除
        del(id) {
          console.log(id);
          // 删除指定id的数据
          const index = this.list.findIndex((item) => item.id === id);
          this.list.splice(index, 1);
        },
        // 查看详情
        showDetail(id) {
          const item = this.list.find((item) => item.id === id);
          alert(JSON.stringify(item));
        },
      },
      components: {
        MyTable,
      },
    };
    </script>
    

    相关文章

      网友评论

        本文标题:Vue2自定义指令和插槽

        本文链接:https://www.haomeiwen.com/subject/gtvtmdtx.html