React中的Hooks

一、概念

Hooks 是 React v16.8 的新特性,可以在不使用类组件的情况下,使用 state 以及其他的 React 特性;

Hooks 是完全可选的,无需重写任何已有代码就可以在一些组件中尝试 Hook
于React v16.8发布,100%向后兼容,Hooks 不包含任何破坏性改动.
React 也没有计划移除 class 类组件,而且 Hooks 不会影响对 React 的理解,它为已知的 React 概念提供了更直接的 API

二、Hooks解决的问题

  1. 函数式组件不能使用 state:函数式组件比类组件更简洁好用,而 Hooks 让它更加丰富强大;
  2. 副作用问题:诸如数据获取、订阅、定时执行任务、手动修改 ReactDOM 这些行为都可以称为副作用;而 Hooks 的出现可以使用 useEffect 来处理这些副作用;
  3. 有状态的逻辑重用组件
  4. 复杂的状态管理:之前通常使用 redux、dva、mobx 这些第三方状态管理器来管理复杂的状态,而 Hooks 可以使用 useReducer、useContext 配合实现复杂的状态管理;
  5. 开发效率和质量问题:函数式组件比类组件简洁,效率高,性能也好。
useState:组件状态管理的钩子
import { useState } from 'react'
const [state, setState] = useState(initState)
//state:管理组件的状态;
//setState:更新state的方法,方法名不可更改!
//initState:初始的state,可以是任意的数据类型(只在初次渲染时有效,二次渲染时会被忽略),也可以是回调函数,但函数必须有返回值。
//函数式组件实现计数器
import { useState } from 'react'
export default function App() {
    const [count, setCount] = useState(0)  // 初始值0
    return (
        <div>
            <div>点击了{ count }</div>
            <button onClick={()=>setCount(count+1)}>点击</button>
        </div>
    ) 
}

useState 让函数式组件具备了管理状态的能力,不再是一个无状态组件;与 classsetState 类似,当向 useState 更新状态的方法 setCount 传递一个函数时,此函数会接收到上一次的状态:setCount(prevCount => prevCount + 1)
但与 classsetState 不同的是,如果状态值是一个对象,useState 更新状态的方法是不会合并更新对象的,可以结合展开运算符来达到合并更新对象的效果:

setState(prevState => {
    return { ...prevState, ...updateValues };
});

useState 当然可以多次调用,从而定义多个状态值;但不管调用多少次,相互之间都是独立的,不会相互污染。这就是为什么 React 否掉了 Mixins ,因为 Mixins 机制让多个 Mixins 共享一个对象的数据空间,这样就很难保证不同 Mixins 依赖的状态不发生冲突;

function App() {
    const [age, setAge] = useState(42);
    const [fruit, setFruit] = useState('banana');
    const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}

React又是如何保证多个useState的相互独立呢,定义时并没有告诉React这些值的keyReact如何保证这三个useState能准确找到它对应的state呢 —> 根据useState出现的顺序!
第一次渲染

useState(42);  //将age初始化为42
useState('banana');  //将fruit初始化为banana
useState([{ text: 'Learn Hooks' }]); //...

第二次渲染

useState(42);  //读取状态变量age的值(这时候传的参数42直接被忽略)
useState('banana');  //读取状态变量fruit的值(这时候传的参数banana直接被忽略)
useState([{ text: 'Learn Hooks' }]); //...

加入条件控制语句

let showFruit = true;
function App() {
    const [age, setAge] = useState(42);
    if(showFruit) {
        const [fruit, setFruit] = useState('banana');
        showFruit = false;
    }
    const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}

这样一来,首次渲染的初始化过程是不变的,但第二次渲染就有所不同了
第二次渲染

useState(42);  //读取状态变量age的值(这时候传的参数42直接被忽略)
// useState('banana');
useState([{ text: 'Learn Hooks' }]);  //读取到的却是状态变量fruit的值,导致报错

鉴于此,React规定:
Hooks必须写在函数的最外层,不能在循环语句、条件判断、子函数中调用;
只能在 React 的函数式组件、自定义Hooks中调用Hooks,不能在其他JavaScript函数中调用。

useEffect

