热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

20210401ReactHook进一步理解

2021-04-01ReactHook进一步理解-ReactHooks是什么,为什么要用它?Hook是React16.8新增的特性,其主要目的是写函数组件时用到state、处理副

React Hooks是什么,为什么要用它?

Hook是React 16.8新增的特性,其主要目的是写函数组件时用到state、处理副作用等React的特性,可以不再编写class组件

之前使用函数组件大多是是无状态的,只能使用props参数,只能是个UI展示组件,没有逻辑状态注入state等,而使用state只能用class组件,但是使用类组件时,如果有大量的业务逻辑需要放在生命周期函数中,会使得项目越来越复杂且难以维护,class中的this问题需要绑定也是个令人棘手的问题,为了解决这些麻烦,Hook就出现了

Hook主要解决了三类问题:

  • 组件之间复用状态逻辑
  • 复杂组件变得难以理解
  • 难以理解的class

Hook使用规则

Hook本质是一类特殊的函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用Hook。不要再循环、条件判断或者子函数中调用
  • 只能在React的函数组件中调用Hook或者在自定义的Hook中调用。不要再其他Javascript函数中调用

React内置的Hook

基础的Hook
  • useState
const [state, setState] = useState(initialState)

返回一个state,以及更新state的函数

在初次渲染期间,返回的状态(state)与传入的第一个参数(initialState)相同,当然这个initialState也可以是一个函数,在初次渲染期间,会调用函数计算初始的state,后续不在调用

setState函数用于更新state。接收一个新的state值并将组件的一次重新渲染加入队列;当然也可以接收一个参数为preState=>{...}的函数并返回函数执行的结果

使用useState时,需要注意以下三个点:

1)useState返回的setState方法同类组件的setState一样,也是一个异步方法,需要组件更新之后,state的值才会变成新值

2)useState返回的setState并不具有类组件setState合并多个state的作用,如果state中有多个state,在更新时,其他值一同更新,可以使用ES6中展开运算符来达到合并的效果

setState(prevState => {
    // 也可以使用Object.assign方法
    return {...prevState, ...updatedValues}
})

3)同一个组件中可以使用useState创建多个state

  • useEffect
useEffect(didUpdate, [依赖项])

该Hook接收一个包含命令式、且可能含有副作用代码的函数

Effect专业术语为副作用。什么是副作用呢?网络请求、DOM操作都是副作用的一种,useEffect就是专门来处理副作用的

在类组件中副作用通常在componentDidMount和componentDidUpdate中进行处理,而useEffect就相当于componentDidMount、componentDidUpdate和componentWillUnmount的集合体

useEffect包含两个参数一个是执行时的回调函数didUpdate和依赖参数数组,并且回调函数还有一个返回函数,在里面会执行诸如清除定时器,取消订阅等的操作。通常,为防止内存泄漏,清除函数会在组件卸载之前执行。另外,如果组件多次渲染(通常如此),则在执行下一个effect之前,上一个effect就已被清除

依赖参数,其本身是一个数组,在数组中放入要依赖的数据,当这些数据有更新时,就只执行回调函数。整个生命周期过程如下:

组件挂载->执行副作用(回调函数)->组件更新->执行清理函数(回调函数里的返回函数)->执行副作用(回调函数)->组件准备卸载->执行清理函数(回调函数里的返回函数)->组件卸载

上述讲的useEffect是componentDidMount、componentDidUpdate和componentWillUnmount的集合体,如果只是单纯的只想要在挂载后、更新后、卸载前其中之一的阶段执行,可以参考一下操作:

componentDidMount:如果只是想在组件挂载后执行,可以把依赖数组置为空数组[],这样在更新时就不会执行该副作用了

componentWillUnmount:如果只想在组件卸载之前执行,同样可以把依赖参数置为空数组[],该副作用的返回函数会在卸载之前执行

componentWillUpdate:只检测更新相对比较麻烦,需要区分更新时还是挂载需要检测依赖数据和初始值是否一致,如果当前的数据和初始数据保持一致就说明是挂载阶段,当然安全起见应和上一次的值进行对比,若当前的依赖数据和上一次的依赖数据完全一样,则说明组件没有更新

  • useContext
const value = useContext(MyContext)

接收一个context对象(React.createContext的返回值)并返回context的当前值。当前的context值由上层组件中距离当前组件最近的valueprop决定

当组件上层最近的更新时,该Hook会触发重新渲染,并使用最新传递给MyContextprovider的contextvalue

调用了useContext的组件总会在context值变化时重新渲染。如果重新渲染组件的开销较大,可以通过使用useMemo来优化

额外的Hook
  • useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init)

这是useState的替代方案。它接收一个形如(state, action) => newState的reducer,并返回当前的state以及与其配套的dispatch方法,和redux很类似

  • useCallback
const memoizedCallback = useCallback(
    ()=>{
        doSomething(a,b);
    },
    [a,b]
)

返回一个memoized函数,大白话就是缓存函数

把内联回调函数以及依赖项数组作为参数传入useCallback,它将返回改回调函数的memoized版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如shouldComponentUpdate)的子组件时,它将非常有用

useCallback(fn, deps)相当于useMemo(()=>fn, deps)

  • useMemo
const memoizedValue = useMemo(() => computedExpensiveValue(a, b), [a, b])

返回一个memoized值,缓存值

把创建函数和依赖项数组作为参数传入useMemo,它仅会在某个依赖项改变时才会重新计算memoized值。这种优化有助于避免在每次渲染时都进行高额开销的计算

请记住,传入useMemo的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect的范畴

  • useRef
