热门标签 | 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框架和Quartz调度器实现每周定时调用Web服务获取数据的小项目。通过详细配置Spring XML文件,展示了如何设置定时任务以及解决可能遇到的自动注入问题。 ... [详细]
  • 个人博客:打开链接依赖倒置原则定义依赖倒置原则(DependenceInversionPrinciple,DIP)定义如下:Highlevelmo ... [详细]
  • 我在尝试将组合框转换为具有自动完成功能时遇到了一个问题,即页面上的列表框也被转换成了自动完成下拉框,而不是保持原有的多选列表框形式。 ... [详细]
  • 本文探讨了如何使用Scrapy框架构建高效的数据采集系统,以及如何通过异步处理技术提升数据存储的效率。同时,文章还介绍了针对不同网站采用的不同采集策略。 ... [详细]
  • 本文介绍了如何使用 Python 的 Pyglet 库加载并显示图像。Pyglet 是一个用于开发图形用户界面应用的强大工具,特别适用于游戏和多媒体项目。 ... [详细]
  • 本文详细介绍如何在SSM(Spring + Spring MVC + MyBatis)框架中实现分页功能。包括分页的基本概念、数据准备、前端分页栏的设计与实现、后端分页逻辑的编写以及最终的测试步骤。 ... [详细]
  • Hibernate全自动全映射ORM框架,旨在消除sql,是一个持久层的ORM框架1)、基础概念DAO(DataAccessorOb ... [详细]
  • ASP.NET 进度条实现详解
    本文介绍了如何在ASP.NET中使用HTML和JavaScript创建一个动态更新的进度条,并通过Default.aspx页面进行展示。 ... [详细]
  • Maven + Spring + MyBatis + MySQL 环境搭建与实例解析
    本文详细介绍如何使用MySQL数据库进行环境搭建,包括创建数据库表并插入示例数据。随后,逐步指导如何配置Maven项目,整合Spring框架与MyBatis,实现高效的数据访问。 ... [详细]
  • 本文探讨了如何通过优化 DOM 操作来提升 JavaScript 的性能,包括使用 `createElement` 函数、动画元素、理解重绘事件及处理鼠标滚动事件等关键主题。 ... [详细]
  • 本文将在前几篇关于Android测试理论知识的基础上,通过ApiDemoTest实例详细探讨如何使用ApplicationTestCase进行Android应用测试。建议读者先阅读Android测试教程系列中的相关内容,以便更好地理解本文的实践部分。 ... [详细]
  • 页面预渲染适用于主要包含静态内容的页面。对于依赖大量API调用的动态页面,建议采用SSR(服务器端渲染),如Nuxt等框架。更多优化策略可参见:https://github.com/HaoChuan9421/vue-cli3-optimization ... [详细]
  • 一、使用Microsoft.Office.Interop.Excel.DLL需要安装Office代码如下:2publicstaticboolExportExcel(S ... [详细]
  • Python网络编程:深入探讨TCP粘包问题及解决方案
    本文详细探讨了TCP协议下的粘包现象及其产生的原因,并提供了通过自定义报头解决粘包问题的具体实现方案。同时,对比了TCP与UDP协议在数据传输上的不同特性。 ... [详细]
  • 利用Node.js实现PSD文件的高效切图
    本文介绍了如何通过Node.js及其psd2json模块,快速实现PSD文件的自动化切图过程,以适应项目中频繁的界面更新需求。此方法不仅提高了工作效率,还简化了从设计稿到实际应用的转换流程。 ... [详细]
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社区 版权所有