useEffect(callback, array):副作用处理的钩子;它也是componentDidMount()、componentDidUpdate()、componentWillUnmount()、这几个生命周期方法的统一,一个顶三个!React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,而生命周期钩子是同步执行的

callback 回调函数,作用是处理副作用的逻辑,可以返回一个函数,用作清理副作用;

import { useEffect } from 'react'
useEffect(() => {
    ......//副作用处理
    return () => {
        ......//清理副作用的清除函数
    }
}, [])

为防止内存泄漏,清除函数会在组件卸载前执行;如果组件多次渲染,则在执行下一个 effect 之前,上一个 effect 就已被清除;
array 可选数组,用于控制useEffect的执行; 省略时,每次渲染都会执行; 空数组时,只会在组件挂载/卸载时执行一次,类似componentDidMountcomponentWillUnmount; 非空数组时,会在数组元素发生改变后执行,且这个变化的比较是浅比较。
提供第二个参数时,相当于告诉React 该组件不依赖于state/props 的变化,只依赖第二个参数的变化。

function App() {
    const [count, setCount] = useState(0)  // 初始值0
    useEffect(()=>{
        console.log('1')  // 初次渲染时执行一次,之后每次 count 变化都执行
        return () => {
            console.log('2')  // 初次渲染时不执行,之后每次 count 变化都最先执行
        }
    }, [count])
    return (<div>
         <div>点击了{ count }</div>
         <button onClick={() => setCount(preCount => preCount+1)}>点击</button>
    </div>) 
}
// 初次渲染:1
// 点击更新count:2 1

父组件也可以通过 props 控制子组件是否执行 useEffect

 useEffect(()=>{
     // 注册事件
     const subscription = props.source.subscribe();
     return ()=>{
         // 解绑事件
         subscription.unsubscribe();
     }
 }, [props.source]);

如前文所述,Hooks可以反复多次使用,相互独立。所以合理的做法是,给每一个副作用加一个单独的useEffect钩子。这样一来,这些副作用不再全堆在class组件的生命周期钩子里,代码变得更加清晰;

useEffect中定义的副作用函数在执行时不会阻碍浏览器更新视图,即这些函数是异步执行的,而class组件中的生命周期钩子都是同步执行的;异步设计对大多数副作用是合理的,但也有特例,比如有时候需要先根据DOM计算出某个元素的尺寸,然后再去渲染,此时则希望二次渲染是同步发生的,也就是在浏览器真的去绘制界面前发生;
为此,React 为此提供了一个额外的 useLayoutEffect Hook 来处理这类 effect。它和 useEffect 的结构相同,区别只是调用时机不同 – useLayoutEffect 是同步的。

useContext

useContext():同一个父组件的后台组件之间的全局数据共享;
useContext() 接收React.createContext()的返回值作为参数,即context对象,并返回最近的context。当最近的context更新时,使用该contextHooks 将会重新渲染;

创建一个Context文件 InfoContext.js

// 设置了默认值 defaultValue
const InfoContext = React.createContext({ name: 'Jerry', age: 18 })
export default InfoContext
父组件
import InfoContext from './context/InfoContext'
const Person = () => {
    const ctx = useContext(InfoContext)
    return(<InfoContext.Provider value={{ username: 'superman' }}>
        <div>
            <AgeCompt></AgeCompt>
        </div>
    </InfoContext.Provider>)
}
export default Person

某一层的后代组件

import { useContext } from 'react'
import InfoContext from './context/InfoContext'
const AgeCompt = () => {
    const { username } = useContext(InfoContext)
    return <p>{username}</p>
}
export default AgeCompt
useReducer

useReducer()useState的一个增强体,用于处理复杂的状态管理,灵感来源于Reduxreducer
useState 内部就是基于 useReducer 实现的,只是对于简单的状态管理,useState()比较好用;

const [state, setState] = useState(initState)

const [state, dispatch] = useReducer(reducer, initState, initAction)

reducer – 一个函数,根据action状态处理并更新state
initState --初始化state
initAction -- useReducer()初次执行时被处理的action,会把第二个参数initState当作参数执行
state – 状态值
dispatch – 更新state的方法,接收action作为参数,当它被调用时,reducer函数也会被调用,同时根据action去更新stateaction是一个描述操作的对象,如

