开发要点-Vue3.0通用知识学习

开发要点-Vue3.0通用知识学习

2020-09-18 发布的Vue3.0正式版

建议先学ts;因为Vue3.0框架多数都是用ts重写的;
API文档仔细阅读;

Vue3亮点:

  • Performance:性能更快;
  • Tree shaking support:按需编译,体积更小;
  • Composition API:组合API(类似React Hooks);
  • Better TypeScript support:更好的ts支持;
  • Custom Renderer API:暴露了自定义渲染API;
  • Fragment Teleport(Protal) Suspense:更先进的组件;

Vue3是如何变快的:

  • diff优化:
    • Vue2中虚拟Dom是进行全量对比;
    • Vue3新增静态标记(PatchFlag),虚拟dom比对时,只对比带有patch flag的节点,而且可以通过flag的信息得知当前接待你要对比的具体内容;
  • hoistStatic 静态提升:
    • Vue2中无论元素是否参与更新,每次都会重新创建;
    • Vue3中对不参与更新的元素,只会创建一次,之后会在每次渲染时候被不停的复用;
  • cacheHandlers 事件侦听器缓存
    • 默认情况下onClick会被视为动态绑定(静态标记是8),所以每次都回去追踪它的变化;使用缓存后,就不会进行静态标记,也就不会再追踪了,因为是同一函数缓存复用即可;
  • ssr渲染:
    • 当有大量静态的内容时,这些内容会被当做纯字符串推进一个buffer里;即使存在动态绑定,也会通过模板插值嵌入;比通过虚拟Dom来渲染的快很多;
    • 当静态内容大到一定程度时,会用_createStaticVNode方法在客户端生成一个static node,它们会被直接innerHtml,无需创建对象;
PatchFlags 静态标记

Vue3.0的diff在创建虚拟Dom的时候会根据DOM中的内容会不会发生变化,添加静态标记;

PatchFlags:

export const enum PatchFlags {
    TEXT = 1, // 动态文本节点
    CLASS = 1 << 1, // 2 动态class
    STYLE = 1 << 2, // 4 动态style
    PROPS = 1 << 3, // 8 动态属性,但不包含类名和样式
    FULL_PROPS = 1 << 4, // 16 具有动态key属性, 当key改变时,需要进行完整的diff比较;
    HYDRATE_EVENTS = 1 << 5, // 32 带有监听事件的节点
    STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的fragment
    KEYED_FRAGMENT = 1 << 7, // 128 带有key属性的fragment 或 部分子节点有key
    UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有key的fragment
    NEED_PATCH = 1 << 9, // 512 一个节点只会进行非props比较
}

可以通过探测Vue代码转换为js之后的内容来观察Vue3中添加的静态标记;

创建Vue3

创建方式:Vue-Cli、Webpack、Vite

基于Webpack快速创建的项目:

git clone https://github.com/vuejs/vue-next-webpack-preview.git projectName
cd projectName
npm install
npm run dev

基于Vue-Cli快速创建的项目:

npm install -g @vue/cli
vue create projectName
cd projectName
vue add vue-next
npm run serve

什么是Vite:

  • Vite是vue作者开发的意图取代webpack的工具;
  • 原理:利用ES6的import会发送请求去加载文件的特性,拦截请求,做一些预编译,省去webpack冗长的打包时间;
// 安装Vite
npm install -g create-vite-app
// 通过Vite创建Vue3项目
create-vite-app projectName
// 安装运行时依赖
cd projectName
npm install
npm run dev

Vue3 兼容 Vue2:

  • 新的->按需导入
  • 新的->组合API
    • Vue2中每新建一个业务逻辑,一般需要在data中加数据,在methods/computed/watch中处理相应的逻辑;
    • 问题是,当需要处理的业务逻辑很多时,就会维护困难;
    • 未解决这个问题,Vue3引入可组合API

Object.assign(newObj, oldObj)API可以实现数据的浅拷贝;

组合API —— Composition API

Vue2中的API一般叫做,Option API;

Vue3的Composition API:(也叫注入API)

  • 将封装好的具有独立功能的函数组合在一起使用;
  • 对组合API的理解 参看如下案例:
