使用React hooks,些许又多了不少摸鱼时间

Posted 星期一研究室

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用React hooks,些许又多了不少摸鱼时间相关的知识,希望对你有一定的参考价值。

一文详解react-hooks

🎙️前言

相传, react 17 出了一个很强大的功能, 也就是 react hooks 。实际上, react hooks 有点类似与 vue3composition API ,都是为了提升开发效率而出现。

那么,在下面的文章中,将从 0到1 开始,带大家了解 react hooks ,以及一些常用的 API

废话不多说,开始 react-hook 之旅叭~

一、📻概述

1、关于React Hooks

  • React Hooks 是一个可选功能,通常用 class 组件 来和它做比较;
  • 100% 向后兼容,没有破坏性改动
  • 不会取代 class 组件,尚无计划要移除 class 组件。

2、认识React Hooks

(1)回顾React函数式组件

我们先来回顾下 class 组件函数组件,具体如下:

class组件:

// class组件
class List extends React.Component 
    constructor() 
        super(props)
    
    render() 
        const  List  = this.props
        
        return <ul>list.map((item, index) => 
            return <li key=item.id>
                	<span>item.title</span>
                </li>
        )</ul>
    

函数组件:

// 函数组件
function List(props) 
    const  list  = props
    
    return <ul>list.map((item, index) => 
        return <li key=item.id>
            	<span>item.title</span>
            </li>
    )</ul>

(2)函数组件的特点

函数组件的特点是:

  • 没有组件实例;
  • 没有生命周期;
  • 没有 statesetState ,只能接收 props

(3)class组件的问题

上面我们说到了函数组件是一个纯函数,只能接收 props ,没有任何其他功能。而 class 组件拥有以上功能,但是呢,class 组件会存在以下问题:

  • 大型组件很难拆分和重构,很难测试(即 class 不易拆分);
  • 相同业务逻辑,分散到各个方法中,逻辑混乱;
  • 复用逻辑变得复杂,如 MixinsHOCRender Props

因此,有了以上问题的出现,也就有了 React Hooks

(4)React 组件

  • React 组件更易于用函数来表达:

  • React 提倡函数式编程,即 view=fn(props)

  • 函数更灵活,更易拆分,更易测试

  • 函数组件太简单,需要增强能力 ——因此,有了 React Hooks

二、🪕几种 Hooks

1、State Hook🗞️

(1)让函数组件实现state和setState

  • 默认函数组件是没有 state 的;
  • 函数组件是一个纯函数,执行完即销毁,无法存储 store
  • 需要 State Hook ,即把 store 功能 “钩” 到纯函数中。

(2)举例阐述

假设我们现在要通过点击按钮,来修改设定的某个值。现在我们用 hooks 中的 useState 来处理。具体代码如下:

import React,  useState  from 'react'

function ClickCounter() 
    // 数组的解构
    // useState 就是一个 Hook “钩”,最基本的一个 Hook
    const [count, setCount] = useState(0) // 传入一个初始值,初始值可以是数值/字符串/数组/对象等类型

    const [name, setName] = useState('星期一研究室')

    // const arr = useState(0)
    // const count = arr[0]
    // const setCount = arr[1]

    function clickHandler() 
        setCount(count + 1)
        setName(name + '2021')
    

    return <div>
        <p>你点击了 countname</p>
        <button onClick=clickHandler>点击</button>
    </div>


export default ClickCounter

此时浏览器的显示效果如下:

在上面的代码中, count 是一个 state 的值, setCount 是修改 state 的一个函数。同理, name 也是一个 state 的值, setName 是修改 name 值得一个函数。

如果使用 hooks 来修改 state 值的话,那么我们只需要以 const [count, setCount] = useState(0) 这种形式去进行,便可修改最后的值,而不需要像 class 组件那么复杂的去使用。


对于上面这个功能来说,如果使用 class 组件来实现的话,具体代码如下:

import React from 'react'

class ClickCounter extends React.Component 
    constructor() 
        super()

        // 定义 state
        this.state = 
            count: 0,
            name: '星期一研究室'
        

        this.clickHandler = this.clickHandler.bind(this)
    
    render() 
        return <div>
            <p>你点击了 this.state.countthis.state.name</p>
            <button onClick=this.clickHandler>点击</button>
        </div>
    
    clickHandler() 
        // 修改 state
        this.setState(
            count: this.state.count + 1,
            name: this.state.name + '2021'
        )
    


export default ClickCounter

大家可以看到,如果使用 class 组件来解决的话,那么需要先定义 state ,然后再定义一个函数,再使用 setState 才能去修改值,这样似乎还麻烦了点。

相信到这里,大家已经感受到 hooks 的快乐之处了。

下面,我们来总结关于 useState 的一些知识点。

(3)useState使用总结

  • useState(xxx) 传入初始值,返回数组 [state, setState]
  • 通过 state 获取值;
  • 通过 setState(xxx) 修改值。