dispatch({type: 'add'})
import { useReducer } from 'react';
const initState = { count: 0 };
const initAction = initState => { count: initState.count + 2 };
const reducer = (state, action) => {
    switch(action.type) {
        case 'ADD':
            return { count: state.count+1 }
        case 'DEL':
            return { count: state.count-1 }
        case 'RESET':
            return initState
        default:
            return state
    }
}
export default function UserCompt() {
    const [state, dispatch] = useReducer(reducer, initState)
    return (<div>
        <p>{state.count}</p>
        <div>
            <button onClick={() => dispatch({ type: 'ADD', /**可以加一些其他属性*/ })}>增加</button>
            <button onClick={() => dispatch({ type: 'DEL' })}>减少</button>
            <button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
        </div>
    </div>)
}
更多Hooks

useMemo(callback, array):性能优化,利用了闭包的特性,通过记忆值来避免在每个渲染上都执行高开销的计算(计算缓存);适用于复杂的计算场景,如复杂的列表渲染,对象深拷贝…
callback - 用于处理逻辑的函数
array - 依赖项,控制useMemo()重新执行的数组,array元素改变时才会重新执行useMemo()
返回值是一个记忆值,也是callback的返回值

import React, { useMemo } from 'react';
export default function UserCompt() {
    const obj1 = { name: 'Tom', age: 15 }
    const obj2 = { name: 'Jerry', age: 18, sex: '男' }
    //合并obj1、obj2
    const memoValue = useMemo(() => Object.assign(obj1, obj2), [obj1, obj2])
    return (<div>
        <p>{ memoValue.name }</p>
        <p>{ memoValue.age }</p>
    </div>)
}

注意:不能在 useMemo() 中处理副作用逻辑,而是把副作用处理逻辑放在useEffect()
useCallback(callback, array):也是用于性能优化,与useMemo()不同的是,返回值是callback本身;

//合并obj1、obj2
const backValue = useCallback(() => Object.assign(obj1, obj2), [obj1, obj2])

<div>{ backValue().name } --- { backValue().age }</div>
当依赖项变化时,返回一个新的函数体callback。
useRef():创建ref,方便访问操作DOM
const RefCompt = () => {
    //创建ref
    const inputRef = useRef();
    const getValue = () => {
        //访问ref
        const inpt = inputRef.current;  // input的DOM对象
        inpt.focus();  // 让 input 框获取焦点
        console.log(inpt.value);  // input输入框的值
    };
    //挂载
    return (<div>
        <input ref={ inputRef } type="text" />
        <button onClick={ getValue }>获取值</button>
    </div>);
}
自定义Hooks

Hooks本质上就是封装好的勾子函数,在自定义Hooks时,最需要关心的就是性能、重复渲染这些问题;
自定义一个Effect Hooks,把可以复用的逻辑抽离出来,变成一个个可插拔的插销;
当标题变化时,则修改标题、否则不执行的Hooks

import { useEffect } from 'react'

//封装Hooks,以 use 开头
const useChangeTitle = (title) => {
    useEffect(() => {
        document.title = title
    }, [title])
}
export default (props) => {
    useChangeTitle("自定义修改标题Hooks")
    return <div>测试</div>
}

一个用来判断某个 id 是否在线的Hooks

import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
    const [isOnline, setIsOnline] = useState(null);
    useEffect(()=>{
        //注册监听事件
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
        return () => {
          //取消监听事件
          ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
        };
    });
    return isOnline;
}

在组件中使用此Hooks

function FriendStatus(props) {
     //调用自定义Hooks
    const isOnline = useFriendStatus(props.friend.id);
    if (isOnline === null) {
       return 'Loading...';
    }
    return isOnline ? 'Online' : 'Offline';
}
Hooks的使用规则

只在顶层调用Hooks
Hooks的调用尽量只在顶层作用域
不要在循环、条件或嵌套函数中调用Hook,否则可能会无法确保每次组件渲染时都以相同的顺序调用Hook
只在函数组件调用Hooks:目前只支持函数式组件,未来版本Hooks会扩展到class类组件;
React Hooks的应用场景:函数式组件、自定义Hooks

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值