发布于 2026-01-06 0 阅读
0

面向 Vue 开发者的 React Hooks DEV's Worldwide Show and Tell Challenge Presented by Mux: Pitch Your Projects!

面向 Vue 开发者的 React Hooks

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

原文发布于michaelzanggl.com。订阅我的电子报,不错过任何新内容。

如果你很久以前就接触过 React,却被它冗长的代码吓跑了(比如你ComponentDidMount等等),不妨再看看。Hooks 将函数式组件提升到了一个新的层次。它带来了你能想象到的所有好处:无需类,无需依赖,无需样板代码。事实证明,我并非孤军奋战,官方文档在阐述 Hooks 的设计初衷时也提到了这些要点。ComponentWillReceivePropsgetDerivedStateFromPropsthis

让我们比较一下 Vue 的一些常见功能,并用 React Hooks 来实现它们,然后列出每种工具的优缺点。这并不是要说服你放弃 Vue 而选择 React,尤其考虑到 Vue 的发展方向与 React 相同(稍后会详细介绍)。但了解其他框架如何完成常见任务总是有益的,因为类似的功能也可能成为 Vue 的未来发展方向。

组件本身

我们需要的 Vue 单文件组件的最低配置如下。

// Counter.vue

<template>
    <div>0</div>
</template>
<script>
    export default {}
</script>
Enter fullscreen mode Exit fullscreen mode

以下是 React 中的相同操作。

function Counter() {
    return <div>0</div>
}
Enter fullscreen mode Exit fullscreen mode

请注意,由于 React 组件只是一个函数,因此它不一定需要放在单独的文件中。

与州政府合作

维尤

// Counter.vue

<template>
    <button @click="increment">{{ count }}</button>
</template>
<script>
    export default {
        data() {
            return {
                count: 1
            }
        },
        methods: {
            increment() {
                this.count++
            }
        }
    }
</script>
Enter fullscreen mode Exit fullscreen mode

并做出反应

import { useState } from 'react'

function Counter() {
    const [count, setCount] = useState(1)
    const increment = () => setCount(count+1)

    return <button onClick={increment}>{ count }</button>
}
Enter fullscreen mode Exit fullscreen mode

如你所见,ReactuseState返回的是一个元组,第二个参数是一个 set 函数。而在 Vue 中,你可以直接设置值来更新状态。

使用 Hooks 时,每当我们的状态/属性更新时,该Counter方法都会再次执行。不过,只有第一次执行时,它会将count变量初始化为 1。这就是 Hooks 的基本原理。这是使用 Hooks 时必须理解的几个概念之一。

Vue 的优缺点

(+) 预定义结构

(-)你不能直接导入某个东西然后在模板中使用它。它必须按照 Vue 的各种概念进行布局,例如 `<div>` datamethods` computed<span>`$store等。这也会导致一些值不必要的响应式,并可能造成混淆(为什么它是响应式的?它会改变吗?在哪里会改变?)

React 的优缺点

(+)它只是一个函数

实际上,这是一个每次状态或属性发生变化时都会执行的函数。对于习惯了 React 旧式无状态函数组件的人来说,这种思路可能没什么问题,但对于只使用过 Vue 的人来说,则需要一种新的思维方式。这种思维方式一开始确实不太自然。

(-)钩子在何处以及如何使用都有各种规则。

传球道具

// Counter.vue

<template>
    <div>
        <h1>{{ title }}</h1>
        <button @click="increment">{{ count }}</button>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                count: 1
            }
        },
        props: {
            title: String
        },
        methods: {
            increment() {
                this.count++
            }
        }
    }
</script>
Enter fullscreen mode Exit fullscreen mode

并做出反应

import { useState } from 'react'