<script>
// ref 函数只能监听简单数据类型的响应式变化
import { ref } from 'vue';
// reactive 函数则可以监听 对象/数组 这样的复杂数据类型的响应式变化
import { reactive } from 'vue'

import useStuAdd from './useStuAdd.js'

export default {
  name:'App',
  // 组合API的入口函数(这是一个同步函数)
  setup(){
    // 组合1
    let {
      count,
      addCount,
    } = useCount();
    // 组合2
    let {
      states,
    } = useStus();
    // 组合3
    let {
      stuInfo,
      addStu
    } = useStuAdd(states);

    return {
      //
      count,// 暴露出的变量 将被注入到 Option API的 data中
      addCount,// 暴露出的方法 将被注入到 Option API的 methods中
      //
      states,
      //
      stuInfo,
      addStu
    }
  }
}
// 组合1
function useCount(){
    // 定义变量 值使用ref函数包裹 获取响应式(响应双向绑定)
    // count实际是一个对象 使用value属性获取值
    let count = ref(0);

    // 定义方法
    function addCount(){
      count.value += 1;
    }

    // 组合API中定义的变量或方法 必须通过 return {xxx,xxx}暴露出去
    return {
      count,
      addCount,
    };
}
// 组合2
function useStus(){
    // 定义复杂数据类型变量
    let states = reactive({
      stus:[],
    })

    // 组合API中定义的变量或方法 必须通过 return {xxx,xxx}暴露出去
    return {
      states,
    };
}
</script>

这些组合还可以定义到其他单独文件模块中:

import { reactive } from 'vue'

// 组合3 
function useStuAdd(state){
  let stuInfo = reactive({
    id:'',
    name:'',
    age:0
  });
  function addStu(){
    let stuOne = Object.assign({}, stuInfo);
    state.stus.push(stuOne);
  }
  return {
    stuInfo,
    addStu
  }
}
export default useStuAdd;
关于setup函数

setup函数执行时机:

  • setup -> beforeCreate -> created;
  • setup:在setup函数中,无法使用data和methods数据;
    • setup中的this被修改为undefined
    • setup只能是同步函数;
  • beforeCreate:组件被创建出来,组件的data和methods还没有初始化;
  • created:组件的data和methods已经初始化;
Vue3中的reactive函数

reactive是Vue3中提供的实现响应式数据的方法:

  • 在Vue2中响应式数据是通过defineProperty修改属性标识符来诗选的;
  • Vue3中响应式数据 则是通过ES6的Proxy来实现;

reactive函数的注意点:

  • 参数为对象/json/arr;即将传入的对象包装在Proxy中;
  • 如果不传对象,那么数据改变时,不会触发响应式的页面变化;
  • 传入的数组,通过下标修改数据也是支持的;
  • 值得注意的是,如果传入的对象/数组,包含其他对象,比如new Date()传入;
    • 这类的值需要重新赋值才能触发响应式,直接修改则无效;
setup(){
  let date = reactive({
    time:new Date()
  });
  function changeDate(){
    // 直接修改(无法触发页面变化)
    date.time.setDate(date.time.getDate() + 1);
    // 重新赋值
    const newTime = new Date(date.time.getTime());
    date.time = newTime.setDate(date.time.getDate() + 1);
  }
  return {date,changeDate};
}

Vue3中的ref函数

ref和reactive一样,也是用来实现响应式数据的方法:

  • reactive必须传递一个对象;ref方法,则实现对简单值的监听(也可以是对象);
  • ref底层还是reactive,系统会根据传入的值进行转换:let age = ref(18) ->let age = reactive({value:18})
    • 如果在template里使用ref类型数据(__v_ifRef:true),会自动添加.value;
    • 如果在template里使用reactive类型数据,不会自动添加.value;
  • 在Vue template模板中使用ref的值,直接用即可,无需通过value获取;
  • 在js中使用ref的值,则必须通过value获取(ref返回的实际是一个对象);

对于setup函数中的数据,可以使用vue提供的isRef、isReactive函数进行类型判断:

import {ref,reactive} from 'vue';
import {isRef,isReactive} from 'vue';

setup(){
  // ...
  isRef(age);// true
  isReactive(state);// true
}

递归监听

