react 是一个流行的javascript前端库,用于构建交互式用户界面。React 的学习曲线相对较浅,这也是它最近备受关注的原因之一。
尽管有许多重要的概念需要介绍,但组件无疑是 React 的核心和灵魂。作为 React 开发人员,对组件有很好的理解应该会让你的生活变得轻松。
先决条件
本教程适用于已经开始学习 React 并需要更好地了解组件的初学者。我们将从组件基础开始,然后转向更具挑战性的概念,例如组件模式以及何时使用这些模式。涵盖了不同的组件分类,例如类与功能组件、有状态与无状态组件以及容器与展示组件。
那么,让我们开始吧。
什么是组件?
组件是描述 UI 的一部分的自我维持、独立的微实体。应用程序的 UI 可以拆分为更小的组件,其中每个组件都有自己的代码、结构和api。
例如,当您查看他们的 Web 应用程序时,Facebook 有数以千计的功能连接在一起。这是一个有趣的事实:Facebook 包含 30,000 个组件,而且这个数字还在增长。组件架构允许您孤立地考虑每个部分。每个组件都可以更新其范围内的所有内容,而不必担心它如何影响其他组件。
如果我们以 Facebook 的 UI 为例,搜索栏将是一个很好的组件候选者。Facebook 的 Newsfeed 将制作另一个组件(或包含许多子组件的组件)。与搜索栏相关的所有方法和ajax调用都在该组件中。
组件也是可重复使用的。如果您在多个地方需要相同的组件,那很容易。借助jsx语法,您可以在任何您希望它们出现的地方声明组件,仅此而已。
<div> Current count: {count} <hr /> {/* Component reusability in action. */ } <Button sign = "+" count={count} updateCount = {setCount(count+1)}/> <Button sign = "-" count={count} updateCount = {setCount(count-1)}/> </div>
道具和状态
组件需要数据才能使用。有两种不同的方式可以组合组件和数据:作为 props 或 state。props 和 state 决定了组件渲染的内容和行为方式。让我们从道具开始。
了解道具
如果组件是纯 JavaScript 函数,那么 props 就是函数输入。按照这个类比,一个组件接受一个输入(我们称之为 props),处理它,然后渲染一些 JSX 代码。
尽管组件可以访问 props 中的数据,但 React 的理念是 props 应该是不可变的和自上而下的。这意味着父组件可以将它想要的任何数据作为道具传递给其子组件,但子组件不能修改其道具。因此,如果您尝试像我在下面所做的那样编辑道具,您将得到 "Cannot assign to read-only" TypeError
。
const Button = (props) => { // props are read only props.count = 21; . . }
状态
另一方面,State 是声明它的组件所拥有的对象。它的范围仅限于当前组件。组件可以初始化其状态并在必要时对其进行更新。父组件的状态通常最终成为子组件的 props。当状态超出当前范围时,我们将其称为道具。
了解了组件基础知识之后,我们来看看组件的基本分类。
类组件与功能组件
React 组件可以有两种类型:类组件或函数式组件。两者的区别从名字就可以看出来。
功能组件
函数式组件只是 javaScript 函数。他们接受一个可选的输入,正如我之前提到的,这就是我们所说的道具。
一些开发人员更喜欢使用新的 ES6 箭头函数来定义组件。箭头函数更紧凑,并为编写函数表达式提供了简洁的语法。通过使用箭头函数,我们可以跳过两个关键字的使用,function
andreturn
和一对curl y 括号。使用新语法,您可以像这样在一行中定义一个组件。
const Hello = ({ name }) => (<div>Hello, {name}!</div>);
功能组件还提供通过钩子使用状态和生命周期事件的能力。钩子是可以在功能组件中运行以执行某些操作的功能。例如,useState()
钩子是这样使用的:
const [count,setCount] = useState(0);
然后,您可以使用获取当前计数并使用count()
设置计数setCount()
。
类组件
类组件可能比函数组件更复杂,但有些人更喜欢这种风格。
state = {count: 1} 语法是公共类字段功能的一部分。更多关于这下面。
您可以通过扩展来创建类组件React.Component
。这是一个接受输入道具并呈现 JSX 的类组件的示例。
class Hello extends React.Component { constructor(props) { super(props); } render() { return( <div> Hello {props} </div> ) } }
我们定义了一个接受 props 作为输入的构造方法。在构造函数内部,我们调用super()
以传递从父类继承的任何内容。
请注意,构造函数在定义组件时是可选的。在上述情况下,组件没有状态,构造函数似乎没有做任何有用的事情。无论是否定义了构造函数,this.props
在内部使用都将起作用。render()
但是,这是官方文档中的一些内容:
类组件应始终使用 props
.
作为最佳实践,我建议对所有类组件使用构造函数。
此外,如果您使用的是构造函数,则需要调用super()
. 这不是可选的,"Missing super() call in constructor"
否则您将收到语法错误。
我的最后一点是关于super()
vs. 的使用super(props)
。super(props)
如果您要this.props
在构造函数内部调用,则应该使用它。否则,单独使用super()
就足够了。
有状态组件与无状态组件
这是对组件进行分类的另一种流行方式,分类标准很简单:有状态的组件和没有状态的组件。
有状态组件
有状态的组件要么是类组件,要么是带有钩子的功能组件。现在大多数有状态的组件都使用钩子,但类组件仍然可用。
// Class component state constructor(props) { super(props); this.state = { count: 0 }; } // Hook state const [count,setCount] = useState(0);
在这两个示例中,我们创建了状态count
和useState
钩子。如果您正在使用类组件,则建议使用另一种语法来简化此操作,称为类字段。
class App extends Component { // constructor not required anymore state = { count: 1 }; handleCount(value) { this.setState((prevState) => ({count: prevState.count+value})); } render() { // ... } }
您可以避免使用此语法完全使用构造函数。
count
如果您使用钩子或this.state.count
使用类组件,我们现在可以使用变量访问状态。
// Classes render() { return ( Current count: {this.state.count} ) } // Hooks return ( Current count: {count} )
这里的this
关键字是指类中当前组件的实例。
但是,初始化状态是不够的——我们需要能够更新状态以创建交互式应用程序。如果您认为以下内容会起作用,不,它不会。
// Wrong way // Classes handleCount(value) { this.state.count = this.state.count +value; } // Hooks count = count + 1
React 类组件配备了一个this.setState()
用于更新状态的方法。setState()
接受一个包含count
. 该useState()
钩子返回第二个函数,允许您使用新值更新状态。
// This works // Hooks const [count,setCount] = useState(0); setCount(count+value); // Classes handleCount(value) { this.setState({count: this.state.count+ value}); }
然后this.setState()
接受setCount()
一个对象作为输入,我们将之前的 count 值增加 1,这符合预期。但是,有一个问题。当有多个setState()
调用读取状态的先前值并将新值写入其中时,我们可能会遇到竞争条件。这意味着最终结果与预期值不匹配。
这是一个应该让您清楚的示例。尝试做这样的事情。
// What is the expected output? Try it in the code sandbox. handleCount(value) { this.setState({count: this.state.count+100}); this.setState({count: this.state.count+value}); this.setState({count: this.state.count-100}); }
我们希望将setState()
计数增加 100,然后将其更新 1,然后删除之前添加的 100。如果setState()
按照实际顺序执行状态转换,我们将得到预期的行为。但是,setState()
它是异步的,多个setState()
调用可能会被批处理在一起以获得更好的 UI 体验和性能。所以上面的代码产生了一个与我们预期不同的行为。
因此,您可以传入一个具有签名的更新函数,而不是直接传递一个对象:
(prevState, props) => stateChange
prevState
是对先前状态的引用,并保证是最新的。props
指的是组件的props,这里我们不需要props来更新状态,所以可以忽略。因此,我们可以使用它来更新状态并避免竞争条件。
// The right way // Classes handleCount(value) { this.setState((prevState) => { count: prevState.count +1 }); } // Hooks setCount((prev)=>prev+1)
该setState()
方法重新呈现组件,并且您有一个工作状态组件。
无状态组件
您可以使用函数或类来创建无状态组件。但是除非你喜欢类组件的风格,否则你应该选择无状态的函数式组件。如果您决定在这里使用无状态功能组件,会有很多好处;它们易于编写、理解和测试,您可以this
完全避免使用该关键字。但是,从 React v16 开始,使用无状态功能组件而不是类组件没有性能优势。
容器组件与展示组件
这是另一种在编写组件时非常有用的模式。这种方法的好处是行为逻辑与表示逻辑分离。
表示组件
表示组件与视图或事物的外观相结合。这些组件接受来自容器对应的道具并渲染它们。与描述 UI 有关的所有内容都应放在此处。
表示组件是可重用的,应该与行为层保持分离。展示组件专门通过 props 接收数据和callback,当有事件发生时,比如按下按钮,它通过 props 对容器组件进行回调,调用事件处理方法。
功能组件应该是您编写展示组件的首选。如果一个展示组件需要一个状态,它应该关注 UI 状态而不是实际数据。展示组件不与 redux 存储交互或进行 API 调用。
容器组件
容器组件将处理行为部分。容器组件告诉展示组件应该使用 props 渲染什么。它不应该包含有限的dom标记和样式。如果您使用 Redux,则容器组件包含将操作分派到商店的代码。或者,您应该在此处放置 API 调用并将结果存储到组件的状态中。
通常的结构是顶部有一个容器组件,它将数据作为道具传递给其子表示组件。这适用于较小的项目;然而,当项目变得更大并且你有很多中间组件只接受道具并将它们传递给子组件时,这将变得令人讨厌且难以维护。遇到这种情况,最好创建一个叶子组件独有的容器组件,这样可以减轻中间组件的负担。
那么什么是记忆组件和纯组件?
你会在 React 圈子里经常听到“纯组件”这个词,然后是React.PureComponent
, 或React.memo
for 钩子。当你刚接触 React 时,所有这些听起来可能有点令人困惑。如果在给定相同的 props 和 state 的情况下保证返回相同的结果,则该组件被称为纯组件。无状态组件是纯组件的一个很好的例子,因为给定输入,您知道将呈现什么。
const HelloWorld = ({name}) => ( <div>{`Hi ${name}`}</div> );
如果您的组件是纯组件,则可以使用memo
和来优化它们PureComponent
。这些方法改变了 React 组件的更新行为。默认情况下,React 组件总是在 state 或 props 发生变化时更新。但是,如果你使用PureComponent
or memo
,React 会对 props 和 state 进行浅比较,这意味着你比较对象的直接内容,而不是递归地比较对象的所有键/值对。所以只比较对象引用,如果状态或道具发生变化,这可能无法按预期工作。
// Classes class MyComponent extends React.PureComponent { // use this instead of React.Component // ... } // Hooks const MyComponent = React.memo(function MyComponent(props) { // Wrap the component function in React.memo // ... });
React.PureComponent
并React.memo
用于优化性能,除非遇到某种性能问题,否则没有理由考虑使用它们。
最后的想法
函数式组件和钩子通常比它们的类对应物要简单得多,所以除非你有特殊偏好,否则函数式组件是最好的选择。
在本教程中,您获得了 React 中基于组件的架构和不同组件模式的高级概述。
- 了解道具
- 状态
- 功能组件
- 类组件
- 有状态组件
- 无状态组件
- 表示组件
- 容器组件