好想用Typescript+React hooks開發啊!(嘴對嘴解釋)

本文直接靈感:終於搞懂 React Hooks了!!!!!
這裏是個人 github/blog 地址,若有幫助,賞個 star~react

看人家 Typescript 和 React hooks 耍的溜的飛起,好羨慕啊~🥺
那來吧,這篇爽文從腦袋到jio乾地教你如何使用這兩大利器開始閃亮開發!✨webpack

image.png

課前預知

🌸我以爲比較好的學習方式就是跟着所講的內容自行實現一遍,因此先啓個項目唄~git

npx create-react-app hook-ts-demo --template typescript
複製代碼

src/App.tsx 內引用咱們的案例組件,在 src/example.tsx 寫咱們的案例組件。github

🌸函數式組件的使用~ 咱們能夠經過如下方式使用有類型約束的函數式組件:web

import React from 'react'

type UserInfo = {
  name: string,
  age: number,
}

export const User = ({ name, age }: UserInfo) => {
  return (
    <div className="App"> <p>{ name }</p> <p>{ age }</p> </div>
  )
}

const user = <User name='vortesnail' age={25} /> 複製代碼

也能夠經過如下方式使用有類型約束的函數式組件:typescript

import React from 'react'

type UserInfo = {
  name: string,
  age: number,
}

export const User:React.FC<UserInfo> = ({ name, age }) => {
  return (
    <div className="User"> <p>{ name }</p> <p>{ age }</p> </div>
  )
}

const user = <User name='vortesnail' age={25} /> 複製代碼

上述代碼中不一樣之處在於:redux

export const User = ({ name, age }: UserInfo) => {}
export const User:React.FC<UserInfo> = ({ name, age }) => {}
複製代碼

使用函數式組件時須要將組件申明爲React.FC類型,也就是 Functional Component 的意思,另外props須要申明各個參數的類型,而後經過泛型傳遞給React.FC緩存

雖然兩種方式都差很少,但我我的更喜歡使用 React.FC 的方式來建立個人有類型約束的函數式組件,它還支持 children 的傳入,即便在咱們的類型中並無定義它:性能優化

export const User:React.FC<UserInfo> = ({ name, age, children }) => {
  return (
    <div className="User"> <p>{ name }</p> <p>{ age }</p> <div> { children } </div> </div>
  )
}

const user = <User name='vortesnail' age={25}>I am children text!</User>
複製代碼

咱們也並不須要把全部參數都顯示地解構:bash

export const User:React.FC<UserInfo> = (props) => {
  return (
    <div className="User"> <p>{ props.name }</p> <p>{ props.age }</p> <div> { /* 仍能夠拿到 children */ } { props.children } </div> </div>
  )
}

const user = <User name='vortesnail' age={25}>I am children text!</User>
複製代碼

好了,咱們暫時知道上面這麼多,就能夠開始使用咱們的 hooks 了~

我將從三個點闡述如何結合 typescript 使用咱們的 hooks :

  • 爲啥使用❓
  • 怎麼使用🛠
  • 場景例舉📖

useState

爲啥使用useState?

可讓函數式組件擁有狀態管理特性,相似 class 組件中的 this.state 和 this.setState ,可是更加簡潔,不用頻繁的使用 this 。

怎麼使用useState?

const [count, setCount] = useState<number>(0)
複製代碼

場景舉例

1.參數爲基本類型時的常規使用:
import React, { useState } from 'react'

const Counter:React.FC<{ initial: number }> = ({ initial = 0 }) => {
  const [count, setCount] = useState<number>(initial)

  return (
    <div> <p>Count: {count}</p> <button onClick={() => setCount(count+1)}>加</button> <button onClick={() => setCount(count-1)}>減</button> </div>
  )
}

export default Counter
複製代碼
2.參數爲對象類型時的使用:
import React, { useState } from 'react'

type ArticleInfo = {
  title: string,
  content: string
}

const Article:React.FC<ArticleInfo> = ({ title, content }) => {
  const [article, setArticle] = useState<ArticleInfo>({ title, content })

  return (
    <div> <p>Title: { article.title }</p> <section>{ article.content }</section> <button onClick={() => setArticle({ title: '下一篇', content: '下一篇的內容', })}> 下一篇 </button> </div>
  )
}

export default Article
複製代碼

在咱們的參數爲對象類型時,須要特別注意的是, setXxx 並不會像 this.setState 合併舊的狀態,它是徹底替代了舊的狀態,因此咱們要實現合併,能夠這樣寫(雖然咱們以上例子不須要):

setArticle({
  title: '下一篇',
  content: '下一篇的內容',
  ...article
})
複製代碼

