vue3.0新特性

性能提升

  • 打包大小减少41%
  • 初次渲染快55%,更新快133%
  • 内存使用减少54%
  • 原因:重写虚拟dom的优化和treeshaking的优化

Componsition API(API组合)

  • ref 和 reactive
  • computed 和watch
  • 新的生命周期函数
  • 自定义函数--hooks函数
  • Teleport - 瞬移组件的位置
  • Suspense - 异步加载组件的新福音
  • 全局API的修改和优化
  • 更多的誓言性特性

更好的Typescript 支持

  • vue3.0源码就是ts写的

为什么要有vue3.0?

vue2 遇到的难题

  • 随着功能的增长,复杂组件的代码变得难以维护

Mixin 的缺点

  • 命名冲突
  • 不清楚暴露吃了变量的作用
  • 重用到其他component经常会遇到问题

对typeScript 的支持非常有限

  • 原因是:依赖this指向上下文,主要是没考虑的ts的集成

总结:vue3 完美解决上诉2.0的问题

ts+ vue Cli 项目搭建环境

image.png
  • 选择多项选择可以更好的支持ts
  • 之后选择版本3.0就行
安装好用的vscode插件
  • eslint 语法检查

3 vue3.0新特性介绍

3.1 ref的妙用

通过vue 3.0实现一个计数器

  • 使用ref一般传入的是原始值,比如count初始化为0
  • setup函数中定义的对象及方法都是响应式的
<template>
  <h1>{{ count }}</h1>
  <h1>{{ double }}</h1>
  <button @click="increase">点击+1</button>
</template>

<script lang="ts">
import { defineComponent, ref, computed } from "vue";

// 实现计数器
export default defineComponent({
  name: "App",
  setup() {
    // 方法和ref,computed的api的使用
    // 无法使用this
    // 创建响应式对象 count,但是模板上通过ref会直接将里面的value展示出来
    const count = ref(0);
    // computed是一个函数,参数是回调
    const double = computed(() => {
      return count.value * 2;
    });
    // 定义一个函数,count是对象,所以要写value++
    const increase = () => {
      count.value++;
    };

    // 导出count
    return {
      count,
      increase,
      double,
    };
  },
});
</script>
  • 注意:setup方法中无法使用this
  • 在setup 方法中定义的函数,变量需要使用return导出才能在template中使用
  • 通过refAPI定义的变量,是一个对象,操作值得时候需要操作value

3.2 reactive函数的用法

  • 将上面的计算器代码优化下
<template>
  <h1>{{ count }}</h1>
  <h1>{{ double }}</h1>
  <button @click="increase">点击+1</button>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
// 定义data的数据类型
interface DataProps {
  count: number;
  double: number;
  increase: () => void;
}
// reactive 也是一个函数,可以批量归类变量
// 实现计数器
export default defineComponent({
  name: "App",
  setup() {
      定义一个reactive对象
      const data: DataProps = reactive({
      count: 0,
      increase: () => {
        data.count++;
      },
      double: computed(() => data.count * 2),
    });
    // 导出data,正常展示count
    // return {
    //   data,
    // };
    // 使用toRefs将数据变成响应式对象
    const refData = toRefs(data);
    // 优化展示,取出来就不是响应式 使用toRefs函数将数据变成响应式对象,数据改变模板更新
    // 点击无反应
    // return {
    //   ...data,
    // };
    // 
    return {
      ...refData,
    };
  },
});
  • 使用reactive可以将变量及函数整理在一个对象中
  • 然后在setup中导出data,zai template中使用data.count调用
  • 当使用es6的结构方法优化时,会出现失去响应式的情况,模板无法更新
  • 结合toRefs将数据变成响应式对象
  • 使用reactive 的同时记得使用toRefs函数变成响应式属性

3.3 vue3响应式对象的新花样

使用es6 proxy 实现响应式,完美支持数组和对象的改变响应式

// vue2.0
Object.defineProperty(data,'count',{
get(){},
set(){}
})
// vue3
new proxy(data,{
    get(key){},
    set(key,value){}
}
)

   const data: DataProps = reactive({
      count: 0,
      increase: () => {
        data.count++;
      },
      double: computed(() => data.count * 2),
      numbers: [1, 2, 3],
      person: {},
    });
        // vue 2.0 直接修改值不更新 3.0 直接修改会是响应式
    data.person.name = "violet";
    data.numbers[0] = 8;

