Vue3.0生命周期,组件通信学习第二篇

658 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

生命周期钩子

与2.x版本生命周期相对应的组合式API

在这里插入图片描述

新建测试组件/components/Test.vue

<template>
  <div id="test">
    <h3>{{a}}</h3>
    <button @click="handleClick">更改</button>
  </div>
</template>

<script>
import {
  ref,
  onMounted,
  onBeforeMount,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
} from "vue";
export default {
  // 初始化数据阶段的生命周期,介于beforeCreate和created之间
  setup() {
    const a = ref(0);
    console.log("👌");
    function handleClick() {
      a.value += 1;
    }
    onBeforeMount(() => {
      console.log("组件挂载之前");
    });
    onMounted(() => {
      console.log("DOM挂载完成");
    });
    onBeforeUpdate(() => {
      console.log("DOM更新之前", document.getElementById("test").innerHTML);
    });
    onUpdated(() => {
      console.log("DOM更新完成", document.getElementById("test").innerHTML);
    });
    onBeforeUnmount(() => {
      console.log("实例卸载之前");
    });
    onUnmounted(() => {
      console.log("实例卸载之后");
    });
    return { a, handleClick };
  }
};
</script>

按照官方上说的那样,你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

在这里插入图片描述

依赖注入

provide和inject提供依赖注入,功能类似2.x的provide/inject。两者都只能在当前组件的setup()中调用

App.vue provide数据源

<template>
  <div>
    <Article></Article>
  </div>
</template>

<script>
import {
  ref,
  provide
} from "vue";
import Article from "./components/Article";
export default {
  setup() {
    const articleList = ref([
      { id: 1, title: "Vue3.0学习", author: "小马哥" },
      { id: 2, title: "componsition api", author: "尤大大" },
      { id: 3, title: "Vue-router最新", author: "vue官方" }
    ]);
    /* 
      provide 函数允许你通过两个参数定义 property:
      property 的 name (<String> 类型)
      property 的 value
    */
    provide("list",articleList);
    return {
      articleList
    };
  },
  components: {
    Article
  }
};
</script>

Article.vue注入数据

<template>
  <div>
    {{articleList[0].title}}
  </div>
</template>

<script>
import { inject } from "vue";
export default {
  setup() {
    const articleList = inject('list',[]);
    return {articleList};
  },
};
</script>

模板引用refs

当使用组合式API时,reactive refstemplate refs的概念已经是统一了。为了获得对模板内元素或者组件实例的引用,可以直接在setup()中声明一个ref并返回它

<template>
  <div>
    <div ref='wrap'>hello vue3.0</div>
    <Article ref='articleComp'></Article>
  </div>
</template>

<script>
import {
  ref,
  onMounted,
  provide
} from "vue";
import Article from "./components/Article";
export default {
  setup() {
    const isShow = ref(true);
    const wrap = ref(null);
    const articleComp = ref(null);

    const articleList = ref([
      { id: 1, title: "Vue3.0学习", author: "小马哥" },
      { id: 2, title: "componsition api", author: "尤大大" },
      { id: 3, title: "Vue-router最新", author: "vue官方" }
    ]);
    /* 
      provide 函数允许你通过两个参数定义 property:
      property 的 name (<String> 类型)
      property 的 value
    */
    provide("list", articleList);

    onMounted(() => {
      console.log(wrap.value); //获取div元素
      console.log(articleComp.value); //获取的article组件实例对象
      
    });
    return {
      articleList,
      wrap,
      articleComp
    };
  },
  components: {

    Article
  }
};
</script>

<style scoped>
</style>

效果图:

在这里插入图片描述

组件通信

  • props
  • $emit
  • expose /ref
  • attrs
  • v-model
  • provide/inject
  • vuex
  • mitt
props
// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2"></child>
<script>
import child from "./child.vue"
import { ref, reactive } from "vue"
export default {
    setup(){
        // 创建一个响应式数据
        const msg1 = ref("这是传级子组件的信息1")
        const msg2 = reactive(["这是传级子组件的信息2"])
        return {
            msg1
            msg2
        }
    }
}
</script>

// Child.vue 接收
<script>
export default {
  props: ["msg1", "msg2"],// 如果这行不写,下面就接收不到
  setup(props) {
    console.log(props) // { msg1:"这是传给子组件的信息1", msg2:"这是传给子组件的信息2" }
  },
}
</script>

$emit
// Child.vue 派发
<template>
  // 写法一
  <button @click="$emit('myClick',123)">按钮</buttom>
</template>
<script> 
 export default {
	emits:['myClick']
	//emits:{
  //myClick:null
  //}
}

</script>

// Parent.vue 响应
<template>
    <child @myClick="onMyClick"></child>
</template>
<script setup>
  import child from "./child.vue"
const onMyClick = (msg) => {
  console.log(msg) // 这是父组件收到的信息 123
}
</script>

重大改变

Teleport

Teleport 就像是哆啦 A 梦中的「任意门」,任意门的作用就是可以将人瞬间传送到另一个地方。有了这个认识,我们再来看一下为什么需要用到 Teleport 的特性呢,看一个小例子: 在子组件Header中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,我们可以用<Teleport>包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。 接下来就举个小例子,看看 Teleport 的使用方式。 我们希望 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:

<body>
  <div id="app"></div>
  <div id="dialog"></div>
</body>

定义一个Dialog组件Dialog.vue, 留意 to 属性, 与上面的id选择器一致:

<template>
  <teleport to="#dialog">
    <!-- 即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,
      我们可以用<Teleport>包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方 -->
    <div class="dialog">
      <div class="dialog_wrapper">
        <div class="dialog_header">
          <h3>我是弹框 {{ count }}</h3>
        </div>
      </div>
    </div>
  </teleport>
