成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

React最佳實踐

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

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

1、在組件外部聲明CSS

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

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

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

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

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

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

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

2、使用 useCallback 防止函數重新創建

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

import { useCallback } from 'react'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4、使用 useCallback 防止 useEffect 觸發

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

import { useCallback, useEffect } from 'react'

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

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

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

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

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

import { useEffect } from 'react'

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

  return <div>Example</div>
}

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

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

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

import { useEffect } from 'react'

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

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

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

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

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

大多數情況下,可以使用 if-case 來解決問題。可以查看當前狀態并從邏輯上決定是否確實需要運行代碼。例如,如果不將 value 值添加為上述 effect 的依賴項的原因是僅在值未定義時運行代碼,則只需在 effect 內添加 if 語句即可。

import { useEffect } from 'react'

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

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

  return <div>{value}</div>

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

7、不要將外部函數包裝在 useCallback 中

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

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 的一個用例是回調調用多個函數或讀取或更新內部狀態(例如 useState  hook 中的值或組件傳入的 props 之一)時。

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

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

  const handleClick = useCallback(() => {
    // 調用了多個函數
    externalFunction()
    anotherExternalFunction()

    // 讀取和或設置內部值或屬性。
    setValue(passedInProp)
  }, [passedInProp, value])

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

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

如果添加了帶有空依賴項數組的 useMemo,問問自己為什么要這樣做。因為它依賴于組件的狀態變量而不想添加它?在這種情況下,應該列出所有依賴變量!因為 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>
}

這樣寫的話,組件內聲明的變量將在每次組件呈現時重新聲明。在這種情況下,這意味著每次父級重新渲染時都必須重新創建功能子組件。就必須在每次渲染時實例化一個函數。React 將無法決定何時進行任何類型的組件優化。如果在 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內部,Hook的調用順序是必須固定的,以確保正確地管理組件狀態和生命周期。如果在if語句內部使用Hook,會導致兩個問題:

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

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

import { useState } from 'react'

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

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

11、使用 useState 而不是變量

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

import AnotherComponent from 'components/AnotherComponent'

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

  return <AnotherComponent value={value} />
}

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

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

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

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

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

  return <AnotherComponent value={value} />
}

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

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

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

12、return 后不使用 Hook

根據定義,if語句是有條件執行的,“return”關鍵字也會導致條件 Hook 渲染。

import { useState } from 'react'

const Component = ({ propValue }) => {

  if (!propValue) {
    return null
  }

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

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

條件語句中的 return 語句會使后續的 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} /> }
  </>
}

以上是有條件地渲染子組件的常見方法。代碼很好,除了在有很多子組件時有點冗長之外。但根據 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 可以繼續渲染 ChildComponent,即使它不可見。這意味著,ChildComponent 可以在隱藏時保持其狀態,然后第二次渲染而不會丟失其狀態。它一直都在那里,只是不可見。

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

如果代碼會觸發一些網絡請求或進行一些復雜的計算,那么當組件再次渲染時,這些請求也會運行。同樣,如果將一些表單數據存儲在組件的內部狀態中,則每次組件隱藏時都會重置。

14、使用 useReducer 而不是多個 useState

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

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、將初始狀態寫為函數而不是對象

來看看下面的 getInitialFormState 函數:

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

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

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

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

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

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

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

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

因此,將初始狀態轉換為返回初始狀態對象的 getter 函數是一個很好的做法。或者更好的是,使用像 Immer 這樣的庫,它用于避免編寫可變代碼。

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

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

import { useEffect } from 'react'

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

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

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

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

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。

責任編輯:武曉燕 來源: 前端充電寶
相關推薦

2017-02-28 21:57:05

React組件

2020-06-03 16:50:24

TypeScriptReact前端

2025-05-12 01:33:00

異步函數Promise

2011-08-18 11:05:21

jQuery

2015-09-23 09:08:38

java反射

2011-12-21 13:52:27

JavaJFreeChart

2023-09-11 08:50:03

Maven工具關系管理

2023-11-29 09:00:55

ReactuseMemo

2014-08-19 10:06:53

IAP

2012-08-09 09:10:56

代碼審查代碼

2024-08-21 08:02:47

2014-06-09 15:50:08

2011-12-21 13:35:39

JavaJFreeChart

2023-09-13 08:00:00

JavaScript循環語句

2019-07-22 10:42:11

React組件前端

2015-09-15 10:44:13

DockerDocker實踐企業IT

2016-12-27 08:49:55

API設計策略

2009-07-01 17:44:46

Servlet和JSP

2014-12-17 09:46:30

AndroidListView最佳實踐

2012-11-30 10:29:47

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 九九热在线视频观看这里只有精品 | 欧美a级成人淫片免费看 | 欧美成年人视频在线观看 | 欧美精品在线一区 | 亚洲女人的天堂 | 国产精品久久久久久久久久久久 | 欧美簧片 | 国产美女一区二区 | 久久蜜桃av一区二区天堂 | 亚洲精品大全 | 国产在线精品一区二区三区 | 黑人一级黄色大片 | 天天操夜夜操 | 欧美在线日韩 | 日韩精品一区二区三区中文在线 | www.亚洲.com | 精品一二区 | 91精品国产综合久久小仙女图片 | 最近日韩中文字幕 | 91资源在线 | 亚洲图片一区二区三区 | 久久国产成人精品国产成人亚洲 | 综合二区 | 日本精品视频在线观看 | 亚洲国产精品久久人人爱 | 成人激情视频免费观看 | 色综合色综合色综合 | 麻豆av在线免费观看 | 天天射色综合 | 成人性视频免费网站 | 国产二区视频 | 欧美三级视频在线观看 | 精品欧美 | 亚洲精品一区二区另类图片 | 欧美日韩国产精品一区 | 综合色久| 超碰520| 国产91在线视频 | 久久久五月天 | 99精品网站 | 性色av一区|