位置: IT常识 - 正文

vue3的ref,reactive的使用和原理解析

编辑:rootadmin
vue3的ref,reactive的使用和原理解析

目录

1.前言

2.比较

3.ref源码解析

4.reactive源码解析

createReactiveObject

handles的组成

get陷阱

set陷阱

5.总结


1.前言

推荐整理分享vue3的ref,reactive的使用和原理解析,希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:,内容如对您有帮助,希望把文章链接给更多的朋友!

vue3新增了ref,reactive两个api用于响应式数据,Ref 系列毫无疑问是使用频率最高的 api 之一,响应式意味着数据变动,页面局部自动更新。数据类型有基本数据类型(string,number,boolean,undfined,null,symbol),引用数据类型(object,array,set,map等)。如何精准检测跟踪js中所有的数据类型变动,并且能够达到vnode的对比后真实dom的渲染?vue中是如何做到的呢?简单实例如下:

import { reactive, ref } from "vue"; 

import type { Ref } from "vue";

// 定义响应式数据

const count: Ref<number> = ref(0);

function countClick() {

        count.value++; // 更新数据

}

// 定义引用类型数据标注

interface TypeForm {

        name: string;

        num: number;

        list?: Array<[]>;

}

const formInline: TypeForm = reactive({

        name: "",

        num: 0,

});

formInline.name = 'KinHKin'

formInline.num = 100

formInline.list = [1,2,3,4]

效果图:

 在线地址:

KinHKinhttps://rondsjinhuajin.github.io/DemoVue/#/但是,这只是简单的使用,配合了ts的类型标注,但是背后的原理是什么呢?🤔

2.比较

先来做个ref,reactive的比较:

比较维度(kin注)refreactive是否响应式对象 JavaScript Proxy是是创建的数据类型任何值类型的响应式响应式对象或数组是否需要.value 属性是否复杂的类型标注Ref 这个类型interface自定义隐式地从它的参数中推导类型是是dom的更新异步异步是否深层次响应默认深层次默认深层次

不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。

ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:

const obj = { foo: ref(1), bar: ref(2)}// 该函数接收一个 ref// 需要通过 .value 取值// 但它会保持响应性callSomeFunction(obj.foo)// 仍然是响应式的const { foo, bar } = obj

简言之,ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中。 

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。下面是之前的计数器例子,用 ref() 代替:

<script setup>import { ref } from 'vue'const count = ref(0)function increment() { count.value++}</script><template> <button @click="increment"> {{ count }} <!-- 无需 .value --> </button></template>

请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 

3.ref源码解析

对于vue3.2.2x版本的源码位于node_moudles/@vue/reactivity/dist/reactivity.cjs.js文件中

执行顺序是ref ->createRef ->new RefImpl 生成实例对象,提供get,set方法

源码中我们可以看到:入口有两个函数默认深层次响应ref,浅层次使用shallowRef,参数一个false,一个是true。

function ref(value) { return createRef(value, false);}function shallowRef(value) { return createRef(value, true);}

 接下来就是走createRef这个方法:

function createRef(rawValue, shallow) { if (isRef(rawValue)) { return rawValue; } return new RefImpl(rawValue, shallow);}

 这个createRef方法接受两个参数,一个是传入的基本类型的默认数值,一个是否是深层次响应的boolean值。

function isRef(r) { return !!(r && r.__v_isRef === true);}

如果rawValue本就是ref类型的会立即返回rawValue,否则返回一个RefImpl实例。

 RefImpl类:

class RefImpl { constructor(value, __v_isShallow) { this.__v_isShallow = __v_isShallow; this.dep = undefined; this.__v_isRef = true; this._rawValue = __v_isShallow ? value : toRaw(value); this._value = __v_isShallow ? value : toReactive(value); } get value() { trackRefValue(this); return this._value; } set value(newVal) { const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal); newVal = useDirectValue ? newVal : toRaw(newVal); if (shared.hasChanged(newVal, this._rawValue)) { this._rawValue = newVal; this._value = useDirectValue ? newVal : toReactive(newVal); triggerRefValue(this, newVal); } }}