通过ref和reactive处理的对象,都是递归监听的:

  • 包含对象嵌套的state数据,每一层都是被监听的;如果数据量比较大,会很耗性能;
    • 外层对象包装成一个Proxy对象,里边的每一层嵌套,也都递归进行Proxy包装;
非递归监听

仅能监听一层:

  • 创建非递归监听,使用shallowReactiveshallowRef;
  • 需要注意的是,如果通过shallowRef创建的数据是嵌套对象,那么Vue监听的是.value的变化,而不是第一层数据,.value发生变化会触发页面重新渲染;shallowReactive监听的是第一层数据,第一层数据变化会触发页面的重新渲染;

如果在非递归监听下修改了嵌套的对象值,还想触发页面的重新渲染,可以使用triggerRef函数:

  • 注意,vue只提供了triggerRef函数,而没有提供triggerReactive函数,也就是说,没有办法主动触发reactive类型数据变化之后的页面渲染;
  • 虽然没有提供triggerReactive函数,但这个函数确实存在,shallowRef类型数据实际就会转换为triggerReactive类型数据去处理;
import { shallowRef, triggerRef } from 'vue'
let state = shallowRef({...});
// ...
state.value.a.b = 'b';
trggierRef(state);
// ...

注意:正常使用递归监听即可,只有数据嵌套很多时,才会考虑使用非递归监听;

toRaw方法

Proxy对象的变化,会引起界面响应式的修改;但如果直接修改引用对象,并不会触发;

toRaw的作用就是,获取响应式数据引用的原始数据:

  • 在某些不需要响应式变化的地方,还想修改响应式的数据,就可以通过toRaw拿到原始数据,来进行操作;
// reactive类型的原始数据获取
import {reactive, toRaw} from 'vue'

setup(){
  let obj1 = {name:'haha',age:18};// 原始数据
  let state = reactive(obj1);// Proxy对象
  let obj2 = toRaw(state);// state引用的原始对象
  obj1 === obj2 // true

  return {state};
}

// ref类型的原始数据获取
import {ref, toRaw} from 'vue'

setup(){
  let obj1 = {name:'haha',age:18};// 原始数据
  let state = ref(obj1);// Proxy对象
  let obj2 = toRaw(state.value);// state引用的原始对象
  obj1 === obj2 // true

  return {state};
}

markRaw方法

markRaw 处理之后的数据 再也不会被追踪 也不会产生响应式的变化

// reactive类型的原始数据获取
import {reactive, markRaw} from 'vue'

setup(){
  let obj = {name:'haha',age:18};// 原始数据
  obj = markRaw(obj);// markRaw 处理之后的数据 再也不会被追踪 也不会产生响应式的变化
  let state = reactive(obj);// Proxy对象

  function changeState(){
    state.name = 'heihei';// 这个赋值操作既不会生效 也不会引起页面响应式的变化;
  }

  return {state, changeState};
}

toRef 和 toRefs

toRef:可以把响应式数据与以前的数据关联起来,而且修改响应式数据还不会更新UI;

ref:

  • 复制,修改响应式数据state.value,obj中name不会改变;
  • 修改响应式数据state.value,页面自动更新;

toRef、toRefs:

  • 引用,修改响应式数据state.value,obj中name也会改变;
  • 修改响应式数据state.value,页面不会自动更新;
// ref
let obj = {name:'haha'};
let state = ref(obj.name);//注意 这里响应式处理的只是obj的一个属性 相当于复制了obj.name的值
state.value = 'heihei';//原始数据 obj.name 不受影响,界面上绑定的数据显示也会改变

// toRef(也是一种变成响应式数据的方式)
let obj = {name:'haha'};
let state = toRef(obj, 'name');
state.value = 'heihei';// 原始数据 obj.name 也会发生变化,界面上绑定的数据显示不受影响

// toRefs
let obj = {name:'haha', age:18};
let state = toRefs(obj);
state.name.value = 'heihei';
state.age.value = 20;

customRef

自定义一个Ref:myRef

import { customRef } from 'vue'

function myRef(value, delay = 200){
  let timeout//这个延时可以不要
  return customRef((track, trigger) => {
    retrun {
      get(){
        track()//当前值需要被追踪
        return value
      },
      set(newValue){
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()// 触发响应式变化
        }, delay)
      }
    }
  })
}

...

