我们缺少的React简介

React是世界上最受欢迎的JavaScript库。但是此库因为受欢迎而不好,但是因为受欢迎而受欢迎。大多数现有的React入门教程都是从如何使用该库的示例开始的。但是这些指南并没有说明为什么React是正确的选择。



这种方法有其优势。如果有人在掌握React的同时努力地立即练习,那么他们只需要研究官方文档并着手开展业务 本材料(如果您有兴趣的话,此处是其视频版本)是为那些希望找到以下问题的答案的人而写的:“为什么要反应?为什么React这样工作?为什么按原样设计React API?”











为什么要反应?



如果组件不了解网络通信,应用程序的业务逻辑或其状态,则生活会变得更加轻松。接收相同输入参数的此类组件始终形成相同的视觉元素。



当React库问世时,它从根本上改变了JavaScript框架和库的工作方式。当其他类似的项目推广了MVC,MVVM等思想时,React采取了不同的方法。即,此处应用程序的可视组件的呈现与模型的呈现是隔离的。感谢React,一个全新的架构出现在JavaScript前端生态系统-Flux中。



React团队为什么要这样做?为什么这种方法比以前的方法更好,例如MVC架构和用jQuery编写的意大利面条代码?如果你有兴趣的这些问题,你可以看这个2013的谈话上的JavaScript应用程序开发在Facebook上。



2013年,Facebook刚刚完成了一些严肃的整合到其聊天平台的工作。这项新功能几乎内置于项目的每个页面中,聊天影响了使用该平台的通常情况。这是一个嵌入在另一个以前不容易的应用程序中的复杂应用程序。 Facebook团队必须处理一些琐碎的任务,处理不受控制的DOM变异,以及在新环境中提供并行异步用户体验的需求。



例如,您如何预先知道在任何时间,由于任何原因可以访问DOM并进行更改的情况下屏幕上显示的内容?如何确保用户看到的内容正确绘制?



使用React之前存在的流行前端工具,就不能保证做到这一点。在早期的Web应用程序中,DOM中的“竞赛条件”是最常见的问题之一。



缺乏确定性=并行计算+可变状态。



Martin Oderski




React开发团队的主要任务是解决这个问题。他们用两种主要的创新方法来处理它:



  • 使用Flux架构的单向数据绑定。
  • 组件状态不变。设置组件的状态后,将无法再对其进行更改。状态更改不会影响渲染的组件。而是,此类更改导致具​​有新状态的新视图的输出。


从概念的角度来看,我们发现构造和渲染组件的最简单方法就是根本不针对零突变。



Tom Ochchino,JSConfUS 2013




React库通过使用Flux架构可以大大减少不受控制的突变问题。 React库没有附加事件处理程序以触发DOM更新到任意数量的任意对象(模型),而是为开发人员提供了一种管理组件状态的方法。这是影响数据仓库的动作的分派。当商店的状态更改时,系统会提示要渲染的组件。





Flux架构



当我被问到为什么我应该关注React时,我给出一个简单的答案:“关键是,我们需要确定性地呈现视图,而React使这项任务变得更加容易。”



请注意,从DOM读取数据以实现某些逻辑是一种反模式。谁这样做都违背了使用React的目的。相反,必须从存储中读取数据,并且必须在呈现相应的组件之前基于该数据进行决策。



如果组件的确定性渲染是React唯一的事情,那么仅此一项将是一项巨大的创新。但是React开发团队并没有就此止步。这个团队向世界展示了一个具有其他有趣,独特功能的图书馆。随着项目的发展,React添加了更多有用的东西。



JSX



JSX是一个JavaScript扩展,允许您声明性地创建用户界面组件。JSX具有以下显着功能:





如果在JSX出现之前必须以声明方式描述接口,那么不使用HTML模板就不可能做到。那时,还没有创建此类模板的公认标准。每个框架都使用自己的语法。这种语法必须由某个人来学习,例如需要循环一些数据,将变量的值嵌入文本模板或决定要显示哪个接口组件而不是哪个接口组件。



如今,如果您查看不同的前端工具,您会发现,如果没有特殊的语法(如*ngForAngular的指令),您将无法完成。但是,由于JSX可以称为JavaScript的超集,因此创建JSX标记可以利用现有的JS功能。



