本文直接靈感:終於搞懂 React Hooks了!!!!!
這裏是個人 github/blog 地址,若有幫助,賞個 star~react
看人家 Typescript
和 React hooks
耍的溜的飛起,好羨慕啊~🥺
那來吧,這篇爽文從腦袋到jio乾地教你如何使用這兩大利器開始閃亮開發!✨webpack
🌸我以爲比較好的學習方式就是跟着所講的內容自行實現一遍,因此先啓個項目唄~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
:
可讓函數式組件擁有狀態管理特性,相似 class 組件中的 this.state
和 this.setState
,可是更加簡潔,不用頻繁的使用 this
。
const [count, setCount] = useState<number>(0)
複製代碼
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
複製代碼
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
看作 componentDidMount
, componentDidUpdate
和 componentWillUnmount
這三個函數的組合。
useEffect(() => {
...
return () => {...}
},[...])
複製代碼
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
複製代碼
useEffect
的邏輯:useEffect(() => {
switchCount += 1
}, [])
複製代碼
const [value, setValue] = useState<string>('I never change')
useEffect(() => {
switchCount += 1
}, [value])
複製代碼
由於 value
咱們不會去任何地方改變它的值,因此在末尾加了 [value]
後, useEffect
內的邏輯也只會執行第一次,至關於在 class 組件中執行了 componentDidMount
,後續的 shouldComponentUpdate
返回所有是 false
。
useEffect(() => {
const handler = () => {
document.title = Math.random().toString()
}
window.addEventListener('resize', handler)
return () => {
window.removeEventListener('resize', handler)
}
}, [])
複製代碼
它不只僅是用來管理 DOM ref 的,它還至關於 this , 能夠存聽任何變量,很好的解決閉包帶來的不方便性。
const [count, setCount] = useState<number>(0)
const countRef = useRef<number>(count)
複製代碼
想一想看,咱們先點擊 加 按鈕 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
複製代碼
.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>
)
}
複製代碼
咱們能夠看到,顯示的老是狀態的前一個值:
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 複製代碼
從 useEffect 能夠知道,能夠經過向其傳遞一些參數來影響某些函數的執行。 React 檢查這些參數是否已更改,而且只有在存在差別的狀況下才會執行此。
useMemo 作相似的事情,假設有大量方法,而且只想在其參數更改時運行它們,而不是每次組件更新時都運行它們,那就可使用 useMemo 來進行性能優化。
記住,傳入
useMemo
的函數會在渲染期間執行。請不要在這個函數內部執行與渲染無關的操做,諸如反作用這類的操做屬於useEffect
的適用範疇,而不是useMemo
。
function changeName(name) {
return name + '給name作點操做返回新name'
}
const newName = useMemo(() => {
return changeName(name)
}, [name])
複製代碼
咱們先來看一個很簡單的例子,如下是還未使用 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
這個函數,而點擊 獲取當前隨機數 已經不會觸發該函數執行了。
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
作比較,並決定是否局部更新。
useMemo
和 useCallback
接收的參數都是同樣,都是在其依賴項發生變化後才執行,都是返回緩存的值,區別在於 useMemo
返回的是函數運行的結果, useCallback
返回的是函數。
useCallback(fn, deps) 至關於 useMemo(() => fn, deps)
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>
)
}
複製代碼
有沒有想過你在某個組件裏寫了不少不少的 useState
是什麼觀感?好比如下:
const [name, setName] = useState<string>('')
const [islogin, setIsLogin] = useState<boolean>(false)
const [avatar, setAvatar] = useState<string>('')
const [age, setAge] = useState<number>(0)
//...
複製代碼
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 方案,往下閱讀。
簡單來講 Context
的做用就是對它所包含的組件樹提供全局共享數據的一種技術。
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' 複製代碼
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 複製代碼
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 而致使的類型約束! 固然,若是有更好的解決方案,但願有大佬提出來,我也能夠多學習學習~
最近也是看了許多好文章,多謝各位掘金的大佬的無私奉獻,本篇文章的靈感來源也是最近蠻火的一篇文章:
這篇文章寫的通俗易懂,可是沒有涉及到在 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