react hook和class的區別有哪些

2022-03-22 13:00:12

區別:1、hooks的寫法比class簡潔;2、hooks的業務程式碼比class更加聚合;3、class元件的邏輯複用通常用render props以及HOC兩種方式,而react hooks提供了自定義hooks來複用邏輯。

本教學操作環境:Windows7系統、react17.0.1版、Dell G3電腦。

react hooks與class元件有哪些區別?下面就來帶大家對比一下react hooks和class元件,聊聊它們的區別。

react-hooks解決的問題

  • 函陣列件中不能擁有自己的狀態(state)。在hooks之前函陣列件是無狀態的,都是通過props來獲取父元件的狀態,但是hooks提供了useState來維護函陣列件內部的狀態。

  • 函陣列件中不能監聽元件的生命週期。useEffect聚合了多個生命週期函數。

  • class元件中生命週期較為複雜(在15版本到16版本的變化大)。

  • class元件邏輯難以複用(HOC,render props)。

hooks對比class的好處(對比)

1、寫法更加的簡潔

我們以最簡單的計數器為例:

class元件

class ExampleOfClass extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }
  handleClick = () => {
    let { count } = this.state
    this.setState({
      count: count+1
    })
  }
  render() {
    const { count } = this.state
    return (
      <div>
        <p>you click { count }</p>
        <button onClick={this.handleClick}>點選</button>
      </div>
    )
  }
}

hooks

function ExampleOfHooks() {
    const [count, setCount] = useState(0)
    const handleClick = () => {
        setCount(count + 1)
    }
    return (
      <div>
        <p>you click { count }</p>
        <button onClick={handleClick}>點選</button>
      </div>
    )
}

可以看到使用hooks的程式碼相比class元件程式碼更加的簡潔、清晰。

2、業務程式碼更加聚合

使用class元件經常會出現一個功能出現在兩個生命週期函數內的情況,這樣分開寫有時候可能會忘記。比如:

let timer = null
componentDidMount() {
    timer = setInterval(() => {
        // ...
    }, 1000)
}
// ...
componentWillUnmount() {
    if (timer) clearInterval(timer)
}

由於新增定時器和清除定時器是在兩個不同的生命週期函數,中間可能會有很多其他的業務程式碼,所以可能會忘記清除定時器,如果在元件解除安裝時沒有新增清楚定時器的函數就可能會造成記憶體漏失、網路一直請求等問題。

但是使用hooks可以讓程式碼更加的集中,方便我們管理,也不容易忘記:

useEffect(() => {
    let timer = setInterval(() => {
        // ...
    }, 1000)
    return () => {
        if (timer) clearInterval(timer)
    }
}, [//...])

3、邏輯複用方便

class元件的邏輯複用通常用render props以及HOC兩種方式。react hooks提供了自定義hooks來複用邏輯。

下面以獲取滑鼠在頁面的位置的邏輯複用為例:

class元件render props方式複用

import React, { Component } from 'react'

class MousePosition extends Component {
  constructor(props) {
    super(props)
    this.state = {
      x: 0,
      y: 0
    }
  }

  handleMouseMove = (e) => {
    const { clientX, clientY } = e
    this.setState({
      x: clientX,
      y: clientY
    })
  }

  componentDidMount() {
    document.addEventListener('mousemove', this.handleMouseMove)
  }

  componentWillUnmount() {
    document.removeEventListener('mousemove', this.handleMouseMove)
  }

  render() {
    const { children } = this.props
    const { x, y } = this.state
    return(
      <div>
        {
          children({x, y})
        }
      </div>
    )
  }

}

// 使用
class Index extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <MousePosition>
        {
          ({x, y}) => {
            return (
              <div>
                <p>x:{x}, y: {y}</p>
              </div>
            )
          }
        }
      </MousePosition>
    )
  }
}

export default Index

自定義hooks方式複用

import React, { useEffect, useState } from 'react'

