react 使用Hook的一些筆記


Hook特點

1.無需修改組件結構的情況下復用狀態邏輯

2.可將組件中相互關聯的部分拆分成更小的函數,復雜組件將變得更容易理解

3.每一個組件內的函數(包括事件處理函數,effects,定時器或者api調用等等)會捕獲某次渲染中定義的props和state

4.memo緩存組件 ,useMemo緩存值, useCallback緩存函數

5.每次render都有自己的props與state。每次render都有自己的effects。(每一個組件內的函數,包括事件處理函數,effects,定時器或者api調用等等,會捕獲某次渲染中定義的props和state)

6.當我們更新狀態的時候(如setCount(count + 1))React會重新渲染組件,每一次渲染都能拿到獨立的count狀態,這個狀態值是函數中的一個常量

 

useEffect

函數式組件中不存在傳統類組件生命周期的概念 

會捕獲props和state,所以即便在回調函數里,你拿到的還是初始的props和state。如果你想得到最新的值,可以使用ref

count累加的例子(並不是count 的值在 不變 的effect中發生了變化,而是effect函數本身在每一次渲染中都是不相同,React會記住你提供的effect函數,並且會在每次更改作用於DOM並讓瀏覽器繪制屏幕厚去調用它)

effect是如何讀取到最新的count?

我們已經知道count是某個特定渲染中的常量。事件處理函數看到的是屬於它那次特定渲染中的count狀態值。對於effects也同樣如此

React會記住你提供的effect函數,並且會在每次更改作用於DOM,並讓瀏覽器繪制屏幕后去調用它

 useMemo

跟vue中的computed計算屬性很類似

主要用來解決使用React hooks產生的無用渲染的性能問題,使用function的形式來聲明組件,失去了shouldCompnentUpdate在組件更新之前,這個聲明周期,也就是我們沒有辦法

通過組件更新前條件來決定組件是否更新,而且在函數組件中,也補在區分mount和update兩個狀態,這意味着函數組件的每一次調用都會執行內部的所有邏輯,就帶來了非常大的性能損耗

 

React.memo

把組件傳遞給memo之后,就會返回一個新的組件,新組件有了一個功能如果屬性不變,則不重新渲染

<div className="App">
      <h1>{ title }</h1>
      <button onClick={() => setTitle("title 已經改變")}>改名字</button>
      <Child name="桃桃" />
    </div>

 

父組件重新渲染了,並且子組件也重新渲染了。你可能會想,傳遞給 Child 組件的 props 沒有變,要是 Child 組件不重新渲染就好了,為什么會這么想呢?

我們假設 Child 組件是一個非常大的組件,渲染一次會消耗很多的性能,那么我們就應該盡量減少這個組件的渲染,否則就容易產生性能問題,所以子組件如果在 props 沒有變化的情況下,就算父組件重新渲染了,子組件也不應該渲染。

那么我們怎么才能做到在 props 沒有變化的時候,子組件不渲染呢?

答案就是用 React.memo 在給定相同 props 的情況下渲染相同的結果,並且通過記憶組件渲染結果的方式來提高組件的性能表現。

export default React.memo(Child) 

 

通過 React.memo 包裹的組件在 props 不變的情況下,這個被包裹的組件是不會重新渲染的,也就是說上面那個例子,在我點擊改名字之后,僅僅是 title 會變,但是 Child 組件不會重新渲染(表現出來的效果就是 Child 里面的 log 不會在控制台打印出來),會直接復用最近一次渲染的結果。

這個效果基本跟類組件里面的 PureComponent效果極其類似,只是前者用於函數組件,后者用於類組件。

 

function App() {
  const [title, setTitle] = useState("這是一個 title");
  const [subtitle, setSubtitle] = useState("我是一個副標題");
  
  const callback = () => {
    setTitle("標題改變了");
  };
  
  return (
    <div className="App">
      <h1>{title}</h1>
      <h2>{subtitle}</h2>
      <button onClick={() => setSubtitle("副標題改變了")}>改副標題</button>
      <Child onClick={callback} name="桃桃" />
    </div>
  );
  
}

在函數式組件每次重新渲染,函數組件都會會重頭開始重新執行,那么這兩次創建的callback函數肯定發生了改變,所以導致了子組件重新渲染

 

React.memo和useCallBack都是為了減少重新render的次數。對於如何減少計算的量,就是userMemo來做 

 

 

 

 

 

 

 

 

 

 

 

useState

當父組件set的時候,父組件里面有包含子組件,子組件會被重新渲染