</template>

<script>
import { reactive, toRefs } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
    })

    return {
      ...toRefs(state),
    }
  },
}
</script>

<style lang="less" scoped></style>


Suspense

试验性

Suspense 是一个试验性的新特性,其 API 可能随时会发生变动。特此声明,以便社区能够为当前的实现提供反馈。

生产环境请勿使用

<suspense> 组件提供了另一个方案,允许将等待过程提升到组件树中处理,而不是在单个组件中。 自带两个 slot 分别为 defaultfallback。顾名思义,当要加载的组件不满足状态时,Suspense 将回退到 fallback状态一直到加载的组件满足条件,才会进行渲染。

Suspense.vue

<template>
  <button @click="loadAsyncComponent">点击加载异步组件</button>
  <Suspense v-if="loadAsync">
    <template #default>
      <!-- 加载对应的组件 -->
      <MAsynComp></MAsynComp>
    </template>
    <template #fallback>
      <div class="loading"></div>
    </template>
  </Suspense>
</template>

<script>
import { ref, defineAsyncComponent } from 'vue'

export default {
  components: {
    MAsynComp: defineAsyncComponent(() => import('./AsynComp.vue')),
  },
  setup() {
    const loadAsync = ref(false)
    const loadAsyncComponent = () => {
      loadAsync.value = true
    }
    return {
      loadAsync,
      loadAsyncComponent,
    }
  },
}
</script>

<style lang="less" scoped>

button {
  padding: 12px 12px;
  background-color: #1890ff;
  outline: none;
  border: none;
  border-radius: 4px;
  color: #fff;
  cursor: pointer;
}
.loading {
  position: absolute;
  width: 36px;
  height: 36px;
  top: 50%;
  left: 50%;
  margin: -18px 0 0 -18px;
  background-image: url('../assets/loading.png');
  background-size: 100%;
  animation: rotate 1.4s linear infinite;
}
@keyframes rotate {
  from {
    transform: rotate(0);
  }
  to {
    transform: rotate(360deg);
  }
}
</style>


AsynComp.vue

<template>
  <h1>this is async component</h1>
</template>

<script>
import { setup } from 'vue'
export default {
  name: 'AsyncComponent',
  async setup() {
    const sleep = (time) => {
      return new Promise((reslove, reject) => {
        setTimeout(() => {
          reslove()
        }, time)
      })
    }
    await sleep(3000) //模拟数据请求
  },
}
</script>


Fragments

Vue3.0组件中可以允许有多个根组件,避免了多个没必要的div渲染

<template>
  <div>头部</div>
  <div>内容</div>
</template>

这样做的好处:

  • 少了很多没有意义的div
  • 可以实现平级递归,对实现tree组件有很大帮助
emits
  • emits 可以是数组或对象
  • 触发自定义事件
  • 如果emits是对象,则允许我们配置和事件验证。验证函数应返回布尔值,以表示事件参数是否有效。

Emits.vue

<template>
<div>
  <button @click="$emit('submit',{username:'xiaomage',password:'123'})">自定义事件</button>
  </div>
</template>

<script>
  export default {
    // emits:['submit'],//可以是数组
    emits: {
      submit: payload => {
        if(payload.username && payload.password){
          return true;
        }else{
          console.warn('无效的payload,请检查submit事件');
          return false
        }
      }
    },
    setup() {
      return {};
    }
  };
</script>

<style scoped>
</style>

App.vue

<Emits @submit="submitHandle"></Emits>
<script>
  import Emits from "./components/Emits";
  export default{
    components:{
      Emits
    },
    setup(){
      function submitHandle(payload) {
        console.warn("自定义事件触发",payload);
      }
      return {

      }
    }
  }

</script>

效果展示: 在这里插入图片描述

全局Vue API更改为应用程序实例

上面已经讲过了,不做一一赘述了。

API可做Tree shakable优化

在vue2.0有不少的全局api是作为静态函数直接挂在在Vue构造函数上的,你应该手动操作过DOM,会遇到如下模式。如果我们未是在代码中用过它们,就会形成我们所谓的"死代码",这类全局api造成的"死代码"无法使用webapck的tree-shaking进行'死代码消除'。

import Vue from 'vue'
Vue.nextTick(()=>{
  //一些和DOM相关的东西
})

因此,vue3.0做了相应的改变,将它们抽离成为独立的函数,这样打包工具的摇树优化可以将这些"死代码"排除掉。全局 API 现在只能作为 ES 模块构建的命名导出进行访问。例如,我们之前的片段现在应该如下所示

import {nextTick} from 'vue'
nextTick(()=>{
  //一些和DOM相关的东西
})

受影响的API
  • Vue2.x中这些全局API受此更改的影响:

  • Vue.nextTick

  • Vue.observable(用Vue.reactive替换)

  • Vue.version

  • Vue.compile(仅完全构建时)

  • Vue.set(仅兼容版本)

  • Vue.delete(仅兼容版本)

TreeShaking.vue

<template>
<div >
  <hr />摇树优化,把没引入的不必要的代码进行优化
  <div id='name'>小马哥</div>
  <h3 ref='myMsg'>{{msg}}</h3>
  <button @click="changeMsg('hai!')">改变</button>
  </div>
</template>

<script>
  import { ref, nextTick } from "vue";
  export default {
    setup() {
      const msg = ref("hello!");
      const myMsg = ref(null);
      async function changeMsg(newV) {
        msg.value = newV;
        // console.log(myMsg.value.innerText); //直接获取DOM还是以前的
        // nextTick返回了promise对象
        await nextTick();
        console.log(myMsg.value.innerText);
      }
      return {
        msg,
        myMsg,
        changeMsg
      };
    }
  };
</script>