function usePosition() {
  const [x, setX] = useState(0)
  const [y, setY] = useState(0)

  const handleMouseMove = (e) => {
    const { clientX, clientY } = e
    setX(clientX)
    setY(clientY)
  } 

  useEffect(() => {
    document.addEventListener('mousemove', handleMouseMove)
    return () => {
      document.removeEventListener('mousemove', handleMouseMove)
    }
  })
  return [
    {x, y}
  ]
}

// 使用
function Index() {
  const [position] = usePosition()
  return(
    <div>
      <p>x:{position.x},y:{position.y}</p>
    </div>
  )
}

export default Index

可以很明顯的看出使用hooks對邏輯複用更加的方便,使用的時候邏輯也更加清晰。

hooks常見的一些API使用

1、useState

語法

const [value, setValue] = useState(0)

這種語法方式是ES6的陣列結構,陣列的第一個值是宣告的狀態,第二個值是狀態的改變函數。

每一幀都有獨立的狀態

個人理解針對每一幀獨立的狀態是採用了閉包的方法來實現的。

function Example() {
  const [val, setVal] = useState(0)
  const timeoutFn = () => {
      setTimeout(() => {
        // 取得的值是點選按鈕的狀態,不是最新的狀態
          console.log(val)
      }, 1000)
  }
  return (
      <>
          <p>{val}</p>
          <button onClick={()=>setVal(val+1)}>+</button>
          <button onClick={timeoutFn}>alertNumber</button>
      </>
  )
}

當元件的狀態或者props更新時,該函陣列件會被重新呼叫渲染,並且每一次的渲染都是獨立的都有自己獨立的props以及state,不會影響其他的渲染。

2、useEffect

語法

