自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React最佳實踐

開發(fā) 前端
如果組件卸載了,當組件再次掛載時,triggered?變量仍然會被設置為true?,因為triggered?變量并沒有綁定到React的生命周期中。當使用 useRef? 時,React 將在組件卸載并再次安裝時重置其值。在這種情況下,就可以要使用 useRef。

本文來分享 React 中的 16 種常見反模式和最佳實踐。

1、在組件外部聲明CSS

如果使用 CSS in JS 的解決方案,盡量避免在組件內(nèi)聲明 CSS。

import makeCss from 'some/css/in/js/library'

const Component = () => {
  // 不要這樣寫
  return <div className={makeCss({ background: red, width: 100% })} />
}

因為在每次渲染時都會重新創(chuàng)建對象,可以將其從組件中提出來:

import cssLibrary from 'some/css/in/js/library'

const someCssClass = makeCss({
  background: red,
  width: 100%
})

const Component = () => {
  return <div className={someCssClass} />
}

2、使用 useCallback 防止函數(shù)重新創(chuàng)建

每當重新渲染 React 組件時,都會重新創(chuàng)建組件中的所有函數(shù)。React 提供了一個 useCallback Hook,可以用來避免這種情況。只要其依賴項不改變,useCallback 就會在渲染之間保留函數(shù)的舊實例。

import { useCallback } from 'react'

const Component = () => {
  const [value, setValue] = useState(false)

  // 該函數(shù)將在每次渲染時重新創(chuàng)建
  const handleClick = () => {
    setValue(true)
  }

  return <button onClick={handleClick}>Click</button>
}
import { useCallback } from 'react'

const Component = () => {
  const [value, setValue] = useState(false)

  // 僅當變量值更新時才會重新創(chuàng)建此函數(shù)
  const handleClick = useCallback(() => {
    setValue(true)
  }, [value])

  return <button onClick={handleClick}>Click</button>
}

對于示例中的小函數(shù),不能保證將函數(shù)包裝在useCallback中確實更好。所以還需要根據(jù)實際情況來判斷是否需要使用 useCallback 包裝。

在底層,React將在每次渲染時檢查依賴關(guān)系,以確定是否需要創(chuàng)建新函數(shù),而且有時依賴關(guān)系經(jīng)常發(fā)生變化。因此,useCallback提供的優(yōu)化并不總是必需的。然而,如果函數(shù)的依賴項不經(jīng)常更新,那么使用useCallback是一種很好的優(yōu)化方法,以避免在每次渲染時重新創(chuàng)建函數(shù)。

3、使用 useCallback 防止依賴項更改

useCallback 不僅可以用于避免函數(shù)實例化,但它也可用于更重要的事情。由于 useCallback 在渲染之間為包裝函數(shù)保留相同的內(nèi)存引用,因此它可用于優(yōu)化其他 useCallback 和記憶的使用。

import { memo, useCallback, useMemo } from 'react'

const MemoizedChildComponent = memo({ onTriggerFn }) => {
  // ...
})

const Component = ({ someProp }) => {
  // 僅當 someProp 發(fā)生變化時,對 onTrigger 函數(shù)的引用才會發(fā)生變化
  const onTrigger = useCallback(() => {
    // ...
  }, [someProp])

  // 這個記憶值只會在 onTrigger 函數(shù)更新時更新
  // 如果 onTrigger 不是 useCallback 中的包裝器,則將在每次渲染時重新計算該值
  const memoizedValue = useMemo(() => {
    // ...
  }, [onTrigger])

  // Memoize子組件只會在onTrigger函數(shù)更新時重新渲染
  // 如果 onTrigger 未包裝在 useCallback 中,MemoizedChildComponent 將在每次渲染此組件時重新渲染
  return (<>
    <MemoizedChildComponent onTriggerFn={onTrigger} />
    <button onClick={onTrigger}>Click me</button>
   </>)
}

4、使用 useCallback 防止 useEffect 觸發(fā)

前面的示例展示了如何借助 useCallback 來優(yōu)化渲染,同樣,也可以避免不必要的 useEffect 觸發(fā)。

import { useCallback, useEffect } from 'react'