RefImpl类在构造函数中,__v_isShallow表示是否是浅层次响应的属性, 私有的 _rawValue 变量,存放 ref 的旧值,_value是ref接受的最新的值。公共的只读变量 __v_isRef 是用来标识该对象是一个 ref 响应式对象的标记与在讲述 reactive api 时的 ReactiveFlag 相同。

在const toReactive = (value) => shared.isObject(value) ? reactive(value) : value;这个函数的内部判断是否传入的是一个对象,如果是一个对象就返回reactive返回代理对象,否则直接返回原参数。

当我们通过 ref.value 的形式读取该 ref 的值时,就会触发 value 的 getter 方法,在 getter 中会先通过 trackRefValue 收集该 ref 对象的 value 的依赖,收集完毕后返回该 ref 的值。

function trackRefValue(ref) { if (shouldTrack && activeEffect) { ref = toRaw(ref); { trackEffects(ref.dep || (ref.dep = createDep()), { target: ref, type: "get" /* TrackOpTypes.GET */, key: 'value' }); } }}

当我们对 ref.value 进行修改时,又会触发 value 的 setter 方法,会将新旧 value 进行比较,如果值不同需要更新,则先更新新旧 value,之后通过 triggerRefValue 派发该 ref 对象的 value 属性的更新,让依赖该 ref 的副作用函数执行更新。

function triggerRefValue(ref, newVal) { ref = toRaw(ref); if (ref.dep) { { triggerEffects(ref.dep, { target: ref, type: "set" /* TriggerOpTypes.SET */, key: 'value', newValue: newVal }); } }}4.reactive源码解析vue3的ref,reactive的使用和原理解析

对于vue3.2.2x版本的源码位于node_moudles/@vue/reactivity/dist/reactivity.cjs.js文件中

整体描述vue3的更新机制:

在 Vue3 中,通过 track 的处理器函数来收集依赖,通过 trigger 的处理器函数来派发更新,每个依赖的使用都会被包裹到一个副作用(effect)函数中,而派发更新后就会执行副作用函数,这样依赖处的值就被更新了。

Proxy 对象能够利用 handler 陷阱在 get、set 时捕获到任何变动,也能监听对数组索引的改动以及 数组 length 的改动。

执行顺序是:reactive -> createReactiveObject ->

function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (isReadonly(target)) { return target; } return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);}

第三行 isReadonly 函数 确定对象是否为只读对象,IS_READONLY key 确定对象是否为只读对象。ReactiveFlags 枚举会在源码中不断的与我们见面,所以有必要提前介绍一下 ReactiveFlags:

function isReadonly(value) {

return !!(value && value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */]);

}