useEffect

爲啥使用useEffect?

你能夠把 useEffect 看作 componentDidMount , componentDidUpdate 和 componentWillUnmount 這三個函數的組合。

怎麼使用useEffect?

useEffect(() => {
  ...
  return () => {...}
},[...])
複製代碼

場景舉例

1.每當狀態改變時,都要從新執行 useEffect 的邏輯:
import React, { useState, useEffect } from 'react'

let switchCount: number = 0

const User = () => {
  const [name, setName] = useState<string>('')
  useEffect(() => {
    switchCount += 1
  })

  return (
    <div> <p>Current Name: { name }</p> <p>switchCount: { switchCount }</p> <button onClick={() => setName('Jack')}>Jack</button> <button onClick={() => setName('Marry')}>Marry</button> </div>
  )
}

export default User
複製代碼
2.即便每次狀態都改變,也只執行第一次 useEffect 的邏輯:
useEffect(() => {
  switchCount += 1
}, [])
複製代碼
3.根據某個狀態是否變化來決定要不要從新執行:
const [value, setValue] = useState<string>('I never change')
useEffect(() => {
  switchCount += 1
}, [value])
複製代碼

由於 value 咱們不會去任何地方改變它的值,因此在末尾加了 [value] 後, useEffect 內的邏輯也只會執行第一次,至關於在 class 組件中執行了 componentDidMount ,後續的 shouldComponentUpdate 返回所有是 false 。

4.組件卸載時處理一些內存問題,好比清除定時器、清除事件監聽:
useEffect(() => {
  const handler = () => {
    document.title = Math.random().toString()
  }

  window.addEventListener('resize', handler)

  return () => {
    window.removeEventListener('resize', handler)
  }
}, [])
複製代碼

useRef

爲啥使用useRef?

它不只僅是用來管理 DOM ref 的,它還至關於 this , 能夠存聽任何變量,很好的解決閉包帶來的不方便性。

怎麼使用useRef?

const [count, setCount] = useState<number>(0)
const countRef = useRef<number>(count)
複製代碼

場景舉例

1.閉包問題:

想一想看,咱們先點擊  按鈕 3 次,再點 彈框顯示 1次,再點  按鈕 2 次,最終 alert 會是什麼結果?

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

const Counter = () => {
  const [count, setCount] = useState<number>(0)

  const handleCount = () => {
    setTimeout(() => {
      alert('current count: ' + count)
    }, 3000);
  }

  return (
    <div> <p>current count: { count }</p> <button onClick={() => setCount(count + 1)}>加</button> <button onClick={() => handleCount()}>彈框顯示</button> </div>
  )
}

export default Counter
複製代碼

結果是彈框內容爲 current count: 3 ,爲何?

當咱們更新狀態的時候, React 會從新渲染組件, 每一次渲染都會拿到獨立的 count 狀態,  並從新渲染一個  handleCount 函數.  每個 handleCount 裏面都有它本身的 count 。

** 那如何顯示最新的當前 count 呢?

const Counter = () => {
  const [count, setCount] = useState<number>(0)
  const countRef = useRef<number>(count)

  useEffect(() => {
    countRef.current = count
  })

  const handleCount = () => {
    setTimeout(() => {
      alert('current count: ' + countRef.current)
    }, 3000);
  }

  //...
}

export default Counter
複製代碼
2.由於變動 .current 屬性不會引起組件從新渲染,根據這個特性能夠獲取狀態的前一個值:
const Counter = () => {
  const [count, setCount] = useState<number>(0)
  const preCountRef = useRef<number>(count)

  useEffect(() => {
    preCountRef.current = count
  })

  return (
    <div> <p>pre count: { preCountRef.current }</p> <p>current count: { count }</p> <button onClick={() => setCount(count + 1)}>加</button> </div>
  )
}
複製代碼

咱們能夠看到,顯示的老是狀態的前一個值:

image.png

3.操做 Dom 節點,相似 createRef():
import React, { useRef } from 'react'

const TextInput = () => {
  const inputEl = useRef<HTMLInputElement>(null)

  const onFocusClick = () => {
    if(inputEl && inputEl.current) {
      inputEl.current.focus()
    } 
  }

  return (
    <div> <input type="text" ref={inputEl}/> <button onClick={onFocusClick}>Focus the input</button> </div> ) } export default TextInput 複製代碼

useMemo

爲啥使用useMemo?

useEffect 能夠知道,能夠經過向其傳遞一些參數來影響某些函數的執行。 React 檢查這些參數是否已更改,而且只有在存在差別的狀況下才會執行此。

