React 函数组件与 Hooks:你需要知道的一切
这篇文章最初发表于https://www.devaradise.com/react- functional-component-with-hooks
您可能知道,在 React 中创建组件有两种方法:函数组件和基于类的组件。
在 2019 年 2 月 16 日发布 React 16.8 之前,我们总是使用基于类的组件来创建有状态组件(具有状态的 React 组件)。那时,函数式组件仅在创建无状态组件时使用。
现在,自从 React Hooks 在 16.8 版本中引入以来,我们无需声明类即可创建有状态组件。我们可以使用 Hooks 从函数组件中“钩入” React 状态和生命周期功能。
相关文章
React 中的函数组件是什么?
函数式组件是一个用普通 JavaScript 函数声明的 React 组件,该函数接受 props 参数并返回 JSX 代码。在 Hooks 引入之前,它也被称为无状态组件。
现在,我们不能再称它为无状态组件,因为它也可以具有状态和生命周期。
有了 Hooks 的存在,React 函数式组件可以取代基于类的组件,因为它编写起来更简单、更简短、易于测试,并且性能更好。
如何编写 React 函数组件?
我们可以使用功能组件创建任何类型的反应组件,从无状态组件到具有状态和生命周期的复杂组件。
1. 一个简单的无状态组件
当您需要没有任何道具/输入或状态的可重复使用 UI 时,通常会创建一个简单的无状态组件。
这是一个非常基本的组件,您最好将其写为功能组件。
import React from 'react'
export default function StatelessComponent() {
return (
<div>
I am a stateless component
</div>
)
}
2. 处理道具
假设你想添加一个带有name
androle
属性的无状态组件。它将在其他组件中像这样被调用。
<StatelessComponent name="Syakir" role="Front-end Developer"/>
要处理提供的输入/道具,您可以按如下方式访问它们。
import React from 'react'
export default function StatelessComponent(props) {
return (
<div>
Hi, I am {props.name}<br/>
I am a {props.role}
</div>
)
}
在功能组件中,props 通过参数(作为对象)传递,该参数将任何输入/prop 存储为其属性。
3. PropTypes 的 Props
为了创建更好的组件,你应该定义并验证 props。我们可以使用 PropTypes 来实现这一点。
import React from 'react';
import PropTypes from 'prop-types';
export default function StatelessComponent(props) {
return (
<div>
Hi, I am {props.name}<br/>
I am a {props.age} years old {props.role}
</div>
)
}
StatelessComponent.propTypes = {
name: PropTypes.string.isRequired,
role: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
};
使用 PropTypes,你可以轻松地对 props 进行类型检查。如果提供的 props 与定义的类型不匹配,则会触发警告。
有关 PropTypes 的更多详细信息,可以访问此页面。
4. 有状态组件(使用 useState Hook)
现在,我们可以使用 useState Hook 创建一个有状态的功能组件。
以下是如何使用它。
import React, { useState } from 'react'
export default function StatefulComponent() {
const [helloMessage, setHelloMessage] = useState('Hello world!');
return (
<div>
<input type="text" value={helloMessage}/>
</div>
)
}
useState 钩子用于声明一个“状态变量”。它返回一对值:当前状态(helloMessage
)和一个更新该状态的函数(setHelloMessage
)。
它们相当于基于类的组件中的this.state.helloMessage
和。this.setState
5. 处理事件
当用户与表单、按钮、链接等组件交互时,我们希望它们按照我们的意愿运行。
因此,我们需要事件处理程序来处理诸如 onClick、onKeyup、onChange 等事件以及其他支持的反应事件。
例如,我们希望helloMessage
当用户更改输入字段的值时进行更改。您可以按如下方式操作。
import React, { useState } from 'react'
export default function StatefulComponent() {
const [helloMessage, setHelloMessage] = useState('Hello world!');
return (
<div>
<input type="text" value={helloMessage} onChange={(e) => setHelloMessage(e.target.value)}/>
{helloMessage}
</div>
)
}
因为我们只需要一行代码来改变状态,所以我们可以将事件处理程序内联编写为箭头函数。
如果您想在输入发生变化时添加其他代码,最好将事件处理程序写为单独的函数,如下所示。
import React, { useState } from 'react'
export default function StatefulComponent() {
const [helloMessage, setHelloMessage] = useState('Hello world!');
const onInputChange = (e) => {
setHelloMessage(e.target.value);
// other codes
}
return (
<div>
<input type="text" value={helloMessage} onChange={onInputChange}/>
{helloMessage}
</div>
)
}
6.处理回调(将数据从子组件传递到父组件)
在实际项目中,我们经常将一个组件包装在另一个组件(父组件)中。
很多时候,我们需要监听子组件发生的事情,并在父组件中创建一个处理程序。简单来说,我们需要将数据从子组件传递到父组件。
我们可以使用回调函数来做到这一点。
回调函数是作为参数传递给另一个函数的函数,然后在外部函数内部调用该函数来完成某种例程或操作。
https://developer.mozilla.org/en-US/docs/Glossary/Callback_function
下面展示了如何使用回调函数监听或将数据从子级传递到父级。
import React, {useState} from 'react';
export default function ParentComponent() {
const [messageFromChild, setMessageFromChild] = useState('');
return (
<div>
parent should listen 'messageFromChild' when it changed: {messageFromChild}
<br/><br/>
<ChildComponent onChangeHelloMessage={(e) => setMessageFromChild(e)}/>
</div>
)
}
function ChildComponent({onChangeHelloMessage}) {
const [helloMessage, setHelloMessage] = useState('Hello world!');
const onInputChange = (e) => {
setHelloMessage(e.target.value);
onChangeHelloMessage(e.target.value);
}
return (
<div>
<input type="text" value={helloMessage} onChange={onInputChange}/>
{helloMessage}
</div>
)
}
上面代码中的回调函数是onChangeHelloMessage
作为 prop 传入的ChildComponent
,在函数onChangeHelloMessage
中调用该值onInputChange
。
回调函数也经常用于创建可重用的组件,其所有状态都依赖于调用它的父组件。
例如,我们创建一个跨组件共享的自定义输入组件(例如自动完成、屏蔽输入)。
import React, {useState} from 'react';
export default function ParentComponent() {
const [customizedInputValue, setCustomizedInputValue] = useState('');
return (
<div>
<ACustomizedInputComponent value={customizedInputValue} onChangeValue={(e) => setCustomizedInputValue(e.target.value)}/>
<br/>
{customizedInputValue}
</div>
)
}
function ACustomizedInputComponent({value, onChangeValue}) {
// Write some functions here that make this as a customized component.
return (
<div>
{/* Just pretend this is a customized input that can't handled with the common input field */}
<input type="text" value={value} onChange={onChangeValue}/>
</div>
)
}
如您所见,ParentComponent
控制传递给的状态ACustomizedInputComponent
并监听所做的更改ACustomizedInputComponent
。
7. 功能组件生命周期(useEffect Hook)
在基于类的组件中,有诸如componentDidMount
、componentDidUpdate
和之类的 生命周期方法componentWillUnmount
。
感谢useEffect
hook,我们现在可以使用等效函数来替代它们。
通过使用useEffect
,你告诉 React 你的组件需要在渲染后执行某些操作。React 会记住你传递的函数,并在执行 DOM 更新后调用它。
在实际项目中,钩子通常用来包装一个 API 调用函数。你可以在我的React 无限滚动useEffect
教程中看到它的用法。
对于简单的例子,你可以看下面的代码。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
这相当于下面的代码(在基于类的组件中)。
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
要进一步了解 useEffect 的使用,Adrian Bece 的这篇文章可能会帮助您理解。
为什么应该使用函数组件而不是类组件?
如果您仍然怀疑是否在 React App 中整体采用功能组件,以下是您应该使用功能组件而不是类组件的完整理由。
1. 更易读,更简洁
相比基于类的组件,函数式组件更容易理解,编写起来也更简洁。请看下面的代码。
import React from 'react';
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
两个代码块都是相同的组件,但声明方式不同。第一个代码块使用基于类的组件声明,而第二个代码块使用函数式组件声明。
使用函数式组件,你只需要 14 行代码即可声明 Example 组件。而使用类式组件声明,则需要 24 行代码。
较短的代码更易于阅读。
2. 更易于测试
由于它是作为一个简单的 javascript 函数编写的,因此您可以像测试函数一样测试功能组件。
你也不必担心隐藏状态或副作用。对于每个输入(props),函数组件只有一个输出。
3. 可能具有更好的性能
在函数组件的发行说明中,它说,
将来,我们还可以通过避免不必要的检查和内存分配来针对这些组件进行性能优化
这篇文章还说,即使没有优化,函数式组件也比基于类的组件快 45%。
4. 强制执行最佳实践
功能组件通常用于创建专注于 UI 而不是行为的展示组件。
我们应该避免在这种组件中使用状态。状态和生命周期应该在更高级别的组件中使用。
通过使用功能组件(作为无状态组件),您可以保持展示组件的纯粹性,而没有状态和生命周期。
5. React 的未来
自从 Hooks 推出以来,许多开发人员选择使用功能组件,因为现在它几乎可以完成基于类的组件可以做的所有事情。
那么,既然函数式组件更好并且许多开发人员喜欢它,为什么仍然使用基于类的组件呢?
文章来源:https://dev.to/syakirurahman/react-function-component-with-hooks-everything-you-need-to-know-5bjj