3.3 生命周期

生命周期名字替换: 更好的语义化
beforeDestory => beforeUnmount
destroyed => unmounted :销毁完毕


image2.png
  • 这些生命周期在setup中使用,需要加'on'关键字
  • setup函数是跟create的一起运行的,只会执行一次
setup() {
    onMounted(() => {
      console.log("mounted");
    });
    // 点击事件更新数据
    onUpdated(() => {
      console.log("update");
    });
    // 调式
    onRenderTracked(() => {
      console.log("onRenderTracked");
    }),
      // 更新时,才会触发,记录了那些值发生了变化
      onRenderTriggered((event) => {
        console.log(event, "onRenderTriggered");
      });
}

3.4 watch 侦测变化

  • watch 函数,前面是监听的值,后面是执行的方法
  • setup跟created只执行一次,所以,点击值发生变化,不会去更新title
<template>
  <el-button @click="upDatedGreeting">点击</el-button>
  </template>
  <script lang="ts">
 setup() {
const greetings = ref("");
    const upDatedGreeting = () => {
      greetings.value += "violet";
    };
    // watch 回调函数里面接入两个参数,变化值及旧的值
    watch(greetings, (newval, oldavl) => {
      console.log("new", newval, "old", oldavl); // new violet old
      document.title = "1" + greetings.value;
    });
    
     // watch 监听多个值,第一个参数可以是数组
    watch([greetings, data], (newval, oldavl) => {
      console.log("new", newval, "old", oldavl); // new violet old
      document.title = "1" + greetings.value;
    });  //["violet", Proxy]0: "violet"1: Proxy {count: 1, double: ComputedRefImpl, numbers: Array(3), person: {…}, increase: ƒ}length: 2__proto__: Array(0) 
 }
 
 </script>
  • 也可以监听data里面的某个变量

注意直接监听 data.count会失去响应式
at; 可以将数组里面的第二个参数变成getter函数

// 会报警告Invalid watch source:  0 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
 watch([greetings, data.count], (newval, oldavl) => {
       console.log("new", newval, "old", oldavl);
       document.title = "1" + greetings.value;
     });
  • 此时根据警告提示,可以使用getter函数
watch([greetings, () => data.count], (newval, oldavl) => {
      console.log("new", newval, "old", oldavl); // 0: "violetviolet" 1:0
      document.title = "1" + greetings.value;
    });

3.5 使用模块化

  • 实现鼠标追踪器
  • 效果,点击鼠标更新鼠标位置
  1. 使用ref实现
// helloword
<template>
  <div class="hello">
    <h1>X:{{ x }} Y:{{ y }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "HelloWorld",
  setup() {
    const x = ref(0);
    const y = ref(1);
    const updateMouse = (e: MouseEvent) => {
      x.value = e.pageX;
      y.value = e.pageY;
    };
    onMounted(() => {
      document.addEventListener("click", updateMouse);
    });
    onUnmounted(() => {
      document.removeEventListener("click", updateMouse);
    });
    return {
      x,
      y,
    };
  },
});
</script>
  1. 将这个功能模块化使用
  • 在src下新建hooks文件夹
// 新建userMousePosition.ts
import { onMounted, onUnmounted, ref } from 'vue'

function useMousePosition() {
   const x = ref(0);
     const y = ref(0);
     const updateMouse = (e: MouseEvent) => {
       x.value = e.pageX;
       y.value = e.pageY;
     };
     onMounted(() => {
       document.addEventListener("click", updateMouse);
     });
     onUnmounted(() => {
       document.removeEventListener("click", updateMouse);
     });
     // 导出x,y
     return {
       x,
       y,
     };
}

export default useMousePosition
  • 在helloword中引用
