作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
卡米尔Jakubczak的头像

卡米尔Jakubczak

Kamil是一个React Native, JavaScript和 .最近一直专注于React Native的。NET粉丝. 他在招聘方面也很有经验.

工作经验

10

Share

正如文章中所指出的 这是React教程的第一部分在美国,入门React相对容易. 首先使用Create React App (CRA),初始化一个新项目,然后开始开发. Sadly, over time, 您可能会遇到这样的情况:您的代码将变得相当难以维护, 特别是如果你是React的新手. 组件可能会变得不必要地大,或者您可能最终得到可能是组件但不是组件的元素, 所以你可以在这里和那里写重复的代码.

这就是你应该尝试真正开始你的React之旅的地方——开始思考 React开发解决方案.

每当你接触一个新的应用程序, 一个新的设计,你需要稍后转换成一个React应用程序, 首先试着决定你的草图中会有哪些组件, 如何将草图分开,使它们更容易管理, 哪些元素是重复的(或它们的行为), at least). 尽量避免添加可能“在将来有用”的代码——这可能很诱人, 但是,这种未来可能永远不会到来,您将保留具有大量可配置选项的额外通用函数/组件.

React教程:React组件的插图

Also, 如果组件长度大于, 比方说2-3个窗高, 也许它值得分开(如果可能的话)——因为这样以后更容易阅读.

控制与. React中未控制的组件

在大多数应用中, 需要输入和与用户进行某种形式的交互, 允许他们输入一些东西, 上传文件, 选择一个字段, and so on. React以两种不同的方式处理用户交互controlled and uncontrolled components.

被控制组件的值, 顾名思义, 是由React通过向与用户交互的元素提供一个值来控制的, 而非受控元素则没有value属性. 多亏了这个, 我们只有一个真实的来源,那就是React状态, 因此,我们在屏幕上看到的和我们目前所处的状态之间并不存在不匹配. 开发人员需要传递一个函数,该函数将响应用户与表单的交互, 哪个会改变它的状态.

类ControlledInput扩展React.Component {
 state = {
   value: ""
 };

 onChange = (e) => this.setState({value:值.target.value });

 render() {
   return (
     
   );
 }
}

在不受控制的React组件中, 我们不关心值如何变化, 但是如果我们想知道确切的值, 我们只是通过ref访问它.

UncontrolledInput类扩展React.Component {
 input = React.createRef();

 getValue = () => {
   console.log(this.input.current.value);
 };

 render() {
   return (
     
   );
 }
}

那么什么时候应该用? 我想说,在大多数情况下,控制组件是可行的, 但也有一些例外. 例如,需要在React中使用非受控组件的一种情况是 file type input, 因为它的值是只读的,不能通过编程方式设置(需要用户交互)。. 此外,我发现受控组件更容易阅读和更容易使用. 对受控组件进行验证是基于呈现的, 状态可以改变, 我们可以很容易地指出输入(e)有问题.g.,格式或为空).

Refs

我们已经提到过 refs,这是在类组件中可用的特殊功能,直到16年出现钩子.8.

ref可以让开发人员通过引用访问React组件或DOM元素(取决于我们附加ref的类型). 最好的做法是尽量避免使用它们,并且只在必须的场景中使用它们, 因为它们使代码更难阅读,并且破坏了从上到下的数据流. 然而,在某些情况下,它们是必要的,特别是在DOM元素上(例如.g.,以编程方式改变焦点). 当附加到React组件元素时, 您可以在所引用的组件中自由地使用方法. 尽管如此,这种做法应该避免,因为有更好的方法来处理它.g.(提升状态并将函数移动到父组件).

Refs也有三种不同的实现方式:

  • 使用字符串字面值(应该避免使用),
  • 使用在ref属性中设置的回调函数,
  • 通过创建ref React.createRef() 并将其绑定到类属性并通过它访问它(注意,引用将从componentDidMount生命周期中可用).

Finally, 在某些情况下,引用没有向下传递,并且当您希望从当前组件访问更深层次的引用元素时(例如.g., you have a

); };

调用它的方法是 initialState (value)并返回一个包含两个元素的数组. 由于数组解构赋值,我们可以立即将变量赋值给这些元素. 第一个总是更新后的最后一个状态, 而另一个是一个函数,我们将使用它来更新值. 看起来很简单,不是吗?