(4)Hooks命名规范

  • 规定所有的 Hooks 都要使用 use 开头,如 useXxx
  • 自定义 Hook 也要以 use 开头;
  • Hooks 的地方,尽量不要使用 useXxx 写法,不然容易造成误解。

2、Effect Hook🗞️

(1)让函数组件模拟生命周期

  • 默认函数组件没有生命周期
  • 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期;
  • 使用 Effect Hook 把生命周期 “钩” 到纯函数中。

(2)举例阐述

同样地,我们用于 useState 相同的例子,来体验下 useEffect

我们先在 src|components 下建立一个文件,命名为 LiftCycles.js具体代码如下:

import React,  useState, useEffect  from 'react'

function LifeCycles() 
    const [count, setCount] = useState(0)
    const [name, setName] = useState('星期一研究室')

    // 模拟 class 组件的 DidMount 和 DidUpdate
    useEffect(() => 
        console.log('在此发送一个 ajax 请求')
    )

    function clickHandler() 
        setCount(count + 1)
        setName(name + '2020')
    

    return <div>
        <p>你点击了 countname</p>
        <button onClick=clickHandler>点击</button>
    </div>


export default LifeCycles

现在,我们在 App.js 中注册该组件。具体代码如下:

import React,  useState  from 'react';
import LifeCycles from './components/LifeCycles'

function App() 
  const [flag, setFlag] = useState(true)

  return (
    <div
      flag && <LifeCycles/>
    </div>
  );


export default App;

此时,浏览器的显示效果如下:

大家可以看到, useEffect 如果只传入一个函数的话,那么它模拟的是 DidMountDidUpdate 这两个生命周期的功能。每当我们点击一次的时候,浏览器就会打印一次,也就是组件加载和组件更新是在一起进行的。


那如果我们想要让组件加载和组件更新分开实现呢,同样有办法。我们来改造下 LiftCycles.js 的代码。具体代码如下:

import React,  useState, useEffect  from 'react'

function LifeCycles() 
    const [count, setCount] = useState(0)
    const [name, setName] = useState('星期一研究室')

    // 模拟 class 组件的 DidMount
    useEffect(() => 
        console.log('加载完了')
    , []) // 第二个参数是 [] (不依赖于任何 state)

    // 模拟 class 组件的 DidUpdate
    useEffect(() => 
        console.log('更新了')
    , [count, name]) // 第二个参数就是依赖的 state

    function clickHandler() 
        setCount(count + 1)
        setName(name + '2020')
    

    return <div>
        <p>你点击了 countname</p>
        <button onClick=clickHandler>点击</button>
    </div>


export default LifeCycles

此时浏览器的显示效果如下:

大家可以看到,第一次的时候分别加载和更新了 1 次。等到我们去点击的时候,因为已经加载完了,所以这个时候就是更新了。

那我们来梳理一下, useEffect 在加载和更新的时候,分别是怎么进行处理的?

看上面的代码中我们可以发现,useEffect 还可以接收第二个参数,第二个参数如果空值,那么它不依赖于任何 state ,表示 componentDidMount 这个生命周期。相反,如果第二个参数没有依赖值或者接收依赖于 state 的值这两种情况时,那么它模拟 componentDidUpdate 这个生命周期。


说到这里,可能有的小伙伴还想问,那跟销毁相关的生命周期,又怎么处理呢?我们继续来对 LiftCycles.js 的代码进行改造。具体如下:

import React,  useState, useEffect  from 'react'

function LifeCycles() 
    const [count, setCount] = useState(0)
    const [name, setName] = useState('星期一研究室')

    // 模拟 class 组件的 DidMount
    useEffect(() => 
        let timerId = window.setInterval(() => 
            console.log(Date.now())
        , 1000)

        // 返回一个函数
        // 模拟 WillUnMount
        return () => 
            window.clearInterval(timerId)
        
    , [])

    function clickHandler() 
        setCount(count + 1)
        setName(name + '2020')
    

    return <div>
        <p>你点击了 countname</p>
        <button onClick=clickHandler>点击</button>
    </div>


export default LifeCycles

大家可以看以上代码,在这里,我们通过 return 一个函数,去模拟 componentWillUnMount 这个生命周期,来销毁每一次定时器中执行的任务。

到这里,相信大家对 useEffect 已经有了一定的了解。现在,我们来对 useEffect 做个小结。

(3)useEffect使用总结

  • 模拟 componentDidMount - useEffect 依赖 []
  • 模拟 componentDidUpdate - useEffect 无依赖,或者依赖 [a, b]
  • 模拟 componentWillUnMount - useEffect返回一个函数

(4)useEffect让纯函数有了副作用

  • 默认情况下,执行纯函数的时候,只需要输入参数并返回结果即可,是没有任何副作用的;
  • 所谓副作用,就是对函数之外造成影响,如设置全局定时任务
  • 而组件需要副作用,所以需要 useEffect “钩” 到纯函数中;
  • 因此, useEffect 这个副作用并不是一件坏事。

(5)useEffect中返回函数 fn