export const enum ReactiveFlags { SKIP = '__v_skip', // 是否跳过响应式 返回原始对象 IS_REACTIVE = '__v_isReactive', // 标记一个响应式对象 IS_READONLY = '__v_isReadonly', // 标记一个只读对象 RAW = '__v_raw' // 标记获取原始值 IS_SHALLOW = '__v_isShallow' // 是否浅层次拷贝}

在 ReactiveFlags 枚举中有 5 个枚举值,这五个枚举值的含义都在注释里。对于 ReactiveFlags 的使用是代理对象对 handler 中的 trap 陷阱非常好的应用,对象中并不存在这些 key,而通过 get 访问这些 key 时,返回值都是通过 get 陷阱的函数内处理的。介绍完 ReactiveFlags 后我们继续往下看。

createReactiveObject

入参部分:

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {}

先看 createReactiveObject 函数的签名,该函数接受 5 个参数:

target:目标对象,想要生成响应式的原始对象。isReadonly:生成的代理对象是否只读。baseHandlers:生成代理对象的 handler 参数。当 target 类型是 Array 或 Object 时使用该 handler。collectionHandlers:当 target 类型是 Map、Set、WeakMap、WeakSet 时使用该 handler。proxyMap:存储生成代理对象后的 Map 对象。

这里需要注意的是 baseHandlers 和 collectionHandlers 的区别,这两个参数会根据 target 的类型进行判断,最终选择将哪个参数传入 Proxy 的构造函数,当做 handler 参数使用。

逻辑部分:

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) { // 如何不是对象 曝出警告 返回其原始值 if (!shared.isObject(target)) { { console.warn(`value cannot be made reactive: ${String(target)}`); } return target; } // target is already a Proxy, return it. // exception: calling readonly() on a reactive object // 如果目标已经是一个代理,直接返回 KinHKin译 // 除非对一个响应式对象执行 readonly if (target["__v_raw" /* ReactiveFlags.RAW */] && !(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) { return target; } // target already has corresponding Proxy // 目标已经存在对应的代理对象 KinHKin译 const existingProxy = proxyMap.get(target); if (existingProxy) { return existingProxy; } // only specific value types can be observed. // 只有白名单里的类型才能被创建响应式对象 KinHKin译 const targetType = getTargetType(target); if (targetType === 0 /* TargetType.INVALID */) { return target; } const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers); proxyMap.set(target, proxy); return proxy;}

在该函数的逻辑部分,可以看到基础数据类型并不会被转换成代理对象,而是直接返回原始值。

并且会将已经生成的代理对象缓存进传入的 proxyMap,当这个代理对象已存在时不会重复生成,会直接返回已有对象。

也会通过 TargetType 来判断 target 目标对象的类型,Vue3 仅会对 Array、Object、Map、Set、WeakMap、WeakSet 生成代理,其他对象会被标记为 INVALID,并返回原始值。

当目标对象通过类型校验后,会通过 new Proxy() 生成一个代理对象 proxy,handler 参数的传入也是与 targetType 相关,并最终返回已生成的 proxy 对象。

所以回顾 reactive api,我们可能会得到一个代理对象,也可能只是获得传入的 target 目标对象的原始值。

handles的组成

在 @vue/reactive 库中有 baseHandlers 和 collectionHandlers 两个模块,分别生成 Proxy 代理的 handlers 中的 trap 陷阱。

例如在上面生成 reactive 的 api 中 baseHandlers 的参数传入了一个 mutableHandlers 对象,这个对象是这样的:

const mutableHandlers = { get, set, deleteProperty, has, ownKeys};

通过变量名我们能知道 mutableHandlers 中存在 5 个 trap 陷阱。而在 baseHandlers 中,get 和 set 都是通过工厂函数生成的,以便于适配除 reactive 外的其他 api,例如 readonly、shallowReactive、shallowReadonly 等。

baseHandlers 是处理 Array、Object 的数据类型的,这也是我们绝大部分时间使用 Vue3 时使用的类型,所以笔者接下来着重的讲一下baseHandlers 中的 get 和 set 陷阱。

get陷阱

上一段提到 get 是由一个工厂函数生成的,先来看一下 get 陷阱的种类。

const get = /*#__PURE__*/ createGetter();const shallowGet = /*#__PURE__*/ createGetter(false, true);const readonlyGet = /*#__PURE__*/ createGetter(true);const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true);

get 陷阱有 4 个类型,分别对应不同的响应式 API,从名称中就可以知道对应的 API 名称,非常一目了然。而所有的 get 都是由 createGetter 函数生成的。所以接下来我们着重看一下 createGetter 的逻辑。

从函数的签名看起,入参有2个,一个是isReadonly,另一个是shallow,让使用 get 陷阱的 api 按需使用。

function createGetter(isReadonly = false, shallow = false) { return function get(target, key, receiver) { }}

函数内部返回一个get函数,使用了闭包的方式,将get函数中的参数传到handlers中。