function Counter({ title }) {
    const [count, setCount] = useState(1)
    const increment = () => setCount(count+1)

    return (
        <>
            <h2>{title}</h2>
            <button onClick={increment}>{count}</button>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Vue 的优缺点

(+) 您可以具体指定道具的类型(无需使用 TS)

(-)访问方式与状态(this.xxx)相同,但实际行为却不同(例如,赋新值会抛出警告)。这容易让初学者误以为可以直接更新 props。

React 的优缺点

(+)易于理解 -> props 就是函数参数

子组件

让我们把按钮提取到一个子组件中。

视图

// Button.vue

<template>
    <button @click="$emit('handle-click')">
        {{ value }}
    </button>
</template>
<script>
    export default {
        props: ['value']
    }
</script>
Enter fullscreen mode Exit fullscreen mode
// Counter.vue

<template>
    <div>
        <h1>{{ title }}</h1>
        <Button @handle-click="increment" :value="count" />
    </div>
</template>
<script>
    import Button from './Button'

    export default {
        components: {
            Button,
        },
        data() {
            return {
                count: 1
            }
        },
        props: ['title'],
        methods: {
            increment() {
                this.count++
            }
        }
    }
</script>
Enter fullscreen mode Exit fullscreen mode

eventsvue在此引入了一个“新”概念。

React 对应物

import { useState } from 'react'

function Button({value, handleClick}) {
    return <button onClick={handleClick}>{value}</button>
}

function Counter({ title }) {
    const [count, setCount] = useState(1)
    const increment = () => setCount(count+1)

    return (
        <>
            <h2>{title}</h2>
            <Button value={count} handleClick={increment}/>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Vue 的优缺点

(+)明确的关注点划分

(+) 事件与 Vue 开发者工具配合得非常好

(+) 事件带有修饰符,使代码非常简洁。例如@submit.prevent="submit",< 会应用 event.preventDefault()

奇怪的大小写规则

(-)算是需要学习的一个额外概念(事件)。实际上,事件与浏览器中的原生事件类似。少数区别之一是它们不会向上冒泡。

React 的优缺点

(+)我们不必创建单独的文件

(+) 没有事件的概念 -> 只需将函数作为 prop 传递即可。要更新 props,也可以直接将函数作为 prop 传递。

(+)总体上更短(至少在这个衍生例子中)

有些优缺点相互矛盾,这是因为最终还是取决于个人喜好。有人喜欢 React 的自由度,而有人则更喜欢 Vue 清晰的结构。

老虎机

Vue 在将模板传递给子组件时引入了另一个概念。让我们来了解如何向按钮传递字符串以外的其他内容。

// Button.vue

<template>
    <div>
        <button @click="$emit('handle-click')">
            <slot>Default</slot>
        </button>
        <slot name="afterButton"/>
    </div>
</template>
<script>
    export default {}
</script>
Enter fullscreen mode Exit fullscreen mode
// Counter.vue

<template>
    <div>
        <h1>{{ title }}</h1>
        <Button @handle-click="increment">
            <strong>{{ count }}</strong>
            <template v-slot:afterButton>
                Some content after the button...
            </template>
        </Button>
    </div>
</template>
<script>
    import Button from './Button'

    export default {
        components: {
            Button,
        },
        data() {
            return {
                count: 1
            }
        },
        props: ['title'],
        methods: {
            increment() {
                this.count++
            }
        }
    }
</script>
Enter fullscreen mode Exit fullscreen mode

<strong>{{ count }}</strong><slot></slot>由于这是默认/未命名的插槽,因此将放入其中。Some content after the button...将放置在其中<slot name="afterButton"/>

在 React 中

import { useState } from 'react'

function Button({AfterButton, handleClick, children}) {
    return (
        <>
            <button onClick={handleClick}>
                {children}
            </button>
            <AfterButton />
        </>
    )
}

function Counter({ title }) {
    const [count, setCount] = useState(1)
    const increment = () => setCount(count+1)

    return (
        <>
            <h2>{title}</h2>
            <Button value={count} handleClick={increment} AfterButton={() => 'some content...'}>
                <strong>{ count }</strong>
            </Button>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Vue 的优缺点

(-)槽可能会让人困惑,尤其是在从子组件向槽发送数据时。

将插槽传递给多个组件就更加令人困惑了。

(-)另一个需要学习的概念

这是 Vue 使用自定义模板语言的后果。虽然大部分情况下都能正常工作,但使用插槽时就会变得复杂。

Vue 3 中插槽将得到简化。

React 的优缺点

(+) 没有新概念——由于组件本质上就是函数,只需创建一个这样的函数并将其作为 prop 传递即可。

(+) 甚至不必是函数。你可以将模板(jsx)保存到一个变量中并传递它。这正是特殊属性的作用children

计算场

让我们再次简化一下例子。

// Counter.vue

<template>
    <div>
        <h1>{{ capitalizedTitle }}</h1>
        <button @click="increment">{{ count }}</button>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                count: 1
            }
        },
        props: ['title'],
        computed: {
            capitalizedTitle() {
                return title.toUpperCase()
            }
        },
        methods: {
            increment() {
                this.count++
            }
        }
    }
</script>
Enter fullscreen mode Exit fullscreen mode

反应

import { useState, useMemo } from 'react'

function Counter({ title }) {
    const [count, setCount] = useState(1)
    const increment = () => setCount(count+1)
    const capitalizedTitle = title.toUpperCase()

    return (
        <>
            <h2>{capitalizedTitle}</h2>
            <button onClick={increment}>{count}</button>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

在 Vue 中,计算字段有两个用途:保持模板简洁,同时提供缓存功能。

在 React 中,我们可以简单地声明一个变量来保存所需的值,从而解决保持模板简洁的问题。( const capitalizedTitle = title.toUpperCase())

为了实现缓存,我们可以使用 React 的useMemohook。

const capitalizedTitle = useMemo(() => title.toUpperCase(), [title])
Enter fullscreen mode Exit fullscreen mode

在第二个参数中,我们必须指定当任何字段的值发生更改时,需要使缓存失效的字段。

useMemo 的工作原理如下:

组件外部标题更改 ->Counter由于 prop 已更新,函数运行 ->useMemo意识到标题已更改,运行作为第一个参数传入的函数,缓存其结果并返回它。

Vue 的优缺点

(+) 职责划分清晰明确

(-)你在函数中定义了计算字段,但却像访问状态/属性一样访问它们。仔细想想,这完全说得通,但我多次收到同行关于这方面的疑问。

这里面有些神奇之处。Vue 是如何知道何时使缓存失效的呢?

(-)计算字段有两个用途

React 的优缺点

(+) 为了保持模板简洁,无需学习任何新概念,只需将其保存到一个变量中,然后在模板中使用该变量即可。

(+) 您可以控制缓存的内容和方式

手表

// Counter.vue

<template>
    <button @click="increment">{{ capitalizedTitle }}</button>
</template>
<script>
    export default {
        data() {
            return {
                count: 1
            }
        },
        watch: {
            count() {
                console.log(this.count)
            }
        },
        methods: {
            increment() {
                this.count++
            }
        }
    }
</script>
Enter fullscreen mode Exit fullscreen mode

反应

import { useState, useEffect } from 'react'

function Counter({ title }) {
    const [count, setCount] = useState(1)
    const increment = () => setCount(count+1)

    useEffect(() => {
        console.log(count)
    }, [count])

    return (
        <button onClick={increment}>{count}</button>
    )
}
Enter fullscreen mode Exit fullscreen mode

useEffect工作原理与此基本相同useMemo,只是没有缓存部分。

setCount->Counter函数运行 ->useEffect意识到计数已改变,并将运行效果。

Vue 的优缺点

(+)清晰易懂,完美!

React 的优缺点

(+) 您可以指定多个字段,而不仅仅是一个字段。

(-) 的用途useEffect不如 Vue 的那么明确watch。这也是因为useEffect的用途不止一种。它还能处理各种副作用。

安装

在组件挂载后执行某些操作是使用 Ajax 请求的好时机。

视图

// Counter.vue

<template>
    <button @click="increment">{{ capitalizedTitle }}</button>
</template>
<script>
    export default {
        data() {
            return {
                count: 1
            }
        },
        mounted() {
            // this.$http.get...
        },
        methods: {
            increment() {
                this.count++
            }
        }
    }
</script>
Enter fullscreen mode Exit fullscreen mode

并做出反应

import { useState, useEffect } from 'react'

function Counter({ title }) {
    const [count, setCount] = useState(1)
    const increment = () => setCount(count+1)

    useEffect(() => {
        // ajax request...
    }, [])

    return (
        <button onClick={increment}>{count}</button>
    )
}
Enter fullscreen mode Exit fullscreen mode

你可以使用useEffect与之前相同的方法,但这次将第二个参数指定为空数组。它只会执行一次,并且由于没有像之前那样指定状态([count]),因此永远不会执行第二次。

Vue 的优缺点

(+)干净方便。

(-)启动某个操作和后续清理工作必须使用两种不同的方法,这会导致不必要的跳转,并迫使你将变量保存在完全不同的地方(稍后会详细介绍)。

React 的优缺点

(-)非常抽象。我更希望它有一个专门的方法。不过,好处是我可以自由地实现它

回调函数useEffect不允许返回 Promise(会导致竞态条件)。

(+) 在同一函数中进行清理:

原来useEffect它还有一个相当有趣且巧妙的功能。如果在组件内部返回一个函数useEffect,那么当组件被卸载/销毁时,该函数就会被调用。这乍听起来可能有点令人困惑,但它可以帮你节省一些临时变量。

看看这个

import { useState, useEffect } from 'react'

function App() {
    const [showsCount, setShowsCount] = useState(true);

    return (
    <div className="App">
        <button onClick={() => setShowsCount(!showsCount)}>toggle</button>
        {showsCount && <Counter />}
    </div>
    );
}

function Counter({ title }) {
    const [count, setCount] = useState(1)
    const increment = () => setCount(count+1)

    useEffect(() => {
        const interval = setInterval(() => {
            increment()
            console.log("interval")
        }, 1000)

        return function cleanup() {
            clearInterval(interval)
        }
    }, [])

    return (
        <button>{count}</button>
    )
}
Enter fullscreen mode Exit fullscreen mode

有趣的地方在于内部useEffect。在同一个作用域内,我们可以创建和清除一个区间。而使用 Vue,我们必须先在其他地方初始化变量,才能mounted在内部填充和清理它destroy

其他的

视图

(+)v-model指令

(+) 像 SSR、VueX 和 vue-router 这样与开发者工具配合良好的第一方工具

(+) 开箱即用的作用域 CSS。SCSS 超级易用

(+) 感觉更像传统的网页开发,也让新用户更容易上手。

反应

(+)越来越多的东西成为第一方功能,并成为 React 核心库的一部分(钩子、代码拆分等)。

(+) 有许多库可供选择

结论

Vue 在某些方面对你有所限制,但正因如此,它也使你的代码结构更加清晰一致。

React 对你的限制不多,但作为回报,你也需要承担更多维护代码整洁的责任。我认为 Hooks 的引入让这一点变得容易得多。

当然,在如此激烈的竞争中,Vue 也不会忽视 React Hooks 的优势,并且已经发布了关于基于函数的组件的RFC 。这看起来很有前景,我很期待它未来的发展!

文章来源:https://dev.to/michi/react-hooks-for-vue-developers-3n9