从源码分析vue3组件的生命周期

18 篇文章 0 订阅
订阅专栏

概览

借官网一张图充篇幅☺
在这里插入图片描述
这张图展示了一个vue组件从开始渲染到卸载结束一整个生命周期经历的每个环节
但只罗列了选项式api生命周期钩子,没有将组合式api的生命周期钩子放进去
下面这个表格列出了所有选项式api生命周期钩子和组合式api生命周期钩子,以及他们的对应关系和执行的时机

组合式api选项式api执行时机
beforeCreate初始化组件内的属性(如:datapropswatchcomputed等)之前
created初始化组件内的属性(如:datapropswatchcomputed等)之后
onBeforeMount()beforeMount组件开始挂载之前
onMounted()mounted组件挂载之后
onBeforeUpdate()beforeUpdate组件数据更新之后,页面更新之前
onUpdated()updated组件数据更新之后,页面更新之后
onBeforeUnmount()beforeUnmount组件即将卸载,但还未卸载
onUnmounted()unmounted组件卸载之后
onErrorCaptured()errorCaptured捕获了后代组件传递的错误时
onRenderTracked()renderTracked响应式依赖被组件的渲染作用追踪后,仅开发模式下使用
onRenderTriggered()renderTriggered响应式依赖被组件触发了重新渲染之后,仅开发模式下使用
onActivated()activated组件被keep-alive包裹,页面从不活动状态变为活动状态执时
onDeactivated()deactivated组件被keep-alive包裹,页面从活动状态变为不活动状态执时
onServerPrefetch()serverPrefetch组件实例在服务器上被渲染之前,为异步函数,仅ssr模式使用

源码分析

由于源码过多,贴源码的时候会省略无关代码
代码里面的英文注释为源码注释,中文注释为笔者所写
准备好开始撸源码了吗😏

我们先看一下vue3是如何处注册生命周期钩子函数的
vue3直接通过类型声明了所有组合式生命周期api,当我们调用这些函数的时候vue会通过类型创建相应的生命周期钩子函数,这个很重要,不但我们实际开发式会这么做,vue也会通过这种方式去处理我们在组件内部定义的生命周期相关的选项式函数,在分析后面源码时会提到。

const onBeforeMount = createHook("bm" /* LifecycleHooks.BEFORE_MOUNT */);
const onMounted = createHook("m" /* LifecycleHooks.MOUNTED */);
const onBeforeUpdate = createHook("bu" /* LifecycleHooks.BEFORE_UPDATE */);
const onUpdated = createHook("u" /* LifecycleHooks.UPDATED */);
const onBeforeUnmount = createHook("bum" /* LifecycleHooks.BEFORE_UNMOUNT */);
const onUnmounted = createHook("um" /* LifecycleHooks.UNMOUNTED */);
const onServerPrefetch = createHook("sp" /* LifecycleHooks.SERVER_PREFETCH */);
const onRenderTriggered = createHook("rtg" /* LifecycleHooks.RENDER_TRIGGERED */);
const onRenderTracked = createHook("rtc" /* LifecycleHooks.RENDER_TRACKED */);

下面我们就从挂载组件开始撸源码
mountComponent是挂载组件的入口,里面包含了两个分支函数setupComponentsetupRenderEffect
看这两个函数名字我们大概知道他们是干嘛的

  • setupComponent:安装组件,主要来初始化定义组件时我们在组件内部定义的所有属性
  • setupRenderEffect:安装渲染特效,那肯定是将定义组件时的模板渲染为我们可以看到的页面内容了
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
        const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));
        //~~~
        //此处省略n行代码
        //~~~
        // resolve props and slots for setup context
        {
            {
                startMeasure(instance, `init`);
            }
            setupComponent(instance);
            {
                endMeasure(instance, `init`);
            }
        }
        //~~~
        //此处省略n行代码
        //~~~
        setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
        //~~~
        //此处省略n行代码
        //~~~
    }

先进setupComponent,重点看一下setupStatefulComponent这个函数

function setupComponent(instance, isSSR = false) {
    isInSSRComponentSetup = isSSR;
    const { props, children } = instance.vnode;
    const isStateful = isStatefulComponent(instance);
    initProps(instance, props, isStateful, isSSR);
    initSlots(instance, children);
    const setupResult = isStateful
        ? setupStatefulComponent(instance, isSSR)
        : undefined;
    isInSSRComponentSetup = false;
    return setupResult;
}

setupStatefulComponent这个函数主要什么工作呢?

  • 首先执行了我们定义组件时的setup函数,当然也包括在setup里面编写的所有生命周期相关的组合式api代码
  • 然后处理setup返回的结果
