[译] 如何写出更好的 React 代码?

本文提供了九个实用建议,帮助开发者写出更高效的React代码,涵盖代码检查、propTypes、PureComponent使用等,适合各水平的React开发者。

写出更好的 React 代码的 9 条实用提示:了解代码检查、propTypes、PureComponent 等。

使用 React 可以轻松创建交互式界面。为应用中的每个状态设计简单的视图,当数据变化时,React 会高效地更新和渲染正确的组件。

这篇文章中,我会介绍一些使你成为更好的 React 开发者的方法。包括从工具到代码风格等一系列内容,这些都可以帮助你提升 React 相关技能。 ?


代码检查

要写出更好代码,很重要的一件事就是使用好的代码检查工具。如果我们配置好了一套代码检查规则,代码编辑器就能帮我们捕捉到任何可能出现的代码问题。

但除了捕捉问题,ES Lint 也会让你不断学习到 React 代码的最佳实践。

import react from 'react';
/* 其它 imports */

/* Code */

export default class App extends React.Component {
  render() {
    const { userIsLoaded, user } = this.props;
    if (!userIsLoaded) return <Loader />;
    
    return (
      /* Code */
    )
  }
}
复制代码

看一下上面的代码。假设你想在 render() 方法中引用一个叫做 this.props.hello 的新属性。代码检查工具会马上把代码变红,并提示:

props 验证没有 'hello' (react/prop-types)
复制代码

代码检查工具会让你认识到 React 的最佳实践并塑造你对代码的理解。很快,之后写代码的时候,你就会开始避免犯错了。

你可以去 ESLint 官网 为 JavaScript 配置代码检查工具,或者使用 Airbnb’s JavaScript Style Guide。也可以安装 React ESLint Package


propTypes 和 defaultProps

上一节中,我谈到了当使用一个不存在的 prop 时,我的代码检查工具是如何起作用的。

static propTypes = {
  userIsLoaded: PropTypes.boolean.isRequired,
  user: PropTypes.shape({
    _id: PropTypes.string,
  )}.isRequired,
}
复制代码

在这里,如果 userIsLoaded 不是必需的,那么我们就要在代码中添加说明:

static defaultProps = {
 userIsLoaded: false,
}
复制代码

所以每当我们要在组件中使用 参数类型检查,就要为它设置一个 propType。如上,我们告诉 React:userIsLoaded 的类型永远是一个布尔值。

如果我们声明 userIsLoaded 不是必需的值,那么我们就要为它定义一个默认值。如果是必需的,就没有必要定义默认值。但是,规则还指出不应该使用像对象或数组这样不明确的 propTypes。

为什么使用 shape 方法来验证 user 呢,因为它内部需要有一个 类型为字符串的 id 属性,而整个 user 对象又是必需的。

确保使用了 props 的每个组件都声明了 propTypesdefaultProps,这对写出更好的 React 代码很有帮助。

当 props 实际获取的数据和期望的不同时,错误日志就会让你知道:要么是你传递了错误的数据,要么就是没有得到期望值,特别是写可重用组件时,找出错误会更容易。这也会让这些可重用组件更可读一些。

注意:

React 从 v15.5 版本开始,不再内置 proptypes,需要作为独立的依赖包添加到你的项目中。

点击下面的链接了解更多:


知道何时创建新组件

export default class Profile extends PureComponent {
  static propTypes = {
    userIsLoaded: PropTypes.bool,
    user: PropTypes.shape({
      _id: PropTypes.string,
    }).isRequired,
  }
  
  static defaultProps = {
    userIsLoaded: false,
  }
  
