网易首页 > 网易号 > 正文 申请入驻

升级 Vue3 大幅提升开发运行效率

0
分享至

作者:louiszhai,腾讯 IEG 前端开发工程师

Vue3 性能提升了 1.3~2 倍,SSR 性能提升了 2~3 倍,升级 Vue3 正是当下。
背景

原计划 2019 年发布的 Vue3,又经过一年的再次打磨,终于于去年 9 月正式发布。随后,不少 UI 组件库都积极参与适配,去年 12 月,Element-plus(Element-ui 官方升级版)也发布了 beta 版。

由于项目中用到了 Element-ui 组件,组件库未适配的情况下,不敢贸然升级 Vue3。Element-plus 发布后,又经过 1 个月的观察、测试和调研,发现 Element-plus 相对成熟(还有少量 bug,后续会讲),便开始尝试升级 Vue3。

如何升级 Vue3

有两种方案可以快速升级 Vue3:

  • 一种是使用微前端轮子,我基于 qiankun2,搭建了 Vue3 项目基座,为了保证平稳升级,子项目继续使用 Vue2,然后不断的把子项目的页面迁移到基座项目。

  • 另一种是,直接升级 Vue3,将项目中的 Vue2 依赖库升级到 Vue3 的最新版(当前最新版是 v3.0.11),并且稍微改造 webpack 编译脚本,使之适配 Vue3。

之所以会有方案一,主要还是担心 Element-plus 不够稳定,如果有天坑,又无法绕过去,除了向饿了么团队提交 PR,微前端兜个底也是不错的应急措施。

就这样微前端方案又运行了 1 个月,部分页面已完成升级,运行良好,实践证明 Element-plus 比想象中稳定,这增加了我对于方案二的信心。考虑到还有少量业务复杂的页面,在微前端模式下,子项目的各种数据多经过一层 qiankun 的 proxy 代理,性能有损耗,影响了页面更新,于是一次性将剩余的页面全部迁移到 Vue3 项目中。

实践证明,除非比较复杂的项目,或者依赖组件库没升级等原因不适合升级外,常规情况下,升级 Vue3 都是一个不错的选择。

为什么要升级 Vue3

为什么要升级 Vue3,这是一个几乎不需要回答的问题。升级 Vue3 后,代码结构更加清晰内聚,响应式数据流更加可控,节省了很多心智成本,从而使得开发效率大幅提升。Vue3 还带来了很多新特性,框架层面运行性能更高(性能提升了 1.3 至 2 倍,SSR 性能提升了 2 至 3 倍),Composition API 使得代码拆分,函数封装更容易,复杂项目也随之更容易管理。

Vue2 中,相关的逻辑经常分散在 option 的 data、watch、computed、created、mounted 等钩子中,阅读一段代码,经常需要上下反复横跳,带来了部分阅读障碍。钩子又依赖 Vue 实例,代码封装基于天生携带钩子的 Mixin 去做,更加容易和相对方便。

但正因为如此,Mixin 的钩子容易不自觉的越界,插手到页面或组件的内部变量和方法管理过程中;甚至,多个不同的 Mixin,相互之间就很容易冲突,项目开发者,在引入 Mixin 和避免冲突之间需要保持微妙的平衡,不但增加心智负担,还带来了副产品:本身扑朔迷离的 this 变得更加不确定。因此,大型项目 Mixin 几乎都是一种反模式。

现在这些框架问题,都由 Vue3 的 Composition API 解决了。

Vue3 带来了哪些新特性

我们先看一些立马能感受到变化的特性。

这是一个一上手 Vue3 就能感知的变化。即使你在 Vue3 中编写 Vue2 风格的基于 option 的代码,Proxy 也是默默提供着数据响应式。