function setupStatefulComponent(instance, isSSR) {
    //~~~
    //此处省略n行代码
    //~~~
    // 2. call setup() 执行setup函数
    const { setup } = Component;
    if (setup) {
		//~~~
		//此处省略n行代码
		//~~~
		// 调用setup并获得返回的结果
        const setupResult = callWithErrorHandling(setup, instance, 0 /* ErrorCodes.SETUP_FUNCTION */, [reactivity.shallowReadonly(instance.props) , setupContext]);
        reactivity.resetTracking();
        unsetCurrentInstance();
        if (shared.isPromise(setupResult)) {
			//~~~
			//此处省略n行代码
			//~~~
        }
        else {
            handleSetupResult(instance, setupResult, isSSR);
        }
    }
    else {
        finishComponentSetup(instance, isSSR);
    }
}

这里不多说,直接看finishComponentSetup

function handleSetupResult(instance, setupResult, isSSR) {
	//~~~
	//此处省略n行代码
	//~~~
    finishComponentSetup(instance, isSSR);
}

再进到applyOptions

function finishComponentSetup(instance, isSSR, skipOptions) {
	//~~~
	//此处省略n行代码
	//~~~
	//处理选项式api
	applyOptions(instance);
	//~~~
	//此处省略n行代码
	//~~~

}

重点来了,睁大你的眼睛
applyOptions这个函数的主要工作:

  • 执行beforeCreate钩子函数
  • 初始化初始化组件属性
  • 执行created钩子函数
  • 将选项式生命周期钩子函数注册为组合式生命周期钩子函数
    例如我们在组件内部定义了mounted函数,这个函数实际上会调用组合式api onMountedmounted函数注册为选项式钩子函数
function applyOptions(instance) {
	//~~~
	//此处省略n行代码
	//~~~
	// 在开始初始化组件属性之前调用了beforeCreate钩子函数
    // call beforeCreate first before accessing other options since
    // the hook may mutate resolved options (#2791)
    if (options.beforeCreate) {
        callHook(options.beforeCreate, instance, "bc" /* LifecycleHooks.BEFORE_CREATE */);
    }
	// 解构获取到组件实实例中的属性
	const { 
    // state 状态属性
    data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions, 
    // lifecycle 生命周期钩子
    created, beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured, serverPrefetch, 
    // public API 公开api
    expose, inheritAttrs, 
    // assets 资源属性
    components, directives, filters } = options;
    
	//~~~
	//此处省略n行代码
	//~~~
	// 这里源码注释说明了选项是属性的初始化顺序,建议拿起小笔记记一下
    // options initialization order (to be consistent with Vue 2):
    // - props (already done outside of this function)
    // - inject
    // - methods
    // - data (deferred since it relies on `this` access)
    // - computed
    // - watch (deferred since it relies on `this` access)
	//~~~
	//此处省略n行代码 此处省略的代码为初始化组件属性的代码
	//~~~
	//初始化完组件属性之后,调用的生命周期的created钩子函数
    if (created) {
        callHook(created, instance, "c" /* LifecycleHooks.CREATED */);
    }
    // 此处定义了一个将选项式生命周期钩子函数注册为组合式生命周期钩子函数的函数
    function registerLifecycleHook(register, hook) {
        if (shared.isArray(hook)) {
            hook.forEach(_hook => register(_hook.bind(publicThis)));
        }
        else if (hook) {
            register(hook.bind(publicThis));
        }
    }
    registerLifecycleHook(onBeforeMount, beforeMount);
    registerLifecycleHook(onMounted, mounted);
    registerLifecycleHook(onBeforeUpdate, beforeUpdate);
    registerLifecycleHook(onUpdated, updated);
    registerLifecycleHook(onActivated, activated);
    registerLifecycleHook(onDeactivated, deactivated);
    registerLifecycleHook(onErrorCaptured, errorCaptured);
    registerLifecycleHook(onRenderTracked, renderTracked);
    registerLifecycleHook(onRenderTriggered, renderTriggered);
    registerLifecycleHook(onBeforeUnmount, beforeUnmount);
    registerLifecycleHook(onUnmounted, unmounted);
    registerLifecycleHook(onServerPrefetch, serverPrefetch);
	//~~~
	//此处省略n行代码
	//~~~
}

ok,初始化完组件属性,下面就是渲染页面