  render() {
    const { userIsLoaded, user } = this.props;
    if (!userIsLoaded) return <Loaded />;
    return (
      <div>
        <div className="two-col">
          <section>
            <MyOrders userId={user._id} />
            <MyDownloads userId={user._id} />
          </section>
          <aside>
            <MySubscriptions user={user} />
            <MyVotes user={user} />
          </aside>
        </div>
        <div className="one-col">
          {isRole('affiliate', user={user._id) &&
            <MyAffiliateInfo userId={user._id} />
          }
        </div>
      </div>
    )
  }
}
复制代码

上面有一个名为 Profile 的组件。这个组件内部还有一些像 MyOrderMyDownloads 这样的其它组件。因为它们从同一个数据源(user)获取数据,所以可以把所有这些组件写到一起。把这些小组件变成一个巨大的组件。

尽管什么时候才要创建一个新组件没有任何硬性规定,但问问你自己:

  • 代码的功能变得笨重了吗?
  • 它是否只代表了自己的东西?
  • 是否需要重用这部分代码?

如果上面有一个问题的答案是肯定的,那你就需要创建一个新组件了。

记住,任何人如果看到你的有 200–300 行的组件时都会抓狂的,然后没人会想再看你的代码。


Component vs PureComponent vs Stateless Functional Component

对于一个 React 开发者,知道在代码中什么时候该使用 ComponentPureComponentStateless Functional Component 是非常重要的。

你可能注意到了在上面的代码中,我没有将 Profile 继承自 Component,而是 PureComponent

首先,来看看无状态函数式组件。

Stateless Functional Component(无状态函数式组件)
const Billboard = () => (
  <ZoneBlack>
    <Heading>React</Heading>
    <div className="billboard_product">
      <Link className="billboard_product-image" to="/">
        <img alt="#" src="#">
      </Link>
      <div className="billboard_product-details">
        <h3 className="sub">React</h3>
        <p>Lorem Ipsum</p>
      </div>
    </div>
  </ZoneBlack>
);
复制代码

无状态函数式组件是一种很常见的组件类型。它为我们提供了一种非常简洁的方式来创建不使用任何 staterefs生命周期方法 的组件。

无状态函数式组件的特点是没有状态并且只有一个函数。所以你可以把组件定义为一个返回一些数据的常量函数。

简单来说,无状态函数式组件就是返回 JSX 的函数。

PureComponents

通常,一个组件获取了新的 prop,React 就会重新渲染这个组件。但有时,新传入的 prop 并没有真正改变,React 还是触发重新渲染。

使用 PureComponent 可以帮助你避免这种重新渲染的浪费。例如,一个 prop 是字符串或布尔值,它改变后,PureComponent 会识别到这个改变,但如果 prop 是一个对象,它的属性改变后,PureComponent 不会触发重新渲染。

那么如何知道 React 何时会触发一个不必要的重新渲染呢?你可以看看这个叫做 Why Did You Update 的 React 包。当不必要的重新渲染发生时,这个包会在控制台中通知你。

一旦你确认了一个不必要的重新渲染,就可以使用 PureComponent 替换 Component 来避免。


使用 React 开发者工具

如果你真想成为一个专业的 React 开发者,那么在开发过程中,就应该经常使用 React 开发者工具。

如果你使用过 React,你的控制台很可能建议过你使用 React 开发者工具。

React 开发者工具适用于所有主流浏览器,例如:ChromeFirefox

通过 React 开发者工具,你可以看到整个应用结构和应用中正在使用的 props 和 state。

React 开发者工具是探索 React 组件的绝佳方式,也有助于诊断应用中的问题。


使用内联条件语句

这个观点可能会引起一些争议,但我发现使用内联条件语句可以明显简化我的 React 代码。

如下:

<div className="one-col">
  {isRole('affiliate', user._id) &&
    <MyAffiliateInfo userId={user._id} />
  }
</div>
复制代码

上面代码中,有一个检查这个人是否是 “affiliate” 的方法,后面跟了一个叫做 <MyAffiliateInfo/> 的组件。

这样做的好处是:

