美文网首页React初阶到进阶
From Vue to React (1): 忘了Vue吧

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

作者: 拜仁的月饼 | 来源:发表于2023-11-06 00:31 被阅读0次

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>
}

相关文章

网友评论

    本文标题:From Vue to React (1): 忘了Vue吧

    本文链接:https://www.haomeiwen.com/subject/ojjtwdtx.html