react-hooks

react-hooks

Hook 是 React16.8 的新特性,Hook 使你在无需修改组件结构的情况下复用状态逻辑。

弥补了 functin Component 没有实例没有生命周期的问题,react 项目基本上可以全部用 function Component 去实现了。

hook 总览

常用的官方的 hook 主要是下面几个:

  • useState()
  • useReducer()
  • useContext()
  • useRef()
  • useImperative()
  • useEffect()
  • useLayoutEffect()
  • useMemo()
  • useCallback()

hook 基础

hook 使用规则:

  • 不要在循环,条件,或者嵌套函数中调用 hook,在最顶层使用 hook
  • 不能在普通函数中调用 hook

下面主要记录每个 hook 基础的用法,

useState

存取数据的一种方式,对于简单的 state 适用,复杂的更新逻辑的 state 考虑使用 useReducer

使用:

const [state, setState] = useState(initState)

更新:

setState(newState)
setState(state => newState) // 函数式更新

setState 是稳定的,所以在一些 hook 依赖中可以省略

useReducer

useState 的一种代替方案,当 state 的处理逻辑比较复杂的时候,有多个子值得时候,可以考虑用 useReducer

使用:

const initialState = { count: 0 }

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 }
    case 'decrement':
      return { ...state, count: state.count - 1 }
    default:
      throw new Error()
  }
}
const [state, dispatch] = useReducer(reducer, initialState)

如果有第三个参数,则第三个参数为一个函数,接受第二个参数的值作为参数,返回初始值。

dispatch 是稳定的,所以在一些 hook 依赖中可以省略

useContext

const value = useContext(MyContext)

接受一个 context 对象,并返回该 context 对象的当前值,配合 context 使用

const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
}

const ThemeContext = React.createContext(themes.light)

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  )
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  )
}

function ThemedButton() {
  const theme = useContext(ThemeContext)
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>I am styled by theme context!</button>
  )
}

useRef

const refContainer = useRef(initValue)

返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

  • 访问 dom 的一个方式
  • 可以将其作为一个值来使用,在每次渲染时都返回同一个 ref 对象
  • 改变其 ref 的值,不会引起组件的重新渲染

例子 1,访问 dom 的方式:

function TextInputWithFocusButton() {
  const inputEl = useRef(null)
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus()
  }
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  )
}

例子 2,作为一个对象来保存值:

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

function App() {
  const [count, setCount] = useState(0)
  // 记录定时器,方便可以随时停止计时器
  let timer = useRef(null)
  useEffect(() => {
    timer.current = setInterval(() => {
      console.log(1)
      setCount(count => count + 1)
    }, 1000)
    return () => {
      clearInterval(timer.current)
      timer.current = null
    }
  }, [])

  const stop = () => {
    if (timer.current) {
      clearInterval(timer.current)
      timer.current = null
    }
  }
  return (
    <div>
      <span>{count}</span>
      <button onClick={stop}>stop</button>
    </div>
  )
}

export default App

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值

useImperativeHandle(ref, createHandle, [deps])
  • ref:需要被赋值的 ref 对象
  • createHandle:的返回值作为 ref.current 的值。
  • [deps]:依赖数组,依赖发生变化重新执行 createHandle 函数

使用例子:

const Child = React.forwardRef((props, ref) => {
  const inputRef = useRef()
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus()
    },
  }))
  return <input ref={inputRef} />
})

或者

// App.js
<Child cRef={this.myRef} />

// Child.js
const Child = props => {
  const inputRef = useRef()
  const { cRef } = props
  useImperativeHandle(cRef, () => ({
    focus: () => {
      inputRef.current.focus()
    },
  }))
  return <input ref={inputRef} />
}

useEffect

引入副作用,销毁函数和回调函数在 commit 阶段异步调度,在 layout 阶段完成后异步执行,不会阻塞 ui 得渲染。

useEffect(() => {
  //...副作用
  return () => {
    // ...清除副作用
  }
}, [deps])
  • 副作用在 commit 阶段异步执行,清除副作用的销毁函数会在下一阶段的的 commit 阶段执行,
  • [deps]:依赖数组,依赖发生变化重新执行

useLayoutEffect

引入副作用的,用法和 useEffect 一样,但 useLayoutEffect 会阻塞 dom 的渲染,同步执行,上一次更新的销毁函数在 commit 的 mutation 阶段执行,回调函数在在 layout 阶段执行,和 componentDidxxxx 是等价的。

useMemo

返回一个memo 值,作为一种性能优化的手段,只有当依赖项的依赖改变才会重新渲染值

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

useCallback

返回一个 memoized 回调函数,作为一种性能优化的手段,只有当依赖项的依赖改变才会重新构建该函数

const memoizedCallback = useCallback(() => {
  doSomething(a, b)
}, [a, b])

useDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签, 浏览器装有 react 开发工具调试代码的时候才有用。

useTransition

返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。

const [isPending, startTransition] = useTransition()
  • isPending: 指示过渡任务何时活跃以显示一个等待状态,为 true 时表示过渡任务还没更新完。
  • startTransition: 允许你通过标记更新将提供的回调函数作为一个过渡任务,变为过渡任务则说明更新往后放,先更新其他更紧急的任务。

例子:

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

const SearchResult = props => {
  const resultList = props.query
    ? Array.from({ length: 50000 }, (_, index) => ({
        id: index,
        keyword: `${props.query} -- 搜索结果${index}`,
      }))
    : []
  return resultList.map(({ id, keyword }) => <li key={id}>{keyword}</li>)
}

export default () => {
  const [isTrans, setIstrans] = useState(false)
  const [value, setValue] = useState('')
  const [searchVal, setSearchVal] = useState('')
  const [loading, startTransition] = useTransition({ timeoutMs: 2000 })

  useEffect(() => {
    // 监听搜索值改变
    console.log('对搜索值更新的响应++++++' + searchVal + '+++++++++++')
  }, [searchVal])

  useEffect(() => {
    // 监听输入框值改变
    console.log('对输入框值更新的响应-----' + value + '-------------')
  }, [value])

  useEffect(() => {
    if (isTrans) {
      startTransition(() => {
        setSearchVal(value)
      })
    } else {
      setSearchVal(value)
    }
  }, [value])

  return (
    <div className="App">
      <h3>StartTransition</h3>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <button onClick={() => setIstrans(!isTrans)}>{isTrans ? 'transiton' : 'normal'}</button>
      {loading && <p>数据加载中,请稍候...</p>}
      <ul>
        <SearchResult query={searchVal}></SearchResult>
      </ul>
    </div>
  )
}

useDeferredValue

useDeferredValue 接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。如果当前渲染是一个紧急更新的结果,比如用户输入,React 将返回之前的值,然后在紧急渲染完成后渲染新的值。本,该副本将推迟到更紧急地更新之后

react-router v6 hooks

useHref

declare function useHref(to: To): string

useHref钩子返回一个 URL,可以用来链接到给定的 to 位置,甚至在 React router 之外。

useHref传入一个字符串或To对象,返回一个 URL 的绝对路径。可以在参数中传入一个相对路径、绝对路径、To 对象。
react hooks v6中的Link组件内部使用useHref获取它的 href 值

export interface Path {
  pathname: Pathname
  search: Search
  hash: Hash
}
export type To = string | Partial<Path>

例子:

当前路由:/page1/page2

import React, { useEffect } from 'react'
import { useHref } from 'react-router-dom'

function Page2(props) {
  useEffect(() => {
    console.log(useHref('../')) // 输出 '/page1/'
    console.log(useHref('../../')) // 输出 '/'
    console.log(useHref('/page1')) // 输出 '/page'
    console.log(useHref('/pageabc')) // 输出 '/pageabc' 可见路径与当前路由无关
    console.log(useHref({ pathname: '/page1', search: 'name=123', hash: 'test' })) // 输出 '/page1?name=123#test'
  }, [])
  return <div></div>
}

export default Page2

useInRouterContext

declare function useInRouterContext(): boolean

如果组件在Router组件中的上下文中渲染,useInRouterContext将返回 true,否则返回 false。这对于一些需要知道是否在react router的上下文中渲染的第三方扩展非常有用。

判断当前组件是否在由Router组件创建的Context

useLocation

declare function useLocation(): Location

interface Location extends Path {
  state: unknown
  key: Key
}

这个hook返回当前路由的location对象。

useMatch

declare function useMatch<ParamKey extends string = string>(pattern: PathPattern | string): PathMatch<ParamKey> | null

返回给定路径上相对于当前位置的路由的匹配数据。

react router v5中的useRouteMatch在 v6 版本中更名为useMatch。内部示例调用了matchPath方法根据 URL 路径名匹配路由路径模式,并返回有关匹配的信息。如果模式与给定路径名不匹配,则返回null

useNavigate

declare function useNavigate(): NavigateFunction

interface NavigateFunction {
  (to: To, options?: { replace?: boolean; state?: any }): void
  (delta: number): void
}

useNavigate钩子返回一个函数,该函数允许您以编程方式进行导航,例如在提交表单之后。

react router v5中组件获取到的useHistory在 v6 版本更名为useNavigate。在用法上作出了较大改变

例子:

当前路由:/user/page2
其他路由:/user/page1

import React, { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'

function Page2(){
  let navigate = useNavigate()
  useEffect(()=>{
    navigate('/user/page1') // 导航到'/user/page1'
    navigate('../') // 导航到'/user'
    navigate(-1) // 导航到/user/page1 相当于v5中history.go(-1) history.goBack()
    navigate(1) //导航到/user 相当于v5中history.go(1) history.goForward()
    navigate('/user/page2/',{ replace: true, state: { name:123 }} // 相当于v5中history.replace(),并同时传入state
  },[])
  return <div></div>
}

export default Page2

useNavigationType

declare function useNavigationType(): NavigationType
type NavigationType = 'POP' | 'PUSH' | 'REPLACE'

这个钩子返回当前的导航类型或用户如何到达当前页面;通过历史堆栈上的poppushreplace操作。

这个hooks类似react router v5中组件传入参数中的history对象的action属性,返回触发当前navigate的是何种操作

useOutlet

declare function useOutlet(): React.ReactElement | null

返回路由当前路由的子路由的元素。这个hookOutlet组件在内部使用用于渲染子路由。

useParams

declare function useParams<K extends string = string>(): Readonly<Params<K>>

useParams返回 URL 参数的键/值对的对象。使用它来访问当前Routematch.params。子路由从其父路由继承所有参数。

useSearchParams

declare function useSearchParams(
  defaultInit?: URLSearchParamsInit
): [URLSearchParams, SetURLSearchParams];

type ParamKeyValuePair = [string, string];

type URLSearchParamsInit =
  | string
  | ParamKeyValuePair[]
  | Record<string, string | string[]>
  | URLSearchParams;

type SetURLSearchParams = (
  nextInit?: URLSearchParamsInit,
  navigateOpts?: : { replace?: boolean; state?: any }
) => void;

useSearchParams用于读取和修改 URL 中当前位置的查询字符串。与useState一样,useSearchParams返回一个包含两个值的数组:当前位置的搜索参数一个可用于更新它们的函数

useSearchParams的工作原理与navigate类似,但仅适用于 URL 的search部分。

setSearchParams 的第二个参数要与 navigate 的第二个参数的类型相同

useResolvedPath

declare function useResolvedPath(to: To): Path

这个hook解析给定to对象中的pathname与当前位置的路径名,并返回一个Path对象

useRoutes

declare function useRoutes(
  routes: RouteObject[],
  location?: Partial<Location> | string;
): React.ReactElement | null;

useRoutes钩子与Route组件是功能相等的,但它使用 JavaScript 对象而不是Route元素取定义你的路由。该对象与Route元素具有相同的属性,但是它们不需要 JSX

useRoutes 的返回值要么是一个可以让你用于渲染路由树的 React Element,要么是 null(路由不匹配时)

react-redux hooks

首先看一个实际应用中使用connect的例子

import { connect } from 'react-redux'

const Home = props=>{
    // 获取数据
    const { user, loading, dispatch } = props

    // 发起请求
    useEffect(()=>{
        dispatch({
            type:'user/fetchUser',payload:{}
        })
    }, [])

    // 渲染页面
    if(loading) return <div>loading...</div>
    return (
        <div>{user.name}<div>
    )
}

export default connect(({ loading, user })=>({
    loading:loading.effects['user/fetchUser'],
    user:user.userInfo
}))(Home)

现在使用useDispatch useSelector改造一下上面的代码:

import { useDispatch, useSelector } from 'react-redux'

const Home = props => {

    const dispatch = useDispatch()

    const loadingEffect = useSelector(state =>state.loading);
    const loading = loadingEffect.effects['user/fetchUser'];
    const user = useSelector(state=>state.user.userInfo)

    // 发起请求
    useEffect(()=>{
        dispatch({
            type:'user/fetchUser',payload:{}
        })
    },[])

    // 渲染页面
    if(loading) return <div>loading...</div>
    return (
        <div>{user.name}<div>
    )
}

export default Home

再来说说hooks,它是React-Redux提供用来替代connect()高阶组件。这些hooks API 允许不使用connect()包裹组件的情况下订阅store和分发actions

hooks 需要在函数组件中使用,不能在 React 类中使用hooks

使用hooksconnect()一样,需要将应用包裹在<Provider/>中。

const store = createStore(rootReducer)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')

useSelector

作用:从reduxstore对象中提取数据(state)。

注意:selector函数应该是个纯函数,因为可能在任何时候执行多次。

import React from 'react'
import { useSelector } from 'react-redux'

export const CounterComponent = () => {
  const counter = useSelector(state => state.counter)
  return <div>{counter}</div>
}

selector函数被调用时将会被传入Redux store的整个state,作为唯一的参数。每次函数组件渲染时,selector函数都会被调用。useSelector()同样会订阅Reduxstore,并且在分发action时,都会被执行一次。

selectorconnectmapStateToProps的差异:

  • selector函数的返回值会被用作调用 useSelector() hook时的返回值,可以是任意类型的值。
  • 当分发action时,useSelector 会将上次调用的结果与当前调用的结果进行引用(===)比较,不一样会进行重新渲染。
  • useSelector()默认使用严格比较===来比较引用,而非浅比较。
  • selector函数不会接收ownProps参数,但是props可以通过闭包获取使用或者通过使用柯里化的selector函数。
import React from 'react'
import { useSelector } from 'react-redux'

export const TodoListItem = props => {
  const todo = useSelector(state => state.todos[props.id])
  return <div>{todo.text}</div>
}

action被分发后,useSelector()selector函数的返回值进行引用比较===,在selector的值改变时会触发 re-render。与connect不同,useSelector()不会阻止父组件重渲染导致的子组件重渲染的行为,即使组件的props没有发生改变。

如果想要进一步的性能优化,可以在React.memo()中包装函数组件。

const CounterComponent = ({ name }) => {
  const counter = useSelector(state => state.counter)
  return (
    <div>
      {name}: {counter}
    </div>
  )
}

export const MemoizedCounterComponent = React.memo(CounterComponent)

useDispatch

const dispatch = useDispatch()

作用:返回Redux store中对dispatch函数的引用。

当使用dispatch函数将回调传递给子组件时,建议使用useCallback函数将回调函数记忆化,防止因为回调函数引用的变化导致不必要的渲染。

import React, { useCallback, memo } from 'react'
import { useDispatch } from 'react-redux'

export const MyIncrementButton = memo(({ onIncrement }) => <button onClick={onIncrement}>Increment counter</button>)

export const Counter = ({ value }) => {
  const dispatch = useDispatch()
  const incrementCounter = useCallback(() => dispatch({ type: 'increment-counter' }), [dispatch])

  return (
    <div>
      <span>{value}</span>
      <MyIncrementButton onIncrement={incrementCounter} />
    </div>
  )
}

useStore()

const store = useStore()

作用:返回传递给组件的 Redux store 的引用。

注意:应该将useSelector()作为首选,只有在个别场景下才会需要使用它,比如替换 storereducers

import React from 'react'
import { useStore } from 'react-redux'

export const Counter = ({ value }) => {
  const store = useStore()

  return <div>{store.getState()}</div>
}

hook 进阶

react hook 工作当中也用了一段时间了,中间踩过一些坑,针对不同 hook 的特点,进行总结。

  • 两个 state 是关联或者需要一起发生改变,可以放在同一个 state,但不要太多
  • state 的更新逻辑比较复杂的时候则可以考虑使用 useReducer 代替
  • useEffectuseLayoutEffectuseMemouseCallbackuseImperativeHandle 中依赖数组依赖项最好不要太多,太多则考虑拆分一下,感觉不超 3 到 4 个会比较合适。
  • 去掉不必要的依赖项
  • 合并相关的 state 为一个
  • 通过 setState 回到函数方式去更新 state
  • 按照不同维度这个 hook 还能不能拆分的更细
  • useMemo 多用于对 React 元素做 memorize 处理或者需要复杂计算得出的值,对于简单纯 js 计算就不要进行 useMemo 处理了。
  • useCallback 要配合memo使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值