Vue 你可能不知道的东西 !

105 阅读1分钟

Vue组件开发


1切换组件案例

我们先从一个案例看起

  • 比如我们现在想要实现一个功能:

    • 点击一个tab-bar,切换不同的组件显示

    • 这个案例通过两种不同的实现思路来实现

      • 方式一:通过v-if来判断,显示不同的组件;

      • 方式二:动态组件的方式

        动态组件是使用 component 组件,通过一个特殊的attribute v-bind:is来实现:

  • App.vue

<template>
  <button v-for="(item,index) in tabs" :key="item" @click="switchTab(item)" :class="{active:currentTab === item }">
    {{item}}
  </button>
  
  <!-- 2.动态组件 -->
  <keep-alive include="home,about">
      <component :is="currentTab" name="tim" :age="18" @PageClick="pageClick" ></component>
  </keep-alive>
  
​
​
  <!-- 1.v-if的判断实现 -->
  <template v-if="currentTab === 'home'">
    <Home />
  </template>
  <template v-else-if="currentTab === 'About'">
    <About />
  </template>
  <template v-else>
    <Category />
  </template>
  
</template>
<script>
  import Home from "./pages/Home.vue"
  import About from "./pages/About.vue"
  import Category from "./pages/Category.vue"
  export default {
    components: {
      Home,
      About,
      Category
    },
    data() {
      return {
        tabs:["home","category","goods"],
        currentTab:"home"
      }
    },
    methods: {
      switchTab(item) {
        this.currentTab = item
      }
      pageClick() {
        console.log("page页面发生修改")
      }
    }
  }
</script>
<style>
  .active {
    color:red
  }
</style>
  • Home.vue
  <template>
    Home组件 -- {{name}} -- {{age}}
    <button @click="btnClick">
      点击触发事件
    </button>
  </template>
  <script>
   export default {
      name:"home"
      props: {
      name: {
        type:String,
        default: ""
      },
      age: {
        type:Number,
        default:0
      }
    }
    emits: ["PageClick"],
    methods: {
      btnClick() {
        this.$emit("PageClickClick")
      }
    }
   }
  </script>
  • About.vue
  <template>
    About组件
    <!-- 如果增加了counter但是又切换了上面的组件,那么会进行这个组件的销毁和重新挂载,这样会浪费性能,那么我闷可以使用keep-alive进行改造,查看app.vue中的keep-alive -->
    <button @click="counter++">
      {{counter}}
    </button>
  </template>
  <script>
    export default {
      name:"about"
      data() {
        return {
          counter:0
        }
      }
    }
  </script>
  • Category.vue
  <template>
    Category组件
  </template>
  <script>
    export default {
      name:"category"
    }
  </script>
  • Keep-alive的一些属性,以下代码中在上面进行示范了

2Webpack分包

  • 默认的打包过程:

    • 默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将组 件模块打包到一起(比如一个app.js文件中);
    • 这个时候随着项目的不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢;
  • 打包时,代码的分包:

    • 所以,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js;
    • 这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容;
  • 那么webpack中如何可以对代码进行分包呢?


3Vue中实现异步组件

3.1创建AsyncCategory.vue

<template>
  <h2>
    {{message}}
  </h2>
</template>
<script>
  export default {
    return {
      message:"Hello Category"
    }
  }
</script>

3.2AsyncCategory组件在App.vue中使用

<template>
  <AsyncCategory></AsyncCategory>
</template>
<script>
  import AsyncCategory from "./AsyncCategory.vue"
  export default {
    components: {
      AsyncCategory
    }
  }
</script>

3.3使用defineAsyncComponent进行vue分包

  • 如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数:defineAsyncComponent

  • defineAsyncComponent接受两种类型的参数:

    • 类型一:工厂函数,该工厂函数需要返回一个Promise对象;
    • 类型二:接受一个对象类型,对异步函数进行配置;
<!-- 方式一 -->
<template>
  <AsyncCategory></AsyncCategory>
</template>
<script>
  import {defineAsyncComponent} from "vue"
  //import AsyncCategory from "./AsyncCategory.vue"
  //该工厂函数需要返回一个Promise对象;
  const AsyncCategory = defineAsyncComponent(()=>import("./AsyncCategory.vue"))
  export default {
    components: {
      AsyncCategory
    }
  }
