本文直接灵感:终于搞懂 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