你应该知道的 8 个 React 应用实用实践

2025-05-25

你应该知道的 8 个 React 应用实用实践

在Medium上找到我

React 经历了许多阶段的转变,每次都让它的粉丝惊叹不已。

起初,我们使用mixin来创建和管理我们的界面,然后出现了类组件的概念,现在又有了react hooks,它改变了我们在 react 中构建应用程序的方式。

你知道还有什么很棒的吗?了解一些 React 中的巧妙技巧,它们能帮助你更好地构建应用(当然,如果你遇到了一些你不知道的事情,你也可以尝试)。

本文将介绍每个 React 开发者都应该知道的 8 个 React 实用技巧。我并不期望列表中的每一项对你来说都是全新的,但我希望你能在其中找到至少一项对你有用,并且是你之前不知道自己可以做到的。

以下是你应该知道的 8 个 React 技巧:

1. 使用字符串创建 React 元素

此列表的第一项将介绍如何使用表示 HTML DOM 元素标签的简单字符串创建常规 React DOM 元素。更准确地说,是一个表示 DOM 元素的字符串。

例如,您可以通过将字符串分配'div'给变量来创建反应组件,如下所示:

import React from 'react'

const MyComponent = 'div'

function App() {
  return (
    <div>
      <h1>Hello</h1>
      <hr />
      <MyComponent>
        <h3>I am inside a {'<div />'} element</h3>
      </MyComponent>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

React 只会调用React.createElement并使用该字符串在内部创建元素。是不是很棒?

在Material-UI等组件库中普遍使用,您可以声明一个componentprop,调用者可以决定组件的根节点作为其值,props.component如下所示:

function MyComponent({ component: Component = 'div', name, age, email }) {
  return (
    <Component>
      <h1>Hi {name}</h1>
      <div>
        <h6>You are {age} years old</h6>
        <small>Your email is {email}</small>
      </div>
    </Component>
  )
}
Enter fullscreen mode Exit fullscreen mode

你可以这样使用它:

function App() {
  return (
    <div>
      <MyComponent component="div" name="George" age={16} email="george@gmail.com">
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

您还可以传递自定义组件,将其用作根节点:

function Dashboard({ children }) {
  return (
    <div style={{ padding: '25px 12px' }}>
      {children}
    </div>
  )
}

function App() {
  return (
    <div>
      <MyComponent component={Dashboard} name="George" age={16} email="george@gmail.com">
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

2. 使用错误边界

在 JavaScript 中,我们习惯于在代码执行过程中使用能够“捕获”发生的错误的代码块来处理大多数错误。当这些错误被 catch 块捕获时,您可以防止应用程序在代码边界内崩溃。try/catch

示例如下:

function getFromLocalStorage(key, value) {
  try {
    const data = window.localStorage.get(key)
    return JSON.parse(data)
  } catch (error) {
    console.error
  }
}
Enter fullscreen mode Exit fullscreen mode

React 本质上只是 JavaScript,因此我们可以假设可以使用相同的策略捕获和处理错误。然而,由于 React 的本质,组件内部的 JavaScript 错误会破坏 React 的内部状态,并导致其在未来的渲染中发出难以理解的 错误

为此,React 团队引入了错误边界,每个 React 开发人员都应该了解它们,以便他们可以在他们的 React 应用程序中使用它们。

错误发生在错误边界之前的问题在于,当这些隐秘的错误在之前的渲染中发生后,在之后的渲染中再次触发时,React 并没有提供在组件中处理和恢复这些错误的方法。这就是为什么我们都需要错误边界!

错误边界是 React 组件,它可以捕获组件树中任何位置的错误,记录错误并显示回退 UI 来替代崩溃的组件树。它们会在渲染过程中、生命周期方法内部以及其下方整个组件树的构造函数中捕获错误(这就是我们在应用程序顶部某处声明和渲染它们的原因)。

以下是来自React 文档的一个示例:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true }
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>
    }

    return this.props.children
  }
}
Enter fullscreen mode Exit fullscreen mode

然后你可以将其用作常规组件:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

3.保留先前的价值观

在更新 props 或 state 时,你可以使用以下方法保留它们之前的值React.useRef

例如,要跟踪项目数组的当前和先前的更改,您可以创建一个React.useRef分配先前值的 ,并创建一个分配React.useState当前值的 :

function MyComponent() {
  const [names, setNames] = React.useState(['bob'])
  const prevNamesRef = React.useRef([])

  React.useEffect(() => {
    prevNamesRef.current = names
  })

  const prevNames = prevNamesRef.current

  return (
    <div>
      <h4>Current names:</h4>
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <h4>Previous names:</h4>
      <ul>
        {prevNames.map((prevName) => (
          <li key={prevName}>{prevName}</li>
        ))}
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

这是因为在组件完成渲染后React.useEffect运行

setNames被调用时,组件会重新渲染,并prefNamesRef保留之前的名称,因为是上一次渲染React.useEffect中执行的最后一段代码。由于我们在 中进行了重新赋值,它在下一个渲染阶段会变为之前的名称,因为它最后被赋值为上一次渲染阶段的名称。prevNamesRef.currentuseEffect

4.React.useRef用于灵活的非陈旧值检查

在 React 中引入 React Hooks 之前,componentDidMount如果我们想确保诸如获取数据之类的操作在组件挂载到 DOM之后发生,我们就有类组件的静态方法。

React Hooks 一经推出,就迅速成为编写组件(而非使用类组件)最流行的方式。当我们想要追踪组件是否已挂载,以防止在组件卸载后再次设置状态时,我们会这样做:

import React from 'react'
import axios from 'axios'

class MyComponent extends React.Component {
  mounted = false

  state = {
    frogs: [],
    error: null,
  }

  componentDidMount() {
    this.mounted = true
  }

  componentWillUnmount() {
    this.mounted = false
  }

  async fetchFrogs = (params) => {
    try {
      const response = await axios.get('https://some-frogs-api.com/v1/', { params })
      if (this.mounted) {
        this.setState({ frogs: response.data.items })
      }
    } catch (error) {
      if (this.mounted) {
        this.setState({ error })
      }
    }
  }

  render() {
    return (
      <div>
        <h4>Frogs:</h4>
        <ul>
        {this.state.frogs.map((frog) => <li key={frog.name}>{frog.name}</li>
        )}
        </ul>
    </div>
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

迁移到 React Hooks 后, Hooks 不再具有componentDidMount内存泄漏的概念,卸载后发生的状态更新导致的内存泄漏仍然适用于 Hooks。

但是,与使用 React Hooks 类似的方法componentDidMount是使用,因为它是在组件渲染完成React.useEffect执行的。如果在这里使用 来赋值,则可以实现与类组件示例相同的效果:React.useRef

import React from 'react'
import axios from 'axios'

function MyComponent() {
  const [frogs, setFrogs] = React.useState([])
  const [error, setError] = React.useState(null)
  const mounted = React.useRef(false)

  async function fetchFrogs(params) {
    try {
      const response = await axios.get('https://some-frogs-api.com/v1/', {
        params,
      })
      if (mounted.current) {
        setFrogs(response.data.items)
      }
    } catch (error) {
      if (mounted.current) {
        setError(error)
      }
    }
  }

  React.useEffect(() => {
    mounted.current = true

    return function cleanup() {
      mounted.current = false
    }
  }, [])

  return (
    <div>
      <h4>Frogs:</h4>
      <ul>
        {this.state.frogs.map((frog) => (
          <li key={frog.name}>{frog.name}</li>
        ))}
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

另一个很好的用例是跟踪最新的更改而不导致重新渲染,将其与React.useMemo如下内容结合使用(来源):

function setRef(ref, value) {
  // Using function callback version
  if (typeof ref === 'function') {
    ref(value)
    // Using the React.useRef() version
  } else if (ref) {
    ref.current = value
  }
}

function useForkRef(refA, refB) {
  return React.useMemo(() => {
    if (refA == null && refB == null) {
      return null
    }
    return (refValue) => {
      setRef(refA, refValue)
      setRef(refB, refValue)
    }
  }, [refA, refB])
}
Enter fullscreen mode Exit fullscreen mode

如果 ref 属性发生变化且已定义,这将创建一个新函数。这意味着 React 将使用 调用旧的分叉引用null,并使用当前引用调用新的分叉引用。由于使用了 ,引用将被记忆,直到或 的React.useMemoref 属性发生变化——此时此行为会被自然清除。refArefB

5.React.useRef用于自定义依赖于其他元素的元素

React.useRef有几个有用的用例,包括将自身分配给 ref prop 以反应节点:

function MyComponent() {
  const [position, setPosition] = React.useState({ x: 0, y: 0 })
  const nodeRef = React.useRef()

  React.useEffect(() => {
    const pos = nodeRef.current.getBoundingClientRect()
    setPosition({
      x: pos.x,
      y: pos.y,
    })
  }, [])

  return (
    <div ref={nodeRef}>
      <h2>Hello</h2>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

如果我们想要获取元素坐标的位置div,这个例子就足够了。但是,如果应用中某个位置的另一个元素想要在位置position发生变化时更新自身位置,或者相应地应用某些条件逻辑,最好的方法是使用ref callback function pattern。使用回调函数模式时,你将接收 React 组件实例或 HTML DOM 元素作为第一个参数。

下面的例子只是一个简单的例子,其中setRef回调函数被应用于refprop。你可以看到,在里面setRef你可以做任何你想做的事情,而不是直接将React.useRef版本应用于 DOM 元素:

const SomeComponent = function({ nodeRef }) {
  const ownRef = React.useRef()

  function setRef(e) {
    if (e && nodeRef.current) {
      const codeElementBounds = nodeRef.current.getBoundingClientRect()
      // Log the <pre> element's position + size
      console.log(`Code element's bounds: ${JSON.stringify(codeElementBounds)}`)
      ownRef.current = e
    }
  }

  return (
    <div
      ref={setRef}
      style={{ width: '100%', height: 100, background: 'green' }}
    />
  )
}

function App() {
  const [items, setItems] = React.useState([])
  const nodeRef = React.useRef()

  const addItems = React.useCallback(() => {
    const itemNum = items.length
    setItems((prevItems) => [
      ...prevItems,
      {
        [`item${itemNum}`]: `I am item # ${itemNum}'`,
      },
    ])
  }, [items, setItems])

  return (
    <div style={{ border: '1px solid teal', width: 500, margin: 'auto' }}>
      <button type="button" onClick={addItems}>
        Add Item
      </button>
      <SomeComponent nodeRef={nodeRef} />
      <div ref={nodeRef}>
        <pre>
          <code>{JSON.stringify(items, null, 2)}</code>
        </pre>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

6. 高阶组件

在纯 JavaScript 中,创建强大可复用函数的常见模式是高阶函数。由于 React 本质上是 JavaScript,因此你也可以在 React 内部使用高阶函数。

对于可重用组件,诀窍是使用高阶组件

高阶组件是指一个函数,它接受一个组件作为参数并返回一个组件。正如高阶函数可以用来抽象逻辑并在应用中与其他函数共享一样,高阶组件也使我们能够从组件中抽象出逻辑并在其他组件之间共享。这意味着您可以在整个应用中使用大量可复用的组件。

下面是一个高阶组件的示例。在此代码片段中,高阶组件withBorder接收一个自定义组件并返回一个隐藏的“中间层”组件。然后,当父级决定渲染这个返回的高阶组件时,它会被调用为一个组件,并接收从“中间层”组件传入的 props:

import React from 'react'

// Higher order component
const withBorder = (Component, customStyle) => {
  class WithBorder extends React.Component {
    render() {
      const style = {
        border: this.props.customStyle
          ? this.props.customStyle.border
          : '3px solid teal',
      }
      return <Component style={style} {...this.props} />
    }
  }

  return WithBorder
}

function MyComponent({ style, ...rest }) {
  return (
    <div style={style} {...rest}>
      <h2>This is my component and I am expecting some styles.</h2>
    </div>
  )
}

export default withBorder(MyComponent, {
  border: '4px solid teal',
})
Enter fullscreen mode Exit fullscreen mode

7.渲染道具

我在 React 库中最喜欢使用的技巧之一是Render Prop 模式。它与高阶组件类似,解决了类似的问题:在多个组件之间共享代码。Render Prop 暴露了一个函数,其目的是将渲染其子组件所需的所有信息传回给外部世界。

在 React 中渲染组件的最基本方法是像这样渲染它们:

function MyComponent() {
  return <p>My component</p>
}

function App() {
  const [fetching, setFetching] = React.useState(false)
  const [fetched, setFetched] = React.useState(false)
  const [fetchError, setFetchError] = React.useState(null)
  const [frogs, setFrogs] = React.useState([])

  React.useEffect(() => {
    setFetching(true)
    api
      .fetchFrogs({ limit: 1000 })
      .then((result) => {
        setFrogs(result.data.items)
        setFetched(true)
        setFetching(false)
      })
      .catch((error) => {
        setError(error)
        setFetching(false)
      })
  }, [])

  return (
    <MyComponent
      fetching={fetching}
      fetched={fetched}
      fetchError={fetchError}
      frogs={frogs}
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

使用渲染 props,渲染其子项的 prop 按照惯例调用render如下:

function MyComponent({ render }) {
  const [fetching, setFetching] = React.useState(false)
  const [fetched, setFetched] = React.useState(false)
  const [fetchError, setFetchError] = React.useState(null)
  const [frogs, setFrogs] = React.useState([])

  React.useEffect(() => {
    setFetching(true)
    api
      .fetchFrogs({ limit: 1000 })
      .then((result) => {
        setFrogs(result.data.items)
        setFetched(true)
        setFetching(false)
      })
      .catch((error) => {
        setError(error)
        setFetching(false)
      })
  }, [])

  return render({
    fetching,
    fetched,
    fetchError,
    frogs,
  })
}
Enter fullscreen mode Exit fullscreen mode

示例中,MyComponent有一个组件,我们称之为渲染 prop 组件,因为它需要renderprop 并调用它来渲染其子组件。这在 React 中是一种非常强大的模式,因为我们可以通过渲染回调将共享状态和数据作为参数传递,从而允许该组件在多个组件中渲染和复用:

function App() {
  return (
    <MyComponent
      render={({ fetching, fetched, fetchError, frogs }) => (
        <div>
          {fetching
            ? 'Fetching frogs...'
            : fetched
            ? 'The frogs have been fetched!'
            : fetchError
            ? `An error occurred while fetching the list of frogs: ${fetchError.message}`
            : null}
          <hr />
          <ul
            style={{
              padding: 12,
            }}
          >
            {frogs.map((frog) => (
              <li key={frog.name}>
                <div>Frog's name: {frog.name}</div>
                <div>Frog's age: {frog.age}</div>
                <div>Frog's gender: {frog.gender}</div>
              </li>
            ))}
          </ul>
        </div>
      )}
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

8. 记忆

作为 React 开发者,最重要的事情之一就是优化组件的性能,例如React.memo。这有助于防止应用程序运行时出现诸如无限循环之类的严重错误,从而导致灾难性的崩溃。

了解以下几种可以在 React 应用中应用记忆的方法:

结论

这篇文章到此结束!希望你觉得这篇文章很有价值,并期待未来有更多精彩内容!

在Medium上找到我

文章来源:https://dev.to/jsmanifest/8-useful-practices-for-react-apps-you-should-know-5k9
PREV
这是 React v16+ 备忘单(PDF/JPEG/自定义主题)
NEXT
React 中的 8 种可能导致应用崩溃的做法