const observe = (data) => {
Object.keys(data).forEach((key) => {
const initValue = data[key];
let value = initValue;
if (typeof initValue === 'object') {
observe(initValue);
return;
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log('visit key value =', key, value);
return value;
},
set(val) {
console.log(`[${key}]changed,old value=${value}, new value = ${val}`);
if(value !== val) {
value = val;


const data = {};
Array.from(new Array(100), () => "").forEach((item, i) => {
data[i] = { value: i * 2 };
console.time();
observe(data);
console.timeEnd(); // default: 0.225ms
data.a = { b: 1 };
data.a.b = 2;

如上所示,Vue2 的数据响应式是通过 Object.defineProperty 实现,这是一个深度遍历的过程,无论 data 中包含多少层数据,都需要全部遍历一遍。深度遍历,给对象的每个自身属性添加 defineProperty,需要不小的性能开销,同时后面新增到 this 中的属性不提供响应式监听,因此我们需要使用诸如this.$set这种方式去添加新属性。

Proxy 就没有这个问题,如下所示。

const observe = (data) => {
return new Proxy(data, {
get(target, key, receiver) {
console.log('visit', key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`[${key}]changed, value = ${value}`);
Reflect.set(target, key, typeof value === 'object' ? observe(value) : value, receiver);

let data = {};
Array.from(new Array(100), () => "").forEach((item, i) => {
data[i] = { value: i * 2 };
console.time();
const proxy = observe(data);
console.timeEnd(); // default: 0.041ms
proxy.a = { b: 1 }; // [a]changed, value = [object Object]
proxy.a.b = 2; // visit a \n [b]changed, value = 2

Proxy 不但使得 data 获得了新属性的响应性,整个响应式处理过程的效率还提升了数倍,由此带来了 Vue3 的大部分性能提升。

为了保持对 Vue2 的向下兼容,Vue3 中仍然支持纯 Option 配置的书写方式,这为升级提供了便利,平移 Vue2 的代码,只需少量改动,便可正常运行。

同时考虑到上手难度,Vue3 的顶层代码风格与 Vue2 保持一致,依然是 export 一个对象,对象包含了一系列的配置,其中便有 setup 入口函数。我们先来看一段代码,然后逐个解读。

import { defineComponent, ref, reactive, toRefs, watch, watchEffect, computed, onMounted } from "vue";
export default defineComponent({
setup(props, context) {
const selectRef = ref(null) // 作为下拉框的ref引用
const state = reactive({ // 响应式数据,类似于Vue2的this
num: 0,
const { init } = toRefs(props);
watch(() => state.num, (newVal, oldVal) => {
console.log(newVal, oldVal);
watchEffect(() => {
console.log(state.num);
const num2 = computed(() => state.num + 1);
onMounted(() => {
state.loaded = true;
return { selectRef, state, num2, init, context };

setup 作为入口函数,包含两个参数,分别是响应式的 props 外部参数,以及 context 对象,context 包含 attrs、emit、expose、props、slots 五个参数,如下所示:

在 Vue3 的设计里,setup,以及从 vue 对象中解构出来的各种生命周期函数,执行优先级高于 Vue2 中的各种生命周期钩子,因此

beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('create');
},
setup() {
console.log('setup');
},

这段代码的输出依次是 setup、beforeCreate、created。

ref、reactive

setup 中,第一句const selectRef = ref(null);,这里定义的是一个响应式的数据,可传递给 template 或 render,用于下拉框组件或下拉框 dom 绑定引用。为什么使用 ref,不使用 reactive 呢?ref 和 reactive 都可以给数据添加响应性,ref 一般用于给 js 基本数据类型添加响应性(当然也支持非基本类型的 object),reactive 只能用于代理非基本数据类型。null 是基本数据类型,只能使用 ref,那既然如此,为什么不在所有情况都使用 ref 呢?我们来看一段代码:

const num = ref(0);
num.value = 1;
const obj = { a: 1 };
const refObj = ref(obj);
const reactiveObj = reactive(obj);
refObj.value.a = 2;
reactiveObj.a = 3;
console.log(num, refObj, reactiveObj);

我们注意到,使用 ref api 时,数据变成了对象,值就是 value 属性的值,如果数据本身就是对象,依然会多一层 value 结构,而 reactive 没有这些副作用。同时,还有一个有意思的现象是,所有的源数据,都需要经过响应式 api 包裹,然后才能使用,这跟前面提到的 Proxy 原理有关,Proxy 代理数据时,需要基于返回的代理进行数据更新。

toRefs

除了 ref、reactive 外,还有一个常用的响应式 api——toRefs。为什么需要它,这是因为响应式对象,经过解构出来的属性不再具有响应性,toRefs 就是为了快速获得响应性的属性,因此这段代码const { init } = toRefs(props);,就是为了获得响应式属性 init,想要保留 props 参数的响应性,建议这么做。

watch、watchEffectconst num = ref(0);
const state = reactive({
num: 0,
const obj = { num: 0 };
watch(num, (newVal, oldVal) => {
console.log("num", newVal, oldVal);
watch(() => state.num, (newVal, oldVal) => {
console.log("num", newVal, oldVal);
watch(() => obj.num, () => {
console.log("这里不会执行");
num++;
state.num++;
obj.num++;

如上,watch api,它需要接受一个具有返回值的 getter 函数或者 ref(如() => state.num,ref)。

如果需要监听多个值,如下所示:

const num1 = ref(0);
const num2 = ref(0);
watch([num1, num2], ([newNum1, newNum2], [prevNum1, prevNum2]) => {
console.log([newNum1, newNum2], [prevNum1, prevNum2]);
num1.value = 1; // [1, 0], [0, 0]
num2.value = 2; // [1, 2], [1, 0]

可见多个数据的每次更新都会触发 watch。想要监听一个嵌套的对象,跟 Vue2 一样,依旧需要使用 deep 选项,如下所示:

const state = reactive({
attr: {
id: 1,
},
watch(() => state, (currState, prevState) => {
console.log(currState.attr.id, prevState.attr.id, currState === prevState, currState === state); // 2, 2, true, true
}, { deep: true });
watch(() => state.attr.id, (currId, prevId) => {
console.log(currId, prevId); // 2, 1
state.attr.id = 2;

看到差别了吗?监听响应式对象时,返回的是对象的引用,因此 currState,prevState 指向是同一个最新的 state,如果需要获取变化前的值,建议返回监听的属性,如watch(() => state.attr.id),刚好 state.attr.id 是一个基本类型的值,那么 deep 也不需要。

watchEffect 是 Vue3 新增的 api,watchEffect 会自动运行一次,用于自动收集依赖,但不支持获取变化前的值,除此之外,与 watch 用法一致。那么 watchEffect 适用什么场景呢?这也是我刚上手 Vue3 的困惑之一。我们来看一段代码:

const rights = {
admin: ["read", "write"],
user: ["read"],
const state = reactive({
rights: "",
const userInfo = reactive({ role: "user" });
userInfo.name = "Tom";
userInfo.role = "admin";
watch(() => userInfo.role, (newVal, oldVal) => {
state.rights = rights[newVal];
watchEffect(() => {
state.rights = rights[userInfo.role];

以上代码中,watch 中的逻辑只能在 userInfo 变化后执行,因此 state.rights 不会提供初始值,相反,watchEffect 中 state.rights 由于自动依赖收集,获得了一次赋值的机会。

这样做的好处是什么呢?在实际项目中,userInfo.role 可能是一个全局 store 中的数据,用户登录进来后,就会通过接口获取初始值,我们并不能确认,用户进到其中一个页面时,userInfo.role 的值是否已经被接口更新,且 userInfo 变化前的值我们也不关心,watchEffect 就非常适合这种场景,它会自动进行一次初始化,并且在变化后,及时更新值。

watch 和 watchEffect 的监听会在组件销毁时自动取消,除此之外,可以通过它们返回的函数手动取消监听,如下所示:

const stopWatch = watch(selectRef, (newVal, oldVal){});
const stopWatchEffect = watchEffect(selectRef, (newVal, oldVal){});
setTimeout(stopWatch, 1000);
setTimeout(stopWatchEffect, 1000);

watchEffect 更多的用法,请参考官方文档。

computed

computed 的使用如下:

const num = ref(1);
const num2 = computed(() => num * 2);
num2.value++; // error

num2 是一个不可变的 ref 对象,不能直接对它的 value 属性赋值。

computed 还可以接收一个带有 get 和 set 函数的对象,来创建一个可读写的 ref 对象,如下所示:

const num3 = computed({
get: () => num.value * 2,
set: (val) => {
num.value = val;
},
num3.value = 100;
console.log(num.value, num3.value); // 100 200
自定义 Hooks

Vue3 的 Composition 之所以这样实现,主要原因就是为了便于代码拆分,降低耦合,我们不妨来实现一个自定义的 hooks。

// page.vue
import useCount from "./useCount";
export default {
setup() {
const { num, double, plus } = useCount(1);
return { num, double, plus };
},
// useCount.js
import { ref, computed } from "vue";
export default (value) => {
const num = ref(value);
const double = computed(() => num.value * 2);
const plus = (val) => num.value + val;
return { num, double, plus };

useCount.js 就是一个自定义的 hooks,得益于 Vue3 的全局 API,我们可以轻松做到代码拆分。Vue3 的 setup 聚合了所有的逻辑,容易产生面条代码,合理使用自定义 hooks,可以有效的减少面条代码,提升代码可维护性。并且 Vue3 的 hooks 比 react 更加简单高效,不会多次执行,不受调用顺序影响,不存在闭包陷阱等等,几乎可以没有任何心智负担的使用。

新的生命周期钩子

看到这里,相信你对 Vue3 的生命周期已经有一些了解了,我们不妨来做个梳理。

Vue3 几乎内置了所有的 Vue2 生命周期钩子,也就是说,刚开始升级项目至 Vue3 时,可以直接使用 Vue2 的钩子,方便平滑升级,如上图左下角所示,有两个钩子发生了替换,beforeDestory 被替换成了 beforeUnmount,destoryed 被替换成了 unmounted。完整的钩子对比如下:

除了 setup 外,Vue3 的其他生命周期钩子都添加了 on 前缀,更加规范统一。新的钩子需要在 setup 中使用,如下所示:

import { onMounted } from "vue";
export default {
setup() {
onMounted(() => {
console.log("onMounted");
},
Tree-Shaking

Vue3 一共开放了 113 个 API,我们可以通过如下方式引用:

import { ref, reactive, h, onMounted } from "vue";

通过 ES6 modules 的引入方式,能够被 AST 静态语法分析感知,从而可以只提取用到的代码片段,最终达到 Tree-Shaking 的效果,这样就使得 Vue3 最终打包出来的包更小,加载更快。据尤大去年 4 月在 B 站的直播:基本的 hello world 项目大小为 13.5kb,Composition API 仅有 11.75kb,包含所有的运行态仅 22.5kb。

Fragment

Vue3 中,Fragment 的引入,解决了组件需要被一个唯一根节点包裹的难题,带来的是 dom 层级的减少,以及渲染性能的提升,某些时候,如下所示:


{{ title }}td>
{{ subtitle }}td>
template>



在 Vue2 中,这意味着我们没办法在 child.vue 的 template 中加入多个 td 节点,多个 td 可以被 tr 包裹,如果 child.vue 根节点替换为 tr,那么就会跟 parent.vue 的 tr 冲突。

同样的代码,在 Vue3 中就能正确编译通过,这是因为 Vue3 中,组件的 template 被一层不可见的 Fragment 包裹,组件天生支持多个根节点的布局。

Teleport 是 Vue3 新增的组件,即传送门,Teleport 能够在不改变组件内部元素父子关系的情况下,将子元素”传送“到其他节点下加载,如下所示:




div>
div>
template>

dialog 直接挂载在 container 下,超出部分将不可见。加一层 Teleport,我们可以轻松将 dialog 展示出来。




div>
teleport>
div>
template>

dialog 依然处于 container 内部,仅仅只是被挂载到 body 上,逻辑关系不变,展示也不会遮挡。

Suspense

Vue2 中,我们经常写这样的 loading 效果,如下所示:




div>


loading~~
div>
div>
template>

Vue3 中,我们可以通过 Suspense 的两个插槽实现以上功能,如下所示:


loading~

Vue3 知识图谱

Vue3 还包括了一些其他常用更新,限于篇幅,这里先列出来,下篇再讲。

实际上,Vue3 带来的更新,远不止这些,为此我梳理了一个 Vue3 的知识图谱,尽可能囊括一些本文未提到的特性。

如上图,Vue 不但重写了 diff 算法,还在编译阶段做了很多优化,编译时优化可以通过这个网站看出来:https://vue-next-template-explorer.netlify.app/。

Vue3 的开放生态

根据 Monterail 2 月份发布的第三版 Vue 生态报告,Vue 的流行度逐年上升,很多非 web 的可视化领域也可以基于 Vue 开发,特别是 Vue3 的渲染 API 的开放,使得基于 Vue 构建 Canvas、WebGL、小程序等应用更加方便,如下图所示,60 行代码实现一个简单的 Canvas 柱状图:

import { createRenderer, h } from "vue";
const renderer = createRenderer({
createElement: (tag) => ({ tag }),
patchProp: (el, key, prev, next) => { el[key] = next; },
insert: (child, parent) => { parent.nodeType === 1 && draw(child) },
let canvas
let ctx;
const draw = (el, noClear) => {
if (!noClear) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 柱状图绘制逻辑
if (el.tag == 'chart') {
const { data } = el;
const barWidth = canvas.width / 10;
const gap = 20;
const paddingLeft = (data.length * barWidth + (data.length - 1) * gap) / 2;
const paddingBottom = 10;
// x轴
// 柱状图
data.forEach(({ title, count, color }, index) => {
const x = paddingLeft + index * (barWidth + gap);
const y = canvas.height - paddingBottom - count;
ctx.fillStyle = color;
ctx.fillRect(x, y, barWidth, count);

// 递归绘制⼦节点
el.childs && el.childs.forEach(child => draw(child, true));
const createCanvasApp = (App) => {
const app = renderer.createApp(App);
const { mount } = app;
app.config.isCustomElement = (tag) => tag === 'chart';
app.mount = (selector) => {
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
document.querySelector(selector).appendChild(canvas);
mount(canvas);
return app;
createCanvasApp({
setup() {
const data = [
{ title: '数据A', count: 200, color: 'brown' },
{ title: '数据B', count: 300, color: 'skyblue' },
{ title: '数据C', count: 50, color: 'gold' },
return () => h("chart", { data });
},
}).mount('#app');

运行结果如下图所示:

Vue3 官方网站

  • Vite 官方网站

  • Vue.js 2021 最新报告

  • Vue Template Explorer

  • 第四届 Vue Conf(预计 2021.5.22)

  • ThisDot 线上分享会 PPT(2020.4.16)

  • Vue Function-based API RFC(2020.1.22)

  • State of Vue(2019.6.8)

  • Vue3 最新进展(2018.11.24)

  • 现状与展望(2017.5.20)

  • 视频号最新视频

    5月28-29日

    QECon全球软件质量&效能大会

    欢迎关注

    tr> table>template>Teleport

    特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

    Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

    相关推荐
    热点推荐
    不顾央视警告顶风作案,与刘涛传出绯闻的杨烁,如今怎么样了?

    不顾央视警告顶风作案,与刘涛传出绯闻的杨烁,如今怎么样了?

    阿柒的讯
    2025-12-08 20:27:42
    就在今天!12月9日早晨,乒乓球领域传来张本智和消息

    就在今天!12月9日早晨,乒乓球领域传来张本智和消息

    皮皮观天下
    2025-12-09 05:17:09
    74岁刘銮雄坐轮椅5人伺候,甘比搀扶起身行走,每一步都小心翼翼

    74岁刘銮雄坐轮椅5人伺候,甘比搀扶起身行走,每一步都小心翼翼

    照见古今
    2025-12-09 18:38:47
    日本民宿被曝变身卖淫场,性工作者称中国游客更大方。

    日本民宿被曝变身卖淫场,性工作者称中国游客更大方。

    环球趣闻分享
    2025-11-09 14:20:06
    马上通车!南京2条重磅地铁通过验收!另外7条线路最新进展来了

    马上通车!南京2条重磅地铁通过验收!另外7条线路最新进展来了

    笑谈历史阿晡
    2025-12-08 15:43:29
    突发调整!12月10日WTT总决赛直播有变,CCTV5不直播“中日大战”

    突发调整!12月10日WTT总决赛直播有变,CCTV5不直播“中日大战”

    墨印斋
    2025-12-09 18:41:00
    他是泰山队最被高估的头牌,两年过去依旧0进球,彻底泯然众人矣

    他是泰山队最被高估的头牌,两年过去依旧0进球,彻底泯然众人矣

    体坛风之子
    2025-12-09 04:30:04
    这块奇怪的石头,在火星热带雨林中被大雨冲刷了几百万年

    这块奇怪的石头,在火星热带雨林中被大雨冲刷了几百万年

    星空天文
    2025-12-08 20:18:29
    丰田终于放下身段!RAV4荣放最高优惠4.5万,你会选择它吗?

    丰田终于放下身段!RAV4荣放最高优惠4.5万,你会选择它吗?

    汽车网评
    2025-12-09 20:58:32
    中日战机对峙后,高市早苗紧急表态,美防长敲打日本:将付出代价

    中日战机对峙后,高市早苗紧急表态,美防长敲打日本:将付出代价

    云鹏叙事
    2025-12-08 11:04:38
    “短命首相”倒计时?高市突然收到坏消息,一封检举信引爆舆论

    “短命首相”倒计时?高市突然收到坏消息,一封检举信引爆舆论

    男女那点事儿儿
    2025-12-09 15:36:05
    为什么人到中年很少有身材苗条的呢?网友:身材管理是个奢侈品

    为什么人到中年很少有身材苗条的呢?网友:身材管理是个奢侈品

    夜深爱杂谈
    2025-12-08 20:08:11
    作用全面经验丰富!马刺真的应该给这位内线老将多些出场时间?

    作用全面经验丰富!马刺真的应该给这位内线老将多些出场时间?

    稻谷与小麦
    2025-12-09 22:40:47
    利物浦换帅候选锁定两人:一人为实力之选,另一人暗藏巨大风险

    利物浦换帅候选锁定两人:一人为实力之选,另一人暗藏巨大风险

    夜白侃球
    2025-12-09 21:54:05
    三军总司令亲自动手,前总理生死不明,“巴铁”撕裂时刻到来?

    三军总司令亲自动手,前总理生死不明,“巴铁”撕裂时刻到来?

    东方点兵
    2025-12-09 15:36:32
    青海一中学校长被通报:培训期间陪妻子到八达岭长城等景点游玩,上月已受警告处分

    青海一中学校长被通报:培训期间陪妻子到八达岭长城等景点游玩,上月已受警告处分

    极目新闻
    2025-12-09 16:53:08
    中国无法原谅的“6大国家”,日本居然仅排第二,第一出乎意料?

    中国无法原谅的“6大国家”,日本居然仅排第二,第一出乎意料?

    爱吃醋的猫咪
    2025-11-27 17:48:57
    1950年11月29日,一位志愿军排长无视上级命令,放弃驻守高地

    1950年11月29日,一位志愿军排长无视上级命令,放弃驻守高地

    忠于法纪
    2025-12-09 21:34:20
    1951年,原马家军改编的解放军7师,部分官兵发动叛乱

    1951年,原马家军改编的解放军7师,部分官兵发动叛乱

    忠于法纪
    2025-12-07 11:15:11
    川普猛烈抨击欧洲;乌克兰将改-革权力机制

    川普猛烈抨击欧洲;乌克兰将改-革权力机制

    近距离
    2025-12-09 18:16:39
    2025-12-09 23:12:49
    腾讯技术工程
    腾讯技术工程
    不止于技术
    1348文章数 599关注度
    往期回顾 全部

    科技要闻

    H200是不是要让中国“上瘾”?

    头条要闻

    31岁中国女留学生让26岁外籍男友检测性病 遭残忍杀害

    头条要闻

    31岁中国女留学生让26岁外籍男友检测性病 遭残忍杀害

    体育要闻

    “苏炳添时代”正式画上句号

    娱乐要闻

    尖叫之夜刘宇宁打包饼干被嘲寒酸?

    财经要闻

    县城经济神话,梦醒时分

    汽车要闻

    旗舰巨作 鸿蒙智行首款MPV智界V9信息披露

    态度原创

    房产
    数码
    艺术
    家居
    公开课

    房产要闻

    年度王炸来了!央企TOP级顶豪落地三亚CBD,引爆富人圈!

    数码要闻

    AI时代电视怎么选?这台追觅Aura Mini LED AI TV V3000一步到位

    艺术要闻

    他把云山雾海画活了,堪称一绝!

    家居要闻

    现代手法 诠释东方文化

    公开课

    李玫瑾:为什么性格比能力更重要?

    无障碍浏览 进入关怀版