React Hooks 详解

为什么会有Hooks?

介绍 Hooks 之前,首先要给大家说一下 React 的组件创建方式,一种是类组件,一种是纯函数组件,并且 React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。也就是说组件的最佳写法应该是函数,而不是类。

但是我们知道,在以往开发中类组件和纯函数组件的区别是很大的,纯函数组件有着类组件不具备的多种特点,简单列举几条:

  • 纯函数组件没有状态
  • 纯函数组件没有生命周期
  • 纯函数组件没有 this
  • 只能是纯函数

这就注定,我们所推崇的函数组件,只能做UI展示的功能,涉及到状态的管理与切换,我们不得不用类组件或者 redux,但我们知道类组件也是有缺点的,比如,遇到简单的页面,你的代码会显得很重,并且每创建一个类组件,都要去继承一个 React 实例,至于 Redux,更不用多说,很久之前 Redux 的作者就说过,“能用 React 解决的问题就不用Redux”,等等一系列的话。关于 React 类组件 redux 的作者又有话说:

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
  • 组件类引入了复杂的编程模式,比如:render、props 和高阶组件。

下面我们用类组件做一个简单的计数器:

import React from 'react'
class AddCount extends React.PureComponent {
  constructor(props){
    super(props)
    this.state = {
      count: 0
    }
  }
  addcount = () => {
    let newCount = this.state.count
    this.setState({
      count: newCount += 1
    })
  }
  render(){
    return (
        <>
    <p>{this.state.count}</p>
    <button onClick={this.addCount}>count++</button>
    </>
    )
  }
}
export default AddCount

可以看出来,上面的代码确实很重。

为了解决这种,类组件功能齐全却很重,纯函数很轻却有上文几点重大限制,React 团队设计了 React Hooks。

React Hooks 就是加强版的函数组件,我们可以完全不使用 class,就能写出一个全功能的组件。

什么是Hooks?

'Hooks'的单词意思为“钩子”。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码“钩进来”。而 React Hooks 就是我们所说的“钩子”。

那么 Hooks 要怎么用呢?“你需要写什么功能,就用什么钩子”。对于常见的功能,React 为我们提供了一些常用的钩子,当然有特殊需要,我们也可以写自己的钩子。下面是 React 为我们提供的默认的四种最常用钩子:

  • useState()
  • useContext()
  • useReducer()
  • useEffect()

不同的钩子为函数引入不同的外部功能,我们发现上面四种钩子都带有 use 前缀,React 约定,钩子一律使用 use 前缀名。所以,你自己定义的钩子都要命名为 sueXxx。

React Hooks的用法

1、useState():状态钩子

我们知道,纯函数组件没有状态,useState() 用于为函数组件引入状态。

下面,我们使用 Hooks 重新写上面的计数器:

import React, {useState} from 'react'
const AddCount = () => {
  const [ count, setCount ] = useState(0)
  const addcount = () => {
    let newCouns = count
    setCount(newCount += 1)
  }
  return {
    <>
    <p>{count}</p>
    <button onClick={addcount++}></button>
    </>
  }
}
export default AddCount

通过上面的代码,我们实现了一个功能完全一样的计数器,代码看起来更加的轻便简介,没有了继承,没有了渲染逻辑,没有了生命周期等。这就是 hooks 存在的意义。

useState() 中,他接受状态的初始值作为参数,即上例中计数的初始值,他返回一个数组,其中数组第一项为一个变量,指向状态的当前值。类似 this.state,第二项是一个函数,用来更新状态,类似 setState。该函数的命名,我们约定为 set 前缀加状态的变量名。

二、useContext():共享状态钩子

该钩子的作用是,在组件之间共享状态。Context 作用就是可以做状态的分发,在 React16.X以后支持,避免了 react 逐层通过 Props 传递数据。

下面是一个例子,现在假设有A组件和B组件需要共享一个状态:

import React, { useContext } from 'react'
const Ceshi = () => {
  const AppContext = React.createContext({})
  const A = () => {
    const { name } = useContext(AppContext)
    return (
      <p>我是A组件的名字{name}<span>我是A的子组件</span></p>
    )
  }
  const B = () => {
    const { name } = useContext(AppContext)
    return (
      <p>我是B组件的名字{name}</p>
    )
  }
    return (
      <AppContext.Provider value={{name: 'hook测试'}}>
      <A/>
      <B/>
      </AppContext.Provider>
    )
}
export default Ceshi

页面显示如下:

 可以看到,我们可以通过 hooks 做状态的共享。

三、useReducer():Action 钩子

我们知道,在使用 React 的过程中,如遇到状态管理,我们一般会用到 Redux,而 React 本身是不提供状态管理的。而 useReducer() 为我们提供了状态管理。首先,关于 redux 我们都知道,其原理是我们通过用户在页面中发起 action,从而通过 reducer 方法来改变 state,从而实现页面和状态的通信。而 Reducer 的形式是 (state, action) => newstate。类似,我们的 useReducer() 是这样的:

const [state, dispatch] = useReducer(reducer, initialState)

他接受 reducer 函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送 action 的 dispath 函数。下面我们依然用来实现一个计数器。

和 redux 一样,我们是需要通过页面组件发起 action 来调用 reducer 方法,从而改变状态,达到改变页面UI的这样一个过程。所以,我们会先写一个 Reducer 函数,然后通过 useReducer() 返回给我们的 state 和 dispatch 来驱动这个数据流:

import React, {useReducer} from 'react'

const AddCount = () => {
  const reducer = (state, action) => {
    if(action.type === ''add){
      return {
        ...state,
        count: state.count + 1
      }
    } else {
        return state
      }
  }
  const addcount = () => {
    dispatch({
      type: 'add'
    })
  }
const [state, dispatch] = useReducer(reducer, {count: 0})
return (
<>
<p>{state.count}</p>
<button onClick={addcount}>count++</button>
</>
)
}
export default AddCount

通过代码我们看到了,我们使用 useReducer() 代替了 Redux 的功能,但 useReducer 无法为我们提供中间件等功能。

四、useEffect():副作用钩子

熟悉 redux-saga 的同学一定对 Effect 不陌生,它可以用来更好的处理副作用,如异步请求等,我们的 useEffect() 也是为函数组件提供了处理副作用的钩子。依然我们会把请求放在 componentDidMount 里面,在函数组件中我们可以使用 useEffect()。其具体用法如下:

useEffect(() => {}, [array])

useEffect() 接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出 Effect 的依赖项。只要这个数组发生变化,useEffect() 就会执行。当第二项省略不填时,useEffect() 会在每次组件渲染时执行。这一点类似于组件的 componentDidMount。下面我们通过代码模拟一个异步加载数据:

import React, { useState, useEffect } from 'react'
const AsyncPage = () => {
const [loading, setLoading] = useState(true)
  useEffect(() => {
    setTimeout(()=> {
      setLoading(false)
    },5000)
  })
return (
loading ? <p>Loading...</p>: <p>异步请求完成</p>
)
}

export default AsyncPage 

上面的代码实现了一个异步加载,下面我们再做一个 useEffect() 依赖第二项数组变化的例子:

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

const AsyncPage = ({name}) => {
const [loading, setLoading] = useState(true)
const [person, setPerson] = useState({})

  useEffect(() => {
    setLoading(true)
    setTimeout(()=> {
      setLoading(false)
      setPerson({name})
    },2000)
  },[name])
  return (
    <>
      {loading?<p>Loading...</p>:<p>{person.name}</p>}
    </>
  )
}

const PersonPage = () =>{
  const [state, setState] = useState('')
  const changeName = (name) => {
    setState(name)
  }
  return (
    <>
      <AsyncPage name={state}/>
      <button onClick={() => {changeName('名字1')}}>名字1</button>
      <button onClick={() => {changeName('名字2')}}>名字2</button>
    </>
  )
}

export default PersonPage 

上面代码中,通过改变传给 AsyncPage 的 props,从而调用 useEffect()。

五、创建自己的Hooks

以上我们介绍了四种最常用的 react 提供给我们的默认 React Hooks,有时我们需要创建我们自己想要的 Hooks,来满足更便捷的开发,无非就是根据业务场景对以上四种 Hooks进行组装,从而得到满足自己需求的钩子。

比如,我们要将上面的代码功能封装成 Hooks,代码如下:

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

const usePerson = (name) => {
const [loading, setLoading] = useState(true)
const [person, setPerson] = useState({})

  useEffect(() => {
    setLoading(true)
    setTimeout(()=> {
      setLoading(false)
      setPerson({name})
    },2000)
  },[name])
  return [loading,person]
}

const AsyncPage = ({name}) => {
  const [loading, person] = usePerson(name)
    return (
      <>
        {loading?<p>Loading...</p>:<p>{person.name}</p>}
      </>
    )
  }

const PersonPage = () =>{
  const [state, setState]=useState('')
  const changeName = (name) => {
    setState(name)
  }
  return (
    <>
      <AsyncPage name={state}/>
      <button onClick={() => {changeName('名字1')}}>名字1</button>
      <button onClick={() => {changeName('名字2')}}>名字2</button>
    </>
  )
}

export default PersonPage 

上面代码中,我们将之前的例子封装成了自己的 Hooks,便于共享。其中,我们定义 usePerson() 为我们的自定义 Hooks,它接受一个字符串,返回一个数组,数组中包含两个数据的状态,之后我们在使用 usePerson() 时,会根据我们传入的参数不同而返回不同的状态,然后很简单的应用于我们的页面中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React Hooks 是 React 16.8 中新增的特性,它可以让你在函数组件中使用 state、生命周期钩子等 React 特性。使用 Hooks 可以让你写出更简洁、可复用且易于测试的代码。 React Hooks 提供了一系列的 Hook 函数,包括 useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect 和 useDebugValue。每个 Hook 都有特定的用途,可以帮助你处理不同的问题。 下面是 React Hooks 的一些常用 Hook 函数: 1. useState useState 是最常用的 Hook 之一,它可以让你在函数组件中使用 state。useState 接受一个初始状态值,并返回一个数组,数组的第一个值是当前 state 值,第二个值是更新 state 值的函数。 ``` const [count, setCount] = useState(0); ``` 2. useEffect useEffect 可以让你在组件渲染后执行一些副作用操作,比如订阅事件、异步请求数据等。useEffect 接受两个参数,第一个参数是一个回调函数,第二个参数是一个数组,用于控制 useEffect 的执行时机。 ``` useEffect(() => { // 这里可以执行副作用操作 }, [dependencies]); ``` 3. useContext useContext 可以让你在组件树中获取 context 的值。它接受一个 context 对象,并返回该 context 的当前值。 ``` const value = useContext(MyContext); ``` 4. useRef useRef 可以让你在组件之间共享一个可变的引用。它返回一个对象,该对象的 current 属性可以存储任何值,并在组件的生命周期中保持不变。 ``` const ref = useRef(initialValue); ref.current = value; ``` 5. useCallback useCallback 可以让你缓存一个函数,以避免在每次渲染时都创建一个新的函数实例。它接受一个回调函数和一个依赖数组,并返回一个 memoized 的回调函数。 ``` const memoizedCallback = useCallback(() => { // 这里是回调函数的逻辑 }, [dependencies]); ``` 6. useMemo useMemo 可以让你缓存一个计算结果,以避免在每次渲染时都重新计算。它接受一个计算函数和一个依赖数组,并返回一个 memoized 的计算结果。 ``` const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); ``` 以上就是 React Hooks 的一些常用 Hook 函数,它们可以帮助你更好地处理组件状态、副作用、上下文和性能优化等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值