学习react_再学习React

学习react

If you’ve been developing React applications for a while, you’ve seen React’s evolution from classes to functions. But even a year after the release of Hooks, the documentation is still largely class-centric. It’s common to hear developers ask, “How do I do componentDidUpdate with Hooks?” because they’re used to the class-based paradigm of component lifecycle.

如果您开发React应用程序已有一段时间,那么您已经看到React从类到函数的演变。 但是即使在Hooks发行一年之后,该文档仍然主要以类为中心。 经常听到开发人员问:“如何使用Hooks来componentDidUpdate ?” 因为它们已经习惯了组件生命周期的基于类的范例。

Now that Hooks have matured, it’s time for us to move from a class-based paradigm to a functional one.

现在,Hooks已经成熟了,是时候让我们从基于类的范式转变为功能性的范式了。

While React’s documentation highlights some of the differences, it mentions them in passing, surrounded by examples. This article dives deep into the philosophical differences and how they change how we think about components.*

尽管React的文档突出了其中的一些差异,但它却顺便提及了这些差异,并附有示例。 本文深入探讨了哲学差异以及它们如何改变我们对组件的看法。*

生命周期与任务 (Lifecycle Versus Tasks)

Functional components break up their work differently than class components.

功能组件与类组件的工作方式不同。

类组件关注生命周期 (Class components focus on the lifecycle)

A class-based component has member methods for every step of the lifecycle of a component:

基于类的组件在组件生命周期的每个步骤都有成员方法:

  • Initialization: constructor

    初始化: constructor

  • Rendering: render

    渲染: render

  • Side-effect generation: componentDidMount/componentDidUpdate

    副作用生成: componentDidMount / componentDidUpdate

  • Cleanup: componentWillUnmount

    清理: componentWillUnmount

There are additional methods for translating data and performance optimization, but these four activities make up the bulk of what a component does. If a class-based component is meant to interact in a React app, it must attach most of its functionality to one of these methods.

还有其他转换数据和性能优化的方法,但是这四个活动构成了组件的大部分工作。 如果要在React应用程序中基于类的组件进行交互,则必须将其大多数功能附加到这些方法之一。

Because these five methods are the backbone of a class-based component, a lot of functionality gets combined. It’s common to look at all the things a component needs to do before splitting them up into one of these five methods. Because of this, class-based components are lifecycle-centric.

由于这五种方法是基于类的组件的骨干,因此将许多功能组合在一起。 在将组件分解为这五个方法之一之前,通常先查看组件需要执行的所有操作。 因此,基于类的组件以生命周期为中心。

功能组件专注于任务 (Functional components focus on tasks)

Functional components are confusing from a lifecycle perspective. There isn’t any obvious lifecycle, just a single function run at render time. Instead of organizing everything that has to run during initialization, rendering, side-effect generation, and cleanup, functional components start at the top and end at the bottom … and that’s it. Where’s the lifecycle?

从生命周期的角度来看,功能组件令人困惑。 没有明显的生命周期,只有一个函数在渲染时运行。 功能组件无需组织初始化,渲染,副作用生成和清除过程中必须运行的所有内容,而是从顶部开始,从底部开始……仅此而已。 生命周期在哪里?

Hooks let functional components participate in the component lifecycle. But instead of focusing on the lifecycle, Hooks focus on tasks.

挂钩使功能组件参与了组件的生命周期。 但是,Hooks并不专注于生命周期,而是专注于任务。

Each time a Hook is called, it spawns off a task of some kind. Calling useState or useReducer returns a state and a method to rerender the component with the new state. Calling useEffect or useLayoutEffect spawns a task that runs when the DOM is available and optionally cleans up after itself. If a dependency changes when calling useMemo or useCallback, a task is spawned to return an object or function.

每次调用Hook时,它都会产生某种任务。 调用useStateuseReducer返回一个状态和一种使用新状态重新呈现组件的方法。 调用useEffectuseLayoutEffect生成一个任务,该任务在DOM可用时运行,并且有选择地在其自身清除之后执行。 如果在调用useMemouseCallback时依赖项发生了变化,则会生成一个任务以返回对象或函数。

Functional components can call multiple Hooks multiple times. This lets functional components be task-oriented. Instead of having to consolidate everything into single monolithic state methods, Hooks let you create small, discrete units of work.