例如,您可以使用方法遍历一组元素Array.prototype.map您可以使用逻辑运算符,使用三元运算符组织条件渲染。您可以使用纯函数,也可以使用模板文字构造字符串通常,描述JSX中的接口的人员都可以使用所有JavaScript功能。我认为这是React相对于其他框架和库的巨大优势。



这是一个示例JSX代码:



const ItemList = ({ items }) => (
  <ul>
    {items.map((item) => (
      <li key={item.id}>
        <div>{item.name}</div>
      </li>
    ))}
  </ul>
);


的确,使用JSX时,您需要考虑一些起初看起来并不常见的功能。



  • , , HTML. , class className. camelCase.
  • , , , JSX- key. . id, key.


React并没有向开发人员强加使用CSS的唯一正确方法。例如,您可以通过将具有样式的JavaScript对象写入属性来将其传递给组件style。通过这种方法,大多数熟悉的样式名称将被替换为它们的camelCase等效项。但是,使用样式的可能性不仅限于此。在实践中,我同时使用不同的方法来设计React应用程序。您选择哪种方法取决于您想要的样式。例如,我使用全局样式来设置应用程序主题和页面布局的样式,并使用本地样式来自定义特定组件的外观。



这是我最喜欢的React样式功能:



  • CSS-, . , . — , .
  • CSS- — CSS- . JavaScript-. CSS-, . Next.js, , .
  • 风格-JSX包,它允许你就在你的阵营组件代码申报的风格。这类似于<style>在HTML中使用标记这种样式的范围可以称为“超本地”。关键是样式仅影响应用样式的元素及其子项。使用Next.js时,可以使用styled-jsx包,而无需自己连接和配置某些东西。


综合事件



React为我们提供了一个跨浏览器包装器SyntheticEvents,该包装器代表了综合事件,并旨在统一DOM事件。合成事件之所以有用,有以下几个原因:



  1. , . .
  2. . , , , JavaScript HTML, . . , , React- .
  3. . . , , . , , , . . , . , JavaScript, .


请注意,由于事件池的原因,无法从异步函数访问合成事件的属性。为了实现这种工作方案,您需要从事件对象中获取数据并将其写入到异步函数可访问的变量中。



组件生命周期



React组件的生命周期概念侧重于保护组件的状态。在显示组件时,组件的状态不应更改。这是由于以下工作方案而实现的:组件处于特定状态并被渲染。然后,由于生命周期事件,可以对其应用效果,您可以影响其状态并使用事件。



了解React组件的生命周期对于开发接口非常重要,同时又不与React斗争,而是按照开发人员的意图使用此库。与React的“战斗”,例如错误地更改组件状态或从DOM读取数据,否定了该库的优势。



在React中,从版本0.14开始,有一个基于类的组件描述语法,使您可以处理组件生命周期事件。React组件生命周期中有三个关键阶段:挂载,更新和卸载。





组件生命周期



更新阶段可分为三个部分:渲染(rendering),预提交(Precommit)(准备对DOM树进行更改),提交(commit)(对DOM树进行更改)。





Update阶段的结构



让我们更详细地讨论组件生命周期的以下阶段:



  • Render — . render() , . , JSX.
  • Precommit — DOM, getSnapShotBeforeUpdate. , , .
  • 提交-组件生命周期的此阶段,React更新DOM和refs在这里您可以使用方法componentDidUpdate或钩子useEffect在这里您可以执行效果,安排更新,使用DOM和其他类似任务。


丹·阿布拉莫夫(Dan Abramov)准备了出色的图表,阐明了组件的生命周期机制是如何工作的。





React组件的生命周期



我认为将组件表示为长寿命类并不是最好的React心理模型。请记住,React组件的状态不得突变。过时的状态必须替换为新状态。每次此类替换都会导致组件重新渲染。这可以说是React的最重要,最有价值的功能:支持确定性方法来呈现组件的视觉效果。



最好将这种行为想象为:每次渲染组件时,库都会调用一个确定性函数,该函数返回JSX。此函数不应自行调用自身的副作用。但是,如果她需要的话,她可以将请求传递给React来执行这种效果。



换句话说,将大多数React组件视为具有输入参数并返回JSX的纯函数是有意义的。纯函数具有以下功能:



  • 当给定相同的输入时,它们总是返回相同的输出(它们是确定性的)。
  • 它们没有副作用(也就是说,它们不使用网络资源,不向控制台输出任何内容,不向其中写入任何内容,localStorage等等)。