  • 不必编写单独的函数
  • 不必在 render 方法中使用 “if” 语句
  • 不必为组件中的其它位置创建“链接”

使用内联条件语句非常简洁。开始你可以把条件写为 true,那么 <MyAffiliateInfo /> 组件无论如何都会显示。

然后我们使用 && 连接条件和 <MyAffiliateInfo />。这样当条件为真时,组件就会被渲染。


尽可能使用代码片段库

打开一个代码编辑器(我用的是 VS Code),新建一个 js 文件。

在这个文件中输入 rc,就会看见如下提示:

按下回车键,会立刻得到下面的代码片段:

这些代码片段的优点不仅是帮助你减少 bug,还能帮助你获取到最新最棒的写法。

你可以在代码编辑器中安装许多不同的代码片段库。我用于 VS Code 的叫做 ES7 React/Redux/React-Native/JS Snippets


React Internals — 了解 React 内部如何工作

React Internals 是一个共五篇的系列文章,帮助我理解 React 的基础知识,最终帮助我成为一个更好的 React 开发者!

如果你对某些问题不能完全理解,或者你知道 React 的工作原理,那么 React Internals 可以帮助你理解何时、如何在 React 中做对的事。

这对那些不清楚在哪里执行代码的人特别有用。

理解 React 内部运行原理会帮助你成为更好的 React 开发者。


在你的组件中使用 BitStoryBook

Bit 是一个将你的 UI 组件转化为可以在不同应用中分享、开发和同步的构建块的工具。

你也可以利用 Bit 管理团队组件,通过 线上组件区,可以使它们容易获取和使用,也便于单独测试。

Storybook 是用于 UI 组件的快速开发环境,可以帮助你浏览一个组件库,查看每个组件的不同状态,交互式开发和测试组件。

Storybook 提供了一个帮你快速开发 React 组件的环境,通过它,当你操作组件的属性时,Web 页面会热更新,让你看到组件的实时效果。


快速回顾