setupRenderEffect这个函数主要是渲染页面内容

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
	// 里面定义了一个更新组件的函数
    const componentUpdateFn = () => {
    	// 判断组件是否以及渲染过
        if (!instance.isMounted) {
        	// 组件第一次渲染
			//~~~
			//此处省略n行代码 
			//~~~
			// 调用beforeMount生命周期钩子函数
            // onVnodeBeforeMount
            if (!isAsyncWrapperVNode &&
                (vnodeHook = props && props.onVnodeBeforeMount)) {
                invokeVNodeHook(vnodeHook, parent, initialVNode);
            }
            //~~~
			//此处省略n行代码 此处省略的代码为的页面渲染过程
			//~~~
			// 此处执行mounted生命周期钩子函数
            // mounted hook
            if (m) {
                queuePostRenderEffect(m, parentSuspense);
            }
			// 此处执行虚拟节点的mounted生命周期钩子函数
            // onVnodeMounted
            if (!isAsyncWrapperVNode &&
                (vnodeHook = props && props.onVnodeMounted)) {
                const scopedInitialVNode = initialVNode;
                queuePostRenderEffect(() => invokeVNodeHook(vnodeHook, parent, scopedInitialVNode), parentSuspense);
            }
            // 如果组件被keep-alive包裹,会执行activated生命周期钩子函数
            // activated hook for keep-alive roots.
            // #1742 activated hook must be accessed after first render
            // since the hook may be injected by a child keep-alive
            if (initialVNode.shapeFlag & 256 /* ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE */ ||
                (parent &&
                    isAsyncWrapper(parent.vnode) &&
                    parent.vnode.shapeFlag & 256 /* ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE */)) {
                instance.a && queuePostRenderEffect(instance.a, parentSuspense);
            }
            //~~~
			//此处省略n行代码 此处省略的代码为的页面渲染过程
			//~~~
        }
        else {
        	// 组件已经渲染,当响应式数据变化时会执行这里代码
            //~~~
			//此处省略n行代码
			//~~~
			// 此处执行beforeUpdate生命周期钩子函数
            // beforeUpdate hook
            if (bu) {
                shared.invokeArrayFns(bu);
            }
            //~~~
			//此处省略n行代码
			//~~~
			// 此处执行updated生命周期钩子函数
            // updated hook
            if (u) {
                queuePostRenderEffect(u, parentSuspense);
            }
            //~~~
			//此处省略n行代码
			//~~~
        }
    };
	//~~~
	//此处省略n行代码 此处省略的代码为如何触发componentUpdateFn函数的代码,不做详细说明
	//~~~
};

到此,组件从开始挂载到挂载成功期间跟生命周期钩子相关的代码已基本分析完毕。
下面我们对其中的部分要点做个总结:

  • 开发过程中通过组合式生命周期api注册的钩子函数要比通过选项式api定义的钩子函数执行的早
    也就是说如果通过onMounted注册一个钩子函数,它会直接在组件里定义mounted函数执行的早
  • vue的状态选项初始化顺序(有先到后)为props inject methods data computed watch
  • activated在组件第一次渲染不会执行,只有组件变为不活动状态然后再变为活动状态时才会执行

如果大家还有什么疑问,可以在评论区留言,相互学习!

