React Hooks

1.关于 React Hooks

React 提倡函数式编程,view = fn(props),函数更灵活,更易拆分,更易测试。尽管函数组件有着许多优势,但是函数组件太简单了,许多功能class组件能轻易实现的功能函数组件很难或者不可能实现。因次,为了让函数组件拥有更强大的能力,Hooks出现了。

关于 Hooks 介绍,无论是官方文档还是网络上都已经有了很多介绍,下面直接对常用的一些 hook 使用进行介绍。

2. State Hook

函数组件是一个纯函数,执行完就销毁了,无法存储state,如果想拥有像 class 组件中的state功能,这时就需要 state hook,把 state 功能 “钩”到纯函数中

2.1 函数组件基本“格式”

import React from 'react'

const HooksT = () => {
  return (
    <>
      <p>这是一个函数组件</p>
    </>
  )
}

export default HooksT;

2.2 使用useState

下面是在函数组件中实现加一减一效果,在class组件中可以通过state和setState来实现(注意,这里的state和setState是class组件中固定的唯一写法,和函数组件中的不同)

import React, { useState } from 'react'

const HooksT = () => {

  /**
   * 数组解构,首先要引入:import { useState } from 'react'
   * 1.[count, setCount],这个可以自定义名字,但是一般来说,都是以[值,set值]这种命名方式
   * 2.useState(0)中的0是值的初始值,也可以是是字符串,数组,对象等
   * 3.如果值为对象,可以这样写:useState({a:'1',b:'2'})
   * 4.setCount这是可以对count进行修改的方法,如设置count值为5,则:setCount(5);设置count值加一,则:setCount(count + 1)
   * 5.这样的useState可以在函数组件中没有数量限制
   */
  const [count, setCount] = useState(0)

  return (
    <>
      <p>这是一个函数组件</p>
      <p>{`num:${count}`}</p>
      <button onClick={() => { setCount(count + 1) }}>num加1</button>
      <button onClick={() => { setCount(count - 1) }}>num减1</button>
    </>
  )
}

export default HooksT;

3. useEffect

函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期,可以用 useEffect 把生命周期 “钩” 到纯函数中,用 useEffect 模拟组件生命周期

3.1 使用 useEffect

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

const HooksT = () => {

  const [count, setCount] = useState(0)

  /**
   * 1.模拟class组件的 DidMound 和 DidUpdate 生命周期
   * 当界面第一次渲染完成,或者更新时重新渲染后都会执行
   */
  useEffect(() => {
    console.log('当界面第一次渲染完成,或者更新时重新渲染后都会执行')
  })

  /**
   * 2.仅模拟class组件的 DidMound 生命周期(在useEffect的第二个参数加一个空数组)
   * 当界面第一次渲染完成执行,更新时不执行
   */
  useEffect(() => {
    console.log('当界面第一次渲染完成执行,更新时不执行')
  }, [])

  /**
   * 3.仅模拟class组件的 DidUpdate 生命周期(在useEffect的第二个参数加一个数组,数组里放入需要监听更新的值)
   * 当界面第一次渲染完成执行,更新时不执行
   * 注意1:这里useEffect中第二个参数其实是一种依赖关系,和第一点中的不同,第一点在useEffect中不加第二个参数时
   * 无论什么数据更新都会执行; 而此处增加 [count] 后,仅仅当count更新时才会执行,除此之外的更新不会执行。
   * 注意2:数组中可以放多个依赖的值,其中任何一个更新都会执行
   */
  useEffect(() => {
    console.log('当界面第一次渲染完成执行,更新时不执行')
  }, [count])

  /**
   * 4.模拟class组件的 WillUnMount ,在销毁时执行,这一步谨慎使用
   * 在定义一些定时任务和全局变量时,一定要返回一个函数去进行销毁,否则会
   * 造成内存泄漏
   */
  useEffect(() => {
    console.log('当界面销毁时执行')
    let timerId = window.setInterval(() => {
      console.log(timerId)
    })
    // 返回一个函数,模拟一个函数
    // 当不return时,定时任务其实不会结束,为了完全销毁,需要增加return,并返回一个函数
    return () => {
      window.clearInterval(timerId)
    }
  }, [])



  return (
    <>
      <p>这是一个函数组件</p>
      <p>{`num:${count}`}</p>
      <button onClick={() => { setCount(count + 1) }}>num加1</button>
      <button onClick={() => { setCount(count - 1) }}>num减1</button>
    </>
  )
}