useEffect(() => {
    //handler function...
    
    return () => {
        // clean side effect
    }
}, [//dep...])

useEffect接收一個回撥函數以及依賴項,當依賴項發生變化時才會執行裡面的回撥函數。useEffect類似於class元件didMount、didUpdate、willUnmount的生命週期函數。

注意點

  • useEffect是非同步的在元件渲染完成後才會執行

  • useEffect的回撥函數只能返回一個清除副作用的處理常式或者不返回

  • 如果useEffect傳入的依賴項是空陣列那麼useEffect內部的函數只會執行一次

3、useMemo、useCallback

useMemo和useCallback主要用於減少元件的更新次數、優化元件效能的。

  • useMemo接收一個回撥函數以及依賴項,只有依賴項變化時才會重新執行回撥函數。

  • useCallback接收一個回撥函數以及依賴項,並且返回該回撥函數的memorize版本,只有在依賴項重新變化時才會重新新的memorize版本。

語法

const memoDate = useMemo(() => data, [//dep...])
const memoCb = useCallback(() => {//...}, [//dep...])

在優化元件效能時針對class元件我們一般使用React.PureComponent,PureComponent會在shouldUpdate進行一次錢比較,判斷是否需要更新;針對函陣列件我們一般使用React.memo。但是在使用react hooks時由於每一次渲染更新都是獨立的(生成了新的狀態),即使使用了React.memo,也還是會重新渲染。

比如下面這種場景,改變子元件的name值後由於父元件更新後每次都會生成新值(addAge函數會改變),所以子元件也會重新渲染。

function Parent() {
  const [name, setName] = useState('cc')
  const [age, setAge] = useState(22)

  const addAge = () => {
    setAge(age + 1)
  }

  return (
    <>
      <p>父元件</p>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <p>age: {age}</p>
      <p>-------------------------</p>
      <Child addAge={addAge} />
    </>
  )
}

const Child = memo((props) => {
  const { addAge } = props
  console.log('child component update')
  return (
    <>
      <p>子元件</p>
      <button onClick={addAge}>click</button>
    </>
  )
})

使用useCallback優化

function Parent() {
  const [name, setName] = useState('cc')
  const [age, setAge] = useState(22)

  const addAge = useCallback(() => {
    setAge(age + 1)
  }, [age])

  return (
    <>
      <p>父元件</p>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <p>age: {age}</p>
      <p>-------------------------</p>
      <Child addAge={addAge} />
    </>
  )
}

const Child = memo((props) => {
  const { addAge } = props
  console.log('child component update')
  return (
    <>
      <p>子元件</p>
      <button onClick={addAge}>click</button>
    </>
  )
})

只有useCallback的依賴性發生變化時,才會重新生成memorize函數。所以當改變name的狀態是addAge不會變化。

4、useRef

useRef類似於react.createRef。

const node = useRef(initRef)

useRef 返回一個可變的 ref 物件,其 current 屬性被初始化為傳入的引數(initRef)

作用在DOM上

const node = useRef(null)
<input ref={node} />

這樣可以通過node.current屬性存取到該DOM元素。

需要注意的是useRef建立的物件在元件的整個生命週期內保持不變,也就是說每次重新渲染函陣列件時,返回的ref 物件都是同一個(使用 React.createRef ,每次重新渲染元件都會重新建立 ref)。

5、useReducer

useReducer類似於redux中的reducer。

語法

const [state, dispatch] = useReducer(reducer, initstate)

useReducer傳入一個計算函數和初始化state,類似於redux。通過返回的state我們可以存取狀態,通過dispatch可以對狀態作修改。

const initstate = 0;
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {number: state.number + 1};
    case 'decrement':
      return {number: state.number - 1};
    default:
      throw new Error();
  }
}
function Counter(){
    const [state, dispatch] = useReducer(reducer, initstate);
    return (
        <>
          Count: {state.number}
          <button onClick={() => dispatch({type: 'increment'})}>+</button>
          <button onClick={() => dispatch({type: 'decrement'})}>-</button>
        </>
    )
}

6、useContext

通過useContext我們可以更加方便的獲取上層元件提供的context。

父元件

import React, { createContext, Children } from 'react'
import Child from './child'

export const MyContext = createContext()

export default function Parent() {

  return (
    <div>
      <p>Parent</p>
      <MyContext.Provider value={{name: 'cc', age: 21}}>
        <Child />
      </MyContext.Provider>
    </div>
  )
}

子元件

import React, { useContext } from 'react'
import { MyContext } from './parent'

export default function Parent() {
  const data = useContext(MyContext) // 獲取父元件提供的context
  console.log(data)
  return (
    <div>
      <p>Child</p>
    </div>
  )
}

使用步驟

  • 父元件建立並匯出context:export const MyContext = createContext()
  • 父元件使用providervalue提供值:<MyContext.provide value={{name: 'cc', age: 22}} />
  • 子元件匯入父元件的context:import { MyContext } from './parent'
  • 獲取父元件提供的值:const data = useContext(MyContext)

不過在多數情況下我們都不建議使用context,因為會增加元件的耦合性。

7、useLayoutEffect

useEffect 在全部渲染完畢後才會執行;useLayoutEffect 會在 瀏覽器 layout之後,painting之前執行,並且會柱塞DOM;可以使用它來讀取 DOM 佈局並同步觸發重渲染。

export default function LayoutEffect() {
  const [color, setColor] = useState('red')
  useLayoutEffect(() => {
      alert(color) // 會阻塞DOM的渲染
  });
  useEffect(() => {
      alert(color) // 不會阻塞
  })
  return (
      <>
        <div id="myDiv" style={{ background: color }}>顏色</div>
        <button onClick={() => setColor('red')}>紅</button>
        <button onClick={() => setColor('yellow')}>黃</button>
      </>
  )
}

上面的例子中useLayoutEffect會在painting之前執行,useEffect在painting之後執行。

hooks讓函陣列件擁有了內部狀態、生命週期,使用hooks讓程式碼更加的簡介,自定義hooks方便了對邏輯的複用,並且擺脫了class元件的this問題;但是在使用hooks時會產生一些閉包問題,需要仔細使用。

【相關推薦:Redis視訊教學

以上就是react hook和class的區別有哪些的詳細內容,更多請關注TW511.COM其它相關文章!