Also, 因为这样的组件过去被称为无状态功能组件, 这样的名字已经不合适了, 因为它们可以有一个如上所示的状态. 因此,这些名字 类组件 and 功能组件 似乎更符合他们的实际行为,至少从16岁开始.8.0.

更新函数(在本例中为 setCounter), 也可以作为一个函数使用,该函数将以以下形式将前一个值作为参数:



然而,与 this.setState 类组件正在进行浅合并,将函数(setCounter 在我们的例子中)重写了整个状态.

In addition, initialState 也可以是一个函数,而不仅仅是一个普通的值. 这有它自己的好处, 因为该函数只会在组件的初始呈现期间和之后运行, 它将不再被调用.

const [counter, setCounter] = useState(() =>  calculateComplexInitialValue());

最后,如果我们要用 setCounter 与我们在当前状态下的同一时刻拥有的值完全相同(counter),则组件 will not rerender.

另一方面, useEffect 是在功能组件中添加副作用吗, 无论是订阅, API calls, timers, 或者是任何我们觉得有用的东西. 我们要传递给的任何函数 useEffect 会在渲染后运行吗, 它将在每次渲染后这样做,除非我们添加一个限制,关于什么属性的变化应该作为函数的第二个参数重新运行. 如果我们只想在mount时运行它,在unmount时进行清理, 然后我们只需要向它传递一个空数组.

const fetchApi = async () => {
 Const value = await fetch("http://jsonplaceholder . value ").typicode.com/todos/1”);
 console.日志(等待的价值.json());
};

导出函数Counter() {
 const [counter, setCounter] = useState(0);
 useEffect(() => {
   fetchApi();
 }, []);


 return (
   
{counter}
); };

上面的代码将只运行一次,因为第二个参数是空数组. 基本上,它是这样的 componentDidMount 在这种情况下,它会晚一点触发. 如果您希望在浏览器绘制之前调用类似的钩子,请使用 useLayoutEffect,但这些更新将同步应用,不像 useEffect.

useContext 这似乎是最容易理解的, 方法返回的一个对象提供了我们想要访问的上下文 createContext 函数),作为回报,它为我们提供该上下文的值.

const context = useContext(context);

最后,要编写你自己的钩子,你可以这样写:

函数useWindowWidth() {
 let [windowWidth, setWindowWidth] = useState(窗口宽度.innerWidth);

 函数handleResize() {
   setWindowWidth(窗口.innerWidth);
 }

 useEffect(() => {
   window.addEventListener(“调整”,handleResize);
   return () => window.removeEventListener(“调整”,handleResize);
 }, []);

 返回windowWidth;
}

基本上,我们用的是普通的 useState 我们将其赋值为初始值窗口宽度的钩子. Then in useEffect, 我们正在添加一个将触发的侦听器 handleResize 在每个窗口上调整大小. 我们也清除组件将卸载后(查看返回在 useEffect). Easy?

Note: The word use 在所有的钩是重要的. 使用它是因为它允许React检查您是否没有做坏事.g.,从常规JS函数调用钩子.

检查类型

在Flow和TypeScript成为选项之前,React有自己的道具检查功能.

PropTypes检查React组件是否接收到属性(props),并检查它们是否与我们所拥有的一致. 每当不同的情况发生时(e).g.(对象而不是数组),我们将在控制台中得到一个警告. 需要注意的是,由于PropTypes对性能的影响和前面提到的控制台警告,所以只有在开发模式下才会检查PropTypes.