有一种值得注意的情况是,在上面我们通过返回一个函数来模拟 WillUnMount ,但这个模拟后的结果还不完全等于 WillUnMount 。现在,我们在 src|components 下建立一个文件,命名为 FriendStatus.js具体代码如下:

import React,  useState, useEffect  from 'react'

function FriendStatus( friendId ) 
    const [status, setStatus] = useState(false)

    // DidMount 和 DidUpdate
    useEffect(() => 
        console.log(`开始监听 $friendId 在线状态`)

        // 【特别注意】
        // 此处并不完全等同于 WillUnMount
        // props 发生变化,即更新,也会执行结束监听
        // 准确的说:返回的函数,会在下一次 effect 执行之前,被执行
        return () => 
            console.log(`结束监听 $friendId 在线状态`)
        
    )

    return <div>
        好友 friendId 在线状态:status.toString()
    </div>


export default FriendStatus

App.js 的代码如下:

import React,  useState  from 'react';
import FriendStatus from './components/FriendStatus'

function App() 
  const [flag, setFlag] = useState(true)
  const [id, setId] = useState(1)

  return (
    <div>
      <div>
        <button onClick=() => setFlag(false)>flag = false</button>
        <button onClick=() => setId(id + 1)>id++</button>
      </div>

      flag && <FriendStatus friendId=id/>
    </div>
  );


export default App;

此时,浏览器的显示效果如下:

大家可以看到,当开始下一个监听时,会先结束掉上一个监听再开始下一个。正如图中所看到的,一开始我们正在监听 id1 的好友,那当我们想要点击 id++ 按钮去监听好友 2 时,useEffect 会先去结束掉 1 的状态,再让好友 2 上线。

此时,我们要注意的一个点是,执行结束监听的这个函数,执行的是 DidUpdate 生命周期,而不是 WillUnMount 生命周期。函数在这个时候是属于更新状态而不是销毁状态

依据以上内容,我们来做个小结。具体如下:

  • useEffect 依赖于 [] 的时候,在组件销毁时执行 return 的函数 fn ,等于 WillUnMount 生命周期;
  • useEffect 无依赖或依赖于 [a, b] 的时候,组件是在更新时执行 return 的函数 fn ;即在下一次执行 useEffect 之前,就会先执行 fn ,此时模拟的是 DidUpdate 生命周期。

3、其他Hooks🗞️

上面我们讲了两个比较常用的 hooksuseStateuseEffect 。接下来,我们来了解其他几个比较不常用的 hooks

(1)useRef

Ref 是用于对 值的修改DOM 元素的获取先来看一段代码:

import React,  useRef, useEffect  from 'react'

function UseRef() 
    const btnRef = useRef(null) // 初始值

    const numRef = useRef(0)
    numRef.current = 2;

    useEffect(() => 
        console.log(btnRef.current) // DOM 节点
        console.log(numRef.current); // 得到值
    , [])

    return <div>
        <button ref=btnRef>click</button>
    </div>


export default UseRef

此时,浏览器的显示效果为:

大家可以看到,如果将 btnRef 绑定到 ref=btnRef 上,那么 btnRef.current 获取到的是当前的 DOM 节点。

另外一种情况是,大家定位到 numRef ,如果我们给其进行赋值并修改,那么 numRef.current 最终得到的值是修改后的值。

(2)useContext

很多时候,我们经常会有一些比较静态的属性需要做切换,比如:主题颜色切换。这个时候就需要用到 context 上下文来处理。那在 hook 中,就有 useContext 可以处理这件事情。先来看一段演示代码:

import React,  useContext  from 'react'

// 主题颜色
const themes = 
    light: 
        foreground: '#000',
        background: '#eee'
    ,
    dark: 
        foreground: '#fff',
        background: '#222'
    


// 创建 Context
const ThemeContext = React.createContext(themes.light) // 初始值

function ThemeButton() 
    const theme = useContext(ThemeContext)

    return <button style= background: theme.background, color: theme.foreground >
        hello world
    </button>


function Toolbar() 
    return <div>
        <ThemeButton></ThemeButton>
    </div>


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


export default App

此时浏览器的显示效果如下:

现在属于黑色背景的主题。如果我们要切换为白色背景的主题,那么只需要把最底下的代码 value=themes.dark 改为 value=themes.light 即可。

(3)useReducer

redux 中,我们会用到 reducer ,但 useReducerredux 还有一点区别。 useReducer 是对单个组件进行状态

先来看一段代码:

import React,  useReducer  from 'react'

const initialState =  count: 0 

const reducer = 以上是关于使用React hooks,些许又多了不少摸鱼时间的主要内容,如果未能解决你的问题,请参考以下文章

使用React hooks,些许又多了不少摸鱼时间

使用React hooks,些许又多了不少摸鱼时间

使用React hooks,些许又多了不少摸鱼时间

通过 React Hooks 声明式地使用 setInterval

写在毕业设计开始之际

安全测试-业务安全的些许“瞎说”