export default HooksT;

3.2 useEffect 使用总结

1.同时模拟 componentDidMount 和 componentDidUpdate --- useEffect无依赖
2.模拟 componentDidMount - useEffect 依赖 []
3.模拟 componentDidUpdate - useEffect依赖[a, b]
4.模拟 componentDidWillUnMount - useEffect 中返回一个函数,依赖 []

3.3 useEffect 让纯函数有了副作用

1.默认情况下,执行纯函数,输入参数,返回结果,无副作用
2.所谓副作用,就是对函数之外造成了影响,如设置全局定时任务
3.而某些时候需要副作用,则可以用 useEffect “钩” 到纯函数

3.4使用 useEffect 模拟 WillUnMount 时的注意事项

注意,模拟 WillUnMount ,但不完全相等

useEffect 中返回函数 fn:

1.useEffect 依赖 [],组件销毁时执行fn,等于 WillUnMount,
2.useEffect 无依赖或者依赖 [a, b] ,组件更新时会执行 fn,

4. 其他 Hooks

针对其他一些不常用到的 hooks 进行简单介绍:
1.useRef
2.useContext
3.useReducer
4.useMemo
5.useCallback

4.1 useRef

1.useRef常用于获取 DOM 节点

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

const HooksT = () => {

  const btnRef = useRef(null)

  useEffect(() => {
    console.log(btnRef.current) // btnRef所绑定的 DOM 节点
  }, [])

  return (
    <>
      <p>这是一个函数组件</p>
      <button ref={btnRef}>click</button>
    </>
  )
}

export default HooksT;

2.可用于保存一些其他数据

// 定义一个ref
  const ref = useRef()

  // 可以对ref进行一个赋值操作
  ref.current = 100;

4.2 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)

/**
 * 下面定义三个组件演示隔层传递
 */

