飞道的博客

vue3细节的改变

340人阅读  评论(0)

异步组件(新增)

以前,异步组件是通过将组件定义为返回 Promise 的函数来创建的,例如:

const asyncModal = () => import('./Modal.vue')

在 Vue 3 中,由于函数式组件被定义为纯函数,因此异步组件需要通过将其包裹在新的 defineAsyncComponent 助手方法中来显式地定义:

<template>
  <div>
    -----异步组件-----
    <button @click="onClick">点击显示</button>
    <asyncChild v-if="isShow" />
  </div>
</template>

<script setup>
  import {
    defineAsyncComponent } from 'vue';
  let isShow = ref(false);
  const asyncChild = defineAsyncComponent(() => import('./child.vue'));
  const onClick = () => {
   
    isShow.value = true;
  };
</script>
子组件:
<template>
  <div> Child: {
   {
    test }} </div>
</template>
<script setup>
  let test = ref('你好!');
</script>

点击显示 后才加载child组件

$attrs 包括class&style

自定义指令

Vue 2.x & Vue 3.x

(1)Vue 2.x 自定义指令的声明周期

  • bind:指令绑定到元素时触发,只触发一次;
  • inserted:绑定元素被插入父DOM时触发
  • update:当元素更新而子元素还没有更新时触发;
  • componentUpdated:组件和子组件更新完成后触发;
  • unbind:一旦指令被移除,就会调用这个钩子。也只调用一次。
<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
Vue.directive('highlight', {
   
  bind(el, binding, vnode) {
   
    el.style.background = binding.value
  }
})

(2)Vue 3.x 自定义指令的声明周期

  • created - 新增!在元素的 attribute 或事件监听器被应用之前调用。
  • beforeMount:替代bind
  • mounted:替代inserted
  • beforeUpdate:移除Vue2.x 中的update,用beforeUpdate和updated来替代
  • updated
  • beforeUnmount:新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用。
  • unmounted:替代unbind
<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
const app = Vue.createApp({
   })
app.directive('highlight', {
   
  beforeMount(el, binding, vnode) {
   
    el.style.background = binding.value
  }
})

自定义指令

全局自定义指令/访问组件实例

<el-button type="primary" v-hasPermi="['system-access:list:query']" >搜索</el-button >
app.directive('hasPermi', {
   
	mounted(el, binding, vnode) {
   
		// 编写
	}
})

局部自定义

<template>
	<div>
		<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
	</div>
</template>

<script>
export default {
   
	directives: {
   
		highlight: {
   
			beforeMount(el, binding, vnode, prevVnode) {
   
				el.style.background = binding.value;
			}
		}
	}
};
</script>

is属性

vue3.0.0+ 中 禁止在 HTML 元素上使用is属性(is 已弃用)

<div is="foo"></div><div :is="foo"></div>  //错误

保留的 标签上使用时,它的行为将与 2.x 中完全相同

<component is="bar" />

当在不同组件标签上使用is时,is会被当做一个不同的prop;

<bar is="plastic-button" />

使用 vue: 前缀来解决 DOM 内模板解析问题

<template>
    <select>   
        <option is="vue:optioncomp"> </option>
    </select>
</template>
<script>
export default {
   
  components:{
   
    'optioncomp':{
   
        template: '<option >下拉选择</option>'
    }
  }
};
</script>

新增v-is来实现在普通的 HTML 元素渲染组件

<template>
    <div v-is="'child'">渲染 child 组件</div>
    <!--  等同于<child>渲染 child 组件</child>  -->
</template>
<script>
import child from "./child.vue";
export default {
   
  components:{
   
    child
  }
};
</script>

Data 选项

在 2.x 中,开发者可以通过 object 或者是 function 定义 data 选项

<!-- Object 声明 -->
<script>
  const app = new Vue({
   
    data: {
   
      apiKey: 'a1b2c3'
    }
  })
</script>

<!-- Function 声明 -->
<script>
  const app = new Vue({
   
    data() {
   
      return {
   
        apiKey: 'a1b2c3'
      }
    }
  })
</script>

3.x 中,data 选项已标准化为只接受返回 object 的 function

createApp({
   
    data() {
   
      return {
   
        apiKey: 'a1b2c3'
      }
    }
  }).mount('#app')

Mixin 合并行为变更

此外,当来自组件的 data() 及其 mixin 或 extends 基类被合并时,合并操作现在将被浅层次地执行:

const Mixin = {
   
  data() {
   
    return {
   
      user: {
   
        name: 'Jack',
        id: 1
      }
    }
  }
}

const CompA = {
   
  mixins: [Mixin],
  data() {
   
    return {
   
      user: {
   
        id: 2
      }
    }
  }
}

在 Vue 2.x 中,生成的 $data 是:

{
   
  "user": {
   
    "id": 2,
    "name": "Jack"
  }
}

在 3.0 中,其结果将会是:

{
   
  "user": {
   
    "id": 2
  }
}

emits(新增)

emits: 列表申明从父组件继承来的事件
$emit: 抛出事件, 告诉父组件解决