useMemo 作相似的事情,假設有大量方法,而且只想在其參數更改時運行它們,而不是每次組件更新時都運行它們,那就可使用 useMemo 來進行性能優化。

記住,傳入 useMemo 的函數會在渲染期間執行。請不要在這個函數內部執行與渲染無關的操做,諸如反作用這類的操做屬於 useEffect 的適用範疇,而不是 useMemo 。

怎麼使用useMemo?

function changeName(name) {
  return name + '給name作點操做返回新name'
}

const newName = useMemo(() => {
	return changeName(name)
}, [name])
複製代碼

場景舉例

1.常規使用,避免重複執行不必的方法:

咱們先來看一個很簡單的例子,如下是還未使用 useMemo 的代碼:

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

// 父組件
const Example = () => {
  const [time, setTime] = useState<number>(0)
  const [random, setRandom] = useState<number>(0)

  return (
    <div> <button onClick={() => setTime(new Date().getTime())}>獲取當前時間</button> <button onClick={() => setRandom(Math.random())}>獲取當前隨機數</button> <Show time={time}>{random}</Show> </div>
  )
}

type Data = {
  time: number
}

// 子組件
const Show:React.FC<Data> = ({ time, children }) => {
  function changeTime(time: number): string {
    console.log('changeTime excuted...')
    return new Date(time).toISOString()
  }

  return (
    <div> <p>Time is: { changeTime(time) }</p> <p>Random is: { children }</p> </div>
  )
}

export default Example
複製代碼

在這個例子中,不管你點擊的是 獲取當前時間 按鈕仍是 獲取當前隨機數 按鈕, <Show /> 這個組件中的方法 changeTime 都會執行。

但事實上,點擊 獲取當前隨機數 按鈕改變的只會是 children 這個參數,但咱們的 changeTime 也會由於子組件的從新渲染而從新執行,這個操做是很不必的,消耗了無關的性能。

使用 useMemo 改造咱們的 <Show /> 子組件:

const Show:React.FC<Data> = ({ time, children }) => {
  function changeTime(time: number): string {
    console.log('changeTime excuted...')
    return new Date(time).toISOString()
  }

  const newTime: string = useMemo(() => {
    return changeTime(time)
  }, [time])

  return (
    <div> <p>Time is: { newTime }</p> <p>Random is: { children }</p> </div>
  )
}
複製代碼

這個時候只有點擊 獲取當前時間 纔會執行 changeTime 這個函數,而點擊 獲取當前隨機數 已經不會觸發該函數執行了。

2.你可能會好奇, useMemo 能作的難道不能用 useEffect 來作嗎?

答案是否認的!若是你在子組件中加入如下代碼:

const Show:React.FC<Data> = ({ time, children }) => {
	//...
  
  useEffect(() => {
    console.log('effect function here...')
  }, [time])

  const newTime: string = useMemo(() => {
    return changeTime(time)
  }, [time])
  
	//...
}
複製代碼

你會發現,控制檯會打印以下信息:

> changeTime excuted...
> effect function here...
複製代碼

正如咱們一開始說的:傳入 useMemo 的函數會在渲染期間執行。 在此不得不提 React.memo ,它的做用是實現整個組件的 Pure 功能:

const Show:React.FC<Data> = React.memo(({ time, children }) => {...}
複製代碼

因此簡單用一句話來歸納 useMemo 和 React.memo 的區別就是:前者在某些狀況下不但願組件對全部 props 作淺比較,只想實現局部 Pure 功能,即只想對特定的 props 作比較,並決定是否局部更新。

useCallback

爲啥使用useCallback?

useMemo 和 useCallback 接收的參數都是同樣,都是在其依賴項發生變化後才執行,都是返回緩存的值,區別在於 useMemo 返回的是函數運行的結果, useCallback 返回的是函數。

useCallback(fn, deps) 至關於 useMemo(() => fn, deps)

怎麼使用useCallback?

function changeName(name) {
  return name + '給name作點操做返回新name'
}

const getNewName = useMemo(() => {
  return changeName(name)
}, [name])
複製代碼

場景舉例

將以前 useMemo 的例子,改一會兒組件如下地方就OK了:

const Show:React.FC<Data> = ({ time, children }) => {
  //...
  const getNewTime = useCallback(() => {
    return changeTime(time)
  }, [time])

  return (
    <div> <p>Time is: { getNewTime() }</p> <p>Random is: { children }</p> </div>
  )
}
複製代碼

useReducer

爲何使用useReducer?

有沒有想過你在某個組件裏寫了不少不少的 useState 是什麼觀感?好比如下:

const [name, setName] = useState<string>('')
const [islogin, setIsLogin] = useState<boolean>(false)
const [avatar, setAvatar] = useState<string>('')
const [age, setAge] = useState<number>(0)
//...
複製代碼

怎麼使用useReducer?

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

type StateType = {
  count: number
}

type ActionType = {
  type: 'reset' | 'decrement' | 'increment'
}

const initialState = { count: 0 }

function reducer(state: StateType, action: ActionType) {
  switch (action.type) {
    case 'reset':
      return initialState
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    default:
      return state
  }
}

function Counter({ initialCount = 0}) {
  const [state, dispatch] = useReducer(reducer, { count: initialCount })

  return (
    <div> Count: {state.count} <button onClick={() => dispatch({ type: 'reset' })}>Reset</button> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div>
  )
}

export default Counter
複製代碼

場景舉例:

useContext 結合代替 Redux 方案,往下閱讀。

useContext

爲啥使用useContext?

簡單來講 Context 的做用就是對它所包含的組件樹提供全局共享數據的一種技術。

怎麼使用useContext?

export const ColorContext = React.createContext({ color: '#1890ff' })
const { color } = useContext(ColorContext)
// 或
export const ColorContext = React.createContext(null)
<ColorContext.Provider value='#1890ff'>
  <App /> </ColorContext.Provider> // App 或如下的全部子組件均可拿到 value const color = useContext(ColorContext) // '#1890ff' 複製代碼

場景舉例

1.根組件註冊,全部子組件均可拿到註冊的值:
import React, { useContext } from 'react'

const ColorContext = React.createContext<string>('')

const App = () => {
  return (
    <ColorContext.Provider value='#1890ff'> <Father /> </ColorContext.Provider> ) } const Father = () => { return ( <Child /> ) } const Child = () => { const color = useContext(ColorContext) return ( <div style={{ backgroundColor: color }}>Background color is: { color }</div> ) } export default App 複製代碼
2.配合 useReducer 實現 Redux 的代替方案:
import React, { useReducer, useContext } from 'react'

const UPDATE_COLOR = 'UPDATE_COLOR'

type StateType = {
  color: string
}

type ActionType = {
  type: string,
  color: string
}

type MixStateAndDispatch = {
  state: StateType,
  dispatch?: React.Dispatch<ActionType>
}

const reducer = (state: StateType, action: ActionType) => {
  switch(action.type) {
    case UPDATE_COLOR:
      return { color: action.color }
    default:
      return state  
  }
}

const ColorContext = React.createContext<MixStateAndDispatch>({
  state: { color: 'black' },
})

const Show = () => {
  const { state, dispatch } = useContext(ColorContext)
  return (
    <div style={{ color: state.color }}> 當前字體顏色爲: {state.color} <button onClick={() => dispatch && dispatch({type: UPDATE_COLOR, color: 'red'})}>紅色</button> <button onClick={() => dispatch && dispatch({type: UPDATE_COLOR, color: 'green'})}>綠色</button> </div>
  )
}

const Example = ({ initialColor = '#000000' }) => {
  const [state, dispatch] = useReducer(reducer, { color: initialColor })
  return (
    <ColorContext.Provider value={{state, dispatch}}> <div> <Show /> <button onClick={() => dispatch && dispatch({type: UPDATE_COLOR, color: 'blue'})}>藍色</button> <button onClick={() => dispatch && dispatch({type: UPDATE_COLOR, color: 'lightblue'})}>輕綠色</button> </div> </ColorContext.Provider> ) } export default Example 複製代碼

以上此方案是值得好好思索的,特別是由於 TypeScript 而致使的類型約束! 固然,若是有更好的解決方案,但願有大佬提出來,我也能夠多學習學習~

結語

最近也是看了許多好文章,多謝各位掘金的大佬的無私奉獻,本篇文章的靈感來源也是最近蠻火的一篇文章:

終於搞懂 React Hooks了!!!!!

這篇文章寫的通俗易懂,可是沒有涉及到在 Typescript 中的使用,且我在掘金上也搜不到相似的帶入門的文章,故決定本身寫一篇,但願能幫助到一些朋友,也能補足下本身的知識點。

參考文章:
TypeScript and React
終於搞懂 React Hooks了!!!!!
用 useContext + useReducer 替代 redux
React Hooks Tutorial on pure useReducer...

好東西不能獨享,我在此強烈推薦一篇從零搭建 React + Typescript 開發環境的系列文章給你們,這是我看到過寫的最清楚且優質的環境搭建文章,你們能夠去看看,絕對收穫滿滿:

從零開始配置 react + typescript(一):dotfiles
從零開始配置 react + typescript(二):linters 和 formatter
從零開始配置 react + typescript(三):webpack

相關文章
相關標籤/搜索