React Hooks

React Hooks 是 React v16.8 中新添加的特性,这个特性主要是对函数型组件的功能进行增强,让函数型组件可以做类组件做的事情。
这个篇文章主要包含三个部分:

  1. 什么是react hooks
    1. react hooks 主要包含哪些功能,要解决哪些问题。
  2. react hooks 的使用
    1. react hooks 到底是什么
    2. 这些函数要在什么场景下使用
  3. 【React Hooks】自定义 react hooks
    1. 通过自己定义函数,如何将组件内部的公共逻辑提取出来
1. 什么是 React Hooks 1.react hooks 功能介绍
对函数组件进行增强,让函数组件可以储存状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下,实现相同的功能。
副作用:在组件中不是将数据转换为视图的代码被称为副作用。
比如:获取DOM元素、为DOM元素添加事件、设置定时器以及发送Ajax请求等,这都属于副作用。
2.类组件的不足
  1. 缺少逻辑复用机制
    1. 为了复用逻辑增加无实际渲染效果的组件,增加了组件层级显示十分臃肿,增加了调试的难度以及运行效率的降低。
  2. 类组件经常会变得很复杂难以维护
    1. 将一组相干的业务逻辑拆分到多个生命周期函数中,例如:1.在组件挂载时要做些什么 2.更新完成之后要做些什么
    2. 在一个生命周期函数内存在多个不相干的业务逻辑
    3. 类成员的方法不能保证this指向的正确性,例如:在声明一些函数要使用bind进行绑定,或者嵌套函数来解决this指向的问题
2. React Hooks 的使用 Hooks 意为钩子,React Hooks 就是一堆钩子函数,React 通过这些钩子函数对函数组型件进行增强,不同的钩子函数提供了不同的功能。
react hooks 提供的钩子函数有:
  • useState()
  • useEffect()
  • useReducer()
  • useRef()
  • useCallback()
  • useContext()
  • useMemo()