</script>
<!-- 方式二 -->
<template>
  <AsyncCategory></AsyncCategory>
</template>
<script>
  import {defineAsyncComponent} from "vue"
  import Loading from "./Loading.vue"
  //import AsyncCategory from "./AsyncCategory.vue"
  //该工厂函数需要返回一个Promise对象;
  const AsyncCategory = defineAsyncComponent({
    loader: () => import("./AsyncCategory.vue"),
    //可以写一个组件进行占位以免没有加载完毕
    loadingComponent: Loading
    
  })
  export default {
    components: {
      AsyncCategory,
      Loading,
      //自己尝试创建error组件试试
      errorComponent: Error 
      //在显示loadingComponent组件之前,等待多长时间
      delay:2000,
      /**
      * err: 错误信息,
      * retry: 函数, 调用retry尝试重新加载
      * attempts: 记录尝试的次数
      */
      onError: (error,retry,fail,attempts)=> {
        
      }
    }
  }
</script>
  • Loading.vue

    • 把这个loading组件在方式二中的loadingComponent中使用
    <template>
      <div>
        Loading
      </div>
    </template><script>
      export default {
        
      }
    </script><style scoped></style>
    

4异步组件和Suspense

  • Suspense是一个内置的全局组件,该组件有两个插槽:

    • default:如果default可以显示,那么显示default的内容;
    • fallback:如果default无法显示,那么会显示fallback插槽的内容;
<!-- 方式一 -->
<template>
  <suspense>
    <template #default>
        <AsyncCategory></AsyncCategory>
    </template>
    <template #fallback>
        <Loading></Loading>
    </template>
  </suspense>
</template>
<script>
  import {defineAsyncComponent} from "vue"
  import Loading from "./Loading.vue"
  //import AsyncCategory from "./AsyncCategory.vue"
  //该工厂函数需要返回一个Promise对象;
  const AsyncCategory = defineAsyncComponent(()=>import("./AsyncCategory.vue"))
  export default {
    components: {
      AsyncCategory,
      Loading
    }
  }
</script>

5.引用元素和组件的使用

  • 某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例**

    • 在Vue开发中我们是不推荐进行DOM操作的;
    • 这个时候,我们可以给元素或者组件绑定一个ref的attribute属性;
  • 组件实例有一个$refs属性:

    • 它一个对象Object,持有注册过 ref attribute 的所有 DOM 元素和组件实例。
  • 父元素和根元素怎么获取呢

    • this.$parent获取父元素
    • this.$root获取根元素
  • 创建一个App.vue

  <template>
    <div>
      <!-- 绑定到一个元素上 -->
      <h2 ref="title">哈哈哈</h2>
  
      <!-- 绑定到一个组件实例上 -->
      <NavBar ref="navBar"></NavBar>
  
      <button @click="btnClick">获取元素</button>
    </div>
  </template>
  
  <script>
    import NavBar from './NavBar.vue';
  
    export default {
      components: {
        NavBar
      },
      data() {
        return {
          names: ["abc", "cba"]
        }
      },
      methods: {
        btnClick() {
          //获取refs dom
          console.log(this.$refs.title);
          //获取子组件的数据
          console.log(this.$refs.navBar.message);
          this.$refs.navBar.sayHello();
  
          // $el
          console.log(this.$refs.navBar.$el);
        }
      }
    }
  </script>
  
  <style scoped>
  
  </style>
  • NavBar.vue
<template>
  <div>
    <h2>NavBar</h2>
    <button @click="getParentAndRoot">获取父组件和根组件</button>
  </div>
</template><script>
  export default {
    data() {
      return {
        message: "我是NavBar中的message"
      }
    },
    methods: {
      sayHello() {
        console.log("Hello NavBar");
      },
      getParentAndRoot() {
        //获取父组件的东西
        console.log(this.$parent);
        //获取根组件的东西
        console.log(this.$root);
      }
    }
  }
</script><style scoped></style>

[注意]  在Vue3中已经移除了$children的属性,所以不可以使用了。