createGetter 的逻辑:

function createGetter(isReadonly = false, shallow = false) { return function get(target, key, receiver) { // 如果key是响应式的对象 就返回不是只读 *KinHKin注释* if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) { return !isReadonly; } // 如果key是只读对象 就返回只读是true *KinHKin注释* else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) { return isReadonly; } // 如果key是浅层次响应对象 就返回浅层次是true *KinHKin注释* else if (key === "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */) { return shallow; } // 如果key是原始值对象并且改变的值和原始标记一致 就返回原始值 *KinHKin注释* else if (key === "__v_raw" /* ReactiveFlags.RAW */ && receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap).get(target)) { return target; } // 判断传入的值是不是数组 const targetIsArray = shared.isArray(target); // 如果不是只读 并且是数组 // arrayInstrumentations 是一个对象,对象内保存了若干个被特殊处理的数组方法,并以键值对的形式存储。 *KinHKin注释* if (!isReadonly && targetIsArray && shared.hasOwn(arrayInstrumentations, key)) { // 特殊处理数组返回结果 return Reflect.get(arrayInstrumentations, key, receiver); } // 获取 Reflect 执行的 get 默认结果 const res = Reflect.get(target, key, receiver); // 如果是 key 是 Symbol,并且 key 是 Symbol 对象中的 Symbol 类型的 key // 或者 key 是不需要追踪的 key: __proto__,__v_isRef,__isVue // 直接返回 get 结果 *KinHKin注释* if (shared.isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res; } // 不是只读对象 执行 track 收集依赖 *KinHKin注释* if (!isReadonly) { track(target, "get" /* TrackOpTypes.GET */, key); } // 是浅层次响应 直接返回 get 结果 *KinHKin注释* if (shallow) { return res; } if (isRef(res)) { // ref unwrapping - skip unwrap for Array + integer key. // 如果是 ref ,则返回解包后的值 - 当 target 是数组,key 是 int 类型时,不需要解包 *KinHKin注释* return targetIsArray && shared.isIntegerKey(key) ? res : res.value; } if (shared.isObject(res)) { // Convert returned value into a proxy as well. we do the isObject check // here to avoid invalid value warning. Also need to lazy access readonly // and reactive here to avoid circular dependency. // 将返回的值也转换成代理,我们在这里做 isObject 的检查以避免无效值警告。 // 也需要在这里惰性访问只读和星影视对象,以避免循环依赖。*KinHKin注释* return isReadonly ? readonly(res) : reactive(res); } // 不是 object 类型则直接返回 get 结果 *KinHKin注释* return res; };}

从这段 createGetter 逻辑中,之前专门介绍过的 ReactiveFlags 枚举在这就取得了妙用。其实目标对象中并没有这些 key,但是在 get 中Vue3 就对这些 key 做了特殊处理,当我们在对象上访问这几个特殊的枚举值时,就会返回特定意义的结果。而可以关注一下 ReactiveFlags.IS_REACTIVE 这个 key 的判断方式,为什么是只读标识的取反呢?因为当一个对象的访问能触发这个 get 陷阱时,说明这个对象必然已经是一个 Proxy 对象了,所以只要不是只读的,那么就可以认为是响应式对象了。

get 的后续逻辑:

继续判断 target 是否是一个数组,如果代理对象不是只读的,并且 target 是一个数组,并且访问的 key 在数组需要特殊处理的方法里,就会直接调用特殊处理的数组函数执行结果,并返回。

arrayInstrumentations 是一个对象,对象内保存了若干个被特殊处理的数组方法,并以键值对的形式存储。

我们之前说过 Vue2 以原型链的方式劫持了数组,而在这里也有类似地作用,下面是需要特殊处理的数组。

对索引敏感的数组方法

includes、indexOf、lastIndexOf会改变自身长度的数组方法,需要避免 length 被依赖收集,因为这样可能会造成循环引用

push、pop、shift、unshift、splice

下面的几个key是不需要被依赖收集或者是返回响应式结果的:

__proto___v_isRef__isVue

在处理完数组后,我们对 target 执行 Reflect.get 方法,获得默认行为的 get 返回值。

之后判断 当前 key 是否是 Symbol,或者是否是不需要追踪的 key,如果是的话直接返回 get 的结果 res。

接着判断当前代理对象是否是只读对象,如果不是只读的话,则运行笔者上文提及的 tarck 处理器函数收集依赖。

如果是 shallow 的浅层响应式,则不需要将内部的属性转换成代理,直接返回 res。

如果 res 是一个 Ref 类型的对象,就会自动解包返回,这里就能解释官方文档中提及的 ref 在 reactive 中会自动解包的特性了。而需要注意的是,当 target 是一个数组类型,并且 key 是 int 类型时,即使用索引访问数组元素时,不会被自动解包。

如果 res 是一个对象,就会将该对象转成响应式的 Proxy 代理对象返回,再结合我们之前分析的缓存已生成的 proxy 对象,可以知道这里的逻辑并不会重复生成相同的 res,也可以理解文档中提及的当我们访问 reactive 对象中的 key 是一个对象时,它也会自动的转换成响应式对象,而且由于在此处生成 reactive 或者 readonly 对象是一个延迟行为,不需要在第一时间就遍历 reactive 传入的对象中的所有 key,也对性能的提升是一个帮助。

当 res 都不满足上述条件时,直接返回 res 结果。例如基础数据类型就会直接返回结果,而不做特殊处理。最后,get 陷阱的逻辑全部结束了。

set陷阱

set 也有一个 createSetter 的工厂函数,也是通过柯里化的方式返回一个 set 函数。

set 的函数比较简短,所以这次一次性把写好注释的代码放上来,先看代码再讲逻辑。

// 纯函数 默认深层次响应 函数不入参 *KinHKin*const set = /*#__PURE__*/ createSetter();// 纯函数 浅层次响应 函数入参是true *KinHKin*const shallowSet = /*#__PURE__*/ createSetter(true);function createSetter(shallow = false) { return function set(target, key, value, receiver) { let oldValue = target[key]; // 如果原始值是只读and是ref类型and新的value属性不是ref类型 直接返回 if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) { return false; } if (!shallow) { // 如果新的值不是浅层次响应对象,也不是只读 更新旧值 新值为普通对象 *KinHKin* if (!isShallow(value) && !isReadonly(value)) { oldValue = toRaw(oldValue); value = toRaw(value); } // 当不是 只读 模式时,判断旧值是否是 Ref,如果是则直接更新旧值的 value // 因为 ref 有自己的 setter *KinHKin* if (!shared.isArray(target) && isRef(oldValue) && !isRef(value)) { oldValue.value = value; return true; } } // 判断 target 中是否存在 key *KinHKin* const hadKey = shared.isArray(target) && shared.isIntegerKey(key) ? Number(key) < target.length : shared.hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); // don't trigger if target is something up in the prototype chain of original // 如果目标是原始对象原型链上的属性,则不会触发 trigger 派发更新 *KinHKin* if (target === toRaw(receiver)) { // 使用 trigger 派发更新,根据 hadKey 区别调用事件 if (!hadKey) { trigger(target, "add" /* TriggerOpTypes.ADD */, key, value); } else if (shared.hasChanged(value, oldValue)) { trigger(target, "set" /* TriggerOpTypes.SET */, key, value, oldValue); } } return result; };}

在 set 的过程中会首先获取新旧与旧值,当目前的代理对象不是浅层比较时,会判断旧值是否是一个 Ref,如果旧值不是数组且是一个 ref类型的对象,并且新值不是 ref 对象时,会直接修改旧值的 value。

看到这里可能会有疑问,为什么要更新旧值的 value?如果你使用过 ref 这个 api 就会知道,每个 ref 对象的值都是放在 value 里的,而 ref 与 reactive 的实现是有区别的,ref 其实是一个 class 实例,它的 value 有自己的 set ,所以就不会在这里继续进行 set 了。

在处理完 ref 类型的值后,会

本文链接地址:https://www.jiuchutong.com/zhishi/298486.html 转载请保留说明!

上一篇:2023英伟达显卡排名天梯图(已更新)(2023英伟达显卡天梯图完整版)

下一篇:Vue3:探讨一下mixin

  • 魅族 18 Pro支持的音频格式有哪些(魅族18pro支持lhdc吗)

    魅族 18 Pro支持的音频格式有哪些(魅族18pro支持lhdc吗)

  • 美团评价可以修改吗(美团评价可以修改吗2022)

    美团评价可以修改吗(美团评价可以修改吗2022)

  • 小米换手机怎么把东西移过去(小米换手机怎么把微信东西移过去)

    小米换手机怎么把东西移过去(小米换手机怎么把微信东西移过去)

  • 苹果充电器上的金属条变黑怎么办(苹果充电器上的黑点怎么弄掉)

    苹果充电器上的金属条变黑怎么办(苹果充电器上的黑点怎么弄掉)

  • 陌陌不实名不能用吗(陌陌不实名制可以正常用吗)

    陌陌不实名不能用吗(陌陌不实名制可以正常用吗)

  • 抖音0播放解决方法(抖音0播放要删除吗)

    抖音0播放解决方法(抖音0播放要删除吗)

  • 华为相册删除照片里清空了能恢复吗(华为相册删除照片删不掉)

    华为相册删除照片里清空了能恢复吗(华为相册删除照片删不掉)

  • ipa是什么文件(iPa是什么文件)

    ipa是什么文件(iPa是什么文件)

  • p40充电器多少w(p40pro充电器参数多少w)

    p40充电器多少w(p40pro充电器参数多少w)

  • 天猫上车是什么意思(天猫上买车便宜吗)

    天猫上车是什么意思(天猫上买车便宜吗)

  • 手机录像能显示时间吗(手机录像能显示位置吗)

    手机录像能显示时间吗(手机录像能显示位置吗)

  • 微信不加对方怎么聊天(微信不加对方怎么确定是否被拉黑)

    微信不加对方怎么聊天(微信不加对方怎么确定是否被拉黑)

  • 华为col-al10配置(华为col-al10多少钱一台)

    华为col-al10配置(华为col-al10多少钱一台)

  • 支付宝刷脸可以改吗(支付宝刷脸可以取消吗)

    支付宝刷脸可以改吗(支付宝刷脸可以取消吗)

  • vivox23录屏功能在哪(vivox23录屏功能怎么打开)

    vivox23录屏功能在哪(vivox23录屏功能怎么打开)

  • 网络用户与计算机网络之间的接口(网络用户和计算机网络的接口)

    网络用户与计算机网络之间的接口(网络用户和计算机网络的接口)

  • 乐2手机怎么刷机(乐2进入刷机模式后怎么办)

    乐2手机怎么刷机(乐2进入刷机模式后怎么办)

  • 手机版wps怎么导入字体(手机版wps怎么导出word文档)

    手机版wps怎么导入字体(手机版wps怎么导出word文档)

  • 抖音号忘了怎么找回(抖音号忘了怎么解绑身份证)

    抖音号忘了怎么找回(抖音号忘了怎么解绑身份证)

  • u盘在电脑上打开步骤(U盘在电脑上打开过的文件可以找回吗)

    u盘在电脑上打开步骤(U盘在电脑上打开过的文件可以找回吗)

  • 华为手机向下滑怎么没有通知状态(华为手机向下滑动设置)

    华为手机向下滑怎么没有通知状态(华为手机向下滑动设置)

  • 怎么压缩照片到200k(怎么压缩照片到20k)

    怎么压缩照片到200k(怎么压缩照片到20k)

  • 如何彻底删除QQ聊天记录的图片?(如何彻底删除QQ频道私信)

    如何彻底删除QQ聊天记录的图片?(如何彻底删除QQ频道私信)

  • 如何进行TP-Link路由器设置?(如何进行降压治疗)

    如何进行TP-Link路由器设置?(如何进行降压治疗)

  • 删除桌面图标快捷方式教程(删除桌面上的图标用什么键)

    删除桌面图标快捷方式教程(删除桌面上的图标用什么键)

  • 百度地图定位不准,和地位不显示的问题,解决方案(百度地图定位不更新)

    百度地图定位不准,和地位不显示的问题,解决方案(百度地图定位不更新)

  • 个体工商户要报工商年报吗?
  • 运动会奖品有什么
  • 员工借款业务流程
  • 小规模企业出租设备税率
  • 公司为员工报销的医药费
  • 工程结算成本和合同成本区别
  • 车辆补贴的钱最快多久到账
  • 期间损益结转错误怎么冲销
  • 贸易公司没有仓库需要做入库
  • 增值税逾期未申报网上可以吗
  • 代发工资开票内容
  • 税率和征收率的含义和区别
  • 社保滞纳金可以计入管理费用吗为什么
  • 物流分公司怎么运作
  • 外购烟丝消费税是多少
  • 发票不一致是什么原因
  • 小规模纳税人咨询服务费税率是多少
  • 对公账户的利息收入如何入账
  • 销售折扣购买方的红字通知单如何开
  • 期交保费资金占用年限计算公式
  • 供热公司向用户提供
  • 增值税普通发票和电子普通发票的区别
  • 软碟通支持uefi吗
  • 销售过程会计核算实训过程
  • 银行同业利息 水利基金
  • 非金融机构从事贷款谁负责监管
  • CNN+LSTM+Attention实现时间序列预测(PyTorch版)
  • js鼠标键盘事件
  • 浏览器你
  • 文心一格百度
  • jquery教程chm
  • 文本超出单元格
  • 工业企业采购部工资计入什么科目
  • opengl 帧率
  • springmvc简介
  • 火车票抵扣进项税需要认证吗
  • 电子软件是如何诞生的
  • phpcms程序
  • mysql5.6解压版安装教程
  • db2 knowledge center
  • 帝国cms使用手册
  • 个税租房租金扣除规则
  • 成本核算方法是指
  • 直接人工成本的计算公式
  • 网银报错
  • 资产总额是营业收入吗
  • mysql在表中添加一个新的属性
  • 文化事业建设税怎么申报
  • 记账凭证错误的更正方法
  • 给员工缴纳的雇工保险
  • 会计科目中其期末余额应列入资产负债表存货项目的有
  • 电子商务会计怎么样
  • 固定资产竣工前予以资本化吗
  • 费用误计入固定资产
  • 车费属于什么会计科目类
  • 农产品进项税额会计分录
  • 明细分类账怎么做表格
  • 三星笔记是干什么用的
  • vcspawn.exe什么意思
  • 360卫士重装电脑够进入不了桌面
  • 图解在OS X中管理窗口大小的多种方法
  • dghm.exe是什么程序
  • win10怎么关闭欢迎界面
  • win7系统玩英雄联盟蓝屏
  • jquery的事件处理
  • nodejs做接口
  • windows visual studio 基于openGL的粒子系统设计
  • linux实现shell
  • node.js中的http.response.write方法使用说明
  • 一篇文章让你彻底读懂德国历史
  • jquery(document).ready
  • 医保可以异地交嘛
  • 税务查询热线
  • 四川省地方税务局2017年1号公告
  • 新能源免税申报,车辆类型怎么填
  • 江苏昆山电费查询电话
  • 人人财富最新消息
  • 农村墓地规划土地使用程序和规定
  • 个人所得税税收完税证明哪里打印
  • 代理记账公司前期准备流程
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设