你应该知道的 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>
)
}
React 只会调用React.createElement
并使用该字符串在内部创建元素。是不是很棒?
在Material-UI等组件库中普遍使用,您可以声明一个component
prop,调用者可以决定组件的根节点作为其值,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>
)
}
你可以这样使用它:
function App() {
return (
<div>
<MyComponent component="div" name="George" age={16} email="george@gmail.com">
</div>
)
}
您还可以传递自定义组件,将其用作根节点:
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>
)
}
2. 使用错误边界
在 JavaScript 中,我们习惯于在代码执行过程中使用能够“捕获”发生的错误的代码块来处理大多数错误。当这些错误被 catch 块捕获时,您可以防止应用程序在代码边界内崩溃。try/catch
示例如下:
function getFromLocalStorage(key, value) {
try {
const data = window.localStorage.get(key)
return JSON.parse(data)
} catch (error) {
console.error
}
}
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
}
}
然后你可以将其用作常规组件:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
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>
)
}
这是因为在组件完成渲染后React.useEffect
运行。
当setNames
被调用时,组件会重新渲染,并prefNamesRef
保留之前的名称,因为是上一次渲染React.useEffect
中执行的最后一段代码。由于我们在 中进行了重新赋值,它在下一个渲染阶段会变为之前的名称,因为它最后被赋值为上一次渲染阶段的名称。prevNamesRef.current
useEffect
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>
)
}
}
迁移到 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>
)
}
另一个很好的用例是跟踪最新的更改而不导致重新渲染,将其与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])
}
如果 ref 属性发生变化且已定义,这将创建一个新函数。这意味着 React 将使用 调用旧的分叉引用null
,并使用当前引用调用新的分叉引用。由于使用了 ,引用将被记忆,直到或 的React.useMemo
ref 属性发生变化——此时此行为会被自然清除。refA
refB
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>
)
}
如果我们想要获取元素坐标的位置div
,这个例子就足够了。但是,如果应用中某个位置的另一个元素想要在位置position
发生变化时更新自身位置,或者相应地应用某些条件逻辑,最好的方法是使用ref callback function pattern
。使用回调函数模式时,你将接收 React 组件实例或 HTML DOM 元素作为第一个参数。
下面的例子只是一个简单的例子,其中setRef
回调函数被应用于ref
prop。你可以看到,在里面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>
)
}
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',
})
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}
/>
)
}
使用渲染 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,
})
}
示例中,MyComponent
有一个组件,我们称之为渲染 prop 组件,因为它需要render
prop 并调用它来渲染其子组件。这在 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>
)}
/>
)
}
8. 记忆
作为 React 开发者,最重要的事情之一就是优化组件的性能,例如React.memo
。这有助于防止应用程序运行时出现诸如无限循环之类的严重错误,从而导致灾难性的崩溃。
了解以下几种可以在 React 应用中应用记忆的方法:
结论
这篇文章到此结束!希望你觉得这篇文章很有价值,并期待未来有更多精彩内容!
在Medium上找到我
文章来源:https://dev.to/jsmanifest/8-useful-practices-for-react-apps-you-should-know-5k9