setup(){
  return {
    text:myRef('DM')// 用起来和Ref一样 只是自定义的
  }
}

自定义一个Ref的意义:

  • 对于需要异步获取的数据A,如果要把它对应成响应式的数据,正常需要在setUp中编写相应的业务网络请求代码,将接口返回数据用准备暴露出去的响应式对象接收;
let state = ref([])
state.value = A
return { state }
  • 使用自定义的Ref可以进一步将业务分离,直接将异步获取的对象转换成响应式的对象
import { customRef } from 'vue'

function myRef(value, delay = 200){
  // 传入的value是一个接口请求的url

  let timeout//这个延时可以不要
  return customRef((track, trigger) => {
    // 这里使用es6的fetch进行网络请求
    fetch(value).then(res => {
      value = data
      trigger()
    }).catch(err => {
      console.log(err)
    })

    retrun {
      get(){
        track()//当前值需要被追踪
        return value
      },
      set(newValue){
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()// 触发响应式变化
        }, delay)
      }
    }
  })
}

...

setup(){
  return {
    A:myRef('../list')// 通过接口获取的服务端对象 通过自定义Ref直接被处理成响应式的数据了
  }
} 

理解更多

ref获取元素

Vue2.0

<div ref="box">test</div>

...
//  获取组件
this.$ref.box

Vue3.0

<div ref="box">test</div>

...
import { ref, onMounted } from 'vue';
setUp(){
  let box = ref(null);
  // 抽离的生命周期函数 需要传入一个回调函数
  onMounted(() => {
    console.log(box.value)
  })
  // 直接在setUp方法中打印组件信息是无效的 因为此时处于的生命周期在created之前

  return { box } 
}

readonly isReadonly shallowReadonly

用于把对象处理成只读的响应式数据:

  • readonly:递归只读处理
  • shallowReadonly:第一层的只读处理(嵌套数据变化 不会引起页面显示变化)
  • isReadonly:判断是不是只读数据;
# URL: http://kongkong.fence.wmdev2.lsh123.com/ # jl-mis(接龙后台管理系统) 项目是基于vue-cli 3+ 构建的,请先将vue-cli升级到3+版本,vue-cli3 传送门(https://cli.vuejs.org/zh/)。 ## 技术栈 vue2 + vuex + vue-router + element-ui + webpack + ES6/7 + axios + less + flex + svg ### 项目图形化管理界面 ``` vue ui ``` ## 项目运行 #### 注意:由于涉及大量的 ES6/7 等新属性,node 需要 8.0 以上版本 ``` npm install npm run serve ``` ### 打包 ``` npm run build ``` ### 代码的 lint ``` npm run lint ``` # 项目布局 ``` . ├── public // HTML 和静态资源 │   ├── favicon.ico // 图标 │   ├── index.html // 入口html文件 ├── src // 源码目录 │   ├── assets // 静态资源 │   │   ├── images // 公共图片 │   ├── components // 组件 │   │   ├── common // 公共组件 │   │   ├── page // 页面 │   ├── libs // 封装库 │   │   ├── storage.js // 对cookie 和 localStorage 的封装 │   ├── plugins // 引用的插件 │   │   ├── axios.js // 对axios的的封装(拦截器相关) │   │   ├── element.js // 引入element-ui │   ├── router │   │   └── router.js // 路由配置 │   ├── service // 数据交互统一调配 │   │   ├── service.js // 获取数据的统一调配文件,对接口进行统一管理 │   ├── store // vuex的状态管理 │   │   ├── actions.js // 配置actions │   │   ├── getters.js // 配置getters │   │   ├── store.js // 引用vuex,创建store │   │   ├── modules // store模块 │   │   │   ├── urlGroups.js // 路由分组(权限相关) │   │   ├── mutation-types.js // 定义常量muations名 │   │   └── mutations.js // 配置mutations │   └── style // 样式字体相关 │   ├── fonts // 字体相关 │   ├── utility.less // 公共样式文件 │   ├── mixin.less // 样式配置文件 │   └── normalize.css // 样式重置 │   ├── App.vue // 页面入口文件 │   ├── main.js // 程序入口文件,加载各种公共组件 ├── vue.config.js // vue-cli 3+ 暴露出来的webpack相关的配置文件 . ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值