<!-- 父组件 App -->
<template>
  <div>
    <div> 父组件:{
   {
   num}}</div>
    <child  @numchange="getNum" :num="num" ></child>
  </div>
</template>
<script>
import {
    ref}from 'vue'
import child from "./child.vue";
export default {
   
  components:{
   
    child,
    },
    setup() {
   
        let num =ref(0);
        const getNum = (res)=>{
   
            num.value = res;
        }
        return{
   
            num,
            getNum
        }
    }
};
</script>




<!-- 子组件 Count -->
<template>
	<div>
       <p>子组件:{
   {
   num}}</p> 
        <button type="button" class="btn btn-danger" @click="add">+1</button>
	</div>
</template>

<script>
export default {
   
    props: ['num'],
    emits: ['numchange'],
    setup(props,ctx){
   
        const add=()=> {
   
            ctx.emit('numchange', props.num + 1)
        }
        return {
   
            add
        }
    }
};
</script>

vue3 移除过滤器

在 3.x 中,过滤器已移除,且不再支持。官网建议用方法调用或计算属性来替换它们。
全局过滤器

// main.js
const app = createApp(App)

app.config.globalProperties.$filters = {
   
  currencyUSD(value) {
   
    return '$' + value
  }
}
使用的时候: <p>{
   {
    $filters.currencyUSD(accountBalance) }}</p>

片段(新增)

在 3.x 中,组件可以包含多个根节点!

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

全局 API

一个新的全局 API:createApp

import {
    createApp } from 'vue'
const app = createApp({
   })

全局 API Treeshaking

Vue 3.x 对 部分全局 API 实现了 tree shacking 功能。

什么是 tree shaking?

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json 的 “sideEffects” 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。
摘抄自——《webpack——tree shaking》

作用:tree shaking 的作用是移除项目代码中上下文中的未引用代码(dead-code),已达到实现项目打包文件的精简。
前提:tree shaking 基于 ES2015模块系统。也就是项目中对模块的应用和导出需要用到import、export。

比如:在Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到

import Vue from 'vue'
 
Vue.nextTick(() => {
   })

而Vue3源码引入tree shaking特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中

import {
    nextTick, observable } from 'vue'
 
nextTick(() => {
   })


作用

通过脚手架vue-cli安装Vue2与Vue3项目。
vue2

<template>
  <div id="app">{
   {
   count}}--{
   {
   double}}
  </div>
</template>

<script>

export default {
   
	name: 'App',
	data(){
   
		return{
   
			count: 1,
			question:'',
			answer:''
		}
	},
	computed: {
   
		double: function () {
   
			return this.count * 2;
		},
	},
	watch: {
   
		question: function () {
   
			this.answer = 'xxxx'
		}
	}
}
</script>

<style>
#app {
   
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>


vue3

<template>
  <div id="app">{
   {
   state.count}}--{
   {
   double}}
  </div>
</template>

<script>

import {
    reactive ,computed, watch} from "vue";
export default {
   
  name: 'App',
  setup() {
   
    const state = reactive({
   
      count: 1,
    });
    const double = computed(() => {
   
      return state.count * 2;
    });

    watch(
      () => state.count,
      (count, preCount) => {
   
        console.log(count);
        console.log(preCount);
      }
    );
    return {
   
      state,
      double,
    };
  },

}
</script>

<style>
#app {
   
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>


对项目进行打包,通过Tree shaking,Vue3给我们带来的好处是:

  • 减少程序体积(更小)
  • 减少程序执行时间(更快)
  • 便于将来对程序架构进行优化(更友好)

$listeners 移除,谁来接手

Vue3 已经不支持 $listeners 了,从文档中可以知道 Vue2 的 $listeners 在 Vue3 中是合并到了 $attrs 里。

<template>
<div>--------父组件--------{
   {
   num}}--</div>
  <Child @call="handleEmit" />
</template>
<script>
import {
    ref}from 'vue'
import Child from "./Child.vue";
export default {
   
    components:{
   
        Child
    },
    setup() {
   
        let num = ref(0)
        const  handleEmit =(res)=> {
   
            num.value = res;
            console.log('父组件:',res)
        }
        return{
   
            num,
            handleEmit
        }
    }
};
</script>

<template>
    <div>---子组件-----</div>
  <!-- 注意传参方式 -->
  <Grandchild v-bind="$attrs" />
</template>
<script>
import {
    ref}from 'vue'
import Grandchild from './Grandchild.vue'

export default ({
   
  components: {
    Grandchild },
  
  setup(props, {
    attrs }) {
   
    console.log('Child 组件:',attrs) // 可以看到 Parent 传入的 call 事件,且前面加上了 on,变成了 onCall
  }
})
</script>

<template>
  <button @click="handleClick">孙组件:点击</button>
</template>
<script>
import {
    ref}from 'vue'

export default ({
   
   emits: ['call'],
    setup(props, {
    emit}) {
   
        const handleClick= ()=> {
   
            emit('call','1') // 成功触发 Parent 里面的 call 事件回调,在控制台1的值
        }
        return {
   
            handleClick
        }
    }
})
</script>


