翻译:疯狂的技术宅
原文: https://www.toptal.com/react/...
本文首发微信公众号:jingchengyideng
欢迎关注,天天都给你推送新鲜的前端技术文章javascript
正如 咱们的React教程的第一部分 中所指出的,开始使用 React 相对容易。首先使用 Create React App(CRA)初始化一个新项目,而后开始开发。不过遗憾的是,随着时间的推移,代码可能会变得难以维护,特别是在你不熟悉 React 的状况下。组件有可能会变大,或者你可能最终获得一堆不是组件的组件,最终你可能会处处编写重复的代码。css
这时候你就应该试着开始真正的 React 之旅了 —— Think in React。html
每当开发一个新的程序时,你须要为其作好在之后转换为 React 应用的新设计,首先试着肯定设计草图中的组件,如何分离它们以使其更易于管理,以及哪些元素是重复的(或他们的行为)。尽可能避免添加可能“未来有用”的代码 —— 虽然这很诱人,但可能将来永远也不会到来,你将留下一堆具备大量可配置选项的多余通用功能/组件。前端
此外,若是一个组件大于 2 到 3 个窗口的高度,也许值得分离(若是可能的话) —— 之后更容易阅读。java
在大多数应用中,须要输入和与用户进行某种形式的交互,容许他们输入内容、上传文件、选择字段等。 React 用两种不一样的方式处理用户交互 —— 受控和非受控组件。react
顾名思义,受控组件的值由 React 控制,能为与用户交互的元素提供值,而不受控制的元素不获取值属性。多亏了这一点,咱们才能把 React 状态做为单一的事实来源,所以咱们在屏幕上看到的与当前拥有的状态是一致的。开发人员须要传递一个函数,该函数用来响应用户与表单的交互,这将会改变它的状态。git
class ControlledInput extends React.Component { state = { value: "" }; onChange = (e) => this.setState({ value: e.target.value }); render() { return ( <input value={this.state.value} onChange={this.onChange}/> ); } }
在 React 的非受控组件中,咱们不关心值的变化状况,若是想要知道其确切的值,只需经过 ref 访问它。程序员
class UncontrolledInput extends React.Component { input = React.createRef(); getValue = () => { console.log(this.input.current.value); }; render() { return ( <input ref={this.input}/> ); } }
那么应该怎么选择呢?在大数状况下用受控组件是可行的,不过也有一些例外。例如使用非受控制组件的一种状况是 file
类型输入,由于它的值是只读的,不能在编码中去设置(须要用户交互)。另外我发现受控组件更容易理解和于使用。对受控组件的验证是基于从新渲染的,状态能够更改,而且能够很轻松的显示输入中存在的问题(例如格式错误或者输入为空)。github
在前面咱们提到过 refs
,这是一个特殊功能,能够在类组件中使用,直到 16.8 中出现了 hooks。web
refs 能够经过引用让开发人员访问 React 组件或DOM元素(取决于咱们附加 ref 的类型)。最好仅在必须的场景中使用它们,由于它们会使代码难以阅读,并打破从上到下的数据流。然而,有些状况下它们是必要的,特别是在DOM元素上(例如:用编码方式改变焦点)。附加到 React 组件元素时,你能够自由使用所引用的组件中的方法。不过仍是应该避免这种作法,由于有更好的方法来处理它(例如,提高状态并将功能移动到父组件)。
refs 还能够作到:
React.createRef()
,并将其绑定到类属性,并经过它去访问(请注意,在 componentDidMount 生命周期中将提供引用)。没有传递引用的一种状况是当在组件上使用高阶组件时 —— 缘由是能够理解的,由于 ref
不是 prop
(相似于 key
)因此它没有被传递下来,而且它将引用 HOC
而不是被它包裹的组件。在这种状况下,咱们可使用React.forwardRef
,它把 props 和 ref 做为参数,而后能够将其分配给 prop
并传递给咱们想要访问的组件。
function withNewReference(Component) { class Hoc extends React.Component { render() { const {forwardedRef, ...props} = this.props; return <Component ref={forwardedRef} {...props}/>; } } return React.forwardRef((props, ref) => { return <Hoc {...props} forwardedRef={ref} />; }); }
事情越复杂,出现问题的几率就越高。这就是为何 React 中会有错误边界。那他们是怎么工做的呢?
若是出现问题而且没有错误边界做为其父级,则会致使整个React 应用失败。不显示信息比误导用户并显示错误信息要好,但这并不意味着你应该听任整个应用崩溃并显示白屏。经过错误边界,能够获得更多的灵活性。你能够在整个应用程序中使用并显示一个错误消息,或者在某些小部件中使用它可是不显示,或者显示少许信息来代替这些小部件。
请记住,它仅涉及声明性代码的问题,而不是你为了处理某些事件或者调用而编写的命令式代码。对于这些状况,你仍应使用常规的 try/catch 方法。
在错误边界也能够将信息发送到你使用的 Error Logger (在 componentDidCatch
生命周期方法中)。
class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, info) { logToErrorLogger(error, info); } render() { if (this.state.hasError) { return <div>Help, something went wrong.</div>; } return this.props.children; } }
高阶组件(HOC)常常在 React 中被说起,这是一种很是流行的模式,你可能会用到它(或者已经在用了)。若是你熟悉 HOC,可能已经在不少库中看到过 withNavigation,connect,withRouter
。
HOC 只是一种把组件做为参数的函数,而且与没有 HOC 包装器的组件相比,可以返回具备扩展功能的新组件。多亏了这一点,你能够实现一些易于扩展的功能,以此加强本身的组件(例如:访问导航)。 HOC 也有一些其它形式的调用方式,这取决于咱们当前拥有什么,惟一的参数必需要传入一个组件,但它也能够接受额外的参数 —— 一些选项,或者像在 connect
中同样,首先使用configurations调用一个函数,该函数稍后返回一个带参组件,并返回 HOC 。
如下是一些你应该作的和要避免作的事情:
为包装器 HOC 函数添加显示名称(这样你就能知道它究竟是干什么用的,其实是经过更改 HOC 组件显示名称来作到)。
React.forwardRef
来解决这些问题。export function importantHoc() { return (Component) => class extends React.Component { importantFunction = () => { console.log("Very Important Function"); }; render() { return ( <Component {...this.props} importantFunction={this.importantFunction} /> ); } }; }
样式不必定与 React 自己有关,但出于各类缘由仍是值得一提的。
首先,常规 CSS/内联样式在这里可以正常应用,你只需在 className 属性中添加 CSS 中的类名,它就能正常工做。内联样式与常规 HTML 样式略有不一样。样式属性也是使用驼峰命名法,所以 border-radius 会变成 borderRadius 。
React 彷佛推广了一些不只在 React 中变得广泛的解决方案,例如最近集成在 CRA 中的 CSS 模块,你能够在其中简单地导入 name.modules.css
并用其属性来调整组件的样式(某些IDE(例如WebStorm)也具备自动完成功能,能告诉你可用的名称。
在 React 中另外一个流行的解决方案是 CSS-in-JS(例如,emotion
库)。再说一点,CSS 模块和 emotion(或者通常来讲是CSS-in-JS)对 React 没有限制。
自重写以来,Hooks 极可能是 React 最受热切期待的补充。这个产品是否能不负众望?从个人角度来看,是的,由于它确实是一个很棒的功能。它们本质上是带来了新的体验,例如:
class
组件,这些组件咱们仅仅是使用而不归咱们拥有,例如本地状态或 ref,因此组件的代码看上去更容易阅读。useEffect
中的 setState
被 useState
使用)。可能会删除 HOC 并在你的应用中渲染 props ,尽管 hook 被设计用于解决其余问题,但仍会引入新问题。
默认的 React hook 不多。其中三个基本的hook是 useState
,useEffect
和 useContext
。还有一些其它的,例如 useRef
和 useMemo
,不过如今咱们把重点放在基础知识上。
先看一下 useState
,让咱们用它来建立一个简单的计数器的。它是如何工做的?基本上整个结构很是简单:
export function Counter() { const [counter, setCounter] = React.useState(0); return ( <div> {counter} <button onClick={() => setCounter(counter + 1)}>+</button> </div> ); };
它用 initialState
(值)调用,并返回一个带有两个元素的数组。因为数组解构分配,咱们能够当即将变量分配给这些元素。第一个是更新后的最后一个状态,而另外一个是咱们将用于更新值的函数。看起来至关容易,不是吗?
此外,因为这些组件曾经被称为无状态功能组件,如今这种名称再也不适用,由于它们能够具备如上所示的状态。因此叫类组件和函数组件彷佛更符合它们的实际操做,至少从16.8.0开始。
更新函数(在咱们的例子中是setCounter
)也能够用做一个函数,它将之前的值做为参数,格式以下:
<button onClick={() => setCounter(prevCounter => prevCounter + 1)}>+</button> <button onClick={() => setCounter(prevCounter => prevCounter - 1)}>-</button>
与执行浅合并的this.setState
类组件不一样,设置函数(在咱们的例子中为 setCounter
)会覆盖整个状态。
另外,initialState
也能够是一个函数,而不只仅是一个普通的值。这有其自身的好处,由于该函数将会只在组件的初始渲染期间运行,以后将再也不被调用。
const [counter, setCounter] = useState(() => calculateComplexInitialValue());
最后,若是咱们要使用 setCounter
与在当前状态(counter
)的同一时刻彻底相同的值,那么组件 将不会 从新渲染。
另外一方面,useEffect
为咱们的功能组件添加反作用,不管是订阅、API调用、计时器、仍是任何咱们认为有用的东西。咱们传给 useEffect
的任何函数都将在 render 以后运行,而且是在每次渲染以后执行,除非咱们添加一个限制,把应该从新运行时须要更改的属性做为函数的第二个参数。若是咱们只想在 mount 上运行它并在unmount 上清理,那么只须要在其中传递一个空数组。
const fetchApi = async () => { const value = await fetch("https://jsonplaceholder.typicode.com/todos/1"); console.log(await value.json()); }; export function Counter() { const [counter, setCounter] = useState(0); useEffect(() => { fetchApi(); }, []); return ( <div> {counter} <button onClick={() => setCounter(prevCounter => prevCounter + 1)}>+</button> <button onClick={() => setCounter(prevCounter => prevCounter - 1)}>-</button> </div> ); };
因为把空数组做为第二个参数,因此上面的代码只运行一次。在这种状况下它相似于 componentDidMount
,但稍后会触发它。若是你想在浏览器处理以前调用一个相似的 hook,能够用 useLayoutEffect
,但这些更新将会被同步应用,这一点与 useEffect
不一样。
useContext
彷佛是最容易理解的,由于咱们提供了想要访问的上下文(由 createContext
函数返回的对象提供),而它为咱们提供了该上下文的值。
const context = useContext(Context);
最后,要编写本身的hook,你能够像这样写:
function useWindowWidth() { let [windowWidth, setWindowWidth] = useState(window.innerWidth); function handleResize() { setWindowWidth(window.innerWidth); } useEffect(() => { window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return windowWidth; }
基本上,咱们使用常规的 useState
hook,咱们将其指定为窗口宽度的初始值,而后在 useEffect
中添加一个监听器,它将在窗口调整大小时触发 handleResize
。在组件被卸载后会咱们会及时知道(查看 useEffect
中的返回值)。是否是很简单?
注意: use 在 hook 中很重要。之因此使用它,是由于它容许 React 检查你是否作了很差的事情,例如从常规JS函数调用hook。
在支持 Flow 和 TypeScript 以前,React有本身的属性检查机制。
PropTypes 检查 React 组件接收的属性(props)是否与咱们的内容一致。若是一致(例如:应该是对象而不是数组),将会在控制台中收到警告。请务必注意:PropTypes 仅在开发模式下进行检查,由于它们会影响性能并在控制台中显示上述警告。
从React 15.5开始,PropTypes 被放到了不一样的包里,须要单独安装。它在名为 propTypes
(surprise)的静态属性中对属性进行声明,能够把它与 defaultProps
结合使用,若是属性未定义就会使用它们(undefined是惟一的状况)。 DefaultProps 与 PropTypes 无关,不过它们能够解决因为 PropTypes 而可能出现的一些警告。
另外两个选择是 Flow 和 TypeScript,它们如今更受欢迎(特别是 TypeScript )。
我发现 TypeScript 更快(几乎是即时的),特别是在自动完成中,Flow 彷佛有点慢。值得注意的是,我本身用的 WebStorm 等 IDE 使用 CLI 与 Flow 集成。可是在文件中集成可选用法彷佛更容易,只须要在文件开头添加 // @flow
就可进行类型检查。另外据我所知,彷佛 TypeScript 最终赢得了与 Flow 的战斗 —— 它如今更受欢迎,而且一些最流行的库正在从 Flow 转向 TypeScript。
官方文档中还提到了更多的选择,例如 Reason(由Facebook开发并在React社区中得到普及),Kotlin(由JetBrains开发的语言)等等。
显然,对于前端开发人员来讲,最简单的方法是使用 Flow 和 TypeScript,而不是切换到 Kotlin 或F#。可是,对于正在转型到前端的后端开发人员来讲,这可能更容易入手。
对于生产模式,你须要作的最基本和明显的改变是:把 DefinePlugin
切换到 “production”,并在Webpack的状况下添加UglifyJsPlugin
。在使用 CRA 的状况下,它就像使用 npm run build
(将运行react-scripts build
)同样简单。请注意,Webpack 和 CRA 不是惟一的选项,由于你可使用其余构建工具,如 Brunch。这一般包含在官方文档中,不管是官方的 React 文档仍是特定工具的文档。要确保模式设置正确,你可使用React Developer Tools,它会告诉你正在用的那种构建(生产与开发)模式应该怎么配置。上述步骤会使你的应用在没有来自 React 的检查和警告的状况下运行,而且 bundle 自己也将被最小化。
你还能够为 React 应用作更多的事。你如何处理构建的 JS 文件?若是尺寸相对较小,你能够从 “bundle.js” 开始,或者作一些相似 “vendor + bundle” 或者 “vendor + 最小化须要部件 + 在须要时导入东西” 之类的处理。当你是处理一个很是大的应用时,不须要在一开始就导入全部内容。请注意,在主 bundle 中去 bundling 一些不会被使用的 JavaScript 代码只会增长 bundle 包的大小,并会使应用在启动时的加载速度变慢。
若是你计划冻结库的版本,并认为它们可能长时间内不会被更改,那么 Vendor bundles 可能颇有用。此外,更大的文件更适合用 gzipping,所以从拆分得到的好处有时可能不值得。这取决于文件大小,有时你须要本身去尝试。
代码拆分的方式比这里给出的建议多得多,但让咱们关注 CRA 和 React 自己可用的内容。基本上,为了将代码分红不一样的块,可使用 import()
,这能够用 Webpack 支持( import
自己是第3阶段的提案,因此它还不是语言标准的一部分)。每当 Webpack 看到 import
时,它就会知道须要在这个阶段开始拆分代码,而且不能将它包含在主包中(它在import中的代码)。
如今咱们能够将它与 React.lazy()
链接起来,它须要 import()
一个文件路径,其中包含须要在那个地方渲染的组件。接下来,咱们能够用 React.suspense()
,它会在该位置显示不一样的组件,一直到导入的组件所有加载完毕。有人可能会想,若是我要导入单个组件,是否是就不须要它了呢?
实际上并不是如此,由于 React.lazy()
将显示咱们 import()
的组件,但 import()
可能会获取比单个组件更大的块。例如这个组件可能包含其余库,或更多代码,因此不仅是须要一个文件 —— 它多是绑在一块儿的多个文件。最后,咱们能够将全部这些包装在 ErrorBoundary
中(你能够在本文关于错误边界的那部分中找到代码) 若是某些内容因咱们想要导入的组件而失败(例如出现网络错误),这将做为备用方案。
import ErrorBoundary from './ErrorBoundary'; const ComponentOne = React.lazy(() => import('./ComponentOne')); function MyComponent() { return ( <ErrorBoundary> <React.Suspense fallback={<div>Loading...</div>}> <ComponentOne/> </React.Suspense> </ErrorBoundary> ); }
这是一个简单的例子,但显然你能够作得更多。你可使用 import
和 React.lazy
进行动态路由划分(例如:管理员与常规用户)。请注意,React.lazy
仅支持默认导出,而且不支持服务器端呈现。
关于性能,若是你的 React 应用运行缓慢,有两种工具能够帮助你找出问题。
第一个是 Chrome Performance Tab,它会告诉你每一个组件会发生什么(例如,mount,update )。有了它你应该可以肯定哪一个组件可能会出现性能问题,而后进行优化。
另外一种选择是 DevTools Profiler ,它在 React 16.5+ 中可用,并与 shouldComponentUpdate
配合(或PureComponent
,在本教程的第一部分中解释),咱们能够提升一些关键组件的性能。
显然,对网络进行基本优化是最佳的,例如对一些事件进行去抖动(例如,滚动),对动画保持谨慎(使用变换而不是经过改变高度并实现动画)等等。这些问题很容易被忽略,特别是若是你刚刚掌握了 React。
若是要讨论 React 的将来,我我的不会太在乎。从个人角度来看,React 在 2019 年及之后的地位很难被撼动。
React 拥有如此强大的地位,在一个大社区的支持下很难被废弃。 React社区很是棒,它老是产生新的创意,核心团队一直在不断努力改进 React,并添加新功能和修复旧问题。 React 也获得了一家大公司的支持,但许可证已经不是问题 —— 它如今使用 MIT license。
是的,有一些事情有望改变或改进;例如,使 React 稍微小一些(提到的一个措施是删除合成事件)或将 className
重命名为 class
。固然,即便这些看似微小的变化也可能致使诸如影响浏览器兼容性等问题。就我的而言,我也想知道当 WebComponent 得到更多人气时会发生什么,由于它可能会增长一些 React 常常用到的东西。我不相信他们会成为一个彻头彻尾的替代者,但我相信他们能够很好地相互补充。
至于短时间,hook 刚刚被加入到 React。这多是自 React 重写以来发生的最大变化,由于它们将带来更多可能性并加强更多功能组件(如今他们真的被大肆宣传)。
最后,正如我最近所说的那样,有React Native。对我来讲,这是一项伟大的技术,在过去的几年中发生了很大的变化。 React Native正在重写它的核心,这应该以与 React 重写相似的方式完成(它所有是内部的,几乎没有任何东西应该为开发人员改变)。异步渲染成为本机和 JavaScript 之间更快更轻量级的桥梁。固然还有更多改变。
在 React 生态中有不少值得期待的东西,但 hook(以及React Native,若是有人喜欢手机应用的话)的更新可能将会是咱们在2019年所能看到的最重要的变化。