vue3细节的改变

Posted 面条请不要欺负汉堡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3细节的改变相关的知识,希望对你有一定的参考价值。

异步组件(新增)

以前,异步组件是通过将组件定义为返回 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  reffrom '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  reffrom '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  reffrom '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  reffrom '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组件</h以上是关于vue3细节的改变的主要内容,如果未能解决你的问题,请参考以下文章

vue3细节的改变

Vue3使用reactive包裹数组赋值

Vue3 中还处在实验性阶段 Suspense 是个啥?

vue3 基础概念 (含与 vue2.6 的对比)

Vue3组件化开发

Vue3.0最新动态:script-setup 定稿,部分实验性 API 将弃用