功能组件可以多次调用多个Hook。 这使功能组件面向任务。 Hooks不必将所有内容整合到单个整体状态方法中,而是使您可以创建小的离散工作单元。

为什么这有关系? (Why does it matter?)

When building class-based components, it’s natural to contemplate what needs to be done — what steps are required to do everything — and then combine those unrelated steps into monolithic methods. This can lead us to blurring effect setup and teardown for different tasks and losing track of everything that needs to be kept together or should remain separate.

在构建基于类的组件时,自然会考虑需要做些什么-做所有事情需要采取什么步骤-然后将这些不相关的步骤组合为整体方法。 这可能导致我们模糊效果设置和针对不同任务的拆卸,并使对需要保持在一起或应该保持分开的所有内容失去跟踪。

With functional components, each Hook can be its own discrete unit of work. Even something as simple as useState lets you initialize and update the state as individual task-based units, rather than combining all states into this.state.

使用功能组件,每个挂钩都可以是其自己的独立工作单元。 甚至像useState这样简单的useState ,您useState可以将其作为单独的基于任务的单元来初始化和更新状态,而不是将所有状态组合到this.state

With a more complex Hook, such as useEffect, all of the setup and teardown of a task’s effects stay in one place. We can focus on a single location in the code for everything related to a given task. We can even create a custom Hook to encapsulate an entire polling function’s setup, teardown, and data propagation.