const ThemeButton = () => {

  const theme = useContext(ThemeContext)

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

const Toolbar = () => {
  return (
    <>
      <ThemeButton></ThemeButton>
    </>
  )
}

const HooksT = () => {

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

export default HooksT;

我们可以不再一层一层的去传递某些值,而是可以统一的在最顶层组件传入,然后可以在下面任何一个组件内传递,从而减少了多层组件传递的麻烦,只需要在顶层做一次修改,就能修改整个大组件,例如做主题管理。而无需再引用 Redux

4.3 useReducer

useRedecer 和 useState很像,当我们想要实现更加复杂的修改值的操作,可以使用 reducer

import React, { useReducer } from 'react'

// 定义初始值
const initialSatate = {
  count: 0,
}

// 定义修改规则
const reducer = (state, action) => {
  switch(action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      return false;
  }
}


const HooksT = () => {

  // 创建一个 reducer ,很像useState
  const [state, dispatch] = useReducer(reducer, initialSatate)

  return (
    <>
      count: {state.count}
      <button onClick={() => {dispatch({type: 'increment'})}}>increment</button>
      <button onClick={() => {dispatch({type: 'decrement'})}}>decrement</button>
    </>
  )
}

export default HooksT;

useRedecer 和 redux 区别:
1.useReducer 是 useState 的代替方案,用于 state 的复杂变化
2.useReducer 是单个组件的状态管理,组件间通讯还是需要 props
3.redux 是全局的状态管理,多组件共享数据

因此,useReducer 本质上只是 useState 的升级版,并不能代替 redux

4.4 useMemo

在react中,默认情况下当父组件重新渲染时,子组件也会无条件重新渲染,当遇到性能瓶颈时,可以考虑使用 useMemo 做性能优化

1.首先要使用memo将子组件封装起来

import React, { memo } from 'react

// 通过memo将子组件封装起来,即作为参数传入memo()中
const Child = memo((info) => {
    console.log("子组件渲染了")
    return (
        <p>子组件</p>
    )
})

2.在父组件中使用 useMemo 对传入子组件的数据进行一个依赖判断

import React, { useMemo } from 'react
import Child from 'path'

const Parent = () => {
        
    // 通过 useMemo 来封装需要传入子组件的数据,即进行缓存数据
    // 数据通过return来返回
    cosnt info = useMemo(() = {
        return {
            name: 'jack',
            age: 18
        }
    },[name])       // 依赖name,只有name变化时子组件才会重新渲染

    console.log("父组件渲染了")

    return (
        <>
            <p>父组件</p>
            <Child info={info} />
        </>
    )

3. useMemo 使用总结

1.React 默认在父组件更新情况下会无条件更新子组件
2.class 组件使用SCU和PureComponent 做优化
3.Hooks 中使用 useMemo做优化
4.上面2,3点的优化原理是一样的,都是通过对 props 的一个浅层对比进行优化

4.5 useCallback

useCallback 是对 useMemo 的一个补充,针对父组件传入函数时进行缓存

只需要在 4.4 中 Parent 组件中增加对函数的缓存即可:

import React, { memo, useMemo, useCallback } from 'react
import Child from 'path'

// 在这增加传入的 onChange 函数
const Child = memo(({info, onChange}) => {
    console.log("子组件渲染了")
    return (
        <p>子组件</p>
        <button onClick={() => {onChange}>change</button>
    )
})

const Parent = () => {
        
    // 通过 useMemo 来封装需要传入子组件的数据
    cosnt info = useMemo(() = {
        return {
            name: 'jack',
            age: 18
        }
    },[name])   

    // 将要传入子组件的函数作为参数传入 useCallback 中即可
    const onChange = useCallback(() => {
        console.log("useCallback")
    })

    console.log("父组件渲染了")

    return (
        <>
            <p>父组件</p>
            <Child info={info} onChange={onChange} />
        </>
    )
}

5.自定义Hook

5.1 通过定义一个axios相关的hook来演示自定义hook

安装Axios: yarn add axios --save

import { useState, useEffect } from 'react';
import axios from 'axios';

/**
 * 封装 axios 自定义hook发送请求 
 */
const useAxios = (url) => {
  
  const [loading, setLoading] = useState(false)
  const [data, setData] = useState()
  const [error, setError] = useState()

  useEffect(() => {
    // 表示开始请求
    setLoading(true)
    // 利用 axios 发送网络请求
    axios.get(url)
         .then(res => {
            setData(res)
          })
         .catch(err => {
            setError(err)
          })
         .finally(() => {
          setLoading(false)
         })
  }, [url])

  return [loading, data, error]
}

export default useAxios;

小结:
1.本质是一个函数,命名以 use 开头
2.内部能正常使用 useState, useEffect 或者其他 Hooks
3.自定义返回结果,格式不限

5.2 第三方Hooks:

https://ahooks.js.org/zh-CN/docs/getting-started

6.Hooks 使用规范

6.1 命名规范

自定义 hooks 使用 use 开头的命名,非 hooks 尽量避免使用 use 开头命名

6.2 Hooks使用规范

1.只能用于 React 函数组件和自定义 Hook 中,其他地方不可以

2.只能用于顶层代码,不能在循环、判断(if)中使用 Hooks

3.eslint 插件 eslint-plugin-react-hook 可以检测使用规范

6.3 关于Hooks的调用顺序

函数组件,本质是一个函数,执行完即销毁;
无论是组件初始化还是组件更新,都会重新去执行这个函数(表示从销毁到再一次初始化);这一点和class组件不同,因为class组件会有组件实例,只要组件不被主动销毁,组件更新是不会导致calss组件销毁。

1.对于 useState

/**
    *   render:初始化 state 的值
    * re-render: 只恢复为第一次初始化的 state 的值,不会再重新设置新的值
    * 只能通过 setState 修改
    */
const [state, useState] = useState("name")

之所以 hooks 能到正确获取到对应的值(多个state能一一对应),是因为 hooks 是严格按顺序读取的,如果有 hooks 在 if 内,当 if 不通过时,if 内的 hooks 就不会执行,也就会导致剩下的hooks 全部错乱。

2.对于 useEffect

/**
    *   render:添加 effect 函数
    * re-render:替换 effect 函数,useEffect内部的匿名函数也会重新定义
    */
useEffect(() => {
    console.log("useEffect")
})

同样,当在 if 中使用 useEffect 时,因为执行的不确定性,也会导致前后的 useEffect 执行错乱

hooks严格依赖于执行顺序,绝对不能在循环,判断中使用

7.React Hooks 组件逻辑复用

7.1 class组件的逻辑复用

1.Mixins 已经废弃

2.高阶组件HOC

3.Render Prop

7.2 使用 Hooks 做组件逻辑复用

1.定义一个公共的自定义hooks

2.直接在需要的地方使用即可

8.React Hooks 注意事项

1.useState 初始化值,只有第一次有效

import React, { memo, useMemo, useCallback } from 'react
import Child from 'path'

const Child = memo(({info}) => {
    const [nameC, setNameC] = useState(info.name)
    return (
        <p>子组件</p>
        <p>传进来 props 的 name1:{info.name}</p>
        <p>初始化 nameC  的name2:{nameC}</p>
    )
})

const Parent = () => {

    const [name, setName] = useState("name")

    const info = {
        name
    }

    return (
        <>
            <p>父组件</p>
            <Child info={info} />
            <button onClick={() => {setName("nameChange")}}>change</button>
        </>
    )


在上面的演示代码中,当第一次运行后:name1 和 name2 值都是 ”name“,而当点击按钮修改传入的info时,name1 变成了 ”nameChange“,而name2 仍然时 ”name“

由此可以印证,useState 只有在初次渲染时会给 nameC 初始值,而在后面即使改变传入的值,useState 也不会再重新设置新的值,只能通过 setNameC来修改

2.useEffect 内部不能修改 state 的情况

使用 useEffect 时,当依赖为 [] 时,re-render 是不会重新执行 effect 函数的,这样就会导致在 useEffect 中的一些 set值 的操作没有效果

当没有依赖,或者依赖 [a, b] 中会 a 或 b 会改变的情况时,re-render 会重新执行函数,这样状态又不能保存下来

这时可以通过useRef来保存一个状态值

解决方案:


// 可以使用 useRef

// 通过 useRef 为 countRef 赋值为 0
const countRef = useRef(0)

// 然后可以进行修改 countRef , countRef.current 就是它的值
++countRef.current

3.useEffect 可能出现死循环

当 useEffect 的依赖里面有 {} , [] 引用类型时,会导致死循环

这是因为,react 的 useEffect 判断依赖是否变化是通过 Object.is() 这个函数,而 Object.is({}, {}) 和 Object.is([], []) 都会返回false,则 useEffect 判断为依赖变化,就会执行 effect 函数,这样就会导致死循环的发生

后续

1.webpack的完整配置流程,帮助掌握基本配置和部分高级配置
2.JavaScript版数据结构与算法,着重于实战练习
3.dva的介绍与使用

小伙伴们有需要想让作者先整理出来的可以留言评论,我会参考大家的意见来选择

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335