6认识生命周期

  • 什么是生命周期呢?

    • 每个组件都可能会经历从创建,挂载,更新,卸载等一系列的过程
    • 在这个过程中的某一个阶段,用于可能会想要添加一些属于自己的代码逻辑
    • 但是我们如何可以知道目前组件正在哪一个过程呢?Vue给我们提供了组件的生命周期函数
  • 生命周期函数:

    • 生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调;
    • 通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段;
    • 那么我们就可以在该生命周期中编写属于自己的逻辑代码了;

6.1组件的生命的周期

  • App.vue
<template>
  <div>
    <!-- 根据这个变量,组件就会卸载和挂载 -->
    <button @click="isShow = !isShow">切换</button>
    <template v-if="isShow"> 
      <home></home>
    </template>
  </div>
</template><script>
  import Home from './Home.vue';
​
  export default {
    components: {
      Home
    },
    data() {
      return {
        isShow: true
      }
    }
  }
</script><style scoped></style>
  • Home.vue
<template>
  <div>
    <h2 ref="title">{{message}}</h2>
    <button @click="changeMessage">修改message</button>
  </div>
</template><script>
  export default {
    data() {
      return {
        message: "Hello Home"
      }
    },
    methods: {
      changeMessage() {
        this.message = "你好啊, 李银河"
      }
    },
    beforeCreate() {
      console.log("home beforeCreate");
    },
    created() {
      console.log("home created");
    },
    beforeMount() {
      console.log("home beforeMount");
    },
    mounted() {
      console.log("home mounted");
    },
    beforeUnmount() {
      console.log("home beforeUnmount");
    },
    unmounted() {
      console.log("home unmounted");
    },
    beforeUpdate() {
      console.log(this.$refs.title.innerHTML);
      console.log("home beforeUpdate");
    },
    updated() {
      console.log(this.$refs.title.innerHTML);
      console.log("home updated");
    }
  }
</script><style scoped></style>

如果是动态组件是不会进行销毁和挂载的,除了第一次加载挂载,使用的是activated和deactivated

<script>
  export default {
    activated() {
      console.log("about activated");
    },
    deactivated() {
      console.log("about deactivated");
    }
  }
</script>

7.组件的v-model

  • 如果我们现在封装了一个组件,其他地方在使用这个组件时,是否也可以使用v-model来同时完成这两个功能呢?

    • 也是可以的,vue也支持在组件上使用v-model;
  • 当我们在组件上使用的时候,等价于如下的操作:

  • 我们会发现和input元素不同的只是属性的名称和事件触发的名称而已;
  • App.vue
<template>
  <div>
    <!-- 可以写多个v-model -->
    <HyInput v-model="message" v-model:title="title"></HyInput>
    <!-- 拆解成为这两部分 -->
    <HyInput :modelValue="message" @update:modelValue="message = $event"></HyInput>
    <!-- 这行代码就是上面这行代码的缩写 -->
    <HyInput v-model="message"></HyInput>
​
    <h2>{{message}}</h2>
    <h2>{{title}}</h2>
  </div>
</template><script>
  import HyInput from './HyInput.vue';
​
  export default {
    components: {
      HyInput
    },
    data() {
      return {
        message: "Hello World",
        title: "哈哈哈"
      }
    }
  }
</script><style scoped></style>
  • Hyinput.vue
<template>
  <div>
    <!--<button @click="btnClick">hyinput</button>-->
    <!--<input :value="modelValue" @input="btnClick">-->    
    <!-- 绑定的第一个v-model message -->
    <input v-model="value">
    <!-- 绑定的第二个v-model title -->
    <input v-model="tle">
  </div>
</template>
<script>
  export default {
    props: {
      modelValue: String,
      title:String
    },
    emits:["update:modelValue","update:title"],
    computed: {
      value: {
        set(value) {
          this.$emit("update:modelValue",value)
        },
        get() {
          return this.modelValue
        }
      }
      tle: {
        set(tle) {
          this.$emit("update:title",tle)
        },
        get() {
          return this.title
        }
      }
    },
    methods: {
      btnClick() {
        this.$emit("update:modelValue","123")
      }
    }
  }
</script>