使用更复杂的Hook(例如useEffect ,任务效果的所有设置和拆卸都将放在一个地方。 我们可以专注于代码中与给定任务相关的所有内容的单个位置。 我们甚至可以创建一个自定义的Hook,以封装整个轮询功能的设置,拆卸和数据传播。

In addition to making components more task-based, Hooks also change the purpose of the component’s main function. Originally, functional components were described as a way to implement the straightforward rendering of content — pure functions that convert props to presentational.

除了使组件更加基于任务,Hooks还改变了组件主要功能的目的。 最初,功能组件被描述为一种实现内容直接呈现的方法,即将道具转换为呈现形式的纯功能。

With Hooks, functional components become miniature dispatchers. The main function collects data from props, and useState/useReducer/useContext update the state for future renders, creating and updating side effects with useEffect/useLayoutEffect, then finally returns a presentation. It centralizes the activity of a component, allowing for a top-down flow of behavior.

使用Hooks,功能组件成为微型调度程序。 main函数从props收集数据, useState / useReducer / useContext更新状态以用于将来的渲染,并使用useEffect / useLayoutEffect创建和更新副作用,然后最终返回演示文稿。 它集中了组件的活动,允许自上而下的行为流。

Classes forced us to split tasks based upon their functionality. Hooks let us bring the tasks back together again.

类迫使我们根据其功能拆分任务。 钩子让我们将任务重新组合在一起。

字段与闭包 (Fields Versus Closures)

Functions and classes handle data storage quite differently as well.

函数和类对数据存储的处理也大不相同。

类组件依赖于字段 (Class components rely on fields)

In a class, everything is based on this. Accessing props and states are done by looking inside this.props and this.state, respectively. When a side effect is generated or an event handler is added to a DOM element, it’s bound to this so it can reference those properties and the state.

在课堂上,一切都基于this 。 通过分别在this.propsthis.state内部this.props来访问道具和状态。 当产生副作用或将事件处理程序添加到DOM元素时,它将绑定this元素,因此它可以引用那些属性和状态。

Side effects and handlers interact on the current state of the object, not the state as it was when they were created. While this may be what you wanted, it means you’re relying on mutable data. Other activities can move the data out from under your side effect.

副作用和处理程序在对象的当前状态上交互,而不是在创建对象时的状态上交互。 尽管这可能是您想要的,但它意味着您依赖可变数据。 其他活动可能会将数据从副作用中移出。

功能组件依赖于闭包中的常量 (Functional components rely on constants in closures)

A closure is the snapshot of all external variables and values used by a function. Every function creates one, but usually, it’s created when the function is called and destroyed when the function is done executing. If that function returns a function that it created, however, that function will keep track of that snapshot:

闭包是函数使用的所有外部变量和值的快照。 每个函数都会创建一个,但通常是在调用函数时创建,而在函数执行完毕后销毁。 但是,如果该函数返回其创建的函数,则该函数将跟踪该快照:

let url;
let period = 1000;


function startPolling() {
  // create a function using external values
  const poller = async () => {
    const response = await fetch(url); // url refers to the value at the time startPolling() was called.
    const json = await response.json();
  };
  
  // poller() is *used* inside the setInterval function, not in startPolling().
  // Since poller() was created in startPolling(), it uses the value of url
  // presented to startPolling(), frozen in time in its closure.
  const intervalId = setInterval(poller, period);
  return intervalId;
}


// Start polling '/api/foo' at 1000ms intervals
url = '/api/foo';
const fooIntervalId = startPolling();


// Start polling '/api/bar' at 5000ms intervals.
// The poller created for fooIntervalId is unchanged.
url = '/api/bar';
period = 5000;
const barIntervalId = startPolling();

Functional components use closures heavily to track information. Instead of binding variables and functions to this, event handlers and effects are created inside the main function.

功能组件大量使用闭包来跟踪信息。 不是将变量和函数绑定this ,而是在主函数内部创建事件处理程序和效果。

When the component returns JSX or queues the effect, the event handlers and effect functions are passed out of the main function, carrying that frozen snapshot of whatever variables the handlers and functions needed.

当组件返回JSX或将效果排队时,事件处理程序和效果函数将从主函数中传递出去,并携带处理程序和函数所需的任何变量的冻结快照。

为什么这有关系? (Why does it matter?)

Functional-component variables don’t change unless you make them change. When you call the methods returned by useState and useReducer, you tell React to rerun the component with a new state. This recreates your event handlers and side effects with new closures, updated with the new state.

除非您进行更改,否则功能组件变量不会更改。 当调用useStateuseReducer返回的方法时,您告诉React使用新状态重新运行组件。 这将使用新的闭包重新创建事件处理程序和副作用,并以新的状态进行更新。

This can be expensive, depending on the complexity of your effects and handlers. React lets you define dependencies onuseEffect/useLayoutEffect, so these snapshots only update when the dependencies change. This gives you fine-grained control over exactly when and how your side effects update.

这可能是昂贵的,具体取决于效果和处理程序的复杂性。 React使您可以定义对useEffect / useLayoutEffect依赖关系,因此这些快照仅在依赖关系更改时更新。 这使您可以精确控制副作用的更新时间和方式。

But you need to get clear on what needs to change. Class-based components make it easy to simply pluck values out of the current state but make it hard to know how the current state interacts with your effects, state, and renders simultaneously.

但是您需要弄清楚需要更改的内容。 基于类的组件可以轻松地简单地从当前状态中提取值,但很难知道当前状态如何与您的效果,状态和渲染同时进行交互。

You can create a side effect for one user, switch users, then attempt to clean up the side effect for the new user without ever having managed the switch between users. componentDidUpdate becomes a maelstrom of transitions, swapping and handing off effects based on state and property changes.

您可以为一个用户创建副作用,切换用户,然后尝试为新用户清除副作用,而无需管理用户之间的切换。 componentDidUpdate会根据状态和属性更改转变,交换和移交效果的漩涡。

With functional components, you need to really know how your state and properties impact your handlers and side effects because your effects will be frozen in time unless you identify their dependencies. The ESLint warning react-hooks/exhaustive-deps will be your friend in tracking down when you’re using props and the state and not accounting for it.

使用功能组件,您需要真正了解状态和属性如何影响处理程序和副作用,因为除非确定其依赖关系,否则效果会被及时冻结。 当您使用道具和状态而又没有考虑到它们时,ESLint警告react-hooks/exhaustive-deps将成为您追踪的朋友。

It may be tempting to ignore or disable it. Don’t. If you reference a function or object that’s created on every render, wrap the function/object in useCallback/useMemo.

忽略或禁用它可能很诱人。 别。 如果引用在每个渲染器上创建的函数或对象,请将函数/对象包装在useCallback / useMemo

This lets you keep track of any dependencies in those items, as well as keeping them from being recreated each time. You’ll still want to identify the function/object as a dependency in your effect, but at least you know it won’t trigger an update unnecessarily.

这使您可以跟踪这些项目中的任何依赖关系,以及防止每次都重新创建它们。 您仍然希望将功能/对象标识为效果中的依赖项,但至少您知道它不会不必要地触发更新。

组成和重用 (Composition and Reuse)

While both functional and class-based components encourage composition and dependency injection at the presentation level, they have different ways of composing functionality.

尽管基于功能的组件和基于类的组件都鼓励在表示层进行组合和依赖注入,但它们具有不同的组合功能方式。

类组件鼓励包装器和装饰器 (Class components encourage wrappers and decorators)

React discourages traditional OOP-based patterns of inheritance and mixins for class-based components — and rightly so. They create brittle, inflexible, and confusing architectures.

React不鼓励使用基于OOP的传统继承模式和基于类的组件的mixin,这是正确的。 它们创建了脆弱,僵化和混乱的架构。

To combine functionality between components, React introduced the concept of higher-order components (HOCs). HOCs wrap other components in a function that generates a new component. This new component can have its own side effects and introduce new properties to the wrapped component.

为了结合组件之间的功能,React引入了高阶组件(HOC)的概念。 HOC将其他组件包装在生成新组件的函数中。 此新组件可能有其自身的副作用,并为包装的组件引入新的属性。

HOCs encourage class-based components to participate in a wrapper/decorator architecture. Rather than building up a component from different pieces of functionality, class-based components become receivers of external functionality.

HOC鼓励基于类的组件参与包装器/装饰器体系结构。 基于类的组件不是从功能的不同部分来构建组件,而是成为外部功能的接收者。

功能组件鼓励功能包含和封装 (Functional components encourage feature inclusion and encapsulation)

While functional components can be used with HOCs, they typically encourage feature inclusion by using custom Hooks.

虽然功能组件可与HOC一起使用,但它们通常鼓励使用自定义挂钩来包含功能。

Custom Hooks combine the basic React Hooks in new and interesting ways. react-redux, for example, uses useContext, useEffect, useLayoutEffect, useReducer, and useRef to build up useSelector.

Custom Hooks以新颖有趣的方式结合了基本的React Hooks。 例如, react-redux使用useContextuseEffectuseLayoutEffectuseReduceruseRef来构建useSelector

From those five core Hooks, useSelector lets us retrieve the global state and ensures the component will rerender when that state is updated. How it does that is hidden from us in one tidy Hook.

通过useSelector ,从这五个核心Hook中检索全局状态,并确保在该状态更新时useSelector组件。 它如何做到这一点隐藏在我们一个整洁的挂钩中。

Custom Hooks let components pick and choose which specialty tasks they need to do their jobs. They continue the task-based paradigm of the basic Hooks but hide the complexity of these custom tasks.

自定义挂钩可让组件选择并选择完成工作所需的特殊任务。 它们延续了基本Hook的基于任务的范例,但隐藏了这些自定义任务的复杂性。

为什么这么重要? (Why does this matter?)

While class-based components apply functionality to a child component, custom Hooks inject functionality into the child component. HOCs were developed to take the place of mixins, but Hooks were developed to provide individual tasks where needed.

当基于类的组件功能应用于子组件时,自定义挂钩功能注入到子组件中。 开发了HOC来代替mixin,但是开发了Hooks来在需要时提供单独的任务。

This changes the paradigm of how to reuse functionality. HOCs tend to be expensive to use, requiring adding a new component layer for every kind of functionality required by the child component. Hooks add specific functionality where we want it.

这改变了如何重用功能的范例。 HOC使用起来往往很昂贵,需要为子组件所需的每种功能添加新的组件层。 挂钩在我们需要的地方添加了特定功能。

Here’s an example of how we might write a simple component that redirects a visitor. It gets the URL from the global state. From there, it also gets whether the component should replace the current URL with the new one or keep the history intact. It also lets the user know they’re being redirected, with an internationalized message. (This is obviously not a production-ready component. It’s just here to demonstrate the differences in construction.)

这是一个示例,说明如何编写一个简单的组件来重定向访问者。 它从全局状态获取URL。 从那里,还可以获取组件是否应使用新URL替换当前URL或保持历史记录完整。 它还通过一条国际化消息使用户知道他们正在被重定向。 (这显然不是可用于生产的组件。它只是在这里说明结构上的差异。)

With a class-based component, this requires wrapping our component with three separate HOCs: one for the global state, one for history management, and one for translation:

对于基于类的组件,这需要用三个独立的HOC包装我们的组件:一个用于全局状态,一个用于历史管理,一个用于翻译:

import React from 'react';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';


class VisualRedirector extends React.Component {
  componentDidMount() {
    if (this.props.history.location.pathname !== this.props.url) {
      const method = this.props.redirect ? 'replace' : 'push';
      this.props.history[method](this.props.url);
    }
  }


  render() {
    const messageKey = this.props.redirect ? 'redirectingTo' : 'movingTo';
    return <p>{this.props.t(messageKey) + ' ' + this.props.url}...</p>;
  }
}


const connector = connect(({ location: { url, redirect} }) => ({ url, redirect }));
const translator = withTranslation('systemMessages');


export default withRouter(translator(connector(VisualRedirector)));

This creates four components every time we ask for a single instance of <VisualRedirector/>.

每当我们要求<VisualRedirector/>的单个实例时,这都会创建四个组件。

With Hooks, the component becomes much leaner:

使用Hooks,组件变得更加精简:

import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';


function VisualRedirector() {
  const { url, redirect } = useSelector(state => state.location);
  const history = useHistory();
  
  useEffect(() => {
    if (history.location.pathname !== url) {
      const method = redirect ? 'replace' : 'push';
      history[method](url);
    }
  }, [url, redirect, history]);


  const { t } = useTranslation('systemMessages');
  const messageKey = redirect ? 'redirectingTo' : 'movingTo';


  return <p>{t(messageKey) + ' ' + url}...</p>;
}


export default VisualRedirector;

When functional components incorporate external functionality, they do so in a targeted fashion. useSelector and useHistory pull specific data to be used by the effect that follows it, while useTranslation returns the translation function when it is needed. (Note that we can do this here because the entire component is run from top to bottom. If a component exits prematurely, all Hooks need to be present before that first exit point.)

当功能组件包含外部功能时,它们将有针对性地实现。 useSelectoruseHistory提取由其后的效果使用的特定数据,而useTranslation在需要时返回转换功能。 (请注意,我们可以在此处执行此操作,因为整个组件是从上到下运行的。如果某个组件过早退出,则所有Hook都需要在该第一个退出点之前出现。)

Hooks tend to be more explicit in their data sharing as well. With HOCs, properties magically appear. You can see that the class-based component accesses history, redirect, t, and url — but not where they came from. Hooks, however, explicitly identify when they return those properties. Hooks are more traceable.

挂钩在数据共享方面也往往更加明确。 使用HOC,可以神奇地显示属性。 您可以看到基于类的组件访问historyredirectturl ,但是不访问它们的来源。 但是,挂钩可以明确标识何时返回这些属性。 钩子更容易追踪。

放在一起 (Putting It All Together)

Moving from a class-based paradigm to a functional paradigm requires some shifts in thinking, but it’s worth it:

从基于类的范式转变为功能范式需要思想上的一些转变,但这是值得的:

  • Shifting from lifecycle to task lets us keep our code together by its purpose, instead of by when it’s supposed to perform

    从生命周期转移到任务,使我们可以按目的将代码保持在一起,而不是按应在何时执行
  • Tracking closures and dependencies instead of member fields makes our data tracking more explicit and self-documenting

    跟踪闭包和依赖项而不是成员字段,使我们的数据跟踪更加明确和具有自记录性
  • Using custom Hooks instead of HOCs lets us use targeted tasks, instead of wrapping our components in other components.

    使用自定义的Hook代替HOC,使我们可以使用目标任务,而不是将组件包装在其他组件中。

React has always had functional programming as its paradigm. Classes were an odd fit into this paradigm. As you move from classes to functions, you’ll find other functional programming structures fitting more easily into the paradigm, including higher-order functions, lambdas, and partial functions. You’ll find yourself thinking less about inheritance and more about chaining.

React一直以函数式编程为范式。 班级很适合这种范例。 当您从类转移到函数时,您会发现其他函数编程结构更容易适应该范例,包括高阶函数,lambda和部分函数。 您会发现自己较少考虑继承,而更多地考虑链接。

Best of luck!

祝你好运!

*I originally started a longer series going into the differences in detail, but it got too long. This article is intended to look more at the mindset and philosophy of class-based and functional-component design, rather than getting into the nitty-gritty.

*我最初开始了一个较长的系列,详细介绍了差异,但是时间太长了。 本文旨在更多地研究基于类和功能组件设计的思想和哲学,而不是深入研究。

翻译自: https://medium.com/better-programming/relearning-react-3db1be5a3567

学习react

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值