1.useState
用于为函数组件引入状态,在现有的认知里面,在函数执行完毕之后该变量就会被释放掉,所以函数型组件原本是不可以保存组件数据的,useState 用来解决这个问题
import React, {useState} from 'react'; function App () { // useState 返回一个数组,count: 状态 setCount: 设置状态的方法 const [count, setCount] = useState(0); return {count} }

使用 setCount 方法设置 count 的值后,组件会被重新渲染,而渲染后count的状态还在。
  • useState 使用细节
    1. 接收唯一的参数即状态初始值,初始值可以是任意数据类型。
    2. 返回为数组,数组中存储状态值和更新状态值的方法,方法名称约定以set开头,后面加上状态的名称。
    3. useState 方法可以被调用多次,用以保存不同的状态值。
    4. 参数可以是一个函数,函数返回什么初始状态值就是什么,函数只会被调用一次,用在初始值是动态值的情况下。
import React, {useState} from 'react'; function UseState (props) { console.log('渲染即执行'); // 传入一个方法 const [count, setCount] = useState(() => { console.log('这个位置只会被执行一次'); return props.count || 0; }); // 可以接收任意数据类型为状态值 const [person, setPerson] = useState({name: 'howie', age: 20}); return {count} {person.name} {person.age} }export default UseState;

  • 设置状态的细节
    1. 设置状态值的方法的参数,可以是一个值也可以是一个函数
    2. 设置状态值的方法本身是异步的
import React, {useState} from 'react'; function UseState (props) { const [count, setCount] = useState(() => { return props.count || 0; }); function handleSetCount () { // 传递回调函数设置状态,setCount 是异步的 setCount(count => { return count + 1; // 这样设置title就会变成同步的 // const newCount = count + 1; // document.title = newCount; // 1 // return newCount; }); document.title = count; // 0 }return {count} }export default UseState;

2.useReducer
useReducer是另一种让函数组件保存状态的方式。
import React, {useReducer} from 'react'; function UseReducer () {function reducer(state, action) { switch (action.type) { case 'increment': return state + 1; case 'decrement': return state - 1; default: return state; } }const [count, dispatch] = useReducer(reducer, 0); return useReducer
{count} }export default UseReducer;

相较于 useState 的优势在于,如果子组件要修改父组件中的数据时,可以直接将父组件中的dispatch方法传递给子组件,
然后根据不同的类型,做不同的处理。
3.useContext
在跨组件层级获取数据时简化获取数据的代码。
  • 跨层级获取数据
    import React, {createContext} from 'react'; const countContext = createContext(); function UseContext () { return UseContext }function Foo () { return { value => { return {value} } } }export default UseContext;

  • 简化代码
    import React, {createContext, useContext} from 'react'; const countContext = createContext(); function UseContext () { return UseContext }function Foo () { const value = https://www.it610.com/article/useContext(countContext); return {value} }export default UseContext;

4.useEffect
让函数组件拥有处理副作用的能力,类似生命周期函数。
1. useEffect 执行时机
  1. useEffect(() => {}): componentDidMount、componentDidUpdate
  2. useEffect(() => {}, []): componentDidMount
  3. useEffect(() => () => {}): componentWillUnMount
可以把 useEffect 看做 componentDidMount、componentDidUpdate、componentWillUnMount 这三个生命周期函数的组合。
import React, {useEffect, useState} from "react"; function UseEffect() {const [count, setCount] = useState(0); // 组件挂载和组件更新时执行 // useEffect(() => { //console.log('run') // }); // 组件挂载时执行 // useEffect(() => { //console.log('run') // }, []); // 清除上一个 effect useEffect(() => { console.log('1') return () => { console.log('run'); } }); return useEffect {count} }export default UseEffect;

2. useEffect 使用方式
  1. 为window对象添加滚动事件
  2. 设置定时器让count数值每隔一秒增加1
import React, {useEffect, useState} from "react"; import {root} from "../index"; function UseEffect() {function onScroll() { console.log('页面滚动了'); }// 挂载之后绑定事件 useEffect(() => { window.addEventListener('scroll', onScroll) return () => { console.log('run') window.removeEventListener('scroll', onScroll); } }, []); const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(count => count + 1); }, 1000) return () => { clearInterval(timer); } }, [])useEffect(() => () => { console.log('卸载组件'); })return (useEffect {count} ) }export default UseEffect;

3. useEffect 解决的问题
  1. 按照用途将代码进行分类(将一组相干的业务逻辑归置到了同一个副作用函数中)
  2. 简化重复代码,是组件内部代码更加清晰(在类组件中通常componentDidMount和componentDidUpdate中的代码是一样的)
4. useEffect 数据监测 只有指定数据发生变化的时候才会去触发 effect
const [count, setCount] = useState(0); const [num, setNum] = useState(0); // 只有count改变的时候才会执行 useEffect(() => { document.title = count; }, [count]); return (useEffect {count} {num} )

5.useEffect 结合异步函数 useEffect中的参数函数不能是异步函数,因为useEffect函数要返回清理资源的函数,如果是异步函数就变成了返回Promise
useEffect(() => { // 编写一个立即执行函数 (async () => { await axios.get() })(); })

6.useMemo
useMemo 的行为类似 Vue 中的计算属性,可以监测某一个值的变化,根据变化值计算新值。
useMemo 会缓存计算结果,如果监测值没有发生变化,即使组件重新渲染也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算。
import {useMemo} from 'react'; // 挂载完成后默认执行一次,如果监听的count发生了变化则再次执行 const result = useMemo(() => { return result; }, [count]);

7.使用memo方法提高组件性能
性能优化,如果本组件中的数据没有发生任何变化,就阻止组件进行更新。类似类组件中的 PureComponent 和 shouldComponentUpdate
import React, {memo, useState} from "react"; function Memo () { const [count, setCount] = useState(0); return {count} }// 每当 count + 1 Foo即使没有改变也会被重新渲染 // function Foo() { //console.log('重新渲染'); //return Foo组件 // }// 这样 Memo 组件更新就不会连带 Foo 更新 const Foo = memo(() => { console.log('重新渲染'); return Foo组件 })// 当Memo没有发生数据变化且父组件发生了变化,Memo组件不会被更新 export default memo(Memo);

8.useCallback
性能优化,缓存函数,使组件重新渲染时得到相同的函数实例。
Foo 子组件被重新渲染
import React, {useCallback, useState, memo} from 'react'; function UseCallback() { const [count, setCount] = useState(0); function resetCount () { setCount(0); }return {count} }const Foo = memo((props) => { /** * 当 count 发生改变时会更新父组件,父组件中的 resetCount 函数被重新定义 * 这时的 resetCount 与 Foo props中的 resetCount 实例已经发生了改变,所以 Foo 会被重新渲染 */ console.log('重新渲染'); return Foo组件 })export default UseCallback;

// 使用 useCallback 优化 resetCount
const resetCount = useCallback(() => setCount(0), [setCount]);

9.useRef
1.获取DOM对象
import React, {useRef} from 'react'; function UseRef () { const userName = useRef(); const handler = () => console.log(userName.current); return UseRef }export default UseRef;

2.useRef保存数据 即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件渲染(跨组件周期)。
// 保存数据 const [count, setCount] = useState(0); // 即使组件更新并不会重置,所以 stopCount 可以拿到 timer let timer = useRef(); useEffect(() => { timer.current = setInterval(() => { setCount(count => count + 1); }, 1000); }, [])const stopCount = () => { clearInterval(timer.current); }return useRef {count}

10.自定义hook
  • 自定义hook是标准的封装和共享逻辑的方式
  • 自定义hook是一个函数,其名称以use开头
  • 自定义hook其实就是逻辑和内置hook的组合
import React, {useEffect, useState} from 'react'; import axios from 'axios'; /** * 当前组件需要再组件挂载完成之后,获取文章数据 * 假设获取文章数据的请求是共享逻辑,尝试把它写到自定义hook当中 */// 创建自定义hook function useGetEssay () {const [essay, setEssay] = useState({}); useEffect(() => { axios.get('https://www.fastmock.site/mock/eefbb8ce7302645510629510865adb64/api/essay') .then(res => setEssay(res.data)); }, []); return [essay, setEssay]; }function CustomizationHook () {const [essay, setEssay] = useGetEssay(null); return CustomizationHook{essay.title}
{essay.content}}export default CustomizationHook;

  1. 提取表单中的常用逻辑
    function useUpdateInput(initialValue) { const [value, setValue] = useState(initialValue); return { value, onChange: e => setValue(value => value = https://www.it610.com/article/e.target.value) } }function CustomizationHook() { const userNameInput = useUpdateInput(''); const passwordInput = useUpdateInput(''); const submitForm = e => { e.preventDefault(); console.log(userNameInput.value, passwordInput.value) }return
    {/*为了绑定数据,每个表单元素都需要设置 value 和 onChange,这就属于共享逻辑*/}
    }

    推荐阅读