从React 15开始.5、PropTypes在不同的包中,需要单独安装. 它们是与静态属性一起声明的 propTypes (惊讶),将它们与 defaultProps 如果属性未定义(undefined 是唯一的情况吗?. DefaultProps与PropTypes无关, 但它们可以解决一些可能由于PropTypes而出现的警告.

另外两个选项是Flow和TypeScript, 它们现在更流行了(尤其是TypeScript).

  • TypeScript 是JavaScript的类型化超集, 微软开发的, 它可以在应用程序运行之前检查错误,并为开发提供卓越的自动完成功能. 它还极大地改进了重构. 由于微软的支持, 谁在类型语言方面有丰富的经验, 这也是一个相当安全的选择.
  • Flow 不是一种语言,不像TypeScript. 它是JavaScript的静态类型检查器, 所以它更像是JavaScript中的一个工具,而不是一门语言. Flow背后的整个思想与TypeScript提供的非常相似. 它允许您添加类型,因此在运行代码之前不太可能出现任何错误. 就像TypeScript一样,Flow现在从一开始就被CRA (Create React App)支持.

Personally, 我发现TypeScript更快(几乎是即时的), 特别是在自动补全方面, 这在Flow中似乎有点慢. 值得注意的是,WebStorm等ide, 我个人使用的, 使用CLI与Flow集成. 然而,在文件中集成可选使用似乎更容易,只需添加 // @flow 在文件的开头开始类型检查. Also, 据我所知, 看起来TypeScript最终战胜了Flow——它现在更受欢迎了, 一些最流行的库正在从Flow重构到TypeScript.

还有更多的选择, 在官方文件中也有提及, 比如Reason(由Facebook开发,在React社区中越来越受欢迎), Kotlin(一种由JetBrains开发的语言)等等.

Obviously, 前端开发人员, 最简单的方法就是开始使用Flow和TypeScript, 而不是切换到Kotlin或f#. However, 对于正在过渡到前端的后端开发人员, 这些可能更容易上手.

生产和反应性能

对于生产模式,您需要做的最基本和最明显的更改是切换到“生产”模式 DefinePlugin and add UglifyJsPlugin 以Webpack为例. 在CRA的情况下,它就像使用一样简单 NPM运行构建 (也就是跑步 react-scripts构建). 请注意,Webpack和CRA不是唯一的选择,因为您可以使用其他构建工具,如Brunch. 这通常包含在官方文档中, 无论是官方的React文档还是特定工具的文档. 要确保模式设置正确,可以使用 React开发工具,这将给你一个指示,你正在使用哪种类型的构建(生产vs. development). 上述步骤将使您的应用程序在没有来自React的检查和警告的情况下运行,并且bundle本身将被最小化, too.

你还可以为你的React应用做更多的事情. 如何处理构建的JS文件? 你可以从“bundle”开始.如果尺寸相对较小,则为Js, 或者做一些类似于“供应商+包”或者“供应商+最小的必需部分+在需要的时候导入东西”的事情.“当你在处理一个非常大的应用程序时,这很有用,你不需要一开始就导入所有内容. 请注意,在主包中捆绑一些甚至不被使用的JavaScript代码只会增加包的大小,并使应用程序在一开始加载得更慢.

如果你意识到库的版本可能在很长一段时间内(如果有的话)不会改变,而打算冻结这些库的版本,供应商捆绑包可能会很有用。. Also, 更大的文件更适合gzip,所以你从分离中获得的好处有时可能不值得. 这取决于文件大小,有时你只需要自己尝试一下.

代码分离

代码分割可以以比这里建议的更多的方式出现, 但让我们把重点放在CRA和React本身可用的东西上. 基本上,为了将代码分成不同的块,我们可以使用 import() 这要感谢Webpack (import 到目前为止,它本身只是第三阶段的一个提案,所以它还不是语言标准的一部分)。. 每当Webpack看到 import, 它将知道它需要在这个阶段开始代码分割,并且不能将其包含在主包中(它在导入中的代码)。.

现在我们可以把它和 React.lazy() 这需要 import() 使用包含需要在该位置呈现的组件的文件路径. 接下来,我们可以用 React.suspense() 在导入的组件被加载之前,哪个组件会在该位置显示不同的组件. One might wonder; if we are importing a single component, then why we would need it?

但事实并非如此 React.lazy() 会显示我们的组件吗 import(), but import() 可能会获取比单个组件更大的数据块. 例如,该特定组件可能包含其他库、更多代码等.,所以一个文件不是必需的,它可能是捆绑在一起的更多文件. 最后,我们可以把所有这些包起来 ErrorBoundary (您可以在错误边界一节中找到代码) 如果我们想要导入的组件出现故障(e.g.,如果有网络错误).

import ErrorBoundary from './ ErrorBoundary”;

const ComponentOne = React.lazy(() => import('./ ComponentOne '));

MyComponent() {
   return (
       
           Loading...
}> ); }