const refCOntainer= useRef(initialValue)

useRef返回一个可变的ref对象,其.current属性被初始化为传入的参数(initialValue)。返回的ref对象在组件的整个生命周期内保持不变

这是一个例子获取上一轮的props或state:

function Counter() {
  const [count, setCount] = useState(0);

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}h1>;
}
  • useImperativeHandle
useImperativeHandle(red, createHandle, [deps])

useImperativeHandle可以让你在使用ref时自定义暴露给父组件的实例值。大多数情况下,应当避免使用ref这样的命令式代码。useImperativeHandle应当与forwardRef一起使用:

function FancyInput(props, ref){
    const inputRef useRef();
    useImperativeHandle(ref, ()=>({
        focus: ()=>{
            inputRef.current.focus();
        }
    }));
    return <input ref={inputRef} ... />
}
FancyInput = forwardRef(FancyInput);
  • useLayoutEffect

其函数签名与useEffect相同,但它会在所有的DOM变更之后同步调用effect。可以使用它来读取DOM布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect内部的更新计划将被同步刷新

尽可能使用标准的useEffect以避免阻塞视觉更新

自定义Hook

在class组件中,要想复用状态逻辑则使用render props高阶组件两种方法,而在Hook中可以把这些状态逻辑提取到一个可重用的函数中,也就是自定义Hook

自定义Hook是一个函数,以“use”开头,函数内部可以调用其他的Hook

例如之前一个例子获取上一轮的props和state,可以使用自定义的Hook如下:

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return <h1>Now: {count}, before: {prevCount}h1>;
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

使用自定义Hook有以下几个注意点:

  1. 自定义Hook必须以“use”开头
  2. 两个组件中使用相同的Hook并不会共享state
  3. 自定义Hook获取state是完全独立的

最后,看了官方文档Ryan Florence介绍React Hook应用,写了例子自定义hook demo:

import Row from "./Row";
import React, { useContext, useEffect, useState } from "react";
import { ThemeContext } from "./context";
import "./index.css";

function Greeting(props) {
  const name = useFormInput("xiaoxu");
  const surname = useFormInput("fangfang");
  const theme = useContext(ThemeContext);
  const width = useWindowWidth();

  useDocumentTitle(name.value + "-" + surname.value);

  return (
    <section className={theme}>
      <Row label="Name">
        <input {...name} />
      Row>
      <Row label="Surname">
        <input {...surname} />
      Row>
      <Row label="Width">{width}Row>
    section>
  );
}

// 自定义副作用title-Hook
function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  });
}

// 自定义width-Hook
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const handleResize = () => {
      setWidth(window.innerWidth);
    };
    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  });
  return width;
}

// 自定义input-Hook
function useFormInput(name) {
  const [value, setValue] = useState(name);
  function handleChange(e) {
    setValue(e.target.value);
  }
  return {
    value,
    onChange: handleChange
  };
}

export default Greeting;

效果如下:


推荐阅读
  • 本文作为“实现简易版Spring系列”的第五篇,继前文深入探讨了Spring框架的核心技术之一——控制反转(IoC)之后,将重点转向另一个关键技术——面向切面编程(AOP)。对于使用Spring框架进行开发的开发者来说,AOP是一个不可或缺的概念。了解AOP的背景及其基本原理,对于掌握这一技术至关重要。本文将通过具体示例,详细解析AOP的实现机制,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 类加载机制是Java虚拟机运行时的重要组成部分。本文深入解析了类加载过程的第二阶段,详细阐述了从类被加载到虚拟机内存开始,直至其从内存中卸载的整个生命周期。这一过程中,类经历了加载(Loading)、验证(Verification)等多个关键步骤。通过具体的实例和代码示例,本文探讨了每个阶段的具体操作和潜在问题,帮助读者全面理解类加载机制的内部运作。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 在基于.NET框架的分层架构实践中,为了实现各层之间的松散耦合,本文详细探讨了依赖注入(DI)和控制反转(IoC)容器的设计与实现。通过合理的依赖管理和对象创建,确保了各层之间的单向调用关系,从而提高了系统的可维护性和扩展性。此外,文章还介绍了几种常见的IoC容器实现方式及其应用场景,为开发者提供了实用的参考。 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • 重要知识点有:函数参数默许值、盈余参数、扩大运算符、new.target属性、块级函数、箭头函数以及尾挪用优化《深切明白ES6》笔记目次函数的默许参数在ES5中,我们给函数传参数, ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 在Delphi7下要制作系统托盘,只能制作一个比较简单的系统托盘,因为ShellAPI文件定义的TNotifyIconData结构体是比较早的版本。定义如下:1234 ... [详细]
  • 本地存储组件实现对IE低版本浏览器的兼容性支持 ... [详细]
  • 在尝试对 QQmlPropertyMap 类进行测试驱动开发时,发现其派生类中无法正常调用槽函数或 Q_INVOKABLE 方法。这可能是由于 QQmlPropertyMap 的内部实现机制导致的,需要进一步研究以找到解决方案。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • 本项目在Java Maven框架下,利用POI库实现了Excel数据的高效导入与导出功能。通过优化数据处理流程,提升了数据操作的性能和稳定性。项目已发布至GitHub,当前最新版本为0.0.5。该项目不仅适用于小型应用,也可扩展用于大型企业级系统,提供了灵活的数据管理解决方案。GitHub地址:https://github.com/83945105/holygrail,Maven坐标:`com.github.83945105:holygrail:0.0.5`。 ... [详细]
author-avatar
mobiledu2502886787
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有