<template>
  <div class="hello">
    <h1>X:{{ x }} Y:{{ y }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
// 引入函数
 import useMousePosition from "../hooks/userMousePosition";
export default defineComponent({
  name: "HelloWorld",
  setup() {
    //  得函数返回x,y
     const { x, y } = useMousePosition();
    // 导出使用
     return {
       x,
       y,
     };
  },
});
</script>
  1. 使用reactive改造这个模块
// userMousePosition.ts
import { onMounted, onUnmounted, reactive, toRefs } from 'vue'

function useMousePosition() {
  const data = reactive({
    x: 0,
    y: 0,
  })
  const updateMouse = (e: MouseEvent) => {
    data.x = e.pageX
    data.y = e.pageY
  }
  onMounted(() => {
    document.addEventListener('click', updateMouse)
  })
  onUnmounted(() => {
    document.removeEventListener('click', updateMouse)
  })
  // 注意使用toRefs将data转换成响应式数据
  const refsData = toRefs(data)
  return {
    ...refsData,
  }
}

export default useMousePosition

3.6 hooks模块化理解

  1. 异步请求模块
  • 在src/hooks中新建useUrlLoader.ts文件
import { reactive, toRefs } from 'vue'
import axios from 'axios'
function useURLloader(url: string) {
  const data = reactive({
    result: null,
    loading: true,
    loaded: false,
    error: null,
  })

  axios
    .get(url)
    .then(res => {
      data.loading = false
      data.result = res.data
      data.loaded = true
    })
    .catch(err => {
      data.error = err
      data.loading = false
    })
  const refsData = toRefs(data)
  return {
    ...refsData,
  }
}
export default useURLloader
  • 在loading.vue中使用
<template>
  <div class="box">
    <h1 v-if="loading">Loading!...</h1>
    <img v-if="loaded" :src="result.message" alt="" />
    <p v-if="error">{{ error }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import useURLloader from '../hooks/useUrlLoader'
export default defineComponent({
  name: 'isLoading',
  setup() {
    const { result, loading, loaded, error } = useURLloader(
      'https://dog.ceo/api/breeds/image/random'
    )
    return {
      loading,
      loaded,
      result,
      error,
    }
  },
})
</script>
  • 结果


    image3.png

2. 模块化结合ts --- 泛型改造

报错(待解决): result: <T | null>null ==

error  Use 'as T | null' instead of '<T | null>'
// useUrlLoader.ts
import { reactive, toRefs } from 'vue';
import axios from 'axios';

function useURLloader<T>(url: string) {
  const data = reactive({
    result: <T | null>null,  
    loading: true,
    loaded: false,
    error: null,
  });

  axios
    .get(url)
    .then(res => {
      data.loading = false;
      data.result = res.data;
      data.loaded = true;
    })
    .catch(err => {
      data.error = err;
      data.loading = false;
    });
  const refsData = toRefs(data);
  return {
    ...refsData,
  };
}
export default useURLloader;
  • 在页面中使用
<template>
  <div class="box">
    <h1 v-if="loading">Loading!...</h1>
    <img v-if="loaded" :src="result.message" alt="" />
    <p v-if="error">{{ error }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, watch } from 'vue';
import useURLloader from '../hooks/useUrlLoader';
// 提前将接口返回的参数类型定义好
interface UrlProps {
  message: string;
  state: string;
}
export default defineComponent({
  name: 'isLoading',
  setup() {
    const { result, loading, loaded, error } = useURLloader<UrlProps>(
      'https://dog.ceo/api/breeds/image/random'
    );
    watch(result, () => {
      if (result.value) {
        console.log(result.value.message, 'value');
      }
    });
    return {
      loading,
      loaded,
      result,
      error,
    };
  },
});
</script>

3.7 ts对vue3的加持

  • defineComponent服务ts 而存在的
  • 兼容vue2.0的3.0的语法提示

3.8 Teleport - 瞬间移动(新增加标签)

  • 场景:全局弹窗的使用,使用v-if
  • 问题:dialog 被包裹在其他组件之中,容易被干扰
    -样式问题边的容易混乱
  • 使用<Teleport></Teleport>
// 在index.html 中新建一个id为model的根节点与app层级一样
// index.html
<div id="model"></div>
// model 中挂载到model节点上
<teleport to="#model"></teleport>
  • 新建model
// model.vue
<template>
  <teleport to="#model">
    <div class="center" v-if="isOpen">
      <h2><slot>this is a modal</slot></h2>
      <button @click="buttonClick">Close</button>
    </div>
  </teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  props: {
    isOpen: Boolean,
  },
  emits: {
    // 向外发送的事件名称,并且验证函数参数
    // 'close-modal': (payload: any) => {
    //   return payload.type === 'close';
    // },
    'close-modal': null, // 这个事件不需要验证
  },
  setup(props, context) {
    // 验证发送参数是否正确
    const buttonClick = () => {
      context.emit('close-modal', false);
      // context.emit('close-modal', { type: 'close' });
    };
    return {
      buttonClick,
    };
  },
});
</script>
<style>
.center {
  width: 200px;
  height: 200px;
  border: 2px solid #eee;
  background: white;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
</style>
  • 子父组件调用
<template>
  <model :isOpen="modelIsOpen" @close-modal="openModel">我的弹窗</model>
  <el-button @click="openModel(true)">打开弹窗</el-button>
</template>
<script lang="ts">
import Model from './components/Model.vue';
import { defineComponent,ref} from 'vue';
export default defineComponent({
  name: 'App',
  components: { HelloWorld, Loading, Model },
  setup() {
    const modelIsOpen = ref(false);
    const openModel = (val: boolean) => {
      modelIsOpen.value = val;
    };
     return {
      openModel,
      modelIsOpen,
    };
    })
</script>

3.9 Suspense - 异步请求

语法:

<Suspense>
      <template #default>
        <!-- <async-show /> -->
        <dog-show />
      </template>
      <template #fallback> <h1>加载中.....</h1></template>
    </Suspense>
  • 异步组件的困境
  • Suspense 是Vue3推出的一个内置的特殊组件
  • 如果使用Suspense,要返回一个promise
  1. 使用Suspense实现一个简单异步请求
//AsyncShow.vue
<template>
  <div>
    <h1>{{ result }}</h1>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  setup() {
    return new Promise(resolve => {
      setTimeout(() => {
        return resolve({
          result: 42,
        });
      }, 3000);
    });
  },
});
</script>


  • 父组件调用
