好想用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

相关文章
相关标签/搜索