请注意,如果要使组件正常工作需要副作用,则可以通过使用useEffect或引用通过输入参数传递给组件的动作创建者并允许在组件外部处理副作用执行它们



反应钩



React 16.8引入了一个称为React钩子的新概念。这些功能使您可以连接到组件生命周期事件,而无需使用类语法,也无需依赖组件生命周期方法。结果,有可能创建的组件不是以类的形式,而是以函数的形式。



通常,调用一个钩子意味着一种副作用-一种允许组件以其状态和I / O子系统工作的副作用。副作用是功能外部可见的任何状态更改,但功能返回的值更改除外。



挂钩使用效果使您可以将副作用排队等待以后执行。它们将在组件生命周期的适当时间被调用。这个时间可以立即落入部件安装后(例如,当componentDidMount生命周期方法被调用),在提交阶段(componentDidUpdate方法),就在该部件被卸载(componentWillUnmount)。



请注意,与一个钩子关联的组件生命周期方法共有三种?这里的要点是,挂钩允许您根据组件生命周期的不同方法来组合相关的逻辑,而不是像之前那样“布局”它。



许多组件在挂载时需要执行某些操作,每次重绘该组件时都需要更新某些内容,并且在卸载该组件之前必须立即释放资源以防止内存泄漏。通过使用,useEffect所有这些任务都可以在一个函数中解决,而无需将其解决方案分为3种不同的方法,而无需将其代码与与它们无关的其他任务的代码混合,但也需要这些方法。



这是React钩子给我们的:



  • 它们使您可以创建以函数而不是类表示的组件。
  • 它们可以帮助您更好地组织代码。
  • 它们使在不同组件之间共享相同逻辑变得更加容易。
  • 可以通过组合现有的钩子(从其他钩子调用它们)来创建新的钩子。


通常,我们建议使用功能组件和挂钩,而不是基于类的组件。功能组件通常比基于类的组件更紧凑。他们的代码组织得更好,更易读,更可重用并且更易于测试。



容器组件和演示组件



为了改善组件的模块化及其可重用性,我专注于开发两种类型的组件:



  • 容器组件是连接到数据源的组件,可能会有副作用。
  • 表示bean在大多数情况下是纯豆,在具有相同的道具和上下文的情况下,它们总是返回相同的JSX。


不应将纯组件与基类React.PureComponent混淆,该基类如此命名是因为使用它来创建非纯组件是不安全的。



▍演示组件



考虑演示组件的功能:



  • 它们不与网络资源交互。
  • 它们不会将数据保存到localStorage那里或从那里加载。
  • 他们不会给出一些不可预测的数据。
  • 它们不直接引用当前系统时间(例如,通过调用method Date.now())。
  • 它们不直接与应用程序状态存储交互。
  • - , , , .


正是由于我在谈到展示组件时提到的该列表的最后一项,这些组件大部分都是纯组件。这些组件从全局React状态读取它们的状态。因此,像钩子一样useStateuseReducer为它们提供一定的隐式数据(即,签名函数中未描述的数据),从技术的角度来看,这些隐式数据不能将此类组件称为“干净的”。如果您需要它们是真正干净的,可以将管理状态的所有任务委托给容器组件,但是我想您不应这样做,至少直到可以使用模块化验证组件的正确操作为止测试。



最好的敌人的好。



伏尔泰




▍容器组件



容器组件是负责管理状态,执行I / O操作或可能产生副作用的任何其他任务的那些组件。他们不必自己渲染任何标记。相反,它们将渲染任务委托给表示组件,并且它们本身充当此类组件的包装。通常,React + Redux应用程序中的容器组件只是调用mapStateToProps()mapDispatchToProps()然后将适当的数据传递给表示组件。容器也可以用于一些常规任务,我们将在下面讨论。



高阶分量



高阶组件(HOC)是采用其他组件并返回一个新组件的新组件,该组件基于原始组件实现新功能。



高阶组件通过将某些组件与其他组件包装在一起而起作用。包装器组件可以实现一些逻辑并创建DOM元素。它可能会也可能不会将其他道具传递给包裹的组件。