解決:用memo包含子組件。(PureComponent也可以用來是否重新渲染,走的是shouldComponentUpdate,但是適用於 類組件,函數式組件用memo)

 memo

1.針對的是一個組件的渲染是否重用,來優化函數組件重渲染行為,
當傳入屬性值都不變的情況下,就不會觸發組件的重新渲染(父組件中傳遞給子組件useState的值變化時,子組件必會渲染)
2.當父組件給子組件傳遞一個函數時(<Foo cb={()=> {}} />),這時候父組件useState時,Foo都會重新渲染
(React.memo類似於React.PureComponent能對props做淺比較,防止無效的重復渲染)
(useCallback用戶緩存函數,繁殖因屬性更新時生成新的函數導致子組件重復渲染)

useMemo
1.一個函數是否重復執行,可以當useMemo當做性能優化手段
2.useMemo需要返回值,返回值直接參與渲染,因此useMemo是在渲染期間完成的



useReducer

局部狀態不推薦使用useReducer,會導致函數內部狀態過於復雜難以閱讀

userReducer建議和useContext一起使用

 

import React from 'react'

const countReducer = (state, newState) => newState(state)

function Counter({step = 1, initialCount = 10}) {
  const [count, setCount] = React.useReducer(countReducer, initialCount)
  const increment = () => setCount(c => c + step)
  return <button onClick={increment}>{count}</button>
}

function Usage() {
  return <Counter />
}

 

import React, {useReducer} from 'react'

const countReducer = (state, newState) => {
  console.log('state, newState------', typeof state, typeof newState)
  return state + newState
}

function Counter({step = 4, initialCount = 10}) {
  
  const [count, setCount] = useReducer(countReducer, initialCount)
  
  const increment = () => {
    setCount(step)
  }
  
  return <div>
    <button onClick={increment}>按鈕{count}</button>
  </div>
}

function Usage() {
  return <Counter />
}
import React from 'react'

const countReducer = (state, action) => ({
  ...state,
  ...(typeof action === 'function' ? action(state) : action),
})

function Counter({initialCount = 0, step = 1}) {
  const [state, setState] = React.useReducer(countReducer, {
    count: initialCount,
  })
  const {count} = state
  const increment = () => setState(currentState => ({count: currentState.count + 1}))
  return <button onClick={increment}>{count}</button>
}

function Usage() {
  return <Counter />
}
import React from 'react'

function countReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT': {
      return {count: state.count + 1}
    }
    default: {
      throw new Error(`Unsupported action type: ${action.type}`)
    }
  }
}

function Counter({initialCount = 0, step = 1}) {
  const [state, dispatch] = React.useReducer(countReducer, {
    count: initialCount,
  })
  const {count} = state
  const increment = () => dispatch({type: 'INCREMENT'})
  return <button onClick={increment}>{count}</button>
}

function Usage() {
  return <Counter />
}

 

useCallback 

所有Function Component內函數必須使用React.useCallback包裹,以保證准確性與性能

 

 

 

 

useContext

在上下文中緩存響應數據 (useContext: Caching response data in context)

const PokemonCacheContext = React.createContext()

function pokemonCacheReducer(state, action) {
  switch (action.type) {
    case 'ADD_POKEMON': {
      return {...state, [action.pokemonName]: action.pokemonData}
    }
    default: {
      throw new Error(`Unhandled action type: ${action.type}`)
    }
  }
}

function PokemonCacheProvider(props) {
  const value = React.useReducer(pokemonCacheReducer, {})
  console.log('value------', value)
  return <PokemonCacheContext.Provider value={value} {...props} />
}

 

在父組件下兩個子組件,當中一個子組件修改了父組件的值,對應組件接收到值的變化

import React from 'react'

const CountContext = React.createContext()

function CountProvider(props) {
  const [count, setCount] = React.useState(0)
  const value = [count, setCount]
  // could also do it like this:
  // const value = React.useState(0)
  return <CountContext.Provider value={value} {...props} />
}

function CountDisplay() {
  const [count] = React.useContext(CountContext)
  return <div>{`The current count is ${count}`}</div>
}

function Counter() {
  const [, setCount] = React.useContext(CountContext)
  const increment = () => setCount(c => c + 10)
  return <button onClick={increment}>Increment count</button>
}

function Usage() {
  return (
    <div>
      <CountProvider>
        <CountDisplay />
        <Counter />
      </CountProvider>
    </div>
  )
}
Usage.title = 'useContext: simple Counter'

export default Usage

 

useImperativeHandle

回到top