import AsyncShow from './components/AsyncShow.vue';
 
 // 支持具名插槽,第一个default是异步请求data,#fallback是数据会返回前展示的data
 
<Suspense>
      <template #default>
         <async-show /> 
      </template>
      <template #fallback> <h1>加载中.....</h1></template>
    </Suspense>
  • 结果如下:先出现加载中.....,3s之后出现42

2.Suspense支持多个请求

  • 新建一个DogShow.vue 组件
// DogShow.vue
<template>
  <div>
    <img :src="result && result.message" alt="" />
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import axios from 'axios';
export default defineComponent({
  // 里面可以采用await
  async setup() {
    const res = await axios.get('https://dog.ceo/api/breeds/image/random');
    return {
      result: res.data,
    };
  },
});
</script>
  • 在父组件中引用

import DogShow from './components/DogShow.vue';
import AsyncShow from './components/AsyncShow.vue';
<Suspense>
      <template #default>
        <div>
          <async-show />
          <dog-show />
        </div>
      </template>
      <template #fallback> <h1>加载中.....</h1></template>
    </Suspense>
    // 展示错误
    <p>{{ error }}</p>
  • 结果如下,先出现加载中....,等两个异步组件成功返回展示对应的数据
image4.png
  • 如果有一个异步请求失败,可以使用onErrorCaptured去监听错误
import { defineComponent, onErrorCaptured, ref } from 'vue';
export default defineComponent({
  name: 'App',
  components: { HelloWorld, Model, AsyncShow, DogShow },
  setup() {
    const error = ref(null);
    // 通过这个生命周期函数可以监听Suspense里面的错误
    onErrorCaptured((err: any) => {
      error.value = err;
      return true;
    });
    
      error,
    };
  • 结果展示如下:

AsyncShow展示42,DogShow接口失败,报错

image5.png

3.10 Global API Change(全局API修改)

Vue2全局API遇到的问题
  1. 在单元测试中,全局配置非常容易污染全局环境
  2. 在不同的apps中,共享一份有不同配置的Vue对象,也变的非常困难

vue3.0 解决了直接修改vue,防止污染

  1. 全局配置修改:
  • Vue.config => app.config
  • config.productionTip 被删除
  • config.ignoredElements 改名为 config.isCustomElement
  • config.keyCodes被删除
  1. 全局注册类API
  • Vue.component -> app.component
  • Vue.directtive -> app.directtive
  1. 行为类API
  • Vue.mixins -> app.mixins
  • Vue.use -> app.use
  1. Global API Treeshaking
// vue2 全局导入,API挂载在Vue原型上
import Vue from 'vue'
Vue.nextTick(()=> {})
const obj = Vue.observable()

// vue3  实名导出,方便Treeshaking
import Vue,{nextTick,observable} from 'vue'
Vue.nextTick // undefined

nextTick(()=> {})
const obj = observable()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容