在 prop 的默认函数中访问this

在vue2 中 props里的参数是可以用this.去访问的。但是vue3不行了,取而代之 组件接收到的原始 prop 将作为参数传递给默认函数。


export default{
   
	props: {
   
		iconClass: {
   
		  type: String,
		  required: true
		}
	},
	setup(props) {
   
	console.log(props.iconClass)
	}
}<script setup>
 const props = defineProps({
   
    info: {
   
      type: Object,
      default: null,
    }
  });
  props.info
</script>

渲染函数

h() 渲染函数

在 2.x 中,render 函数会自动接收 h 函数 (它是 createElement 的惯用别名) 作为参数:

// Vue 2 渲染函数示例
export default {
   
  render(h) {
   
    return h('div')
  }
}

在 3.x 中,h 函数现在是全局导入的,而不是作为参数自动传递。

// Vue 3 渲染函数示例
import {
    h } from 'vue'

export default {
   
  render() {
   
    return h('div')
  }
}

<h1>{
   {
    blogTitle }}</h1> 等于
render() {
   
  return h('h1', {
   }, this.blogTitle)
}

h() 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为 VNode。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

h() 参数

h(
  // {
   String | Object | Function | null} tag
  // 一个 HTML 标签名、一个组件、一个异步组件,或者 null。
  // 使用 null 将会渲染一个注释。
  //
  // 必需的。
  'div',


  // {
   Object} props
  // 与 attribute、prop 和事件相对应的对象。
  // 我们会在模板中使用。
  //
  // 可选的。
  {
   },


  // {
   String | Array | Object} children
  // 子 VNodes, 使用 `h()` 构建,
  // 或使用字符串获取 "文本 Vnode" 或者
  // 有 slot 的对象。
  //
  // 可选的。
  [
    'Some text comes first.',
    h('h1', 'A headline'),
    h(MyComponent, {
   
      someProp: 'foobar'
    })
  ]
)

约束

VNodes 必须唯一

render() {
   
  const myParagraphVNode = Vue.h('p', 'hi')
  return Vue.h('div', [
    // 错误 - 重复的Vnode!
    myParagraphVNode, myParagraphVNode
  ])
}
如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落:

render() {
   
  return Vue.h('div',
    Array.apply(null, {
    length: 20 }).map(() => {
   
      return Vue.h('p', 'hi')
    })
  )
}

注册组件

在 3.x 中,由于 VNode 是上下文无关的,不能再用字符串 ID 隐式查找已注册组件。取而代之的是,需要使用一个导入的 resolveComponent 方法

// 3.x
import {
    h, resolveComponent } from 'vue'

export default {
   
  setup() {
   
    const ButtonCounter = resolveComponent('button-counter')
    return () => h(ButtonCounter)
  }
}

Suspense作用

等待异步组件时渲染一些额外内容,让应用有更好的用户体验。

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

使用步骤

异步引入组件

import {
   defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

使用Suspense包裹组件,并配置好default 与 fallback

<template>
    <div class="app">
        <h3>我是App组件</h3>
        <Suspense>
            <template v-slot:default>
                <Child/>
            </template>
            <template v-slot:fallback>
                <h3>加载中.....</h3>
            </template>
        </Suspense>
    </div>
</template>

其中 fallback是由于网速或者其他原因没有加载成功时显示的组件,当加载成功后显示default(default 与 fallback 不可改名,因为Suspense相当于定义好的slot具名插槽)

Suspense搭配async函数的setup

App.vue(异步加载组件的父组件)
<template>
  <div class="app">
    <h3>我是App组件</h3>
    <Suspense>
      <template v-slot:default>
        <Child />
      </template>
      <template v-slot:fallback>
        <h3>稍等,加载中...</h3>
      </template>
    </Suspense>
  </div>
</template>
 
<script>
// import Child from './components/Child'//静态引入
import {
    defineAsyncComponent } from "vue";
const Child = defineAsyncComponent(() => import("./components/Child")); //异步引入
export default {
   
  name: "App",
  components: {
    Child },
};
</script>
 
<style>
.app {
   
  background-color: gray;
  padding: 10px;
}
</style>
定义setup为async的组件Child.vue
<template>
  <div class="child">
    <h3>我是Child组件</h3>
    {
   {
    sum }}
  </div>
</template>
 
<script>
import {
    ref } from "vue";
export default {
   
  name: "Child",
  async setup() {
   
    let sum = ref(0);
    let p = new Promise((resolve) => {
   
      setTimeout(() => {
   
        resolve({
    sum });
      }, 3000);
    });
    return await p;
  },
};
</script>
 
<style>
.child {
   
  background-color: skyblue;
  padding: 10px;
}
</style>

移除v-on.native修饰符

<my-component
  v-on:click.native="handleNativeClickEvent"
/>
改成
<my-component
  v-on:click="handleNativeClickEvent"
/>

删除 .native 修饰符的所有实例。确保所有组件都使用 emits 选项记录其事件。


转载:https://blog.csdn.net/gao_xu_520/article/details/125622292
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场