import React, {useImperativeHandle, forwardRef} from 'react'

// 🐨 wrap this in a React.forwardRef and accept `ref` as the second argument

const MessagesDisplay = forwardRef(function MessagesDisplay({messages}, ref) {
  const containerRef = React.useRef()
  React.useLayoutEffect(() => {
    scrollToBottom()
  })
  
  useImperativeHandle(ref, () => ({
    scrollToTop,
    scrollToBottom,
  }))
  
  // 💰 you're gonna want this as part of your imperative methods
  function scrollToTop() {
    containerRef.current.scrollTop = 0
  }
  function scrollToBottom() {
    containerRef.current.scrollTop = containerRef.current.scrollHeight
  }
  
  // 🐨 call useImperativeHandle here with your ref and a callback function
  // that returns an object with scrollToTop and scrollToBottom
  
  return (
    <div
      ref={containerRef}
      role="log"
      style={{
        height: 300,
        overflowY: 'scroll',
        width: 300,
        outline: '1px solid black',
        paddingLeft: 10,
        paddingRight: 10,
      }}
    >
      {messages.map(message => (
        <div key={message.id}>
          <strong>{message.author}</strong>: <span>{message.content}</span>
          <hr />
        </div>
      ))}
    </div>
  )
})

 

注意點:

1.useEffect

useEffect(async () => )

 

react首次渲染和之后的每次渲染都會調用一遍useEffect函數,而之前我們要用兩個生命周期函數分別表示首次渲染(componentDisMount)和更新導致的重新渲染(componentDidUpdte)

useEffect中定義的函數執行不會阻塞瀏覽器更新視圖,也急速說這些函數是異步執行的,而componentDidMount和componentDidUpdate中的代碼都是同步執行

 
         
function Counter() { const [count, setCount] = useState(0); return ( <div>  <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>  Click me  </button>  </div> ); }
上面例子中,count僅是一個數字而已,它不是神奇的 databinding,watcher,proxy或者其他東西,它就是一個普通的數字 const count = 0
我們的組件第一次渲染的時候,從useState()拿到count的初始值0,當我們調用setCount(1),React會再次渲染組件,這次count是1,如此等等

首先,我們聲明了一個狀態變量 count,將它的初始值設為0,然后我們告訴react,我們的這個組件有一個副作用。給 useEffecthook傳了一個匿名函數,這個匿名函數就是我們的副作用。在這里我們打印了一句話,當然你也可以手動的去修改一個 DOM元素。當React要渲染組件時,它會記住用到的副作用,然后執行一次。等Reat更新了State狀態時,它再一次執行定義的副作用函數。

//
During first render function Counter() { // ... useEffect( // Effect function from first render () => { document.title = `You clicked ${0} times`; } ); // ... } // After a click, our function is called again function Counter() { // ... useEffect( // Effect function from second render () => { document.title = `You clicked ${1} times`; } ); // ... } // After another click, our function is called again function Counter() { // ... useEffect( // Effect function from third render () => { document.title = `You clicked ${2} times`; } ); // .. }

React會記住你提供的effect函數,並且會在每次更改作用於DOM並讓瀏覽器繪制屏幕后去調用它。不過在class中的this state並不是這樣運行的,

所以雖然我們說的是一個 effect(這里指更新document的title),但其實每次渲染都是一個不同的函數 — 並且每個effect函數“看到”的props和state都來自於它屬於的那次特定渲染。

 

計數器版本模擬class中的行為

 
         
function App() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);

useEffect(() => {
// Set the mutable latest value
latestCount.current = count;
setTimeout(() => {
// Read the mutable latest value
console.log(`You clicked ${latestCount.current} times`);
}, 3000);
})

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
 

 

每秒遞增的計數器,可能會有如下代碼

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{count}</h1>;
}

在第一次渲染中,count0。因此,setCount(count + 1)在第一次渲染中等價於setCount(0 + 1)

既然我們設置了[]依賴,effect不會再重新運行,它后面每一秒都會調用setCount(0 + 1) (依賴沒有變,所以不會再次運行effect。)

 

2.mapDispatchToProps里不要使用第二個參數

3.為了避免子組件發生不必要的re-render,一些表單提交handleSubmit其實也應該用useCallback包裹,

useCallback會返回被它包裹的函數memeorized版本,只要依賴項不變,memorize的函數就不會更新

const handleSubmit = useCallback(fieldValues => {
        // 在組件內使用dispatch
        dispatch(submitFormData(fieldValues))
          .then(res => {
            history.push('/home');
          });
})


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM