前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React Hooks

React Hooks

作者头像
Leophen
发布2021-07-13 11:31:08
2.1K0
发布2021-07-13 11:31:08
举报
文章被收录于专栏:Web前端开发Web前端开发

一、React 的两套 API

以前,React API 只有一套,现在有两套:类(class)API 和基于函数的钩子(hooks) API。

任何一个组件,可以用类来写,也可以用函数来写。

1、类组件

代码语言:javascript
复制
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

类(class)是数据和逻辑的封装。 也就是说,组件的状态和操作方法是封装在一起的。如果选择了类的写法,就应该把相关的数据和操作,都写在同一个 class 里面。

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

2、 函数组件

代码语言:javascript
复制
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>
}

这个函数只做一件事,就是根据输入的参数,返回组件的 HTML 代码。这种只进行单纯的数据计算(换算)的函数,在函数式编程里面称为 "纯函数"(pure function)。

函数一般来说,只应该做一件事,就是返回一个值。 如果有多个操作,每个操作应该写成一个单独的函数。而且,数据的状态应该与操作方法分离。 根据这种理念,React 的函数组件只应该做一件事情:返回组件的 HTML 代码,而没有其他的功能。

React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。组件的最佳写法应该是函数,而不是类。

副作用是什么? 看到这里,你可能会产生一个疑问:如果纯函数只能进行数据计算,那些不涉及计算的操作(比如生成日志、储存数据、改变应用状态等等)应该写在哪里呢? 函数式编程将那些跟数据计算无关的操作,都称为 "副作用" (side effect) 。如果函数内部直接包含产生副作用的操作,就不再是纯函数了,我们称之为不纯的函数。

纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副作用。

二、React Hooks

Hook(钩子)是 React 函数组件的副作用解决方案,用来为函数组件引入副作用。 函数组件应写成纯函数,只用来返回组件的 HTML 代码,如果需要外部功能和副作用,就用钩子把外部代码 "钩" 进来。

你需要什么功能,就使用什么钩子。所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。

下面是 React 默认提供的四个最常用的钩子:

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

1、useState():状态钩子


useState() 用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。

代码语言:javascript
复制
import React, { useState } from 'react'

export default function Button() {
  const [buttonText, setButtonText] = useState('Click me, please')

  function handleClick() {
    return setButtonText('Thanks, been clicked!')
  }

  return <button onClick={handleClick}>{buttonText}</button>
}

运行结果:

上面代码中,Button 组件是一个函数,内部使用 useState() 钩子引入状态。

useState()这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。 该函数返回一个数组,数组的第一个成员是一个变量(上例是buttonText),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是 set 前缀加上状态的变量名(上例是 setButtonText)。

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


如果需要在组件之间共享状态,可以使用 useContext()

现在有两个组件 A 和 B,我们希望它们之间共享状态。

代码语言:javascript
复制
<div class>
  <A/>
  <B/>
</div>

第一步就是使用 React Context API,在组件外部建立一个 Context:

代码语言:javascript
复制
const AppContext = React.createContext({});

组件封装代码如下:

代码语言:javascript
复制
<AppContext.Provider value={{
  username: 'superawesome'
}}>
  <div class>
    <A/>
    <B/>
  </div>
</AppContext.Provider>

上面代码中,AppContext.Provider 提供了一个 Context 对象,这个对象可以被子组件共享。

A 组件的代码如下:

代码语言:javascript
复制
const A = () => {
  const { username } = useContext(AppContext)

  return (
    <div class>
      <span>A:</span>
      <span>{username}</span>
    </div>
  )
}

上面代码中,useContext() 钩子函数用来引入 Context 对象,从中获取 username 属性。

B 组件的代码也类似。

代码语言:javascript
复制
const B = () => {
  const { username } = useContext(AppContext)

  return (
    <div class>
      <span>B:</span>
      <span>{username}</span>
    </div>
  )
}

运行结果:

3、useReducer():action 钩子


React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。

Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是 (state, action) => newState

useReducers() 钩子用来引入 Reducer 功能。

代码语言:javascript
复制
const [state, dispatch] = useReducer(reducer, initialState);

上面是 useReducer() 的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的 dispatch 函数。

下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。

代码语言:javascript
复制
const myReducer = (state, action) => {
  switch (action.type) {
    case 'countUp':
      return {
        ...state,
        count: state.count + 1
      }
    default:
      return state
  }
}

组件代码如下。

代码语言:javascript
复制
function App() {
  const [state, dispatch] = useReducer(myReducer, { count: 0 })
  return (
    <div class>
      <button onClick={() => dispatch({ type: 'countUp' })}>+1</button>
      <p>Count: {state.count}</p>
    </div>
  )
}

运行结果:

由于 Hooks 可以提供共享状态和 Reducer 函数,所以它在这些方面可以取代 Redux。但是,它没法提供中间件(middleware)和时间旅行(time travel),如果要用这两个功能,还是要用 Redux。

4、useEffect():副作用钩子


useEffect() 用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在 componentDidMount 里面的代码,现在可以放在 useEffect()

① 基本用法

举个例子,我们希望组件加载以后,网页标题(document.title)会随之改变。那么,改变网页标题这个操作,就是组件的副作用,需要通过 useEffect() 来实现:

代码语言:javascript
复制
import React, { useEffect } from 'react'

function Welcome(props) {
  useEffect(() => {
    document.title = '加载完成'
  })
  return <h1>Hello, {props.name}</h1>
}

上面例子中,useEffect() 的参数是一个函数,它就是所要完成的副作用(改变网页标题)。组件加载以后,React 就会执行这个函数。

useEffect() 的作用就是指定一个副作用函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副作用函数也会执行。

② 第二个参数

有时候,我们不希望 useEffect() 每次渲染都执行,这时可以使用它的第二个参数,使用一个数组指定副作用函数的依赖项,只有依赖项发生变化,才会重新渲染。

代码语言:javascript
复制
function Welcome(props) {
  useEffect(() => {
    document.title = `Hello, ${props.name}`
  }, [props.name])
  return <h1>Hello, {props.name}</h1>
}

上面例子中,useEffect() 的第二个参数是一个数组,指定了第一个参数(副作用函数)的依赖项(props.name)。只有该变量发生变化时,副作用函数才会执行。

如果第二个参数是一个空数组,就表明副作用参数没有任何依赖项。因此,副作用函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。这很合理,由于副作用不依赖任何变量,所以那些变量无论怎么变,副作用函数的执行结果都不会改变,所以运行一次就够了。

③ 用途

只要是副作用,都可以使用 useEffect() 引入。它的常见用途有下面几种:

  • 获取数据(data fetching)
  • 事件监听或订阅(setting up a subscription)
  • 改变 DOM(changing the DOM)
  • 输出日志(logging)

下面是从远程服务器获取数据的例子:

代码语言:javascript
复制
import React, { useState, useEffect } from 'react'
import axios from 'axios'

function App() {
  const [data, setData] = useState({ hits: [] })

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux'
      )

      setData(result.data)
    }

    fetchData()
  }, [])

  return (
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  )
}

export default App

上面例子中,useState() 用来生成一个状态变量(data),保存获取的数据;useEffect() 的副作用函数内部有一个 async 函数,用来从服务器异步获取数据。拿到数据以后,再用 setData() 触发组件的重新渲染。

由于获取数据只需要执行一次,所以上例的 useEffect() 的第二个参数为一个空数组。

④ 返回值

副作用是随着组件加载而发生的,那么组件卸载时,可能需要清理这些副作用。

useEffect() 允许返回一个函数,在组件卸载时,执行该函数,清理副作用。如果不需要清理副作用,useEffect() 就不用返回任何值。

代码语言:javascript
复制
useEffect(() => {
  const subscription = props.source.subscribe()
  return () => {
    subscription.unsubscribe()
  }
}, [props.source])

上面例子中,useEffect() 在组件加载时订阅了一个事件,并且返回一个清理函数,在组件卸载时取消订阅。

实际使用中,由于副作用函数默认是每次渲染都会执行,所以清理函数不仅会在组件卸载时执行一次,每次副作用函数重新执行之前,也会执行一次,用来清理上一次渲染的副作用。

⑤ 注意事项

使用 useEffect() 时,如果有多个副作用,应该调用多个 useEffect(),而不应该合并写在一起。

代码语言:javascript
复制
function App() {
  const [varA, setVarA] = useState(0)
  const [varB, setVarB] = useState(0)
  useEffect(() => {
    const timeoutA = setTimeout(() => setVarA(varA + 1), 1000)
    const timeoutB = setTimeout(() => setVarB(varB + 2), 2000)

    return () => {
      clearTimeout(timeoutA)
      clearTimeout(timeoutB)
    }
  }, [varA, varB])

  return (
    <span>
      {varA}, {varB}
    </span>
  )
}

上面的例子是错误的写法,副作用函数里面有两个定时器,它们之间并没有关系,其实是两个不相关的副作用,不应该写在一起。正确的写法是将它们分开写成两个 useEffect()

代码语言:javascript
复制
function App() {
  const [varA, setVarA] = useState(0)
  const [varB, setVarB] = useState(0)

  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000)
    return () => clearTimeout(timeout)
  }, [varA])

  useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000)

    return () => clearTimeout(timeout)
  }, [varB])

  return (
    <span>
      {varA}, {varB}
    </span>
  )
}

5、自定义 Hooks


上例的 Hooks 代码还可以封装起来,变成一个自定义的 Hook,便于共享。

代码语言:javascript
复制
const usePerson = (personId) => {
  const [loading, setLoading] = useState(true)
  const [person, setPerson] = useState({})
  useEffect(() => {
    setLoading(true)
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then((response) => response.json())
      .then((data) => {
        setPerson(data)
        setLoading(false)
      })
  }, [personId])
  return [loading, person]
}

上面代码中,usePerson()就是一个自定义的 Hook。

Person 组件就改用这个新的钩子,引入封装的逻辑。

代码语言:javascript
复制
const Person = ({ personId }) => {
  const [loading, person] = usePerson(personId)

  if (loading === true) {
    return <p>Loading ...</p>
  }

  return (
    <div>
      <p>You're viewing: {person.name}</p>
      <p>Height: {person.height}</p>
      <p>Mass: {person.mass}</p>
    </div>
  )
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-05-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、React 的两套 API
    • 1、类组件
      • 类组件的缺点
    • 2、 函数组件
    • 二、React Hooks
      • 1、useState():状态钩子
        • 2、useContext():共享状态钩子
          • 3、useReducer():action 钩子
            • 4、useEffect():副作用钩子
              • ① 基本用法
              • ② 第二个参数
              • ③ 用途
              • ④ 返回值
              • ⑤ 注意事项
            • 5、自定义 Hooks
            相关产品与服务
            消息队列 TDMQ
            消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档