面向 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>
以下是 React 中的相同操作。
function Counter() {
return <div>0</div>
}
请注意,由于 React 组件只是一个函数,因此它不一定需要放在单独的文件中。
与州政府合作
维尤
// Counter.vue
<template>
<button @click="increment">{{ count }}</button>
</template>
<script>
export default {
data() {
return {
count: 1
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
并做出反应
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(1)
const increment = () => setCount(count+1)
return <button onClick={increment}>{ count }</button>
}
如你所见,ReactuseState返回的是一个元组,第二个参数是一个 set 函数。而在 Vue 中,你可以直接设置值来更新状态。
使用 Hooks 时,每当我们的状态/属性更新时,该Counter方法都会再次执行。不过,只有第一次执行时,它会将count变量初始化为 1。这就是 Hooks 的基本原理。这是使用 Hooks 时必须理解的几个概念之一。
Vue 的优缺点
(+) 预定义结构
(-)你不能直接导入某个东西然后在模板中使用它。它必须按照 Vue 的各种概念进行布局,例如 `<div>` data、methods` 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>
并做出反应
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>
</>
)
}
Vue 的优缺点
(+) 您可以具体指定道具的类型(无需使用 TS)
(-)访问方式与状态(this.xxx)相同,但实际行为却不同(例如,赋新值会抛出警告)。这容易让初学者误以为可以直接更新 props。
React 的优缺点
(+)易于理解 -> props 就是函数参数
子组件
让我们把按钮提取到一个子组件中。
视图
// Button.vue
<template>
<button @click="$emit('handle-click')">
{{ value }}
</button>
</template>
<script>
export default {
props: ['value']
}
</script>
// 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>
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}/>
</>
)
}
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>
// 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>
<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>
</>
)
}
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>
反应
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>
</>
)
}
在 Vue 中,计算字段有两个用途:保持模板简洁,同时提供缓存功能。
在 React 中,我们可以简单地声明一个变量来保存所需的值,从而解决保持模板简洁的问题。( const capitalizedTitle = title.toUpperCase())
为了实现缓存,我们可以使用 React 的useMemohook。
const capitalizedTitle = useMemo(() => title.toUpperCase(), [title])
在第二个参数中,我们必须指定当任何字段的值发生更改时,需要使缓存失效的字段。
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>
反应
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>
)
}
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>
并做出反应
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>
)
}
你可以使用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>
)
}
有趣的地方在于内部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