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

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


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


課前預知

我覺得比較好的學習方式就是跟著所講的內容自行實現一遍,所以先啟個項目唄~

<code>npx create-react-app hook-ts-demo --template typescript/<code>

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

函數式組件的使用~ 我們可以通過以下方式使用有類型約束的函數式組件:

<code>import React from 'react'

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

export const User = ({ name, age }: UserInfo) => {
return (

{ name }


{ age }



)

}

const user = <user>/<code>

也可以通過以下方式使用有類型約束的函數式組件:

<code>import React from 'react'

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

export const User:React.FC<userinfo> = ({ name, age }) => {
return (

{ name }


{ age }



)
}

const user = <user>/<userinfo>/<code>

上述代碼中不同之處在於:

<code>export const User = ({ name, age }: UserInfo)  => {}
export const User:React.FC<userinfo> = ({ name, age }) => {}/<userinfo>/<code>

使用函數式組件時需要將組件申明為React.FC類型,也就是 Functional Component 的意思,另外props需要申明各個參數的類型,然後通過泛型傳遞給React.FC。

雖然兩種方式都差不多,但我個人更喜歡使用 React.FC 的方式來創建我的有類型約束的函數式組件,它還支持 children 的傳入,即使在我們的類型中並沒有定義它:

<code>export const User:React.FC<userinfo> = ({ name, age, children }) => {
return (

{ name }


{ age }



{ children }


)
}

const user = <user>I am children text!/<user>/<userinfo>/<code>

我們也並不需要把所有參數都顯示地解構:

<code>export const User:React.FC<userinfo> = (props) => {
return (

{ props.name }


{ props.age }



{ /* 仍可以拿到 children */ }
{ props.children }


)
}

const user = <user>I am children text!/<user>/<userinfo>/<code>

好了,我們暫時知道上面這麼多,就可以開始使用我們的 hooks 了~

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

  • 為啥使用❓
  • 怎麼使用
  • 場景例舉

useState

為啥使用useState?

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

怎麼使用useState?

<code>const [count, setCount] = useState<number>(0)/<number>/<code>

場景舉例

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

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

return (

Count: {count}


<button> setCount(count+1)}>加/<button>

<button> setCount(count-1)}>減/<button>

)
}

export default Counter/<number>/<code>
2.參數為對象類型時的使用:
<code>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 (

Title: { article.title }


{ article.content }/
<button> setArticle({
title: '下一篇',
content: '下一篇的內容',
})}>
下一篇
/<button>

)
}

export default Article/<articleinfo>/<articleinfo>/<code>

在我們的參數為對象類型時,需要特別注意的是, setXxx 並不會像 this.setState 合併舊的狀態,它是完全替代了舊的狀態,所以我們要實現合併,可以這樣寫(雖然我們以上例子不需要):

<code>setArticle({
title: '下一篇',
content: '下一篇的內容',
...article
})/<code>

useEffect

為啥使用useEffect?

你可以把 useEffect 看做 componentDidMount , componentDidUpdate 和 componentWillUnmount 這三個函數的組合。

怎麼使用useEffect?

<code>useEffect(() => {
...
return () => {...}
},[...])/<code>

場景舉例

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

let switchCount: number = 0

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

return (

Current Name: { name }


switchCount: { switchCount }


<button> setName('Jack')}>Jack/<button>
<button> setName('Marry')}>Marry/<button>


)
}

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

因為 value 我們不會去任何地方改變它的值,所以在末尾加了 [value] 後, useEffect 內的邏輯也只會執行第一次,相當於在 class 組件中執行了 componentDidMount ,後續的 shouldComponentUpdate 返回全部是 false 。

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

window.addEventListener('resize', handler)

return () => {
window.removeEventListener('resize', handler)
}
}, [])/<code>

useRef

為啥使用useRef?

它不僅僅是用來管理 DOM ref 的,它還相當於 this , 可以存放任何變量,很好的解決閉包帶來的不方便性。

怎麼使用useRef?

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

場景舉例

1.閉包問題:

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

<code>import React, { useState, useEffect, useRef } from 'react'

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

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

return (

current count: { count }


<button> setCount(count + 1)}>加/<button>
<button> handleCount()}>彈框顯示/<button>


)
}

export default Counter/<number>/<code>

結果是彈框內容為 current count: 3 ,為什麼?

當我們更新狀態的時候, React 會重新渲染組件, 每一次渲染都會拿到獨立的 count 狀態, 並重新渲染一個 handleCount 函數. 每一個 handleCount 裡面都有它自己的 count 。

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

<code>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/<number>/<number>/<code>
2.因為變更 .current 屬性不會引發組件重新渲染,根據這個特性可以獲取狀態的前一個值:
<code>const Counter = () => { 

const [count, setCount] = useState<number>(0)
const preCountRef = useRef<number>(count)

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

return (

pre count: { preCountRef.current }


current count: { count }


<button> setCount(count + 1)}>加/<button>

)
}/<number>/<number>/<code>

我們可以看到,顯示的總是狀態的前一個值:


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


3.操作 Dom 節點,類似 createRef():
<code>import React, { useRef } from 'react'

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

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