const Component = ({ someProp }) => {
  // 僅當 someProp 發(fā)生變化時,對 onTrigger 函數(shù)的引用才會發(fā)生變化
  const onTrigger = useCallback(() => {
    // ...
  }, [someProp])

  // useEffect 僅在 onTrigger 函數(shù)更新時運行
  // 如果 onTrigger 未包裝在 useCallback 中,則 useEffect 將在每次此函數(shù)渲染時運行
  useEffect(() => {
    // ...
  }, [onTrigger])

  return <button onClick={onTrigger}>Click me</button>
}

5、當不需要依賴項時,向 useEffect 添加空依賴項

如果 effect 不依賴于任何變量,可以將空依賴項數(shù)組作為 useEffect 的第二個參數(shù)。否則,effect 將在每次渲染時運行。

import { useEffect } from 'react'

const Component = () => {
  useEffect(() => {
    // ...
  }, [])

  return <div>Example</div>
}

這個邏輯也適用于其他 React hook,例如 useCallback 和 useMemo。不過,如果沒有任何依賴項,可能根本不需要使用這些 Hooks。

6、始終將所有依賴項添加到 useEffect 和其他 React Hooks

在處理內(nèi)置 React Hooks 的依賴項項(例如 useEffects 和 useCallback)時,請將所有依賴項添加到依賴項列表(Hooks 的第二個參數(shù))。當省略依賴項時,effect 或回調(diào)可能會使用它的舊值,這通常會導致難以預測的錯誤。

import { useEffect } from 'react'

const Component = () => {
  const [value, setValue] = useState()

  useEffect(() => {
    // 使用 value 變量的代碼

    // 將變量添加到依賴項數(shù)組中,應在此處添加 value 變量
  }, [])

  return <div>{value}</div>
}

那當 useEffect 被觸發(fā)的次數(shù)比希望的次數(shù)多時,如何避免副作用?不幸的是,沒有完美的解決方案。不同的場景需要不同的解決方案。可以嘗試使用 hook 僅運行一次代碼,這有時很有用,但實際上并不是一個值得推薦的解決方案。

大多數(shù)情況下,可以使用 if-case 來解決問題??梢圆榭串斍盃顟B(tài)并從邏輯上決定是否確實需要運行代碼。例如,如果不將 value 值添加為上述 effect 的依賴項的原因是僅在值未定義時運行代碼,則只需在 effect 內(nèi)添加 if 語句即可。

import { useEffect } from 'react'