这是一个基本的例子,但显然你可以做更多. You can use import and React.lazy 对于动态路由拆分(e.g., admin vs. 普通用户,或者只是真正的大路径,带来很多). 请注意 React.lazy 目前只支持默认导出,不支持服务器端呈现.

React代码性能

关于性能, 如果你的React应用程序运行滞后, 有两个工具可以帮助您解决这个问题.

第一个是 Chrome性能标签,它会告诉你每个分量(e.g., mount, update). 多亏了这个, 您应该能够确定哪个组件出现了性能问题,然后对其进行优化.

另一个选择是使用 DevTools分析器 在React 16中可用.5+,并与shouldComponentUpdate(或PureComponent)合作,这在 本教程的第一部分),我们可以提高某些关键组件的性能.

显然,采用基本的web最佳实践是最优的,例如公开某些事件(例如.g., scroll), 小心使用动画(使用变换而不是改变高度和动画)等等. 使用最佳实践很容易被忽视, 特别是如果你刚刚开始掌握React.

2019年及以后的React状态

如果我们要讨论React的未来,我个人不会太担心. 在我看来,React在2019年及以后将毫无疑问地保持其宝座.

React拥有如此强大的地位, 由一个庞大的社区支持, 很难推翻他的王位. React社区很棒, 它并没有耗尽创意, 核心团队也在不断改进React, 添加新功能和修复旧问题. React也得到了一家大公司的支持, 但是授权问题已经解决了——现在是麻省理工学院授权的.

Yes, there are a few things that are expected to be changed or improved; for instance, 让React更小一点(其中一个提到的措施是删除合成事件)或重命名 className to class. Of course, 即使这些看似微小的变化也可能导致诸如影响浏览器兼容性之类的问题. Personally, 我也想知道当WebComponent变得更受欢迎时会发生什么, 因为它可能会增强React今天经常使用的一些东西. 我不相信他们会完全取代, 但我相信它们可以很好地互补.

至于短期,hook只是出现在React中. 这可能是自React重写以来最大的变化, 因为它们将开辟许多可能性,并进一步增强功能组件(它们现在真的被炒作了)。.

最后,这也是我最近一直在做的事情 React Native. For me, 这是一项伟大的技术,在过去几年中发生了很大的变化(对大多数人来说,缺乏原生响应链接可能是最大的问题), 显然有很多bug). React Native的核心正在被重写, 这应该以类似于React重写的方式完成(都是内部的), 开发者不应该做出任何改变。). 异步渲染,本地和JavaScript之间更快、更轻量的桥梁,等等.

React生态系统中有很多值得期待的东西, 但是hooks(以及React Native,如果有人喜欢手机的话)的更新可能是我们在2019年看到的最重要的变化.

了解基本知识

  • 什么是React钩子?

    钩子是让你从函数组件中“钩入”React状态和生命周期特性的函数.

  • 他们有什么值得记住的呢?

    它们应该以“use”开头,并且只能从React组件函数中调用. 您也可以创建自己的钩子!

  • 不受控制的组件有意义吗?

    在某些情况下,是的. 上传文件, 它们是必要的, 但大多数情况下, 受控组件更容易使用.

  • 所有这些都能在React Native中使用吗?

    Mostly. First of all, 与分割相关的性能在RN中不存在(它可以完成,但不是开箱-它需要相当黑,因为Metro还不能处理这样的东西). 其他的都适用于RN.

  • 我应该使用哪个ref?

    不要使用旧版本. 首选的方法是使用createRef,但是回调也可以.

  • 钩子、HOC或渲染道具?

    如果有些事可以用钩子来解决,我就用钩子. 它们更容易使用(至少对我来说),因为它们似乎更容易与不同的组件共享,而且它们不会在渲染道具的情况下带来厄运金字塔,或者在hoc中“这是从哪里来的”.

  • 为什么人们如此期待钩子?

    它们可以解决我们之前使用hoc和渲染道具模式时遇到的一些代码共享问题,这些问题有时会让代码变得有点复杂.

就这一主题咨询作者或专家.
预约电话
卡米尔Jakubczak的头像
卡米尔Jakubczak

Located in Poznań,波兰

Member since 2017.9.18

作者简介

Kamil是一个React Native, JavaScript和 .最近一直专注于React Native的。NET粉丝. 他在招聘方面也很有经验.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

工作经验

10

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® community.