From Vue to React (1): 忘了Vue吧

Vue核心概念回顾

提到Vue一般会想到的是响应式。如果经历过面试,或者想稍微深入一些Vue原理,都会清楚这个词,然后立马脱口而出Object.definePropertyProxy API可用于实现这一特性。那么问题再深入一些,响应式到底是要响应些什么呢?这个问题如果看过官方文档就很容易答出来:状态变更驱动视图更新。与此同时,与响应式这一概念配套的还有诸如自动收集依赖细粒度更新等概念。

除了"响应式", Vue中另一个高频概念是生命周期 在生命周期中做什么事情,在你的业务中都安排的明明白白。

虽然本节标题是“Vue核心概念回顾”,但本节的内容并不是对这些名词一一详解。恰恰相反,本节想强调的是在学习React之前,如果能把上面的概念通通忘掉最好,如果没法忘掉,那么也需要记住的是,Vue和React的心智模型不同,换成白话说,就是两个库是两回事,没有太多共通之处,尤其对于只会写Vue 2的同学来说很重要。

不过,如果你有使用Vue 3的经验,那就太好了。Vue 3和React Hooks虽然在写法上有一些神似,但还是那句话,Vue和React的心智模型不同,不是一个东西。

总之,忘了Vue吧

From Vue 3 to React

本节仅适用于有Vue 3 (包括Vue 2.7)使用经验的读者。如果完全不会Vue 3, 或即使写Vue 3也只写data / methods / computed / watch这种写法的,直接划过去,读下一节。


虽然上一小节本人最终还是强调"忘了Vue吧",不过如果从使用的角度来说,Vue 3转React还是相对容易些,这是因为Vue 3主推的composition API (下文简称Vue 3,或称Vue 3 hooks以示区分)借鉴了React hooks的思想,现在也是全hooks的写法。

下面是本人总结的Vue 3 hooks与React hooks的约等于对照表。但还是那句话,两个库是两回事

vue 3 react
ref<T>, shallowRef<T>(不常用) useState<T>, 在ref DOM时为useRef<T extends Element>
reactive<T extends object> useState<T extends object>, 配合immer库则可使用useImmer<T extends object>
watch / watchEffect / 各种生命周期hook useEffect
computed 不需要, 勉强可认为useMemo对应
nextTick
defineProps props直接作为函数式组件的参数传入
defineEmits 单向数据流, 需要自定义事件放props时传
defineExposeref协同 使用forwardRefuseRef协同实现
defineSlots 不需要

这样看来, 是不是React更简洁一些?

From Vue all to React

这一章的核心内容是教你科学地忘掉Vue。对于读过上一节的读者来说,请第三遍告诉自己Vue 和 React两个库是两回事。如果直接划到这一节,那么恭喜你,你将深刻认识到为什么说Vue和React不是一回事。

如果我们用Vue写一个倒计时组件,你可能会写出下面的代码 (不计空格共43行):

<template>
    <div class="hello-world">{{ innerTime }}</div>
</template>

<script>
export default {
    props: {
        startTime: {
            type: Number,
            default() {
                return 60;
            }
        }
    },
    data() {
        return {
            innerTime: this.startTime,
            timer: null,
        };
    },
    watch: {
        startTime: {
            immediate: true,
            handler(newValue) {
                this.innerTime = newValue;
            }
        }
    },
    mounted() {
        this.timer = setInterval(() => {
            if (!this.innerTime) {
                clearInterval(this.timer);
            } else {
                this.innerTime--;
            }
        }, 1000);
    },
    beforeDestroy() {
        if (this.timer) {
            clearInterval(this.timer);
        }
    }
};
</script>

如果用React hooks实现相同的功能则只需要22行(除去空格):

import { useState, useEffect, useRef } from 'react';

export function MyCountdown(props: { readonly startTime: number }) {
    const { startTime } = props;

    const [innerTime, setInnerTime] = useState(startTime);
    useEffect(() => {
        setInnerTime(startTime);
    }, [startTime]);

    const timer = useRef<ReturnType<typeof setInterval>>();

    useEffect(() => {
        timer.current = setInterval(() => {
            if (!innerTime) {
                clearInterval(timer.current);
            } else {
                setInnerTime(pre => pre - 1);
            }
        }, 1000);

        return  () => {
            clearInterval(timer.current);
        };
    }, [innerTime]);

    return <p className="my-countdown">{innerTime}</p>;
}

可以看出, 代码量直接减少了50%(这还是在React开了TS的情况下)。并且仔细看代码,发现我们在React中并没有写任何的data以及生命周期函数,全靠useEffect实现。如果写惯了Vue,去读下面的useEffect会感到疑惑,可能的疑点在于--组件挂载后setInterval, 组件卸载前clearInterval,可是为什么在React的代码中没有感受到这一变化呢? 后续会详谈为什么,这一小节只需要忘掉生命周期的概念。React hooks没有生命周期一说,总想着生命周期会干扰你对React的理解。

再比如,如果我们写一个“输入框+搜索按钮”的组件(不考虑CSS)。使用Vue实现如下 (不考虑空行22行):

<template>
    <div class="input-search">
        <input class="input-search-input" v-model="inputValue" />
        <button @click="handleSearch">
            Search
        </button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            inputValue: '',
        };
    },
    methods: {
        handleSearch() {
            const { $emit: emit,  inputValue } = this;
            emit('search', inputValue);
        }
    }
}
</script>

如使用React, 需要20行:

import { useRef } from 'react';

export function InputSearch(props: { onSearch(inputValue: string): void }) {
    const { onSearch } = props;

    const inputRef = useRef<HTMLInputElement>(null);

    function handleSearch() {
        onSearch(inputRef.current?.value || '');
    }

    return (
        <div className="input-search">
            <input ref={inputRef} />

            <button
                onClick={handleSearch}
                type="button"
            >
                Search
            </button>
        </div>
    );
}

我们可以发现,在React中并不存在Vue的v-model语法糖。所以,忘掉各种好用的语法糖

另外,Vue可以透传class, style等属性,也可以使用css scoped这一特性。而这一点在React中却不可以。

ChildComponent.vue:

<template>
  <div>child component</div>
</template>

ParentComponent.vue:

<script setup lang="ts">
import ChildComponent from './ChildComponent.vue';
</script>

<template>
  <div>
    <!--- 这样做在Vue中是允许的 --->
    <child-component class="child-comp" />
  </div>
</template>

ChildComponent.tsx

// ChildComponent.tsx
import React from 'react';

export function ChildComponent() {
  return <div>child component</div>;
}

ParentComponent.tsx

// ChildComponent.tsx
import React from 'react';
import ChildComponent from './ChildComponent.tsx';

export function ParentComponent() {
  // 这样不可以!
  return <div> <ChildComponent className="child-comp" /> </div>
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容