位置: IT常识 - 正文

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

发布时间:2024-01-17
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

  • 我的电脑图标怎么调出来(我的电脑图标怎么改变)

    我的电脑图标怎么调出来(我的电脑图标怎么改变)

  • 微信折叠群怎么设置(微信折叠群怎么取消)

    微信折叠群怎么设置(微信折叠群怎么取消)

  • 微信添加好友有个箭头是什么意思(微信添加好友有上限吗)

    微信添加好友有个箭头是什么意思(微信添加好友有上限吗)

  • 闲鱼举报人对方会知道吗(闲鱼举报成功后对方知道是谁举报的吗)

    闲鱼举报人对方会知道吗(闲鱼举报成功后对方知道是谁举报的吗)

  • word文档页面横着排版怎么设置(word文档怎么单独一页横向排版)

    word文档页面横着排版怎么设置(word文档怎么单独一页横向排版)

  • cpu的h是什么意思(cpu后缀字母h)

    cpu的h是什么意思(cpu后缀字母h)

  • 视频可以裁剪尺寸吗(视频可以裁剪尺寸吗剪映)

    视频可以裁剪尺寸吗(视频可以裁剪尺寸吗剪映)

  • 苹果8p频繁死机怎么解决(iphone8p频繁死机)

    苹果8p频繁死机怎么解决(iphone8p频繁死机)

  • 小米chrome浏览器闪退(小米 chrome浏览器)

    小米chrome浏览器闪退(小米 chrome浏览器)

  • 微信群里抢红包行为异常怎么解除(微信群里抢红包怎么才能抢到最佳)

    微信群里抢红包行为异常怎么解除(微信群里抢红包怎么才能抢到最佳)

  • 苹果7什么处理器(苹果换机)

    苹果7什么处理器(苹果换机)

  • 手机电池温度45度正常吗(手机电池温度45度)

    手机电池温度45度正常吗(手机电池温度45度)

  • 美版苹果怎么看是不是翻新机(美版苹果怎么看激活时间)

    美版苹果怎么看是不是翻新机(美版苹果怎么看激活时间)

  • 5g手机插4g卡网速快吗(5g手机插4g卡网速慢)

    5g手机插4g卡网速快吗(5g手机插4g卡网速慢)

  • apple pencil美版和国行区别(pencil美版区别)

    apple pencil美版和国行区别(pencil美版区别)

  • 红米k20pro断流怎么解决(红米k20pro wifi断流官方回应)

    红米k20pro断流怎么解决(红米k20pro wifi断流官方回应)

  • 苹果xr是几代手机(苹果xr是第几代)

    苹果xr是几代手机(苹果xr是第几代)

  • win10开始菜单点击无效(win10开始菜单点击出现关键错误)

    win10开始菜单点击无效(win10开始菜单点击出现关键错误)

  • 京东预售定金能退款吗(京东预售定金能改定其它产品吗)

    京东预售定金能退款吗(京东预售定金能改定其它产品吗)

  • f1键盘代表什么(键盘上的f1是干什么用的)

    f1键盘代表什么(键盘上的f1是干什么用的)

  • 荣耀20pro是屏幕解锁吗(荣耀20pro屏幕护眼吗)

    荣耀20pro是屏幕解锁吗(荣耀20pro屏幕护眼吗)

  • 微信被冻结了钱怎么取出来(微信被冻结了钱显示没有了)

    微信被冻结了钱怎么取出来(微信被冻结了钱显示没有了)

  • arm是什么(arm是什么牌子品牌)

    arm是什么(arm是什么牌子品牌)

  • 如何在vue项目中使用rem布局(如何在vue项目中运行后端)

    如何在vue项目中使用rem布局(如何在vue项目中运行后端)

  • 小规模不超过30万怎么交税
  • 广州二手房交易契税
  • 小规模纳税人收入超过500万怎么办
  • 延期申报后可否延期缴纳税款
  • 疫情期间餐饮发票税点
  • 投资款转账没备案怎么办
  • 未确认融资收益对应的科目是什么
  • 固定资产报废清理净损失属于什么费用
  • 公司注销之后股东还承担责任吗
  • 钻井勘探支出已费用化的探井
  • 股权计税成本如何计算?
  • 个税手续费返还会计分录
  • 非独立核算的公司怎么报税
  • 所得税费用为负数是什么意思
  • 增值税多交可以在下一期直接抵吗
  • 计提应付利息编制记账凭证用什么原始凭证?
  • 税控盘服务费抵税会计分录
  • 应交税费属于什么费用科目
  • 个体工商户开票免税额度是多少
  • 2020年餐饮行业免税政策
  • 委托个人加工应税消费品纳税地点
  • 收取赔偿金开具什么发票
  • 发票金额大于开票金额
  • 增值税贷方余额为负数怎么调整
  • 退回多扣的社保费给员工,怎样做会计分录?
  • 非应纳增值税项目
  • 公司设计费属于什么费用
  • win10专业版19042.630
  • 重装win10系统怎么跳过创建用户名
  • linux i
  • ecshop有哪些bug
  • 房地产企业前期工程费包括什么项目
  • 前端文件预览
  • PHP:pg_fetch_all()的用法_PostgreSQL函数
  • php知识总结
  • 补缴企业所得税和滞纳金如何入账
  • 企业所得税退税流程
  • Symfony学习十分钟入门经典教程
  • 跨年庆典中燃放的歌曲
  • 加计扣除减免政策
  • php 带cookie post
  • php自定义变量的方法是
  • 什么叫非侵入性装置
  • nlp track
  • react 入门教程
  • 国内版的chatpdf
  • chkconfig命令参数
  • 城建税减半征收会计分录
  • xml 入门
  • 帝国cms视频教程
  • php网站访问量大怎么优化
  • 免征增值税的会计处理一般纳税人
  • 汇算清缴怎么弥补亏损怎么操作
  • 用于集体福利的进项税额怎么处理
  • 应收帐款周转率计算公式为
  • 收到社保局返还的各类社保款项应从哪个表单发起
  • 管理费用如何结转成本
  • 五金领用流程
  • 拆除费计入哪个科目
  • 计提工资与实发工资有差额怎么计算成本
  • 新组建的公司该怎样开展工作
  • 管理费怎么扣除
  • mysql5.7.21安装
  • window10安装mysql5.7
  • 打造安全稳定
  • windows xp怎么设置桌面
  • win7系统运行速度提升
  • windows资源监视器
  • mac我的所有文件 删除
  • cocos creator 动画制作
  • div +css
  • python中列表删除
  • jquery validate
  • 了不起的狐狸爸爸
  • Node.js中的事件循环是什么意思
  • unity连接
  • 基于vue的app
  • jquery获取指定元素
  • 驻国家税务总局纪检组副组长
  • 税务局发票邮寄费用谁承担
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号