const Component = () => {
  const [value, setValue] = useState()

  useEffect(() => {
    if (!value) {
      // ...
    }
  }, [value])

  return <div>{value}</div>

其他場景可能更復雜,也許使用 if 語句來防止 effect 多次發(fā)生不太可行。在這種情況下,首先應該確定,真的需要 effect 嗎?在很多情況下,開發(fā)人員在實際上不應該這樣做時卻使用了 effect。

7、不要將外部函數(shù)包裝在 useCallback 中

不需要 useCallback 來調(diào)用外部函數(shù)。只需按原樣調(diào)用外部函數(shù)即可。這使得 React 不必檢查 useCallback 是否需要重新創(chuàng)建,并且使代碼更簡潔。

import { useCallback } from 'react'
import externalFunction from '/services/externalFunction'

const Component = () => {
  // ?
  const handleClick = useCallback(() => {
    externalFunction()
  }, [])

  return <button onClick={handleClick}>Click me</button>
}
import externalFunction from '/services/externalFunction'

const Component = () => {
  // ?
  return <button onClick={externalFunction}>Click me</button>
}

使用 useCallback 的一個用例是回調(diào)調(diào)用多個函數(shù)或讀取或更新內(nèi)部狀態(tài)(例如 useState  hook 中的值或組件傳入的 props 之一)時。

import { useCallback } from 'react'
import { externalFunction, anotherExternalFunction } from '/services'

const Component = ({ passedInProp }) => {
  const [value, setValue] = useState()

  const handleClick = useCallback(() => {
    // 調(diào)用了多個函數(shù)
    externalFunction()
    anotherExternalFunction()

    // 讀取和或設置內(nèi)部值或?qū)傩浴?    setValue(passedInProp)
  }, [passedInProp, value])

  return <button onClick={handleClick}>Click me</button>
}

8、不要將 useMemo 與空依賴數(shù)組一起使用

如果添加了帶有空依賴項數(shù)組的 useMemo,問問自己為什么要這樣做。因為它依賴于組件的狀態(tài)變量而不想添加它?在這種情況下,應該列出所有依賴變量!因為 useMemo 沒有任何依賴項?那就不需要使用 useMemo 了。

import { useMemo } from 'react'

const Component = () => {
  // ?
  const memoizedValue = useMemo(() => {
    return 3 + 5
  }, [])

  return <div>{memoizedValue}</div>
}
// ?
const memoizedValue = 3 + 5

const Component = () => {
  return <div>{memoizedValue}</div>
}

9、不要在其他組件中聲明組件

const Component = () => {

  // ?
  const ChildComponent = () => {
    return <div>child</div>
  }

  return <div><ChildComponent /></div>
}

這樣寫的話,組件內(nèi)聲明的變量將在每次組件呈現(xiàn)時重新聲明。在這種情況下,這意味著每次父級重新渲染時都必須重新創(chuàng)建功能子組件。就必須在每次渲染時實例化一個函數(shù)。React 將無法決定何時進行任何類型的組件優(yōu)化。如果在 ChildComponent 中使用 hooks,它們將在每次渲染時重新啟動。

那該怎么辦呢?只需在父組件之外聲明子組件即可。

const ChildComponent = () => {
    return <div>child</div>
}

const Component = () => {
  return <div><ChildComponent /></div>
}

或者,更好的方式是:

import ChildComponent from 'components/ChildComponent'

const Component = () => {
  return <div><ChildComponent /></div>
}

10、不要在 If 語句中使用 Hook

在React內(nèi)部,Hook的調(diào)用順序是必須固定的,以確保正確地管理組件狀態(tài)和生命周期。如果在if語句內(nèi)部使用Hook,會導致兩個問題:

違反Hook的調(diào)用規(guī)則:根據(jù)React的規(guī)定,Hook應該在每次渲染中按照相同的順序被調(diào)用。當條件發(fā)生變化時,Hook調(diào)用的順序可能會發(fā)生變化,這會破壞React對Hook調(diào)用順序的依賴,導致無法預料的行為和錯誤。

Hook的依賴關(guān)系無效:Hook的工作原理是基于依賴項列表,它可以檢測依賴項的變化,并在需要時重新運行。如果將Hook放在if語句中,它的依賴關(guān)系可能無法正確地捕捉到變化,從而導致狀態(tài)更新或副作用的錯誤。

import { useState } from 'react'

const Component = ({ propValue }) => {
  if (!propValue) {
    const [value, setValue] = useState(propValue)
  }

  return <div>{value}</div>
}

11、使用 useState 而不是變量

在React中,存儲狀態(tài)應該始終使用 React hooks(如useState或useReducer),不要直接將狀態(tài)聲明為組件中的變量。這樣做會導致在每次渲染時重新聲明變量,這意味著React無法像通常一樣對其進行記憶化處理。

import AnotherComponent from 'components/AnotherComponent'

const Component = () => {
  const value = { someKey: 'someValue' }

  return <AnotherComponent value={value} />
}

在上述情況下,依賴于value的AnotherComponent及其相關(guān)內(nèi)容將在每次渲染時重新渲染,即使它們使用memo、useMemo或useCallback進行了記憶化處理。

如果將一個帶有value作為依賴的useEffect添加到組件中,它將在每次渲染時觸發(fā)。因為每次渲染時 value 的JavaScript引用都會不同。

通過使用 React 的useState,React會保留value的相同引用,直到使用setValue進行更新。然后,React 將能夠檢測何時觸發(fā)和何時不觸發(fā) effect ,并重新計算記憶化處理。

import { useState } from 'react'
import AnotherComponent from 'components/AnotherComponent'

const Component = () => {
  const [value, setValue] = useState({ someKey: 'someValue' })

  return <AnotherComponent value={value} />
}

如果只需要一個狀態(tài),在初始化后就不再更新,那么可以將變量聲明在組件外部。這樣 JavaScript 引用將不會改變。

// 如果不需要更新該值,就可以這樣聲明變量
const value = { someKey: 'someValue' }

const Component = () => {
  return <AnotherComponent value={value} />
}

12、return 后不使用 Hook

根據(jù)定義,if語句是有條件執(zhí)行的,“return”關(guān)鍵字也會導致條件 Hook 渲染。

import { useState } from 'react'

const Component = ({ propValue }) => {

  if (!propValue) {
    return null
  }

  // 這個 hook 是有條件的,因為只有當 propValue 存在時才會調(diào)用它
  const [value, setValue] = useState(propValue)

  return <div>{value}</div>
}

條件語句中的 return 語句會使后續(xù)的 Hook 成為有條件的。為了避免這種情況,將所有的 Hook 放在組件的第一個條件渲染之前。也就是說,始終將 Hook 放在組件的頂部。

import { useState } from 'react'

const Component = ({ propValue }) => {
  // 在條件渲染之前放 hooks
  const [value, setValue] = useState(propValue)

  if (!propValue) {
    return null
  }

  return <div>{value}</div>
}

13、讓子組件決定是否應該渲染

在許多情況下應該讓子組件決定是否應該渲染:

import { useState } from 'react'

const ChildComponent = ({ shouldRender }) => {
  return <div>Rendered: {shouldRender}</div>
}

const Component = () => {
  const [shouldRender, setShouldRender] = useState(false)

  return <>
    { !!shouldRender && <ChildComponent shouldRender={shouldRender} /> }
  </>
}

以上是有條件地渲染子組件的常見方法。代碼很好,除了在有很多子組件時有點冗長之外。但根據(jù) ChildComponent 的作用,可能存在更好的解決方案。下面來稍微重寫一下代碼。

import { useState } from 'react'

const ChildComponent = ({ shouldRender }) => {

  if (!shouldRender) {
    return null
  }

  return <div>Rendered: {shouldRender}</div>
}

const Component = () => {
  const [shouldRender, setShouldRender] = useState(false)

  return <ChildComponent shouldRender={shouldRender} />
}

這里重寫了兩個組件,將條件渲染移至子組件中。那條件渲染移至子組件有什么好處?

最大的好處是 React 可以繼續(xù)渲染 ChildComponent,即使它不可見。這意味著,ChildComponent 可以在隱藏時保持其狀態(tài),然后第二次渲染而不會丟失其狀態(tài)。它一直都在那里,只是不可見。

如果組件像第一個代碼那樣停止渲染,則 useState 中保存的狀態(tài)將被重置,并且一旦組件再次渲染,useEffect、useCallback 和 useMemo 都需要重新運行并重新計算新值。

如果代碼會觸發(fā)一些網(wǎng)絡請求或進行一些復雜的計算,那么當組件再次渲染時,這些請求也會運行。同樣,如果將一些表單數(shù)據(jù)存儲在組件的內(nèi)部狀態(tài)中,則每次組件隱藏時都會重置。

14、使用 useReducer 而不是多個 useState

可以使用一個 useReducer 來代替使用多個 useState,這樣寫起來可能比較麻煩,但是這樣既可以避免不必要的渲染,又可以讓邏輯更容易理解。一旦有了 useReducer,向組件添加新邏輯和狀態(tài)就會容易得多。

import { useState } from 'react'

const Component = () => {
  // ?
  const [text, setText] = useState(false)
  const [error, setError] = useState('')
  const [touched, setTouched] = useState(false)

  const handleChange = (event) => {
    const value = event.target.value
    setText(value)

    if (value.length < 6) {
      setError('Too short')
    } else {
      setError('')
    }
  }

  return <>
    {!touched && <div>Write something...</div> }
    <input type="text" value={text} onChange={handleChange} />
    <div>Error: {error}</div>
  </>
}
import { useReducers } from 'react'

const UPDATE_TEXT_ACTION = 'UPDATE_TEXT_ACTION'
const RESET_FORM = 'RESET_FORM'

const getInitialFormState = () => ({
  text: '',
  error: '',
  touched: false
})

const formReducer = (state, action) => {
  const { data, type } = action || {}

  switch (type) {
    case UPDATE_TEXT_ACTION:
      const text = data?.text ?? ''

      return {
        ...state,
        text: text,
        error: text.length < 6,
        touched: true
      }
    case RESET_FORM:
      return getInitialFormState()
    default:
      return state
  }
}

const Component = () => {
  // ?
  const [state, dispatch] = useReducer(formReducer, getInitialFormState());
  const { text, error, touched } = state

  const handleChange = (event) => {
    const value = event.target.value
    dispatch({ type: UPDATE_TEXT_ACTION, text: value})
  }

  return <>
    {!touched && <div>Write something...</div> }
    <input type="text" value={text} onChange={handleChange} />
    <div>Error: {error}</div>
  </>
}

15、將初始狀態(tài)寫為函數(shù)而不是對象

來看看下面的 getInitialFormState 函數(shù):

// 初始狀態(tài)是一個函數(shù)
const getInitialFormState = () => ({
  text: '',
  error: '',
  touched: false
})

const formReducer = (state, action) => {
  // ...
}

const Component = () => {
  const [state, dispatch] = useReducer(formReducer, getInitialFormState());
  // ...
}

這里將將初始狀態(tài)寫成了一個函數(shù),但其實直接使用一個對象也是可以的。

// 初始狀態(tài)是一個對象
const initialFormState = {
  text: '',
  error: '',
  touched: false
}

const formReducer = (state, action) => {
  // ...
}

const Component = () => {
  const [state, dispatch] = useReducer(formReducer, getInitialFormState());
  // ...
}

那為什么不直接寫成對象呢?答案很簡單,避免可變性。在上面的例子中,當initialFormState是一個對象時,我們可能會一不小心就在代碼中的某個地方改變了該對象。如果這樣,當再次使用該變量,例如在重置表單時,將無法恢復初始狀態(tài)。相反,會得到變異的對象。

因此,將初始狀態(tài)轉(zhuǎn)換為返回初始狀態(tài)對象的 getter 函數(shù)是一個很好的做法?;蛘吒玫氖牵褂孟?nbsp;Immer 這樣的庫,它用于避免編寫可變代碼。

16、當組件不應重新渲染時,使用 useRef 而不是 useState

可以通過用 useRef 替換 useState 來優(yōu)化組件渲染。來看下面的例子:

import { useEffect } from 'react'

const Component = () => {
  const [triggered, setTriggered] = useState(false)

  useEffect(() => {
    if (!triggered) {
      setTriggered(true)
      // ...
    }
  }, [triggered])
}

當運行上面的代碼時,組件將在調(diào)用 setTriggered 時重新渲染。在這種情況下,觸發(fā)狀態(tài)變量可能是確保 effect 僅運行一次的一種方法。

由于在這種情況下觸發(fā)變量的唯一用途是跟蹤函數(shù)是否已被觸發(fā),因此不需要組件渲染任何新狀態(tài)。因此,可以將 useState 替換為 useRef,這樣更新時就不會觸發(fā)組件重新渲染。

import { useRef } from 'react'

const Component = () => {
  const triggeredRef = useRef(false)

  useEffect(() => {
    if (!triggeredRef.current) {
      triggeredRef.current = true

      // ...
    }

  }, [])
}

那為什么需要使用 useRef,而不簡單地使用組件外部的變量呢?

const triggered = false

const Component = () => {
  useEffect(() => {
    if (!triggered) {
      triggered = true

      // ...
    }
  }, [])
}

這里需要 useRef 的原因是因為上面的代碼不能以同樣的方式工作!上面的變量只會為 false 一次。如果組件卸載了,當組件再次掛載時,triggered變量仍然會被設置為true,因為triggered變量并沒有綁定到React的生命周期中。當使用 useRef 時,React 將在組件卸載并再次安裝時重置其值。在這種情況下,就可以要使用 useRef。

責任編輯:武曉燕 來源: 前端充電寶
相關(guān)推薦

2017-02-28 21:57:05

React組件

2020-06-03 16:50:24

TypeScriptReact前端

2011-08-18 11:05:21

jQuery

2023-11-29 09:00:55

ReactuseMemo

2014-08-19 10:06:53

IAP

2024-08-21 08:02:47

2012-08-09 09:10:56

代碼審查代碼

2014-06-09 15:50:08

2011-12-21 13:35:39

JavaJFreeChart

2011-12-21 13:52:27

JavaJFreeChart

2015-09-23 09:08:38

java反射

2023-09-11 08:50:03

Maven工具關(guān)系管理

2019-07-22 10:42:11

React組件前端

2023-09-13 08:00:00

JavaScript循環(huán)語句

2010-02-04 11:55:27

ibmdwDB2

2010-07-06 09:07:09

2023-05-15 08:24:46

2013-06-13 09:21:31

RESTful APIRESTfulAPI

2024-09-03 16:28:20

2011-12-21 13:44:33

JavaJFreeChart
點贊
收藏

51CTO技術(shù)棧公眾號