  1. 使用代码检查工具,使用 ES Lint、Airbnb’s JavaScript Style Guide 和 ESLint React 插件。
  2. 使用 propTypes 和 defaultProps。
  3. 知道何时创建新组件。
  4. 知道何时使用 Component、PureComponent 和 Stateless Functional Component。
  5. 使用 React 开发者工具。
  6. 使用内联条件语句。
  7. 使用代码片段库,节省浪费在样板代码上的时间。
  8. 通过 React Internals 了解 React 如何工作。
  9. 使用像 Bit、StoryBook 这样的工具来优化开发流程。

掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

React 核心理解 react16之前更新 React Fiber是16版本之后的一种更新机制,使用链表取代了树,是一种fiber数据结构,其有三个指针,分别指向了父节点、子节点、兄弟节点,当中断的时候会记录下当前的节点,然后继续更新,而15版本中的DOM stack不能有中断操作,它把组件渲染的工作分片,到时会主动让出渲染主线程;提炼fiber的关键词,大概给出如下几点: fiber是一种数据结构。 fiber使用父子关系以及next的妙用,以链表形式模拟了传统调用栈。 fiber是一种调度让出机制,只在有剩余时间的情况下运行。 fiber实现了增量渲染,在浏览器允许的情况下一点点拼凑出最终渲染效果。 fiber实现了并发,为任务赋予不同优先级,保证了一有时间总是做最高优先级的事,而不是先来先占位死板的去执行。 fiber有协调与提交两个阶段,协调包含了fiber创建与diff更新,此过程可暂停。而提交必须同步执行,保证渲染不卡顿。 react17更新 1、新的JSX转换,不需要手动引入react React 16: babel-loader会预编JSX为 React.createElement(...) React 17: React 17中的 JSX 转换不会将 JSX 转换为 React.createElement,而是自动从 React 的 package 中引入react并调用。 另外此次升级不会改变 JSX 语法,旧的 JSX 转换也将继续工作。 2、事件代理更改 在React 17中,将不再在后台的文档级别附加事件处理程序,不在document对象上绑定事件,改为绑定于每个react应用的rootNode节点,因为各个应用的rootNode肯定不同,所以这样可以使多个版本的react应用同时安全的存在于页面中,不会因为事件绑定系统起冲突。react应用之间也可以安全的进行嵌套。 3、事件池(event pooling)的改变 React 17去除了事件池(event pooling),不在需要e.persist(),现在可以直接在异步事件中(回掉或timeout等)拿到事件对象,操作更加直观,不会令人迷惑。e.persist()仍然可用,但是不会有任何效果。 4、异步执行 React 17将副作用清理函数(useEffect)改为异步执行,即在浏览器渲染完毕后执行。 5、forwardRef 和 memo组件的行为 React 17中forwardRef 和 memo组件的行为会与常规函数组件和class组件保持一致。它们在返回undefined时会报错。 react18更新 并发模式 v18的新特性是使用现代浏览器的特性构建的,彻底放弃对 IE 的支持。 v17 和 v18 的区别就是:从同步不可中断更新变成了异步可中断更新,v17可以通过一些试验性的API开启并发模式,而v18则全面开启并发模式。 并发模式可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整,该模式通过使渲染可中断来修复阻塞渲染限制。在 Concurrent 模式中,React 可以同时更新多个状态。 这里参考下文区分几个概念: 并发模式是实现并发更新的基本前提 v18 中,以是否使用并发特性作为是否开启并发更新的依据。 并发特性指开启并发模式后才能使用的特性,比如:useDeferredValue/useTransition 更新 render API v18 使用 ReactDOM.createRoot() 创建一个新的根元素进行渲染,使用该 API,会自动启用并发模式。如果你升级到v18,但没有使用ReactDOM.createRoot()代替ReactDOM.render()时,控制台会打印错误日志要提醒你使用React,该警告也意味此项变更没有造成breaking change,而可以并存,当然尽量是不建议。 自动批处理 批处理是指 React 将多个状态更新,聚合到一次 render 中执行,以提升性能 在v17的批处理只会在事件处理函数中实现,而在Promise链、异步代码、原生事件处理函数中失效。而v18则所有的更新都会自动进行批处理。 Suspense 支持 SSR SSR 一次页面渲染的流程: 服务器获取页面所需数据 将组件渲染成 HTML 形式作为响应返回 客户端加载资源 (hydrate)执行 JS,并生成页面最终内容 上述流程是串行执行的,v18前的 SSR 有一个问题就是它不允许组件"等待数据",必须收集好所有的数据,才能开始向客户端发送HTML。如果其中有一步比较慢,都会影响整体的渲染速度。 v18 中使用并发渲染特性扩展了Suspense的功能,使其支持流式 SSR,将 React 组件分解成更小的块,允许服务端一点一点返回页面,尽早发送 HTML和选择性的 hydrate, 从而可以使SSR更快的加载页面: startTransition Transitions 是 React 18 引入的一个全新的并发特性。它允许你将标记更新作为一个 transitions(过渡),这会告诉 React 它们可以被中断执行,并避免回到已经可见内容的 Suspense 降级方案。本质上是用于一些不是很急迫的更新上,用来进行并发控制 在v18之前,所有的更新任务都被视为急迫的任务,而Concurrent Mode 模式能将渲染中断,可以让高优先级的任务先更新渲染。 React 的状态更新可以分为两类: 紧急更新:比如点击按钮、搜索框打字是需要立即响应的行为,如果没有立即响应给用户的体验就是感觉卡顿延迟 过渡/非紧急更新:将 UI 从一个视图过渡到另一个视图。一些延迟可以接受的更新操作,不需要立即响应 startTransition API 允许将更新标记为非紧急事件处理,被startTransition包裹的会延迟更新的state,期间可能被其他紧急渲染所抢占。因为 React 会在高优先级更新渲染完成之后,才会渲染低优先级任务的更新 React 无法自动识别哪些更新是优先级更高的。比如用户的键盘输入操作后,setInputValue会立即更新用户的输入到界面上,是紧急更新。而setSearchQuery是根据用户输入,查询相应的内容,是非紧急的。 React无法自动识别,所以它提供了 startTransition让我们手动指定哪些更新是紧急的,哪些是非紧急的,从而让我们改善用户交互体验。 useTransition 当有过渡任务(非紧急更新)时,我们可能需要告诉用户什么时候当前处于 pending(过渡) 状态,因此v18提供了一个带有isPending标志的 Hook useTransition来跟踪 transition 状态,用于过渡期。 useTransition 执行返回一个数组。数组有两个状态值: isPending: 指处于过渡状态,正在加载中 startTransition: 通过回调函数将状态更新包装起来告诉 React这是一个过渡任务,是一个低优先级的更新 直观感觉这有点像 setTimeout,而防抖节流其实本质也是setTimeout,区别是防抖节流是控制了执行频率,让渲染次数减少了,而 v18的 transition 则没有减少渲染的次数。 useDeferredValue useDeferredValue 和 useTransition 一样,都是标记了一次非紧急更新。useTransition是处理一段逻辑,而useDeferredValue是产生一个新状态,它是延时状态,这个新的状态则叫 DeferredValue。所以使用useDeferredValue可以推迟状态的渲染 useDeferredValue 接受一个值,并返回该值的新副本,该副本将推迟到紧急更新之后。如果当前渲染是一个紧急更新的结果,比如用户输入,React 将返回之前的值,然后在紧急渲染完成后渲染新的值。 JavaScript 运行代码 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Typeahead() { const query = useSearchQuery(''); const deferredQuery = useDeferredValue(query); // Memoizing 告诉 React 仅当 deferredQuery 改变, // 而不是 query 改变的时候才重新渲染 const suggestions = useMemo(() => <SearchSuggestions query={deferredQuery} />, [deferredQuery] ); return ( <> <SearchInput query={query} /> <Suspense fallback="Loading results..."> {suggestions} </Suspense> </> ); } 这样一看,useDeferredValue直观就是延迟显示状态,那用防抖节流有什么区别呢? 如果使用防抖节流,比如延迟300ms显示则意味着所有用户都要延时,在渲染内容较少、用户CPU性能较好的情况下也是会延迟300ms,而且你要根据实际情况来调整延迟的合适值;但是useDeferredValue是否延迟取决于计算机的性能。 useId useId支持同一个组件在客户端和服务端生成相同的唯一的 ID,避免 hydration 的不匹配,原理就是每个 id 代表该组件在组件树中的层级结构: JavaScript 运行代码 复制代码 1 2 3 4 5 6 7 8 9 function Checkbox() { const id = useId() return ( <> <label htmlFor={id}>Do you like React?</label> <input id={id} type="checkbox" name="react" /> </> ) } React.memo 和性能优化。当某个组件状态更新时,它的所有子组件树将会重新渲染。 React.memo 和记忆化数据 React.memo 和 React.useMemo 优化性能 React.memo 和 React.useCallback 优化性能 React useEffect cleanup。在这段代码中,示例演示 cleanup 的时机 React 中可以以数组的 index 作为 key 吗?。在这段代码中,使用 index 作为 key,其中夹杂了 input,引发 bug React 中以数组的 index 作为 key。在这段代码中,使用 index 作为 key,其中夹杂了随机数,引发了 bug React 兄弟组件通信。兄弟组件在 React 中如何通信 React 中合成事件。React 中事件为合成事件,你可以通过 e.nativeEvent 获取到原生事件,观察 e.nativeEvent.currentTarget 你将会发现 React 将所有事件都绑定在了 #app(React 应用挂载的根组件) React 中 input.onChange 的原生事件是什么?。观察 e.nativeEvent.type 可知 React hooks 如何实现一个计数器 Counter React FiberNode 数据结构。贯彻 element._owner 可知 FiberNode 数据结构 React 点击按钮时自增三次。此时需使用回调函数,否则会报错 React 不可变数据的必要性。 React 不可变数据的必要性之函数组件。当在 React hooks 中 setState 两次为相同数据时,不会重新渲染 React 状态批量更新之事件处理。事件处理中的状态会批量更新,减少渲染次数 React 状态批量更新之异步请求。异步请求中的状态不会批量更新,将会造成多次渲染 React18 状态批量更新。在 React 18 中所有状态将会批量更新 React capture value 整理成markerdown代码风格写出来 我copy
08-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值