与React钩子和渲染道具不同,高阶组件使用标准的功能组合方法进行组合。这使您可以声明性地描述打算在应用程序的不同位置使用的功能组合的结果。同时,现成的组件不应意识到某些可能性的存在。这是EricElliottJS.com提供的HOC示例



import { compose } from 'lodash/fp';
import withFeatures from './with-features';
import withEnv from './with-env';
import withLoader from './with-loader';
import withCoupon from './with-coupon';
import withLayout from './with-layout';
import withAuth from './with-auth';
import { withRouter } from 'next/router';
import withMagicLink from '../features/ethereum-authentication/with-magic-link';

export default compose(
  withEnv,
  withAuth,
  withLoader,
  withLayout({ showFooter: true }),
  withFeatures,
  withRouter,
  withCoupon,
  withMagicLink,
);


这里显示的是站点上所有页面共享的许多功能的混合体。也就是说,它withEnv从环境变量中读取设置,withAuth实现GitHub身份验证机制,withLoader在加载用户数据时, withLayout({ showFooter: true })显示动画,显示带有页脚的标准布局,withFeature显示设置,withRouter加载路由器,withCoupon负责处理优惠券,并withMagicLing支持用户身份验证而无需使用密码魔术



顺便说一句,鉴于密码认证已经过时并且是一种危险的做法,因此如今值得使用其他用户认证方法。



上述站点上的几乎所有页面都利用了所有这些功能。鉴于它们是由一个高阶组件组合而成的,因此只需一行代码即可将它们全部包含在一个容器组件中。例如,以下是教程页面的外观:



import LessonPage from '../features/lesson-pages/lesson-page.js';
import pageHOC from '../hocs/page-hoc.js';
export default pageHOC(LessonPage);


这些高阶组件有一个替代方案,但这是一个可疑的构造,称为“厄运金字塔”,最好不要使用。看起来是这样的:



import FeatureProvider from '../providers/feature-provider';
import EnvProvider from '../providers/env-provider';
import LoaderProvider from '../providers/loader-provider';
import CouponProvider from '../providers/coupon-provider';
import LayoutProvider from '../providers/layout-provider';
import AuthProvider from '../providers/auth-provider';
import RouterProvider from '../providers/RouterProvider';
import MagicLinkProvider from '../providers/magic-link-provider';
import PageComponent from './page-container';

const WrappedComponent = (...props) => (
  <EnvProvider { ...props }>
    <AuthProvider>
      <LoaderProvider>
        <LayoutProvider showFooter={ true }>
          <FeatureProvider>
            <RouterProvider>
              <CouponProvider>
                <MagicLinkProvider>
                  <YourPageComponent />
                </MagicLinkProvider>
              </CouponProvider>
            </RouterProvider>
          </FeatureProvider>
        </LayoutProvider>
      </LoaderProvider>
    </AuthProvider>
  </EnvProvider>
);


这将必须在每页上重复。而且,如果需要在此结构中进行某些更改,则无论存在于何处,都必须对其进行更改。我认为这种方法的缺点非常明显。



使用组合来解决一般问题是降低应用程序代码复杂性的最佳方法之一。作文非常重要,我甚至写了一关于它的



结果



  • 为什么要反应?React为我们提供了组件可视化表示的确定性渲染,该渲染基于单向数据绑定和组件的不变状态。
  • JSX使我们能够轻松地以声明方式描述JavaScript代码中的接口。
  • - .
  • . , . , DOM DOM.
  • React , . , , .
  • - . - .
  • , . ( , ).


?



在这篇React文章中,我们涵盖了许多功能编程概念。如果你追求的阵营应用开发的原则有深刻的理解,这将是有益的给你刷上你的关于知识的纯函数,约不变性,约柯里和功能的部分应用程序,有关函数组成。您可以在EricElliottJS.com上找到相关材料



我建议将React与ReduxRedux-SagaRITEway结合使用。建议将Redux与AutoduxImmer一起使用... 要组织复杂的状态方案,可以尝试使用Redux-DSM



当您掌握基础知识并准备好构建真正的React应用程序时,请查看Next.jsVercel这些工具将帮助自动化项目构建系统和CI / CD管道的配置,借助它们,您可以为在服务器上优化部署准备项目。它们具有与整个DevOps团队相同的作用,但完全可以免费使用。



在开发React应用程序时,您使用什么辅助工具?










All Articles