Vue 3 里的 onMounted 怎么用?
Becomin' Charles
03-20 5066
本文介绍了一个小迷惑,onMounted 和在代码里直接调用的区别,以及什么时候该使用 onMounted。
Vue3.x 生命周期
飞扬小阁
12-02 1556
Vue2中有 beforeCreate 、created 、beforeMount 、mounted 、beforeUpdate 等生命周期函数 而在Vue3中,这些生命周期部分有所变化,并且调用的方式也有所改变,下面放上一张变化图来简单了解一下 Vue2 Vue3 beforeCreate setup created setup beforeMount onBeforeMount mounted onMounted beforeUpdate on
学习Vue 3.0中的onMounted和onUnmounted钩子函数
最新发布
李长渊的博客
05-06 1764
学习Vue 3.0中的onMounted和onUnmounted钩子函数
Vue3.0实现todolist之常用的生命周期onMounted
祈澈菇凉
09-28 4260
生命周期:一个组件从创建到销毁的过程 1:setup 不需要引入的生命周期setup 代表组件创建的过程 可以直接在里面使用 2:onMounted import{ defineComponent, ref, onMounted } from "vue"; onMounted组件挂载的生命周期setup晚一些,是一个函数,里面传入回调函数 onMounted(() =>{ //组件挂...
onmounted vue3_vue3快速拥抱elementui
weixin_39854923的博客
11-20 768
前序Vuejs 3.0 在北京时间2020年9月 19 日凌晨,终于发布了 3.0 版本,代号:One Piece对于我们日常维护的vue2.5.x版本,尤大并不建议我们来升级它,那么我们又如何快速体验hook带来的体验呢?用完hook的我,只能说一句真香!!!再也不担心mixins的乱套,也不用逼逼赖赖的找mixins带来的变量覆盖,找不到变量在哪一次的使用vue官网也提供了相对应的a...
前端Vue生命周期函数(详细讲解+中文图解)
热门推荐
山赶着山,山山漫漫结成关
08-09 1万+
Vue生命周期函数(详细讲解+中文图解)
Vue3 源码导读(推荐)
10-16
建议先从`reactivity`开始,理解响应式系统的实现,然后阅读`runtime`,掌握组件生命周期的细节,如果还有余力,再研究`compiler`,探索编译优化过程。测试用例是了解功能的好途径,Vue3目前有近700个测试用例。 ...
vue组件横向树实现代码
10-18
`mounted` 是 Vue 生命周期的一个钩子,当组件实例被挂载到 DOM 上时调用。在这个钩子里,我们使用 `$nextTick` 确保 DOM 已经更新后执行 `editDom` 方法,以正确地计算和设置元素的位置。 `editDom` 方法用于调整...
详解Vue-Router源码分析路由实现原理
10-16
2. 定义路由组件,每个路由映射一个Vue组件。 3. 创建一个新的VueRouter实例,传递配置项,其中最重要的就是`routes`数组。 4. 将VueRouter实例注入到Vue根实例中,并挂载到DOM上。 `Vue.use(VueRouter)`方法在内部...
Vue源码分析Vue实例初始化详解
10-16
这些步骤共同构成了Vue实例的生命周期,确保了组件的正常工作。理解这些过程对于深入学习Vue.js的内部机制和优化代码性能至关重要。在实际开发中,理解这些初始化流程可以帮助我们更好地设计组件结构,编写出更高效...
以v-model与promise两种方式实现vue弹窗组件
08-27
Vue 弹窗组件的两种实现方式 ...* Vue 组件生命周期详解:https://cn.vuejs.org/v2/guide/instance.html * Promise 详解:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
Vue3基础+代码语法+例子大全(七)——组件生命周期
m0_46610349的博客
09-26 1983
组件基础;组件数据的存放;父子组件之间的数据传递;props、$refs、$parent、$root;插槽,slot 元素;组件之间的跨级通信 Provide / Inject 生命周期钩子;生命周期钩子函数;
Vue——vue3 生命周期onMounted 和 onUnmounted使用场景
qq_43201350的博客
02-01 5503
Vue——vue3 生命周期onMounted 和 onUnmounted使用场景
Vue3中的computed、watch和watchEffect、生命周期函数
qq554244261的博客
02-08 1383
由于Vue3是兼容Vue2代码的,所以原来的watch用法也是可以使用的。 但是我们可以通过引入,在setup内部使用watch。 1、 watch多个值,第一个参数变为数组 reactive对象在结果数组中显示Proxy 2、 在watch中把响应对象的属性用 xxx.pro拿出来也是没有响应效果的,可以 ()=> xxx.data来实现返回一个响应值 ...
Vue3基础(3)——组件生命周期、watch、组件之间数据共享
SongD1114的博客
04-08 1340
Vue3.0 组件生命周期、watch、组件之间数据共享
Vue3-04-生命周期
晴雪月乔的博客
06-12 7162
Vue 生命周期
Vue3学习笔记
qq_45953233的博客
07-30 726
然而当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。想象一下这样的结构有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。npmrunbuild打包完成后,异步组件有单独的js文件,是从主体js包中分离出来的。立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。控制,组件自身的加载、错误、延迟和超时选项都将被忽略。...
学习Vue3 第二十章(keep-alive缓存组件
qq1195566313的博客
02-15 1万+
内置组件keep-alive 有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。 开启keep-alive 生命周期的变化 初次进入时:onMounted> onActivated 退出后触发 deactivated 再次进入: 只会触发 onActivated 事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivat.
【简述】watch函数5种监听方式与vue3的生命周期函数
liuxin1008的博客
01-06 791
watch函数5种监听方式与vue3的生命周期函数
vue3 组件生命周期
08-16
- *2* [Vue3组件生命周期](https://blog.csdn.net/qq_15103197/article/details/130460222)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • vite 设置部署的访问路径 13137
  • echarts饼图pie中间显示总数 13053
  • openpyxl 数字格式处理 11992
  • openpyxl单元格合并 11324
  • openpyxl 日期格式处理 11269

分类专栏

  • 30天精通Nodejs 付费 31篇
  • python基础 3篇
  • 智慧城市 1篇
  • http 1篇
  • vite 3篇
  • echarts 4篇
  • electron 5篇
  • vue3 18篇
  • TypeScript 2篇
  • Nuxt3 1篇
  • HarmonyOS 2篇
  • linux 1篇
  • markdown 1篇
  • openpyxl 6篇
  • python 17篇
  • css 9篇
  • windwos 3篇
  • html 6篇
  • nodejs 9篇
  • js 13篇
  • docker 2篇
  • 设计模式 2篇
  • quasar 4篇
  • vue 4篇
  • Dhtmlx-Gantt 2篇
  • 随笔 5篇
  • java 7篇
  • android 6篇
  • 笔记 9篇
  • android studio 1篇

最新评论

  • Dhtmlx-Gantt 任务的增删改查

    白嫖叫上我: 来自两年后的回答:那是双击任务的窗口事件,在里面点删除才能触发

  • html+js+css实现一个圆形滑块

    今天开心嘛.: 嗯嗯感谢您的回复 我已经处理好了

  • html+js+css实现一个圆形滑块

    bdawn: 这个是基于整个圆的事件做处理的,不是那个圆点

  • html+js+css实现一个圆形滑块

    今天开心嘛.: 不好意思 其实是我修改的问题 我想问问基于您的这个滑块怎么实现鼠标移出元素后依旧能够改变圆点的位置

  • html+js+css实现一个圆形滑块

    bdawn: 修改宽高之后需要调整相关参数

大家在看

  • vulnhub靶场-hackme打靶教程 431
  • API-操作元素属性
  • Webmagic使用报错
  • hackbar插件安装教程 349
  • KNN算法详解

最新文章

  • 前端汪的逆袭:从Excel表格到网页魔幻秀
  • nodejs 版本管理
  • python快速入门三
2024年23篇
2023年28篇
2022年15篇
2021年35篇
2020年28篇
2019年16篇
2016年1篇
2015年7篇
2014年6篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bdawn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值

哆哆女性网av天天看体育教学反思魔兽战役地图中华取名网免费取名韩汉互译窦姓女孩起名多开分身pcbmatrix叫兽系列形容水的字女孩起名混乱武林加钱版微起名网智能测名答案茶汉语字典起名字悦字起名女孩名字杨姓双胞胎男孩起名周易起名靠谱吗羊羊小心愿最酷的qq头像起名字大全男孩郑引人注目的反义词起名免费应用hogtied起名和笔画百步亭花园五行缺金怎么办起名大全暗黑者darker真实身份白月光替身下岗再就业生辰八字五行免费起名字文化广场起名淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻不负春光新的一天从800个哈欠开始有个姐真把千机伞做出来了国产伟哥去年销售近13亿充个话费竟沦为间接洗钱工具重庆警方辟谣“男子杀人焚尸”男子给前妻转账 现任妻子起诉要回春分繁花正当时呼北高速交通事故已致14人死亡杨洋拄拐现身医院月嫂回应掌掴婴儿是在赶虫子男孩疑遭霸凌 家长讨说法被踢出群因自嘲式简历走红的教授更新简介网友建议重庆地铁不准乘客携带菜筐清明节放假3天调休1天郑州一火锅店爆改成麻辣烫店19岁小伙救下5人后溺亡 多方发声两大学生合买彩票中奖一人不认账张家界的山上“长”满了韩国人?单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#青海通报栏杆断裂小学生跌落住进ICU代拍被何赛飞拿着魔杖追着打315晚会后胖东来又人满为患了当地回应沈阳致3死车祸车主疑毒驾武汉大学樱花即将进入盛花期张立群任西安交通大学校长为江西彩礼“减负”的“试婚人”网友洛杉矶偶遇贾玲倪萍分享减重40斤方法男孩8年未见母亲被告知被遗忘小米汽车超级工厂正式揭幕周杰伦一审败诉网易特朗普谈“凯特王妃P图照”考生莫言也上北大硕士复试名单了妈妈回应孩子在校撞护栏坠楼恒大被罚41.75亿到底怎么缴男子持台球杆殴打2名女店员被抓校方回应护栏损坏小学生课间坠楼外国人感慨凌晨的中国很安全火箭最近9战8胜1负王树国3次鞠躬告别西交大师生房客欠租失踪 房东直发愁萧美琴窜访捷克 外交部回应山西省委原副书记商黎光被逮捕阿根廷将发行1万与2万面值的纸币英国王室又一合照被质疑P图男子被猫抓伤后确诊“猫抓病”

哆哆女性网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化