return (


<button>Focus the input/<button>

)
}

export default TextInput/<htmlinputelement>/<code>

useMemo

為啥使用useMemo?

useEffect 可以知道,可以通過向其傳遞一些參數來影響某些函數的執行。 React 檢查這些參數是否已更改,並且只有在存在差異的情況下才會執行此。

useMemo 做類似的事情,假設有大量方法,並且只想在其參數更改時運行它們,而不是每次組件更新時都運行它們,那就可以使用

useMemo 來進行性能優化。

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

怎麼使用useMemo?

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

const newName = useMemo(() => {
\treturn changeName(name)
}, [name])/<code>

場景舉例

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

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

<code>import React, { useState, useMemo } from 'react'

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

return (

<button> setTime(new Date().getTime())}>獲取當前時間/<button>
<button> setRandom(Math.random())}>獲取當前隨機數/<button>
<show>{random}/<show>


)
}

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 (

Time is: { changeTime(time) }


Random is: { children }



)
}

export default Example/<data>/<number>/<number>/<code>

在這個例子中,無論你點擊的是 獲取當前時間 按鈕還是 獲取當前隨機數 按鈕, <show> 這個組件中的方法 changeTime 都會執行。

但事實上,點擊 獲取當前隨機數 按鈕改變的只會是 children 這個參數,但我們的 changeTime 也會因為子組件的重新渲染而重新執行,這個操作是很沒必要的,消耗了無關的性能。

使用 useMemo 改造我們的 <show> 子組件:

<code>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 (

Time is: { newTime }


Random is: { children }



)
}/<data>/<code>

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

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

答案是否定的!如果你在子組件中加入以下代碼:

<code>const Show:React.FC<data> = ({ time, children }) => {
\t//...

useEffect(() => {

console.log('effect function here...')
}, [time])

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

\t//...
}/<data>/<code>

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

<code>> changeTime excuted...
> effect function here.../<code>

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

<code>const Show:React.FC<data> = React.memo(({ time, children }) => {...}/<data>/<code>

所以簡單用一句話來概括 useMemo 和 React.memo 的區別就是:前者在某些情況下不希望組件對所有 props 做淺比較,只想實現局部 Pure 功能,即只想對特定的 props 做比較,並決定是否局部更新。

useCallback

為啥使用useCallback?

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

useCallback(fn, deps) 相當於 useMemo(() => fn, deps)

怎麼使用useCallback?

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

const getNewName = useMemo(() => {
return changeName(name)
}, [name])/<code>

場景舉例

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

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

return (

Time is: { getNewTime() }


Random is: { children }



)
}/<data>/<code>

useReducer

為什麼使用useReducer?

有沒有想過你在某個組件裡寫了很多很多的 useState 是什麼觀感?比如以下:

<code>const [name, setName] = useState<string>('')
const [islogin, setIsLogin] = useState<boolean>(false)
const [avatar, setAvatar] = useState<string>('')

const [age, setAge] = useState<number>(0)
//...複製代碼/<number>/<string>/<boolean>/<string>/<code>

怎麼使用useReducer?

<code>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 (

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

)
}

export default Counter/<code>

場景舉例:

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

useContext

為啥使用useContext?

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

怎麼使用useContext?

<code>export const ColorContext = React.createContext({ color: '#1890ff' })
const { color } = useContext(ColorContext)
// 或
export const ColorContext = React.createContext(null)
<colorcontext.provider>

/<colorcontext.provider>
// App 或以下的所有子組件都可拿到 value
const color = useContext(ColorContext) // '#1890ff'/<code>

場景舉例

1.根組件註冊,所有子組件都可拿到註冊的值:
<code>import React, { useContext } from 'react'

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

const App = () => {
return (
<colorcontext.provider>
<father>
/<colorcontext.provider>
)
}

const Father = () => {
return (
<child>
)

}

const Child = () => {
const color = useContext(ColorContext)
return (
Background color is: { color }

)
}

export default App/<string>/<code>
2.配合 useReducer 實現 Redux 的代替方案:
<code>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 (


當前字體顏色為: {state.color}
<button> dispatch && dispatch({type: UPDATE_COLOR, color: 'red'})}>紅色/<button>
<button> dispatch && dispatch({type: UPDATE_COLOR, color: 'green'})}>綠色/<button>

)
}

const Example = ({ initialColor = '#000000' }) => {
const [state, dispatch] = useReducer(reducer, { color: initialColor })
return (
<colorcontext.provider>

<show>
<button> dispatch && dispatch({type: UPDATE_COLOR, color: 'blue'})}>藍色/<button>
<button> dispatch && dispatch({type: UPDATE_COLOR, color: 'lightblue'})}>輕綠色/<button>

/<colorcontext.provider>
)
}

export default Example/<mixstateanddispatch>/<actiontype>/<code>

以上此方案是值得好好思索的,特別是因為 TypeScript 而導致的類型約束! 當然,如果有更好的解決方案,希望有大佬提出來,我也可以多學習學習~


作者:vortesnail
鏈接:https://juejin.im/post/5e652741518825494822d569


分享到:


相關文章: